Nigel Thompson
Microsoft Developer Network Technology Group
July 24, 1995
Click to open or copy the files in the NTCPLAPP sample application for this technical article.
This article describes a simple step-by-step process for building Win32® Control Panel applets using Microsoft® Visual C++™ and a single C++ class module. The accompanying NTCPLAPP sample application was built using Microsoft Visual C++ version 2.2 and tested on Microsoft Windows NT™ version 3.51 and Microsoft Windows® 95.
Creating Control Panel applets is not something most of us do very often. I found the need to create one while building a Microsoft® Windows NT™ service application. In order to manage my service, I needed some sort of user interface, and putting that in the Control Panel seemed the logical thing to do. Creating Control Panel applets, as they are called, does involve quite a bit of digging through the documentation to find out exactly what you need to do. I've been through that process and have built a single C++ class (CControlPanel) to do the grunt work for you. This article describes the steps you need to follow to build your own applet. I kept my sample very simple, providing only one applet in the sample. If you want to modify it to support multiple applets, you should find that relatively easy to do. One warning about the code in CControlPanel: Because the code sample uses a global C++ object pointer, you cannot have more than one object (CControlPanel) in the applet you create. This should not be a limitation because one CControlPanel object can support multiple applets.
My application was created in C:\APPS\NTCPLAPP2 and the target applet was copied to C:\NT351\SYSTEM23. Obviously, your own development configuration might be different from mine, so bear this in mind while following these instructions.
Executable: <systempath>/SYSTEM32/CONTROL.EXE
Working Directory: C:\APPS\NTCPLAPP2 (your source code directory)
Additional DLLs: <systempath>/SYSTEM32/MYCPLAPP.CPL (your executable)
When you click the Run button, the Control Panel will start and then load your applet, so you can set breakpoints and debug it.
// MYCPLAPP.H : Main header file for MYCPLAPP.DLL
//
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif
#ifdef IDC_STATIC // <-- You add this.
#undef IDC_STATIC // <-- You add this.
#endif // <-- You add this.
#include "resource.h" // <-- You add this.
#include "mypanel.h" // <-- You add this.
#include "mydialog.h" // <-- You add this.
/////////////////////////////////////////////////////////////////////////////
// CMyCplAppApp
// See MYCPLAPP.CPP for the implementation of this class.
//
class CMyCplAppApp : public CWinApp
{
public:
CMyCplAppApp();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyCplAppApp)
//}}AFX_VIRTUAL
//{{AFX_MSG(CMyCplAppApp)
// NOTE - ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
// local data // <-- You add this.
CMyPanel m_Control; // <-- You add this.
};
// MYPANEL.H
#include "ctrlpan.h"
class CMyPanel : public CControlPanel
{
public:
virtual LONG OnInquire(UINT uAppNum, NEWCPLINFO* pInfo);
virtual LONG OnDblclk(HWND hwndCPl, UINT uAppNum, LONG lData);
};
As you can see, it's very simple and overrides only two member functions from CMyPanel. We'll look at the implementation of MYPANEL.CPP later.
; MYCPLAPP.DEF : Declares the module parameters for the DLL.
LIBRARY MYCPLAPP
DESCRIPTION 'MYCPLAPP Windows Dynamic Link Library'
EXPORTS
; Explicit exports can go here
CPlApplet
Add member variables as needed. I added a single integer value, so there would be something simple to show in the sample.
To implement even the simplest applet, you must override CControlPanel::OnInquire and CControlPanel::OnDblclk. OnInquire is called to get information about your icon, caption, and so on. Here's how I implemented it for the sample:
LONG CMyPanel::OnInquire(UINT uAppNum, NEWCPLINFO* pInfo)
{
// Fill in the data.
pInfo->dwSize = sizeof(NEWCPLINFO); // Important
pInfo->dwFlags = 0;
pInfo->dwHelpContext = 0;
pInfo->lData = 0;
pInfo->hIcon = ::LoadIcon(AfxGetResourceHandle(),
MAKEINTRESOURCE(IDI_MYICON));
strcpy(pInfo->szName, "My Applet");
strcpy(pInfo->szInfo, "My Control Panel Applet");
strcpy(pInfo->szHelpFile, "");
return 0; // OK (Don't send CPL_INQUIRE msg)
}
The entries you might change are the icon ID (if yours isn't IDI_MYICON), the name, and the information string that appears in the Control Panel status bar when your applet is selected. You might also create a Help file, in which case you need to supply its filename (and possibly its path, if it isn't in the same location as the applet) in the szHelpFile field.
The implementation of OnDblclk is a bit longer because it really does all the work of the applet. It essentially does three things:
Here's how I did this for the sample:
LONG CMyPanel::OnDblclk(HWND hwndCPl, UINT uAppNum, LONG lData)
{
// Create the dialog box using the parent window handle.
CMyDialog dlg(CWnd::FromHandle(hwndCPl));
// Set the default value.
dlg.m_iMyValue = 0;
// Read the current state from the registry.
// Try opening the registry key:
// HKEY_CURRENT_USER\Control Panel\<AppName>
HKEY hcpl;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
"Control Panel",
0,
KEY_QUERY_VALUE,
&hcpl) == ERROR_SUCCESS) {
HKEY happ;
if (RegOpenKeyEx(hcpl,
"MyPanel",
0,
KEY_QUERY_VALUE,
&happ) == ERROR_SUCCESS) {
// Yes we are installed
DWORD dwType = 0;
DWORD dwSize = sizeof(dlg.m_iMyValue);
RegQueryValueEx(happ,
"MyValue",
NULL,
&dwType,
(BYTE*)&dlg.m_iMyValue,
&dwSize);
RegCloseKey(happ);
}
RegCloseKey(hcpl);
}
// Show the dialog box.
if (dlg.DoModal() != IDOK) return 0;
// Update the registry.
// Try creating/opening the registry key.
if (RegOpenKeyEx(HKEY_CURRENT_USER,
"Control Panel",
0,
KEY_WRITE,
&hcpl) == ERROR_SUCCESS) {
HKEY happ;
DWORD dwDisp;
if (RegCreateKeyEx(hcpl,
"MyPanel",
0,
"",
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
NULL,
&happ,
&dwDisp) == ERROR_SUCCESS) {
// Set the value.
RegSetValueEx(happ,
"MyValue",
0,
REG_DWORD,
(BYTE*)&dlg.m_iMyValue,
sizeof(dlg.m_iMyValue));
// Finished with keys.
RegCloseKey(happ);
}
RegCloseKey(hcpl);
}
return 0;
}
This looks a bit complex, but most of it is dealing with opening registry keys. For this sample, I chose to keep the data in the registry under HKEY_CURRENT_USER\Control Panel\<AppName> and used a key called MyValue to store the single value the dialog box allows the user to vary. So this sample stores data on a per-user basis. In other words, different users could use this dialog box to set personal preferences. If your need is for something user-independent, you probably want to store your data under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<yourservice>.
If your Control Panel utility contains more than one applet, you'll need to override OnGetCount to report how many applets you have, and your OnInquire function will need to respond once for each applet. You may also have some global memory allocation to do. If so, do this in the OnInit function, because this is the only place your applet can fail and have the Control Panel respond sensibly (that is, by not running the applet). Use the OnStop function to clean up any global allocations.
You should note that the Control Panel will run your applet once when the Control Panel initially starts up in order to get a count of the applets and the icons for each applet. It will then run your applet again if the user double-clicks its icon. That's why my sample does all its state reading in the OnDblclk function. It's redundant to do it in the OnInit function because this means doing all the work reading the state information just to show the icon, as well as showing the dialog box if the applet is actually run.
The Control Panel uses the single entry point, CPlApplet, to communicate with your applet. It sends CPL_DBLCLK, CPL_EXIT, CPL_GETCOUNT, CPL_INIT, CPL_INQUIRE, CPL_NEWINQUIRE, CPL_SELECT and CPL_STOP messages to this function. For more information, see the Win32® SDK documentation for these messages.