Wednesday, December 19, 2007

How to detect if Visual Studio 2005 SP1 is installed on Windows

If you don't need to be specific about a particular Visual Studio 2005 SKU, you can detect SP1 for a particular written language (for VSTS and TFS) or for a particular project language (for Express SKUs) using an older registry key under,

HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\Installed Components\{PatchCode}

You can find the {PatchCode} in the .msp file. In Windows 2003 and earlier platforms, you'll see this as the Revision number property in the properties Summary tab in Windows Explorer. In all cases, you can query the Summary Information stream or open the .msp file in Orca, then click the View -> Summary Information menu item.

For the English Visual Studio 2005 Service Pack 1 for Standard, Professional, and Team editions (VS80sp1-KB926601-X86-ENU) an example of the registry key above follows.

HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\Installed Components\{D93F9C7C-AB57-44C8-BAD6-1494674BCAF7}

You can also determine the service pack level of a more general, larger scope by reading a REG_DWORD registry value named SP from under,

HKEY_LOCAL_MACHINE\Software\Microsoft\DevDiv\[ProductFamily]\Servicing\8.0

…and for a specific language SKU using a sub-key,

HKEY_LOCAL_MACHINE\Software\Microsoft\DevDiv\[ProductFamily]\Servicing\8.0\[ProductEdition]\[ProductLanguage]

Product families are pretty broad in scope, and include for "Whidbey":

URT (.NET Framework, once known as the Universal Runtime)
VB (Microsoft Visual Basic 2005 Express)
VC (Microsoft Visual C++ 2005 Express)
VCS (Microsoft Visual C# 2005 Express)
VJS (Microsoft Visual J# 2005 Express)
VNS (Microsoft Visual Web Developer 2005 Express)
VS (Visual Studio 2005 Standard, Professional, Team Suite, etc.)
VSTF (Visual Studio 2005 Team Foundation Services)
An example using the English Visual Studio 2005 Team Suite Service Pack 1 of the registry keys above follows. Under each key you would find a registry value named SP set to 1.

HKEY_LOCAL_MACHINE\Software\Microsoft\DevDiv\VS\Servicing\8.0
HKEY_LOCAL_MACHINE\Software\Microsoft\DevDiv\VS\Servicing\8.0\VSTS\1033

Visual Studio 2005 Express SKUs will always use the ProductEdition of EXP, so you can combine the ProductFamily values documented above with EXP to form registry keys like the following, for Visual C# 2005 Express.

HKEY_LOCAL_MACHINE\Software\Microsoft\DevDiv\VCS\Servicing\8.0
HKEY_LOCAL_MACHINE\Software\Microsoft\DevDiv\VCS\Servicing\8.0\EXP\1033

Sunday, December 16, 2007

Locking header control in MFC to prevent the column to be resized

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.



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;
•••
}


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.

Sunday, December 02, 2007

How lucky you are if you read this text

Docteur Phillip M Harter, Professeur à l'école de Médecine de l'Université de Stanford .

Si on pouvait réduire la population du monde en un village de 100 personnes tout en maintenant les proportions de tous les peuples existants sur la Terre, ce village serait ainsi composé :

57 asiatiques,

21 européens,

14 américains (Nord, Centre et Sud)

8 africains.

Il y aurait :

52 femmes et 48 hommes

30 blancs et 70 non blancs

30 chrétiens et 70 non chrétiens

89 hétérosexuels et 11 homosexuels

6 personnes posséderaient 59% de la richesse totale et tous les 6 seraient américains

80 vivraient dans des maisons vétustes

70 seraient analphabètes

50 souffriraient de malnutrition

1 serait en train de mourir

1 serait en train de naître

1 posséderait un ordinateur

1 (oui, un seulement) aurait un diplôme universitaire

Si on considère le monde de cette manière, le besoin d'accepter et de comprendre devient évident.

Prenez en considération aussi ceci :

Si vous vous êtes levé ce matin avec plus de santé que de maladie, vous êtes plus chanceux que le million de personnes qui ne verra pas la semaine prochaine.

Si vous n'avez jamais été dans le danger d'une bataille, la solitude de l'emprisonnement, l'agonie de la torture, l'étau de la faim, vous êtes mieux que 500 millions de personnes.

Si vous pouvez aller à l'église ou dans un temple sans peur d'être menacé, torturé ou tué, vous avez plus de chance que 3 milliards de personnes.

Si vous avez de la nourriture dans votre réfrigérateur, des habits sur vous, un toit sur votre tête et un endroit pour dormir, vous êtes plus riche que 75% des habitants de la Planète.

Si vous avez de l'argent à la banque, dans votre portefeuille ou de la monnaie dans une petite boite, vous faite partie des 8% les plus privilégiés du monde.

Si vos parents sont encore vivants et toujours mariés, vous êtes des personnes réellement rares.

Si, enfin, vous lisez ce message, vous venez de recevoir un double cadeau parce que quelqu'un a pensé à vous et parce que vous ne faites pas partie des deux milliards de personnes qui ne savent pas lire...