C++ Q&A: Locking Column Headers, Implementing S...

We were unable to locate this content in de-de.

Here is the same content in en-us.

C++ Q&A
Locking Column Headers, Implementing Singleton Classes
Paul DiLascia

Code download available at: CQA0306.exe (137 KB)
Browse the Code Online

Q I have developed an ActiveX® Template Library (ATL) composite control, containing a ListView and Buttons, specifically for use in an ASP Web application. Now I want to prevent the sizing of the column headers. I have tried to use messages such as HDN_TRACK, HDN_BEGINTRACK, and HDN_ENDTRACK, replacing the ID=0 in the message map notification handler with my ID. I even tried the ITEM_CHANGING message, but all to no avail.
Q I have developed an ActiveX® Template Library (ATL) composite control, containing a ListView and Buttons, specifically for use in an ASP Web application. Now I want to prevent the sizing of the column headers. I have tried to use messages such as HDN_TRACK, HDN_BEGINTRACK, and HDN_ENDTRACK, replacing the ID=0 in the message map notification handler with my ID. I even tried the ITEM_CHANGING message, but all to no avail.
Deryl Francis Dsouza

A You're on the right track with HDN_BEGINTRACK, but you may have missed a detail. When a user drags the mouse over the size bars in the column header, the header control sends its parent an HDN_BEGINTRACK notification. The way to prevent sizing is to eat this notification (without passing it to the header control), but as with many other Windows® messages and notifications, HDN_BEGINTRACK comes in two flavors: HDN_BEGINTRACKW (wide-character, Unicode) and HDN_BEGINTRACKA (ANSI). The "neuter" symbol is #defined to one or the other of these, based on the value of UNICODE defined in your project, as shown here:
// From commctrl.h
#ifdef UNICODE
#define HDN_BEGINTRACK HDN_BEGINTRACKW
#else
#define HDN_BEGINTRACK HDN_BEGINTRACKA
#endif
A You're on the right track with HDN_BEGINTRACK, but you may have missed a detail. When a user drags the mouse over the size bars in the column header, the header control sends its parent an HDN_BEGINTRACK notification. The way to prevent sizing is to eat this notification (without passing it to the header control), but as with many other Windows® messages and notifications, HDN_BEGINTRACK comes in two flavors: HDN_BEGINTRACKW (wide-character, Unicode) and HDN_BEGINTRACKA (ANSI). The "neuter" symbol is #defined to one or the other of these, based on the value of UNICODE defined in your project, as shown here:
// From commctrl.h
#ifdef UNICODE
#define HDN_BEGINTRACK HDN_BEGINTRACKW
#else
#define HDN_BEGINTRACK HDN_BEGINTRACKA
#endif
So when you implement a handler for HDN_BEGINTRACK, you're actually implementing it for HDN_BEGINTRACKA or HDN_BEGINTRACKW, depending on the value of UNICODE. But which message does the header control actually send? Remember, the header control is part of Windows, one of the common controls in comctl32.dll. Since the DLL is already compiled into executable code, changing the value of UNICODE in your project has absolutely no effect on its operation. How does the header control know which flavor of notification to send—A or W?
The answer lies in an oft-forgotten message, WM_NOTIFYFORMAT. When a control is first created, it sends a message to its parent, in effect asking, "do you want ANSI or Unicode notifications?" The parent responds with NFR_ANSI or NFR_UNICODE. If the parent doesn't handle WM_NOTIFYFORMAT, the Windows DefWindowProc responds based on the preference of the parent window or dialog itself. The default is Unicode. So I suspect the reason you had no luck trapping HDN_BEGINTRACK is that you compiled an ANSI program without handling WM_NOTIFYFORMAT. Your application is looking for HDN_BEGINTRACKA (ANSI) while the header control is sending HDN_BEGINTRACKW (Unicode).
One way to fix the problem is to implement a WM_NOTIFYFORMAT handler for your list control, one that returns NFR_ANSI. When I tried this, the header control did indeed send HDN_BEGINTRACKA and I was able to prevent sizing. But using NFR_ANSI broke other features. For example, the list control no longer repaints its columns while sizing.
A simpler, more reliable way to prevent sizing header columns is to implement handlers for both HDN_BEGINTRACKA and HDN_BEGINTRACKW. This not only obviates the need to process WM_NOTIFYFORMAT, it lets your code work in both ANSI and Unicode modes. I wrote a little program, LockHeader, that shows how to do it. LockHeader has a menu command View | Lock Columns that's similar to the Lock Toolbars command in Explorer (see Figure 1). When the user invokes View | Lock Columns, LockHeader prevents sizing the column headers. Invoking the command again unlocks the columns.
Figure 1 Lock Columns 
Figure 2 shows the source, which is relatively straightforward. The header control sends HDN_XXX notifications to the parent (list control) window, but when using MFC you can use message reflection to handle the notifications in the header itself. Since the "lockable columns" feature is more a property of the header than the list control, this is the approach I chose. If you're not using MFC, you'll have to handle these notifications in the list control. To do message reflection, you can use ON_NOTIFY_REFLECT in your header control's message map or simply override the virtual function OnChildNotify, as shown here:
BOOL CLockableHeader::OnChildNotify(
 UINT msg, WPARAM wp, LPARAM lp, LRESULT* pRes)
{
  NMHDR& nmh = *(NMHDR*)lp;
  if (nmh.code==HDN_BEGINTRACKW || nmg.code==HDN_BEGINTRACKA)
    return *pRes=TRUE;
  •••
}

Header.h
#pragma once

//////////////////
// Header control with lockable columns.
// Call Lock to lock/unlock them.
//
class CLockableHeader : public CHeaderCtrl
{
public:
   CLockableHeader() : m_bLocked(FALSE) { }

   // turn locking on/off
   void Lock(BOOL bLock) {
      m_bLocked = bLock;
   }

   // find out if locked
   BOOL IsLocked() {
      return m_bLocked;
   }

protected:
   BOOL m_bLocked;   // columns are locked
   virtual BOOL OnChildNotify(UINT msg, WPARAM wp, LPARAM lp,
                              LRESULT* pRes);
   afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
   DECLARE_MESSAGE_MAP();
};
Header.cpp
#include "stdafx.h"
#include "Header.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

BEGIN_MESSAGE_MAP(CLockableHeader, CHeaderCtrl)
   ON_WM_SETCURSOR()
END_MESSAGE_MAP()

//////////////////
// If columns are locked, don't display size cursor.
//
BOOL CLockableHeader::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg)
{
   return m_bLocked ? TRUE :
      CHeaderCtrl::OnSetCursor(pWnd, nHitTest, msg);
}

//////////////////
// The header will send either HDN_BEGINTRACKA or HDN_BEGINTRACKW
// depending on how your parent list control responds to
// WM_NOTIFYFORMAT (default=HDN_BEGINTRACKW, Unicode). But if you're
// implementing a control to be used in any app, you should handle both
// notifications if you want it to work in either situation.
//
// Since OnChildNotify is virtual, all you have to do is override
// it—no need for message map entries to handle HDN_BEGINTRACKA/W.
//
BOOL CLockableHeader::OnChildNotify(UINT msg, WPARAM wp, LPARAM lp, 
   LRESULT* pRes)
{
   NMHDR& nmh = *(NMHDR*)lp;
   if (nmh.code == HDN_BEGINTRACKA || nmh.code == HDN_BEGINTRACKW) {
      if (m_bLocked)
         return *pRes=TRUE; // eat message to disallow sizing
   }
   // otherwise, pass to header control for default processing
   return CHeaderCtrl::OnChildNotify(msg, wp, lp, pRes);
}
Since OnChildNotify is virtual, there's no need for message map entries. All you have to do is implement it. In any given application, the header will send one or the other, not both. Either way, CLockableHeader eats the notification—that is, returns TRUE (handled) without passing on to the default header control. CLockableHeader controls locking through a flag m_bLocked which the app can set by calling CLockableHeader::Lock.
If you're going to prevent sizing, you should also disable the size cursor. Otherwise users may think your app is either broken or lame. Fortunately, it's trivial:
BOOL 
CLockableHeader::OnSetCursor(
  CWnd* pWnd, UINT nHit, UINT msg)
{
  return m_bLocked ? TRUE :
    CHeaderCtrl::OnSetCursor(pWnd, nHit, msg);
}
In other words: if the columns are locked, OnSetCursor returns TRUE without setting the cursor; otherwise, let the header control do its size cursor thing. Now when the columns are locked, Windows displays its standard arrow cursor instead of displaying the left-right sizing cursor.
Once you've implemented your custom header control by deriving from CHeaderCtrl, how do you get Windows to use it? The same way you would for any dialog control, by subclassing. The right place is in the parent window's OnCreate handler:
// CMyView is derived from CListView
int CMyView::OnCreate(LPCREATESTRUCT lpcs)
{
  VERIFY(CListView::OnCreate(lpcs)==0);
  return m_header.SubclassDlgItem(0,this) ? 0 : -1;
}
This works because the header control always has ID = 0. With all this in place, the only thing left to do is implement the command and UI update handlers for the View | Lock Columns command. Figure 3 shows the relevant snippets. As always, you can download the full source from the link at the top of this article.

View.h
#pragma once
#include "Header.h"

//////////////////
// Generic list view uses lockable header
//
class CMyView : public CListView {
   
   •••
   
protected:
   CLockableHeader m_header; // header control with lockable columns

   afx_msg void OnLockHeader();
   afx_msg void OnUpdateLockHeader(CCmdUI* pCmdUI);
   afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);

   DECLARE_MESSAGE_MAP()
};
View.cpp
#include "stdafx.h"
#include "View.h"
#include "resource.h"

BEGIN_MESSAGE_MAP(CMyView, CListView)
   ON_WM_CREATE()
   ON_COMMAND(ID_VIEW_LOCKHEADER, OnLockHeader)
   ON_UPDATE_COMMAND_UI(ID_VIEW_LOCKHEADER, OnUpdateLockHeader)
END_MESSAGE_MAP()

•••

//////////////////
// View created: subclass the header control, which has ID=0.
//
int CMyView::OnCreate(LPCREATESTRUCT lpcs)
{
   VERIFY(CListView::OnCreate(lpcs)==0);
   return m_header.SubclassDlgItem(0,this) ? 0 : -1;
}

//////////////////
// Initial update: add some items to the list control.
//
void CMyView::OnInitialUpdate()
{

   •••  // Add items

}

//////////////////
// View | Lock Columns command: lock them.
//
void CMyView::OnLockHeader()
{
   m_header.Lock(!m_header.IsLocked());
}

//////////////////
// Set checked state of menu command based on current locked state.
//
void CMyView::OnUpdateLockHeader(CCmdUI* pCmdUI)
{
   pCmdUI->SetCheck(m_header.IsLocked());
}

Q I was reading your answers about singleton classes in the Microsoft® .NET Framework in the February 2003 issue. Your example works when the same application tries to create more than one instance, but fails when multiple applications access the singleton class. It creates one instance for each application. How can I implement a singleton that enforces the pattern across different instances of the same application?
Q I was reading your answers about singleton classes in the Microsoft® .NET Framework in the February 2003 issue. Your example works when the same application tries to create more than one instance, but fails when multiple applications access the singleton class. It creates one instance for each application. How can I implement a singleton that enforces the pattern across different instances of the same application?
Naveen Venkatraghavan

A If you're writing a distributed application that communicates over a channel across host boundaries, then you need .NET Framework Remoting (see "Design and Develop Seamless Distributed Applications for the Common Language Runtime" by Dino Esposito in the October 2002 issue). But if all you want to do is share a small amount of simple data among multiple processes running on the same machine, you can accomplish this without Remoting.
A If you're writing a distributed application that communicates over a channel across host boundaries, then you need .NET Framework Remoting (see "Design and Develop Seamless Distributed Applications for the Common Language Runtime" by Dino Esposito in the October 2002 issue). But if all you want to do is share a small amount of simple data among multiple processes running on the same machine, you can accomplish this without Remoting.
The .NET Framework has no notion of shared objects because managed objects are created in the managed heap (which is why they're called managed), and each process has its own. DLLs, however, provide a way to share data. Normally, a DLL shares only its code, not its data. When a program loads a DLL, Windows gives each instance a private copy of the DLL's static data. But you can share it by putting it in a shared segment:
// in DLL .cpp file
#pragma data_seg (".mydata")
long foo = 0;
int bletch = 17;
#pragma data_seg ()
#pragma comment(linker, "/SECTION:.mydata,RWS")
The data_seg pragmas tell the compiler/linker to put the variables foo and bletch in a segment called "mydata". The third pragma tells the linker to make it shared (RWS = Read, Write, Shared). Now all instances of the DLL will be accessing the same data. If process A sets foo = 3, process B will see the new value.
How can you take advantage of this in your .NET Framework-based app? By using managed C++, of course! At first you might be tempted to try something like this:
#pragma data_seg (".mydata")
Singleton* theInstance = NULL;
#pragma data_seg ()
// initialization code
if (!theInstance) 
  theInstance = new Singleton();
But you'll quickly discover that managed C++ doesn't let you store global pointers to managed objects. Nor does using GCHandle circumvent the problem. If you think about it, you can see why: when you call new, the allocator creates the object in the managed heap of whatever process happens to be the one calling the initialization code, so how can other processes possibly see it? In general, the common language runtime (CLR) must be free to move objects as part of its garbage collection routine, without worrying that it will leave dangling C++ pointers. This explains why you can't store a global pointer to a managed object in managed C++. So what are you going to do?
To get around the problem, you can implement the singleton object as either raw data or an unmanaged C++ object. Either way, you can then wrap a managed class around your implementation. Figure 4 shows what I have in mind. GlobalCounter is a simple managed class that implements a global counter that's shared among all processes that use the class. To use GlobalCounter in your .NET Framework-based app, you can write:
// C#
using Counter;
•••
label1.Text = String.Format("Count={0}",
  GlobalCounter.Increment());

Counter.h
#pragma once

using namespace System;

//////////////////
// Global counter class shows how to share data among multiple
// instances of an application running on the same machine.
// Each application using GlobalCounter accesses the same underlying
// counter through the static methods of a managed wrapper class,
// GlobalCounter.
//
namespace Counter
{
   public __gc class GlobalCounter {
   public:
      GlobalCounter() { }
      __property static int get_Count(); // Count property is read-only
      static int Increment();            // increment the global counter
      static int Decrement();            // decrement the global counter
   };
}
Counter.cpp
#include "stdafx.h"
#include "Counter.h"
#include "TraceWin.h"
#using <System.dll>
using namespace Counter;

// Put the counter in a shared data segment
#pragma data_seg (".mydata")
long g_count = 0;
#pragma data_seg ()
#pragma comment(linker, "/SECTION:.mydata,RWS") // tell linker: make it 
                                                // shared

//////////////////
// Implement Count property: get counter's current value.
//
int GlobalCounter::get_Count()
{
   return g_count;
}

//////////////////
// Increment the counter. Important to use InterlockedIncrement to
// avoid contention.
//
int GlobalCounter::Increment()
{
   return InterlockedIncrement(&g_count);
}

//////////////////
// Decrement the counter. Important to use InterlockedDecrement to
// avoid contention.
//
int GlobalCounter::Decrement()
{
   return InterlockedDecrement(&g_count);
}
I put these lines in a generic Windows.Forms app, TestCounter.exe, to display the counter in a text label. There's no point printing the source here, since everything except the previous lines is boilerplate (curious-minded readers can download the full sample from the MSDN Magazine Web site), but Figure 5 shows the desired result of running TestCounter three times: all three instances increment the same global counter.
Figure 5 Running TestCounter 
How does GlobalCounter work? Counter.cpp implements a variable g_count in a shared segment, as described earlier. Increment and Decrement methods increment and decrement g_count, and get_Count returns its value as a property. Since the methods are static, applications don't have to create an object instance. Using the class name as the scope qualifier makes the counter look like a global object:
int c = GlobalCounter.Count;
int d = GlobalCounter.Decrement();
Since the underlying variable g_count is shared, it's important to use InterlockedIncrement and InterlockedDecrement to avoid contention. If you use a shared segment to implement a more complex class, don't forget to lock your member data before manipulating it. If you like, you can implement your singleton as a global static instance of an unmanaged (__nogc) class, with a managed wrapper class that accesses it through static methods.
GlobalCounter is as simple as I could make it, but the basic approach of using a shared data segment works correctly in more complex situations—up to a point. Shared data works best with "raw" data and/or unmanaged objects. If your singleton needs to manipulate other managed objects, you're likely to run into trouble. The fundamental problem is that whereas C++ lets you override operator new to write custom allocators, the .NET Framework does not. This is one of the big trade-offs of garbage collection. Thus, for example, you can't use memory-mapped files to share managed objects because there's no way to create the objects in the mapped memory. (Although you could do it by serializing or deserializing objects to or from mapped memory or by using only blittable types.) To learn more about mixing managed and unmanaged objects, you should read Tomas Restrepo's article in the February 2002 issue of MSDN Magazine. Happy programming!

Send your questions and comments for Paul to  cppqa@microsoft.com.


Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or http://www.dilascia.com.

Page view tracker