Related Articles
We take a look at planned support for parallel programming for both managed and native code in the next version of Visual Studio. Stephen Toub and Hazim Shafi MSDN Magazine October 2008 ... Read more!
Getting the performance you want in concurrent applications is not as straightforward as you might think. See how common threading issues can affect your application. Erika Fuentes and Eric Eilebrecht MSDN Magazine December 2008 ... Read more!
This article presents an overview of the motivation behind new techniques that decompose problems into independent pieces for optimal use of parallel programming. David Callahan MSDN Magazine October 2008 ... Read more!
Ken Getz shows how the CollectionChanged event lets you reflect changes to your underlying data source in your bound data controls. Ken Getz MSDN Magazine December 2008 ... Read more!
This month we explain how pseudo variables and format specifiers provide a wealth of information for use in debugging. Kenny Kerr MSDN Magazine December 2008 ... Read more!
Also by this Author
Paul DiLascia MSDN Magazine July 2006 ... Read more!
Paul DiLascia MSDN Magazine May 2005 ... Read more!
Paul DiLascia MSDN Magazine December 2005 ... Read more!
Paul DiLascia MSDN Magazine February 2006 ... Read more!
What's the deal with const functions, and lots more on the reasoning behind the design of the C++/CLI. Paul DiLascia MSDN Magazine February 2007 ... Read more!
Popular Articles
Kenny Kerr sings the praises of the new Visual C++ 2008 Feature Pack, which brings modern conveniences to Visual C++. Kenny Kerr MSDN Magazine May 2008 ... Read more!
Ray Djajadinata MSDN Magazine May 2007 ... Read more!
Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation. Josh Smith MSDN Magazine July 2008 ... Read more!
C# 2.0 introduces a wealth of exiting new features, such as generics, iterators, partial classes and anonymous methods. While generics are the most talked-about feature especially for former classic C++ developers, the rest of the new features are important additions to your .NET development arsenal, enhancing power and improving overall productivity. This article is dedicated to all the new C# 2.0 capabilities besides generics to give you a good overall picture of the upcoming features. Juval Lowy MSDN ...
Read more!
Here are some design patterns that allow you to achieve higher cohesion and looser coupling for more flexible, reusable applications. Jeremy Miller MSDN Magazine October 2008 ... Read more!
Our Blog
So many factors can affect the performance of a Web page—the distance between server and client, the size of the elements on the page, how the browser loads these elements, available bandwidth. Finding those bottlenecks and identifying the culprits is no easy task. In the November 2008 issue of MSDN Magazine, Jim Pierson introduces ...
Read more!
A team project is simply a bucket that stores and partitions all of the artifacts you track and use within a Team Foundation Server (TFS) project. In the December 2008 issue of MSDN Magazine, Brian A. Randell explains how you can use and customize the MSF Agile and MFS CMMI process templates to get the most out of them for your ...
Read more!
Windows Presentation Foundation (WPF) adds functionality to the Microsoft .NET Framework so that you actually can reliably keep bound controls synchronized with their data sources. In the December 2008 issue of MSDN Magazine, Ken Getz demonstrates how to use the ObservableCollection class provided by WPF to keep bound controls in ...
Read more!
Silverlight and SharePoint provide a simple, yet powerful, infrastructure for building intranet and extranet applications with sophisticated user interface designs and interactions. In the November 2008 issue of MSDN Magazine, Steve Fox and Paul Stubbs demonstrate how to build a SharePoint Web Part as a wrapper for a Silverlight application. ... Read more!
Because Windows Workflow Foundation (WF) is based on a runtime that manages the execution of workflows and activities, testing must, in almost all cases, involve the use of the runtime – and this can introduce some interesting challenges. In the November 2008 issue of MSDN Magazine, Matt Milner presents some techniques for unit testing ...
Read more!
|
C++ At Work
Implement Triple-Click, Subclass the Main Window
Paul DiLascia
Code download available at:
CAtWork0604.exe
(163 KB)
Browse the Code Online
Q In many Windows-based apps, clicking the mouse moves the cursor and double-clicking selects the word containing the cursor. In Microsoft® Internet Explorer, if I triple-click the mouse, Internet Explorer selects the entire paragraph. I want to do this in my app, but while there's a WM_LBUTTONDBLCK, there's no WM_LBUTTONTRIPLECLICK. How can I implement triple-click?
Q In many Windows-based apps, clicking the mouse moves the cursor and double-clicking selects the word containing the cursor. In Microsoft® Internet Explorer, if I triple-click the mouse, Internet Explorer selects the entire paragraph. I want to do this in my app, but while there's a WM_LBUTTONDBLCK, there's no WM_LBUTTONTRIPLECLICK. How can I implement triple-click?
Tony Veteri
A It's not just Internet Explorer, it's also Microsoft Word and Outlook®, though Outlook is slightly different in that triple-click selects lines instead of paragraphs. You're right, there's no WM_LBUTTONTRIPLECLICK, but it's not hard to implement one yourself. After all, what's a triple click but three clicks in rapid succession? Or a double-click and single-click in rapid succession. All you have to know is how quickly do the clicks have to arrive to count as a triple-click? To find out, you can call the appropriately named ::GetDoubleClickTime, which returns the double-click time in milliseconds. So if you get a double-click and then a single-click within this many milliseconds, it counts as a triple-click.
A It's not just Internet Explorer, it's also Microsoft Word and Outlook®, though Outlook is slightly different in that triple-click selects lines instead of paragraphs. You're right, there's no WM_LBUTTONTRIPLECLICK, but it's not hard to implement one yourself. After all, what's a triple click but three clicks in rapid succession? Or a double-click and single-click in rapid succession. All you have to know is how quickly do the clicks have to arrive to count as a triple-click? To find out, you can call the appropriately named ::GetDoubleClickTime, which returns the double-click time in milliseconds. So if you get a double-click and then a single-click within this many milliseconds, it counts as a triple-click.
To implement triple-clicks, I wrote a class, CTripleClick, that lets you easily handle triple-clicks in any window. All you have to do is instantiate a CTripleClick object and call CTripleClick::Install with a pointer to your window and the message codes you want for left, middle, and right-button triple-click:
m_tripleClicker.Install(this,
MYWM_LBTRIPLECLICK,
MYWM_MBTRIPLECLICK,
MYWM_RBTRIPLECLICK);
The last two message codes are optional (default=0, ignore). Once you Install your CTripleClick, it sends the appropriate message code to your window whenever the user triple-clicks one of the mouse buttons. I wrote a test program, Click3, to verify that CTripleClick works as claimed. Click3 is a simple Single Document Interface (SDI) text editor based on CEditView. Triple-clicking selects an entire paragraph of text. Figure 1 shows the view class for Click3. It has a handler, OnLbTripleClick, that handles an application-defined message, MYWM_LBTRIPLECLICK, by selecting the paragraph that contains the mouse. See CMyView::SelectPara for details of how to select a paragraph.
 Figure 1 View
View.h
#pragma once
#include "Doc.h"
#include "TripleClick.h"
/////////////////
// Standard Edit view. Handles triple-click: select entire paragraph.
//
class CMyView : public CEditView {
public:
virtual ~CMyView();
virtual void OnDraw(CDC* pDC);
CMyDoc* GetDocument() { return (CMyDoc*)m_pDocument; }
protected:
CTripleClick m_tripleClicker;
CMyView();
void SelectPara(CEdit& edit);
DECLARE_DYNCREATE(CMyView)
DECLARE_MESSAGE_MAP()
afx_msg LRESULT OnLbTripleClick(WPARAM wp, LPARAM lp);
afx_msg LRESULT OnMbTripleClick(WPARAM wp, LPARAM lp);
afx_msg LRESULT OnRbTripleClick(WPARAM wp, LPARAM lp);
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
};
View.cpp
#include "StdAfx.h"
#include "View.h"
#include "Click3.h"
...
const UINT MYWM_LBTRIPLECLICK = WM_APP + 1;
const UINT MYWM_MBTRIPLECLICK = WM_APP + 2;
const UINT MYWM_RBTRIPLECLICK = WM_APP + 3;
LPCTSTR FINDPARA = _T("\r\n\r\n");
////////////////////////////////////////////////////////////////
// CMyView
//
IMPLEMENT_DYNCREATE(CMyView, CEditView)
BEGIN_MESSAGE_MAP(CMyView, CEditView)
ON_WM_CREATE()
ON_MESSAGE(MYWM_LBTRIPLECLICK, OnLbTripleClick)
ON_MESSAGE(MYWM_MBTRIPLECLICK, OnMbTripleClick)
ON_MESSAGE(MYWM_RBTRIPLECLICK, OnRbTripleClick)
END_MESSAGE_MAP()
CMyView::CMyView() {}
CMyView::~CMyView() {}
int CMyView::OnCreate(LPCREATESTRUCT lpcs)
{
// install triple-click handler
m_tripleClicker.Install(this,
MYWM_LBTRIPLECLICK,
MYWM_MBTRIPLECLICK,
MYWM_RBTRIPLECLICK);
return CEditView::OnCreate(lpcs);
}
void CMyView::OnDraw(CDC* pDC)
{
CEditView::OnDraw(pDC);
}
LRESULT CMyView::OnLbTripleClick(WPARAM wp, LPARAM lp)
{
SelectPara(GetEditCtrl());
return 0;
}
LRESULT CMyView::OnMbTripleClick(WPARAM wp, LPARAM lp)
{
TRACE(_T("CMyView::OnMbTripleClick\n"));
return 0;
}
LRESULT CMyView::OnRbTripleClick(WPARAM wp, LPARAM lp)
{
TRACE(_T("CMyView::OnRbTripleClick\n"));
return 0;
}
//////////////////
// This fn selects the paragraph containing the current selection
//
void CMyView::SelectPara(CEdit& edit)
{
int begin, end;
edit.GetSel(begin, end);
// Get edit control memory.
HLOCAL h = edit.GetHandle();
LPCTSTR text = (LPCTSTR)::LocalLock(h);
// Search backward for paragraph break. Wimpy algorithm.
for (; begin>0; begin--) {
if (_tcsncmp(&text[begin],FINDPARA,4)==0)
break;
}
// Search forward for paragraph break. Wimpy algorithm.
int max = edit.GetWindowTextLength();
for (; end<max; end++) {
if (_tcsncmp(&text[end],FINDPARA,4)==0)
break;
}
::LocalUnlock(h); // unlock buffer
edit.SetSel(begin, end); // select paragraph
}
The code in Figure 2 shows how CTripleClick works. CTripleClick is derived from CSubclassWnd, a class I use frequently in my columns. CSubclassWnd lets you handle messages sent to another window without deriving a new window (CWnd) class. CSubclassWnd subclasses the window by installing its own window procedure that calls CSubclassWnd::WindowProc. Derived classes override this virtual function to intercept messages sent to the target window; CTripleClick overrides it to intercept mouse messages. When CTripleClick gets a double-click (WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK, or WM_RBUTTONDBLCLK), it sets a flag and calls clock() to note the clock time.
 Figure 2 CTripleClick
TripleClick.h
#pragma once
#include "Subclass.h"
//////////////////
// Triple-click handler. To use, instantiate in your window and
// call Install:
// m_tripleClick.Install(this, MY_MESSAGE);
// where MY_MESSAGE is the message code to send when the user
// triple-clicks the left mouse button. To handle other buttons
// (middle, right), call with additional message codes.
//
class CTripleClick : public CSubclassWnd {
protected:
enum { LEFT=1, MIDDLE, RIGHT }; // codes for which button
UINT m_uMsgs[4]; // callback message codes (1-offset)
UINT m_uWhichButton; // which button clicked?
UINT m_uTimeLastDblClk; // clock time of last double click
UINT m_uClocksPerDblClk; // max clocks for double-click
// Return enum/button code or FALSE if not a double-click.
UINT IsDoubleClick(UINT msg)
{
return msg==WM_LBUTTONDBLCLK ? LEFT :
msg==WM_MBUTTONDBLCLK ? MIDDLE :
msg==WM_RBUTTONDBLCLK ? RIGHT : FALSE;
}
// Return enum/button code or FALSE if not a button-down.
UINT IsButtonDown(UINT msg)
{
return msg==WM_LBUTTONDOWN ? LEFT :
msg==WM_MBUTTONDOWN ? MIDDLE :
msg==WM_RBUTTONDOWN ? RIGHT : FALSE;
}
public:
CTripleClick() { }
~CTripleClick() { }
BOOL Install(CWnd* pWnd, UINT uMsgLeft,
UINT uMsgMid=0, UINT uMsgRight=0);
virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
};
TripleClick.cpp
#include "StdAfx.h"
#include "StdAfx.h"
#include "TripleClick.h"
...
//////////////////
// Install triple-click handler. Hooks window using base CSubclassWnd.
//
BOOL CTripleClick::Install(CWnd* pWnd,
UINT uMsgLeft, UINT uMsgMiddle, UINT uMsgRight)
{
m_uMsgs[LEFT] = uMsgLeft;
m_uMsgs[MIDDLE] = uMsgMiddle;
m_uMsgs[RIGHT] = uMsgRight;
return HookWindow(pWnd);
}
//////////////////
// Window got a message: Look for double-click followed quickly by
// button-down.
//
LRESULT CTripleClick::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
if (IsDoubleClick(msg)) {
m_uWhichButton = IsDoubleClick(msg); // save which button
m_uTimeLastDblClk = clock(); // ..and current time
// update double-click time. This is inefficient, but
// expedient because I don't have to worry about WM_SETTINGCHANGE.
m_uClocksPerDblClk = GetDoubleClickTime() * CLOCKS_PER_SEC / 1000;
} else if (IsButtonDown(msg) && IsButtonDown(msg)==m_uWhichButton) {
CSubclassWnd::WindowProc(msg, wp, lp); // do default operation
if ((clock() - m_uTimeLastDblClk) < m_uClocksPerDblClk) {
// if this is a triple-click, send callback message to client
msg = m_uMsgs[m_uWhichButton]; // callback message to send
m_uWhichButton = FALSE; // reset state
return SendMessage(m_hWnd, msg, wp, lp); // send the message!
}
}
return CSubclassWnd::WindowProc(msg, wp, lp);
}
The clock time is the number of clock ticks since the process started. How many clock ticks does a clock-ticker tick? The #define symbol CLOCKS_PER_SEC tells the answer: 1000. That is, a thousand ticks per second, or a millisecond per tick. On my system, the double-click time is 480, or about half a second.
If CTripleClick sees one of the button-down messages (WM_LBUTTONDOWN, WM_MBUTTONDOWN or WM_RBUTTONDOWN), and the button is the same one that was double-clicked, CTripleClick compares the clock times of the two events. If the elapsed time is less than the double-click time as defined by ::GetDoubleClickTime, CTripleClick sends a triple-click message to its client's window, whichever message code the client supplied. For details, see Figure 2.
Figure 3 Mouse Control Panel
Performance-conscious readers have already noticed that CTripleClick calls ::GetDoubleClickTime every time it gets a double-click message. This is somewhat inefficient since the double-click time rarely changes. I said rarely, because it can change. The user can use the Mouse control panel applet to change the double-click time (see Figure 3). That means you can't call ::GetDoubleClickTime to grab the double-click time when your app first starts, because the user can change it while your app is running. To get the current value, you must call ::GetDoubleClickTime when you need it. Alternatively, you can store the double-click time and handle WM_SETTINGCHANGE to update your stored value whenever it changes. Which leads me naturally to the next question.
Q I'm trying to write a component (child control) that displays some text using the same font as the current menu font. My application calls SystemParametersInfo to get the NONCLIENTMENTRICS with the menu font in lfMenuFont. Currently my application calls SystemParametersInfo to get the menu font whenever I need to display it. It would be more efficient to load the font once when my control is initialized, then handle WM_SETTINGCHANGE to reload it if the user changes the menu font. The problem is that Windows® only sends WM_SETTINGCHANGE to the top-level window; my window is a child window. I can make the client app call my control when the font changes, but I would prefer to update my font automatically, without making clients call a function. How can I handle WM_SETTINGCHANGE from my child control?
Q I'm trying to write a component (child control) that displays some text using the same font as the current menu font. My application calls SystemParametersInfo to get the NONCLIENTMENTRICS with the menu font in lfMenuFont. Currently my application calls SystemParametersInfo to get the menu font whenever I need to display it. It would be more efficient to load the font once when my control is initialized, then handle WM_SETTINGCHANGE to reload it if the user changes the menu font. The problem is that Windows® only sends WM_SETTINGCHANGE to the top-level window; my window is a child window. I can make the client app call my control when the font changes, but I would prefer to update my font automatically, without making clients call a function. How can I handle WM_SETTINGCHANGE from my child control?
Tom Ng
A You're right to look for a way to handle WM_SETTINGCHANGE on your own. Polite programmers always strive to make their code as self-contained and easy to use as possible—even for other programmers. But alas, Windows was designed in the bygone time of monolithic apps that exercised total control. In those days, it made sense to send messages like WM_SYSCOLORCHANGE and WM_FONTCHANGE to top-level windows— what else was there? The idea of separately installable controls and components hadn't been conceived yet or, if it had, was beyond the technical capacity of Windows. But today we dwell in a Tinkertoy world where programmers cobble apps from pluggable pieces. If the user changes the menu font or system colors, you want to update your control automatically—and don't bother the app about it, please!
A You're right to look for a way to handle WM_SETTINGCHANGE on your own. Polite programmers always strive to make their code as self-contained and easy to use as possible—even for other programmers. But alas, Windows was designed in the bygone time of monolithic apps that exercised total control. In those days, it made sense to send messages like WM_SYSCOLORCHANGE and WM_FONTCHANGE to top-level windows— what else was there? The idea of separately installable controls and components hadn't been conceived yet or, if it had, was beyond the technical capacity of Windows. But today we dwell in a Tinkertoy world where programmers cobble apps from pluggable pieces. If the user changes the menu font or system colors, you want to update your control automatically—and don't bother the app about it, please!
So how do you handle WM_SETTINGCHANGE in your control when this message goes to the main window? By subclassing, of course. The same CSubclassWnd class I described in the previous question works here, too. Just derive from CSubclassWnd and override the virtual WindowProc function to handle WM_SETTINGCHANGE. If you don't want to use CSubclassWnd, you can write your own class and window proc—but one way or another you have to subclass the main window in order to intercept WM_SETTINGCHANGE. Then you can examine WPARAM to see what changed. If it's the font, update your font. In practice, many programmers don't bother to check WPARAM, but simply update all stored parameters whenever any one of them changes:
case WM_SETTINGCHANGE:
UpdateMySystemSettings();
break;
Of course, to subclass the main window you have to find it first. If you have a handle or CWnd pointer to the client window, you can call GetParent repeatedly until you get the top-level window, or use MFC's CWnd::GetTopLevelParent. If you don't have a client window, you can call AfxGetMainWnd. And if that fails too, you can use the technique I described in my July 2002 column (see C++ Q&A: Get the Main Window, Get EXE Name): call EnumWindows to iterate all the top-level windows, looking for one whose process ID matches the running process. A process can have more than one top-level window, but for the purpose of catching WM_SETTINGCHANGE, any one will do. Just make sure you don't find a modeless dialog that's transient (hint: check the window class name).
Since handling WM_SETTINGCHANGE is a useful thing to do, I implemented a little class, CSystemChange, that encapsulates the grungies. In my March 2006 column, I described how to implement an event mechanism for C++ (see C++ At Work: Event Programming, Part 2). CSystemChange is a singleton C++ class that catches WM_SETTINGCHANGE and several other main-window system-change messages, and translates them into C++ events any class can handle—even non-window classes. Just derive from ISystemChangeEvents, override the handler functions for the events you want to handle, and call CSystemChange::Register to register your target class:
// during object initialization
theSystem.Register(this);
Just like MFC's theApp, theSystem is a global singleton CSystemChange object. It raises the events when the system changes. Actually, there are two theSystem symbols: CSystemChange::theSystem is a static data member of CSystemChange; ::theSystem is a global reference to CSystemChange::theSystem. In code, it looks like this:
CSystemChange CSystemChange::theSystem;
CSystemChange& theSystem = CSystemChange::theSystem;
This syntactic sleight-of-hand lets me keep the CSystemChange constructor private and thus prevents anyone from creating another instance. There's no need to create another CSystemChange object, since one global is all you need for any client to register.
Figure 4 shows the event interface ISystemChangeEvents. It has an event SettingChange (OnSettingChange method) that's raised when Windows sends WM_SETTINGCHANGE, as well as events for other system-changed messages like WM_SYSCOLORCHANGE, WM_FONTCHANGE, and WM_DISPLAYCHANGE. You can handle these events by overriding the corresponding ISystemChangeEvents methods (for example, OnDisplayChange).
 Figure 4 SysChange
SysChange.h
#pragma once
#include "Subclass.h"
#include "EventMgr.h"
/////////////////
// Interface for system change events. To handle system-change events,
// derive from this and override the handlers you want.
//
class ISystemChangeEvents {
DECLARE_EVENTS(ISystemChangeEvents);
public:
DEFINE_EVENT2(ISystemChangeEvents, SettingChange, UINT, LPCTSTR);
DEFINE_EVENT0(ISystemChangeEvents, SysColorChange);
DEFINE_EVENT0(ISystemChangeEvents, FontChange);
DEFINE_EVENT0(ISystemChangeEvents, TimeChange);
DEFINE_EVENT2(ISystemChangeEvents, InputLangChange, UINT, LANGID);
DEFINE_EVENT0(ISystemChangeEvents, UserChanged);
DEFINE_EVENT2(ISystemChangeEvents, DisplayChange, UINT, CSize);
DEFINE_EVENT2(ISystemChangeEvents, DeviceChange, UINT, DWORD_PTR);
DEFINE_EVENT0(ISystemChangeEvents, ThemeChanged);
};
IMPLEMENT_EVENTS(ISystemChangeEvents);
//////////////////
// Singleton class to raise system change events.
//
class CSystemChange : public CSubclassWnd
{
protected:
CEventMgr<ISystemChangeEvents> m_eventmgr;
CSystemChange() { }
~CSystemChange() { }
static HWND FindMainWindow();
public:
static CSystemChange theSystem;
BOOL Register(ISystemChangeEvents* client, CWnd* pWnd=NULL);
BOOL Unregister(ISystemChangeEvents* client)
{
m_eventmgr.Unregister(client);
}
virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
};
// Global, like theApp. By making it a reference, I can keep ctor
// private and prevent anyone from creating an instance.
extern CSystemChange& theSystem;
SysChange.cpp
#include "StdAfx.h"
#include "SysChange.h"
#include "WinList.h"
...
// Global system object raises changed events:
CSystemChange CSystemChange::theSystem;
CSystemChange& theSystem = CSystemChange::theSystem;
//////////////////
// Register object to receive system-change events. Optional window used
// to find main top-level window. Otherwise will try to find it myself.
//
BOOL CSystemChange::Register(ISystemChangeEvents* client, CWnd* pWnd)
{
if (m_hWnd==NULL) {
pWnd = pWnd ? pWnd->GetTopLevelParent() : AfxGetMainWnd();
HWND hwnd = pWnd->GetSafeHwnd();
if (hwnd==NULL) {
hwnd = FindMainWindow();
}
ASSERT(hwnd); // app has no main window?!
VERIFY(HookWindow(hwnd));
}
m_eventmgr.Register(client);
return TRUE;
}
//////////////////
// Find (a) main window for current running process: look for top-level
// window with same process ID.
//
HWND CSystemChange::FindMainWindow()
{
DWORD myProcessId = GetCurrentProcessId();
CWinList wins;
for (WINLIST::iterator it=wins.begin(); it!=wins.end(); it++) {
HWND hwnd = *it;
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (pid==myProcessId) {
return hwnd;
}
}
return NULL;
}
//////////////////
// Main window got a message: Look for system change messages.
//
LRESULT CSystemChange::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_SETTINGCHANGE:
m_eventmgr.Raise(ISystemChangeEvents::SettingChange((UINT)wp,
(LPCTSTR)lp));
break;
case WM_SYSCOLORCHANGE:
m_eventmgr.Raise(ISystemChangeEvents::SysColorChange());
break;
.
. // etc.
.
}
return CSubclassWnd::WindowProc(msg, wp, lp);
}
To show how it works in practice, I wrote a test program, SysMon, that monitors all the different system-changed events. SysMon uses a class, CTraceSysChange, that handles ISystemChangeEvents by displaying a message in a client-supplied window.
Figure 5 shows the messages displayed when I changed the theme on my computer. The "what" parameter for OnSettingChange is the SystemParametersInfo code for the system setting that changed; for example, code 13 (0x000D) corresponds to SPI_ICONHORIZONTALSPACING.
Figure 5 Displaying Messages
Internally, CSystemChange holds a data member, CEventMgr<ISystemChangeEvents>, that uses the template class CEventMgr defined in EventMgr.h to instantiate an event manager for ISystemChangeEvents. (Read my March 2006 column for a description of CEventMgr.) CSystemChange::Register calls CEventMgr::Register to register the client with the event manager. Register also calls a helper function FindMainWindow to find the main window and then subclasses it. You can pass a pointer to a CWnd to use for finding the main window; otherwise, FindMainWindow looks for AfxGetMainWnd or a top-level window with the same process ID as the running process. Once CSystemChange subclasses the main window, CSystemChange::WindowProc handles the various setting-change messages by converting WPARAM and LPARAM to type-safe parameters and raising the corresponding event. For example:
// in CSystemChange::WindowProc
switch (msg) {
case WM_SETTINGCHANGE:
m_eventmgr.Raise(ISystemChangeEvents::SettingChange((UINT)wp,
(LPCTSTR)lp));
break;
You can use CSystemChange for all your system-changed event needs. Figure 6 shows the messages handled. For full details, see Figure 4 or download the source at the MSDN®Magazine Web site.
 Figure 6 Messages Handled by CSystemChange
| Message |
Description |
| WM_SETTINGCHANGE |
SystemParametersInfo or policy settings have changed. WPARAM and LPARAM tell what changed. |
| WM_SYSCOLORCHANGE |
System colors have changed—these are the colors used with GetSysColor. |
| WM_FONTCHANGE |
A font was added or removed from the system. |
| WM_TIMECHANGE |
The system time was changed. |
| WM_INPUTLANGCHANGE |
The input language was changed. WPARAM is the new character set and LPARAM the locale ID. |
| WM_USERCHANGED |
The user logged on or off. |
| WM_DISPLAYCHANGE |
The display resolution changed. WPARAM is the new image depth (bits per pixel) and LPARAM is the new screen resolution. |
| WM_DEVICECHANGE |
The hardware configuration changed. See documentation for details. |
| WM_THEMECHANGED |
The Windows XP theme changed. |
Happy programming!
Send your questions and comments for Paul to cppqa@microsoft.com.
Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site, www.dilascia.com.
|
|