An Ancient Story of Control Panel Applets

Control Panel is really an ancient creature in MS Windows. It was introduced when Windows first began. But nevertheless, it is still alive, and can serve your needs perfectly, providing an easy and powerful mechanism for Control Panel applets. You will definitely find a lot of articles on this theme all over the Web, which more or less desribe what you should do to develop an applet. In this tutorial, I'd like to combine different aspects of Control Panel programming, so you will be able to get a relatively full picture of what's happening there.

Dry Theory

Prior to diving into coding, let me formalize what I am going to speak about. Each Control Panel applet is just a DLL that exports only one function: CPlApplet. Well, applets also have an extension of 'cpl' instead of 'dll,' so you will simply need to change it in project settings. Thus, a typical DEF-file may look like this:

LIBRARY TESTAPPLET

EXPORTS CPlApplet

CPlApplet is called upon for a few basic events. I'll show you its schematic definition:

LONG CALLBACK CPlApplet (HWND hwndCPL, UINT uMsg, LONG lParam1,
                         LONG lParam2)
{
...
   switch (uMsg)
   {
      // First message sent. It is sent only once to allow the
      // DLL to initialize its applet(s)
      case CPL_INIT:
      ...
         return TRUE;

      // Second message sent. Return the count of applets supported
      // by this DLL
      case CPL_GETCOUNT:
         // Return the number of applets we support
         return (LONG) nApplets;

      // Third message sent. Sent once for each applet supported by
      // this DLL.
      // The lParam1 contains the number that indicates which applet
      // this is for, from 0 to 1 less than the count of applets.
      // lParam2 is a NEWCPLINFO that should be filled with
      // information about this applet before returning
      case CPL_NEWINQUIRE:
         {
            LPNEWCPLINFO lpNewCPlInfo;
            // Fill this struct in
            ...
         }

         break;

      // This is sent whenever the user clicks an icon in Settings
      // for one of the applets supported by this DLL. lParam1
      // contains the number indicating which applet. Returns 0 if
      // applet was successfully launched; non-zero otherwise
      case CPL_DBLCLK:
         ...
         break;

      // Sent once per applet, before CPL_EXIT
      case CPL_STOP:
         break;

      // Sent once before the DLL is unloaded
      case CPL_EXIT:
         ...
         break;

      default:
         break;
   }

   return 0;
}

You'll find it quite simple. The OS informs your DLL when it should perform the following actions:

  • DLL's initialization—Here you can proceed any required 'global' initialization
  • DLL's deinitialization—Here you will release all 'globally' allocated resources
  • Provide a number of applets in the DLL—Each DLL can contain several applets, so the OS wants to know how many your DLL keeps inside
  • An intialization for each applet in the DLL—Here you will initialize each applet; for example, create some GUI components and so forth.
  • Stop notification for each applet in the DLL—that's a point to release applets' resources
  • Launch a selected applet—and finally—Go!

That is actually the whole fact sheet you need to know about this business to start programming Control Panel applets.

Turning to Practice

Well, you will be quite right in claiming that all that was written above is not enough to produce a standard-fit CP applet. This section will provide you with some additional useful information on this topic. You will add some flesh to the previously created skeleton.

Initializing an applet

First, I'll present the NEWCPLINFO struct. A pointer to this data is sent to CPlApplet as an lParam2 parameter of the CPL_NEWINQUIRE message when Control Panel needs to obtain initial information regarding your applet and initialize it. The following table contains some short explanations and comments on NEWCPLINFO's members:

typedef struct tagNEWCPLINFO {
   DWORD dwSize;
   DWORD dwFlags;
   DWORD dwHelpContext;
   LONG lData;
   HICON hIcon;
   TCHAR szName[32];
   TCHAR szInfo[64];
   TCHAR szHelpFile[128];
} NEWCPLINFO;
Name Description
dwSize The length of struct in bytes; in other words, sizeof(NEWCPLINFO)
dwFlags Ignored in Windows CE
dwHelpContext Ignored in Windows CE
lData Application-defined data sent to your applet by Control Panel when it launches and stops it. The application should return an icon ID in this member
hIcon Icon handle to be displayed in Control Panel
szName Null-terminated string that contains the name displayed below the icon in Control Panel
szInfo Null-terminated string that contains the applet's description. The description is intended to be displayed when the icon for the applet is selected
szHelpFile Ignored in Windows CE

To illustrate all this, the tiny code snippet below guides you through the flow:

int            iApplet      = (int)lParam1;
LPNEWCPLINFO   lpNewCPlInfo = (LPNEWCPLINFO)lParam2;

lpNewCPlInfo->dwSize        = (DWORD)sizeof(NEWCPLINFO);
lpNewCPlInfo->dwFlags       = 0;
lpNewCPlInfo->dwHelpContext = 0;
lpNewCPlInfo->lData         = g_nIconID;
lpNewCPlInfo->hIcon         = LoadIcon(g_hInstance,
                              (LPCTSTR)MAKEINTRESOURCE(g_nIconID));
lpNewCPlInfo->szHelpFile[0] = '\0';

LoadString(g_hInstance,IDS_NAME,lpNewCPlInfo->szName,32);
LoadString(g_hInstance,IDS_DESC,lpNewCPlInfo->szInfo,64);

An Ancient Story of Control Panel Applets

Responding to the CPL_DBLCLK message

Microsoft's GUI requirements for 'Pocket PC breeded' systems say that you should create a full-screen window with its own title (not a navigation bar). Surely, you are free to choose which GUI you will support in your own applet. But, as in many other cases, a standard look-and-feel will be more comfortable for the end user. Hence, the property sheet style looks like the best choice for a Control Panel applet. Nevertheless, you always have another option to run an arbitrary standalone application in response to CPL_DBLCLK. The next sample shows you both opportunities:

// Create appropriate Property Sheet
case CPL_DBLCLK:
   {
   int iApplet = (UINT)lParam1;
   if (!CreatePropertySheet(hwndCPL,iApplet))
      return 1;
   }
   break;
...

// Launch arbitrary application
case CPL_DBLCLK:
   {
   int iApplet = (UINT)lParam1;
                 PROCESS_INFORMATION pi;
   if ( CreateProcess(L"SomeApp.exe",L"CmdLine",0,0,0,0,0,0,0,&pi) )
   {
      CloseHandle(pi.hThread);
      CloseHandle(pi.hProcess);
   }
   else
      return 1;
   }
   break;

Hosting applets with Control Panel

A 'host' application that calls applets is ctlpnl.exe. It enables you to easily call other applets programmatically. A syntax for a call is as follows:

ctlpnl.exe cplmain.cpl,<AppletIndex>[,<PropPageIndex>]

All it does is activate the default applet (cplmain.cpl), passing the desired predefined index of the system applet and, optionally, the property page index if the applet has several pages. Unfortunately, indexes of system applets vary between different versions of Windows CE, but this can be overcome easily. Below is a sample table that contains applets available on my Pocket PC 2003 SE. Notice that all indexing starts from 0:

Name Index Page
Contrast 0 n/a
Password 1
  • 0 - Password page
  • 1 - Hint page
Owner Information 2
  • 0 - Identification page
  • 1 - Notes page
  • 2 - Options page
Power 3
  • 0 - Main or Battery page
  • 1 - This can be a Standby or Wireless page
  • 2 - Advanced page
Memory 4
  • 0 - Main page
  • 1 - Storage Card page (if it exists)
  • 2 - Running Programs page
About 5
  • 0 - Version page
  • 1 - Device ID page
  • 2 - Copyrights page
Backlight 6
  • 0 - Battery Power page
  • 1 - External Power page
  • 2 - Brightness page (optional)
Screen 7
  • 0 - General page
  • 1 - Clear Type page
  • 2 - Text size page
Input 8
  • 0 - Input Method page
  • 1 - Word Completion page
  • 2 - Options page
Sounds & Notifications 9
  • 0 - Sounds page
  • 1 - Notifications page
Remove Programs 10 n/a
Menus 11
  • 0 - Start Menu page
  • 1 - New Menu page
Buttons 12
  • 0 - Program Buttons page
  • 1 - Up/Down Control page
Today 13
  • 0 - Appearance page
  • 1 - Items page
Not In Use 14 n/a
Beam 15 n/a
Clock & Alarms 16
  • 0 - Time page
  • 1 - Alarms page
Network Cards 17 n/a
Regional Settings 18
  • 0 - Region page
  • 1 - Number page
  • 2 - Currency page
  • 3 - Time page
  • 4 - Date page
Connections 19
  • 0 - Tasks page
  • 1 - Advanced page
Not in Use 20 n/a
Not in Use 21 n/a
Certificates 22
  • 0 - Personal page
  • 1 - Root page
Bluetooth 23
  • 0 - Mode page
  • 1 - Bounded Devices page

You can continue to explore cplmain.cpl even more. Becayse it is just a regilar DLL with a known exported function, you can easily load it and call CPlApplet directly:

typedef LONG (CALLBACK *LPCPlAppletType)(HWND hwndCPL, UINT uMsg,
                                         LONG lParam1, LONG lParam2);

#include <cpl.h>
void CCPTestDlg::OnButtonHack()
{
   HINSTANCE hInst = ::LoadLibrary(L"cplmain.cpl");
   if ( hInst )
   {
      LPCPlAppletType pfnCPlApplet =
         (LPCPlAppletType)GetProcAddress(hInst,L"CPlApplet");
      if ( pfnCPlApplet )
      {
         LONG lRes = pfnCPlApplet(NULL,CPL_GETCOUNT,0,0);
         CString sInfo;
         sInfo.Format(L"AppletsCount = %lu",lRes);
         AfxMessageBox(sInfo);
      }
      ::FreeLibrary(hInst);
   }
}

Continuing on, you can call CPlApplet with CPL_NEWINQUIRE / CPL_STOP messages, getting more info about the implemented applets.

I personally prefer a PropertySheet-like style for Control Panel applets because Property Sheet gives me a simple mechanism to provide a GUI title and hyperlinks to any other resources, including applets or whatever else. These features are available on the Pocket PC platform only. Actually, a window procedure should handle two messages: PSCB_GETTITLE and PSCB_GETLINKTEXT. The last code snippet show those simple handlers:

case PSCB_GETTITLE:
   wcscpy((TCHAR*)lParam,TEXT("Test Applet"));
   break;

case PSCB_GETLINKTEXT:
   wcscpy((TCHAR*)lParam,TEXT("Go to <file:ctlpnl.exe cplmain.cpl,
                               11,1{menu}> applet.\r\n"));
   wcscat((TCHAR*)lParam,TEXT("Go to <file:ctlpnl.exe cplmain.cpl,
                               4,1{memory}> applet.\r\n"));
   wcscat((TCHAR*)lParam,TEXT("Go to <file:calc.exe{calculator}>.
                               \r\n"));
   break;

Conclusions

Control Panel applets are still alive. They are easily doable and provide a convenient mechanism to configure your applications at the 'back-door'. You can use them even if your applications have their own GUI. I believe you'll enjoy developing applets for Control Panel.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • Best-in-Class organizations execute on a strategy that supports the multi-channel nature of customer requests. These leading organizations do not just open up their service infrastructures to accommodate new channels, but also empower their teams to deliver an effective and consistent experience regardless of the channel selected by the customer. This document will highlight the key business capabilities that support a Best-in-Class customer engagement strategy.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds