Q. I am writing a dialog-only application using MFC and I want to prevent the user from running multiple instances of it. If the app is already running, I want to activate the running instance instead of starting another instance. How can I do this in Windows® 95 and Windows NT®?
Everyone
A. This has got to be one of my all-time most common questions. It’s certainly in the top ten. In the old days, this was trivial: just look at the hPrevInstance (handle to previous instance) argument passed to your WinMain function. In MFC, it comes through as CWinApp::m_hPrevInstance.
CWinApp::InitInstance()
{
if (m_hPrevInstance) {
// activate it
return FALSE;
} else {
// run my app
return TRUE;
}
}
But we now live in the modern age. We have Tamagotchis, 32-bit APIs, and true multitasking! In these heady times, hPrevInstance is always NULL, even when the running instance is the second instance of your app. That’s because in Win32®, each instance of an app lives in its very own virtual universe. So, what to do?
I wrote an app, OneDlg (see Figure 1), that does what you want. Figure 2 shows the code. All the action takes place in CApp::InitInstance. The trick is to call CWnd::FindWindow with the title of your window. If such a window exists on the desktop, I activate it by calling CWnd::SetForegroundWindow. If not, I run the dialog. As an extra precaution, just in case by some slim chance someone else has written an application with the title “Only One Instance Dialog”—the chances of which are approximately smaller than the chances of your being abducted by aliens—I also send the window a special message, MY_WM_PING, and look for the magic return value OpenSesame = 0x1234abcd. It is, mind you, still theoretically possible that another app called “Only One Instance Dialog” that is not mine might actually respond to MY_WM_PING by returning the same magic value. The chances of this are even smaller, roughly the same, give or take a few orders of magnitude, as the chances that all the molecules in your body will suddenly shift in the same direction, moving you three feet to the left from where you are now. If you’re the kind of person who likes Oliver Stone movies, you can remove all doubt by using ::RegisterWindowMessage to create your own WM_XXX message value that’s guaranteed to be unique throughout the system. And remember: just because you’re paranoid doesn’t mean they’re not out to get you!
Figure 1: OneDialog
Figure 2: OneDlg.cpp
////////////////////////////////////////////////////////////////
// OneDlg 1997 Microsoft Systems Journal.
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxpriv.h> // for WM_KICKIDLE
#include "resource.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
const LRESULT OpenSesame = 0x1234abcd;
const MY_WM_PING = WM_USER;
//////////////////
// Dialog class that alters the TAB sequence and handles the RETURN key.
//
class CMyDialog : public CDialog {
public:
CMyDialog();
~CMyDialog();
DECLARE_MESSAGE_MAP()
afx_msg LRESULT OnPing(WPARAM, LPARAM);
};
////////////////////////////////////////////////////////////////
// Application class
//
class CApp : public CWinApp {
public:
CApp() { }
virtual BOOL InitInstance();
} theApp;
/////////////////
// Initialize: just run the dialog and quit.
//
BOOL CApp::InitInstance()
{
TRACE("previous instance=%p\n", m_hPrevInstance); // always NULL
CWnd *pWnd = CWnd::FindWindow(NULL, "Only One Instance Dialog");
if (pWnd && pWnd->SendMessage(MY_WM_PING)==OpenSesame) {
// My dialog already exists: activate it
pWnd->SetForegroundWindow();
} else {
// Dialog doesn't exist
CMyDialog dlg;
dlg.DoModal();
}
return FALSE;
}
////////////////////////////////////////////////////////////////
// CMyDialog
//
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_MESSAGE(MY_WM_PING, OnPing)
END_MESSAGE_MAP()
//////////////////
// Construct dialog: set everything to zero or NULL.
//
CMyDialog::CMyDialog() : CDialog(IDD_DIALOG1)
{
}
CMyDialog::~CMyDialog()
{
}
LRESULT CMyDialog::OnPing(WPARAM, LPARAM)
{
return OpenSesame;
}
Well! Judging from all the email I got, everyone and her uncle was happy to get their coolbars working with MFC, and loved my August column. I knew this topic would generate a lot of interest, but sheesh. And yes, someone did ask how to get icons to appear in his menu items, which I will treat in a future column. Today, though, I want to present an improvement to my CCoolBar class from the August issue. OK, so technically it’s a bug fix—but only because I never tested CCoolBar exhaustively. That’s what readers are for!
As a number of you pointed out, there are two problems with CFlatToolBar. First, the display bug I described and supposedly fixed in the August issue still appears on Windows NT 4.0 (but not Windows 95). Second, the flat-style toolbar and coolbar buttons don’t display properly with the CCmdUI functions SetCheck and SetRadio. I’ll tackle the harder problem first.
When one of your ON_UPDATE_COMMAND_UI handlers calls CCmdUI::SetCheck(TRUE) to “check” the button, the toolbar button appears light gray (see Figure 3), without any shadow border to give it the depressed look. That’s depressed as in pushed-in, not melancholy. As if that’s not bad enough, when you uncheck the button by calling CCmdUI::SetCheck(FALSE), the toolbar fails to display the button in its new state until you size the window or move the mouse over it. Can you believe that?
The problem has to do with a bug in MFC. (When something doesn’t work, blame MFC.) To see where the problem comes from requires delving into MFC command routing internals, which I described in my “Meandering Through the Maze of MFC Message and Command Routing” article in the July 1995 issue. Since that was eons ago and it’s such an important topic, I’m sure you won’t mind if I rehash some of that material here.
Anyone who’s ever programmed in MFC has probably implemented an ON_UPDATE_COMMAND_UI handler. Those are the little functions you write to enable and disable menu items and buttons and display them as checked or unchecked. For example, I added a new command to my sample FlatBar (FbApp) and CoolBar (CbApp) apps. View Red toggles the view from normal to “red” mode, with a toolbar button for the command (see Figure 4). To update the menu with a checkmark when the display is red, and to show the View Red toolbar button in the pressed state, I implemented a UI command handler.
void CMyView::OnUpdateViewRed(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_bRed);
}
This is where the problem lies. To understand what’s going on, you have to ask the question: what is this CCmdUI object, and where does it come from? That’s where you get into MFC command routing.
MFC uses a common class, CCmdUI, to update various kinds of user interface items—toolbar buttons, status bar panes, and menu items. The basic idea is this: MFC creates a CCmdUI object for each user interface item—button, status pane, or menu item—and routes it via your app’s message maps to any function with an ON_UPDATE_COMMAND_UI handler for that particular user interface item. There’s a different kind of CCmdUI object for each kind of user interface item. The mechanics of how a CCmdUI object gets created and updated depends on what kind of object it is. MFC updates menu items when Windows sends WM_INITMENUPOPUP. MFC’s default handler for this message looks something like this:
// highly simplified pseudo code
void CFrameWnd::OnInitMenuPopup(CMenu* pMenu...)
{
CCmdUI state; // here's the little UI object
state.m_pMenu = pMenu;
for (/* each menu item in pMenu */) {
state.m_Index = // index of menu item;
state.m_nID = // menu item ID;
state.DoUpdate(...);
}
}
I’ve greatly simplified the code to highlight the main point: MFC creates a CCmdUI object, initializes it with information about each menu item, and calls CCmdUI::DoUpdate. This magic function sends a CN_UPDATE_COMMAND_UI message through your frame’s virtual OnCmdMsg function, passing a pointer to the CCmdUI object as an argument. OnCmdMsg uses your program’s message maps to route the message to whatever object has an ON_UPDATE_COMMAND_UI handler for it—frame, view, document, or some other CCmdTarget-derived object. Once your handler gets it, you can call any of the CCmdUI functions, like CCmdUI::SetCheck to check/uncheck the UI item or CCmdUI::Enable to enable/disable it.
The clever thing is that since these functions are virtual, different kinds of CCmdUI objects can implement them differently. For example, the base class CCmdUI, used for menu items, implements SetCheck like so:
// simplified
void CCmdUI::SetCheck(int nCheck)
{
m_pMenu->CheckMenuItem(m_nIndex,
MF_BYPOSITION | (nCheck ?
MF_CHECKED : MF_UNCHECKED));
}
That is, the way you “check” a menu item is by calling CMenu::CheckItem with the appropriate flags. Note that m_pMenu and m_nIndex have already been set up by CFrameWnd::OnInitMenuPopup.
So much for menu items. For status panes and toolbar buttons, something different happens. One of the things MFC does as part of its standard idle cycle (when there are no messages in your app’s queue), in CWinThread::OnIdle, is broadcast a special MFC message, WM_IDLEUPDATECMDUI, to your frame and all its descendants—including your toolbar and status bar. The base class handler for this message, CControlBar::OnIdleUpdateCmdUI, does some stuff and then passes control to a special virtual function for control bars, OnUpdateCmdUI. Toolbars implement OnUpdateCmdUI like so:
void CToolBar::OnUpdateCmdUI(...)
{
CToolCmdUI state; // different kind of CCmdUI!
state.m_pOther = this;
for (/* each button in toolbar */)
state.m_nID = // button ID
state.DoUpdate(...);
}
}
CToolBar uses a different CCmdUI class, CToolCmdUI, to update your toolbar buttons. CToolCmdUI is derived from CCmdUI and implements the virtual function CCmdUI::SetCheck differently.
void CToolCmdUI::SetCheck(int nCheck)
{
CToolBar* pToolBar = (CToolBar*)m_pOther;
UINT nNewStyle;
if (nCheck == 1)
nNewStyle |= TBBS_CHECKED;
else if (nCheck == 2)
nNewStyle |= TBBS_INDETERMINATE;
pToolBar->SetButtonStyle(m_nIndex, nNewStyle);
}
CStatusBar uses yet another CCmdUI-derived class, CStatusCmdUI, with a SetCheck function that gives the pane the outdented (as opposed to indented) look. You don’t need to understand all the details—the important thing is that MFC uses different kinds of CCmdUI objects to update different kinds of user interface items. The basic CCmdUI for menus has a SetCheck function that adds a checkmark to the menu item. CToolCmdUI::SetCheck has a different implementation that changes the button style. CStatusCmdUI::SetCheck changes the style of the status pane. The way CCmdUI objects are routed through the system (via DoUpdate, OnCmdMsg, and CN_UPDATE_COMMAND_UI) is the same for every kind of CCmdUI object; what’s different is where, when and how the CCmdUI object is created, and how it implements functions like SetCheck, Enable, and SetText.
You never have to worry about all these different kinds of CCmdUI objects since MFC creates the appropriate one for each user interface item at the proper time and routes it to your handler functions. It really is a cool way of updating user interface items.
Now that I’ve shown you all that, you’re in a position to understand where the problem for CoolBars is. MFC’s implementation of CToolCmdUI::SetCheck sets the toolbar button style to TBBS_CHECKED if the nCheck parameter is 1. (By the way, I bet you didn’t know you could use the value 2 to set an “indeterminate” state.) TBBS_CHECKED is MFC’s equivalent of TBSTATE_CHECKED. For old-style toolbars, setting the state to TBSTATE_CHECKED causes the toolbar to draw the button in the pressed state. This is what it means for a toolbar button to be checked. The new toolbar and rebar/coolbar in the Microsoft Internet Explorer (IE) 4.0 common controls in COMCTL32.DLL, however, do something else. When you set TBSTATE_CHECKED with these new toolbar window classes, the button gets the strange gray-without-border look shown in Figure 3. To get the depressed look, you have to set the style to TBBS_PRESSED, not TBBS_CHECKED. Don’t ask me why, that’s just the way it is. Sigh.
Figure 3: FlatBar
Figure 4: View Red coolbar
You probably already have an idea how to fix this. The idea is to invent a new CCmdUI class, CFlatOrCoolBarCmdUI, with a SetCheck function that uses TBBS_PRESSED instead of TBBS_CHECKED. I also had to implement CFlatBar::OnUpdateCmdUI and CCoolBar::OnUpdateCmdUI to use it. The details are a bit tedious, but my new, improved CFlatBar and CCoolBar classes do it right (see Figures 5 and 6). Even though I only had to change a few lines of code, I had to copy the whole shebang from MFC since MFC doesn’t provide the virtual functions needed to tweak the behavior slightly.
Figure 5: FlatBar
FlatBar.h
////////////////////////////////////////////////////////////////
// CFlatToolBar 1997 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// This code compiles with Visual C++ 5.0 on Windows 95
//
#ifndef TBSTYLE_FLAT
#define TBSTYLE_FLAT 0x0800 // (in case you don't have the new commctrl.h)
#endif
//////////////////
// "Flat" style tool bar. Use instead of CToolBar in your CMainFrame
// or other window to create a tool bar with the flat look.
//
// CFlatToolBar fixes the display bug described in the article. It also has
// overridden load functions that modify the style to TBSTYLE_FLAT. If you
// don't create your toolbar by loading it from a resource, you should call
// ModifyStyle(0, TBSTYLE_FLAT) yourself.
//
class CFlatToolBar : public CToolBar {
public:
BOOL LoadToolBar(LPCTSTR lpszResourceName);
BOOL LoadToolBar(UINT nIDResource)
{ return LoadToolBar(MAKEINTRESOURCE(nIDResource)); }
protected:
DECLARE_DYNAMIC(CFlatToolBar)
virtual void OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler);
DECLARE_MESSAGE_MAP()
afx_msg void OnWindowPosChanging(LPWINDOWPOS lpWndPos);
};
FlatBar.cpp
////////////////////////////////////////////////////////////////
// CFlatToolBar 1997 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#include "StdAfx.h"
#include "FlatBar.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
////////////////////////////////////////////////////////////////
// CFlatToolBar--does flat tool bar in MFC.
//
IMPLEMENT_DYNAMIC(CFlatToolBar, CToolBar)
BEGIN_MESSAGE_MAP(CFlatToolBar, CToolBar)
ON_WM_WINDOWPOSCHANGING()
ON_WM_WINDOWPOSCHANGED()
END_MESSAGE_MAP()
////////////////
// Load override modifies the style after loading toolbar.
//
BOOL CFlatToolBar::LoadToolBar(LPCTSTR lpszResourceName)
{
if (!CToolBar::LoadToolBar(lpszResourceName))
return FALSE;
ModifyStyle(0, TBSTYLE_FLAT); // make it flat
return TRUE;
}
//#define ILLUSTRATE_DISPLAY_BUG // remove comment to see the bug
//////////////////
// MFC doesn't handle moving a TBSTYLE_FLAT toolbar correctly.
// The simplest way to fix it is to repaint the old rectangle and
// toolbar itself whenever the toolbar moves.
//
void CFlatToolBar::OnWindowPosChanging(LPWINDOWPOS lpwp)
{
CToolBar::OnWindowPosChanging(lpwp);
Figure 6: CoolBar
CoolBar.h
////////////////////////////////////////////////////////////////
// CCoolBar 1997 Microsoft Systems Journal.
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
//////////////////
// CCoolBar encapsulates IE 4.0 common coolbar for MFC.
//
class CCoolBar : public CControlBar {
protected:
DECLARE_DYNAMIC(CCoolBar)
public:
CCoolBar();
virtual ~CCoolBar();
BOOL Create(CWnd* pParentWnd, DWORD dwStyle,
DWORD dwAfxBarStyle = CBRS_ALIGN_TOP,
UINT nID = AFX_IDW_TOOLBAR);
// Message wrappers
BOOL GetBarInfo(LPREBARINFO lp)
{ ASSERT(::IsWindow(m_hWnd));
return (BOOL)SendMessage(RB_GETBARINFO, 0, (LPARAM)lp); }
BOOL SetBarInfo(LPREBARINFO lp)
{ ASSERT(::IsWindow(m_hWnd));
return (BOOL)SendMessage(RB_SETBARINFO, 0, (LPARAM)lp); }
BOOL GetBandInfo(int iBand, LPREBARBANDINFO lp)
{ ASSERT(::IsWindow(m_hWnd));
return (BOOL)SendMessage(RB_GETBANDINFO, iBand, (LPARAM)lp); }
BOOL SetBandInfo(int iBand, LPREBARBANDINFO lp)
{ ASSERT(::IsWindow(m_hWnd));
return (BOOL)SendMessage(RB_SETBANDINFO, iBand, (LPARAM)lp); }
BOOL InsertBand(int iWhere, LPREBARBANDINFO lp)
{ ASSERT(::IsWindow(m_hWnd));
return (BOOL)SendMessage(RB_INSERTBAND, (WPARAM)iWhere, (LPARAM)lp); }
BOOL DeleteBand(int nWhich)
{ ASSERT(::IsWindow(m_hWnd));
return (BOOL)SendMessage(RB_INSERTBAND, (WPARAM)nWhich); }
int GetBandCount()
{ ASSERT(::IsWindow(m_hWnd));
return (int)SendMessage(RB_GETBANDCOUNT); }
int GetRowCount()
{ ASSERT(::IsWindow(m_hWnd));
return (int)SendMessage(RB_GETROWCOUNT); }
int GetRowHeight(int nWhich)
{ ASSERT(::IsWindow(m_hWnd));
return (int)SendMessage(RB_GETROWHEIGHT, (WPARAM)nWhich); }
protected:
// new virtual functions you must/can override
virtual BOOL OnCreateBands() = 0; // return -1 if failed
virtual void OnHeightChange(const CRect& rcNew);
// CControlBar Overrides
virtual CSize CalcFixedLayout(BOOL bStretch, BOOL bHorz);
virtual CSize CalcDynamicLayout(int nLength, DWORD nMode);
virtual void OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler);
// message handlers
DECLARE_MESSAGE_MAP()
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
For those of you who don’t mind the gray-without-borders checked look, I still let you specify TBBS_CHECKED by calling pCmdUI->Enable(3). The only advantage to using TBBS_CHECKED is that Windows highlights the button as you move the mouse over it; with TBBS_PRESSED, nothing happens when you move the mouse over the button. (It’s possible to fix this, too, but that requires more lines of code than I can describe here.)
Also, with TBBS_CHECKED you get the other display bug I mentioned at the outset: the button fails to update itself when going from checked to unchecked states. Here’s the fix:
if (nNewStyle != nOldStyle) {
·
·
·
pToolBar->Invalidate(); // repaint
}
This forces the toolbar to repaint itself when the state changes. All I really need to paint is the one button, so I should’ve gotten the button rectangle and called InvalidateRect instead of Invalidate. What can I say, I’m a lazy kind of guy. (The best programmers are lazy; they only implement things once.) It’s no big deal because I’ve enclosed the entire setting of the style and invalidating of the window within the conditional clause for (nNewStyle != nOldStyle). This prevents the flicker that would otherwise result from obsessively setting the state to the same value on each idle update cycle—something the MFC programmers could have done too, for more efficiency. (There sure is a lot of stuff that goes on with every idle cycle in a typical MFC app!)
Before leaving SetCheck, I should mention one other thing: some readers have pointed out that the TBBS_GROUP flag doesn’t work properly with the new IE 4.0 flat toolbar and coolbar. I’ll take your word for it, but there’s no reason to bother fixing it when you can trivially implement an ON_UPDATE_COMMAND_UI handler for each button. In fact, if you give the buttons in your group sequential IDs starting at ID_BASE, you can use the same handler for each one:
void CMyView::OnUpdateGroupButton(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck((pCmdUI->nID - ID_BASE) ==
m_iState);
}
Here, m_iState is a CMyView member that is assumed to have values from 0 to n–1, depending on which button or option is selected. If m_iState has the value i, then the i+1 button will be checked and all the others unchecked.
Now, finally, on to the Windows NT display bug. Fortunately, this one is easier. In the August issue, I described a display update bug that caused the CFlatBar buttons to not get updated properly when you drag the toolbar from one location to another in the toolbar area. I fixed it by invalidating the old toolbar location in OnWindowPosChanging, then posting a WM_NCPAINT message to repaint the old toolbar location. Well, for some reason this doesn’t work under Windows NT because WM_NCPAINT gets handled right away, before the window position changes, so Windows NT repaints the toolbar before it’s moved. The solution is to send—not post—WM_NCPAINT from OnWindowPosChanged—not OnWindowPosChanging. This ensures that the toolbar is not repainted until after Windows has moved it. Thanks to my friend Tim Anderson for helping me test this, and to reader John Taswell who was the first to discover this problem.
To obtain complete source code listings, see the MSJ Web site at
http://www.microsoft.com/msj/.
Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com