Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Programming User Interface in InstallScript

4.56/5 (6 votes)
1 May 2011CPOL8 min read 61.9K   1.2K  
This article describes advanced techniques for programming user interface in pure InstallScript projects.

Table of Contents

Introduction

InstallScript language is generally based on C/C++ and InstallScript GUI programming uses Win32 API as its base. Despite that, InstallScript still lacks a lot of Win32 API’s advanced features. Many times, some of its functionality is removed on purpose by producer’s staff to make it more clear and usable for ”dummies” despite the fact that the language syntax can theoretically support it.

InstallScript controls wizard follows the MSI standards and misses a number of standard Windows controls, like an IP Address control, Animation control and many more.
Existing InstallShield controls miss also some control styles which are present in Win32 API, like LBS_NOSEL – a ListView control style (we will go over it later in future chapters).
All these missed controls can be created manually with CreateWindowsEx and be managed by a standard windows messaging mechanism.

A Windows notification catching is another problem.
The WaitOnDialog is a function which is intended to catch dialog events. In DLG_MSG_ALL mode, it passes through to user functions only a limited number (about 10%) of censored control notifications based on WM_COMMAND. The remaining 90% of windows messages probably are considered excessive for “dummies”. No events for static controls (Icons, Images and TextLabels) are allowed because they are automatically set to a disabled state in a resource file and their state cannot be set from the wizard.
Such approach can be found acceptable for Basic MSI projects but not in a pure InstallScript environment. The reason for this is that MSI technology initially intended to support only a limited number of windows features opposite InstallScript which appears to be a fully functional scripting language and actually has very little in common with MSI.

The events shown below are everything I was able to catch in DLG_MSG_ALL mode:

PushButton, CheckBox, RadioButtonComboBoxListBox 
BN_SETFOCUSCBN_DROPDOWNLBN_SELCHANGELVN_COLUMNCLICK
BN_KILLFOCUSCBN_SELENDOKLBN_DBLCLKLVN_HOTTRACK
BN_CLICKEDCBN_CLOSEUP  
BN_DOUBLECLICKEDCBN_SELENDCANCELListViewTreeView
  LVN_ITEMCHANGINGTVN_SELCHANGINGA
 EditBoxLVN_ITEMCHANGEDTVN_SELCHANGEDA
 EN_UPDATELVN_DELETEALLITEMSNM_DBLCLK
 EN_CHANGELVN_INSERTITEM 

Windows messages, which remain unhandled by built-in InstallScript functions, can be intercepted in two ways. The first method consists in catching BN_CLICKED or EN_CHANGE notification by using a regular built-in InstallScript mechanism of WaitOnDialog and after that in going manually over a message loop by PeekMessage/GetMessage functions without leaving the WaitOnDialog switch/case block. The second method is more sophisticated and more reliable as well - this is the windows subclassing method. We will show how to use it in the last chapter.

Modal Dialog with Progress Bar

Let’s look over our first basic example. There we create a modal dialog which works like an InstallShield SdShowMsg function with an addition of a progress bar and a status message.

First of all, you have to create a new dialog of a MessageBox size with ProgressBar and StaticText controls to show status messages.

Our dialog definition in a resource file will look like a definition for an SdShowMsg dialog.

  1. Create an empty dialog and resize it to a MessageBox dialog size
  2. Set its Resource Identifier = 20009
  3. Set its style Modal = TRUE
  4. Set its Other Windows Styles as shown here:

    MsgDialog.JPG

  5. Create StaticText control
  6. Set its Control Identifier = 1303
  7. Set its stiles to:
    1. No Prefix = TRUE
    2. No Text Wrap = TRUE
    3. Transparent = FALSE
  8. Create ProgressBar control
  9. Set its Control Identifier = 1301

Define dialog and control IDs in an InstallScript header file:

C++
#define CTRL_PROGRESS	1301
#define CTRL_STATIC	1303
#define DLG_ID		20009

Define progress bar message codes (WM_USER is already defined by InstallShield) and Win32 API functions:

C++
#define PBM_SETPOS		(WM_USER+2)
#define PBM_SETSTEP	(WM_USER+4)
#define PBM_STEPIT		(WM_USER+5)
#define PBM_SETRANGE32	(WM_USER+6)

prototype User32.UpdateWindow(HWND);

This is a ShowProgressDialog’s main function. The dialog is created by executing this function with bShow=TRUE. When the function is called a second time with the same dialog name and bShow=FALSE, the dialog destructs.
The progress bar has tickles ranged from 0 to 100 but this setting can be changed by your wish.

C++
prototype number ShowProgressDialog(string, string, BOOL);
function number  ShowProgressDialog(szDlg, szTitle, bShow)
	number nResult;
	HWND hwndDlg,hCtrl;
begin
	if(bShow) then
    		if(EzDefineDialog( szDlg, "", "", DLG_ID ) = DLG_ERR) then
        			return -1;
    		endif;

		nResult = WaitOnDialog(szDlg);

		// wait until Error or DLG_INIT message
		while(nResult !=DLG_INIT && nResult >= 0)
			nResult = WaitOnDialog(szDlg);
		endwhile;
		hwndDlg = CmdGetHwndDlg(szDlg);
		SdSetDlgTitle(szDlg, hwndDlg, szTitle);

		//initialize progress bar control
		hCtrl = GetDlgItem(hwndDlg, CTRL_PROGRESS);
	    	nResult = SendMessage(hCtrl, PBM_SETRANGE32, 0, 100);
	    	nResult = SendMessage(hCtrl, PBM_SETSTEP, 10, 0);

	    	ShowWindow(hwndDlg, SW_SHOW);
	    	User32.UpdateWindow(hwndDlg);
   	else
   		EndDialog(szDlg);
		ReleaseDialog(szDlg);
   	endif;

	return 0;
end;

Each time we want to increment the progress bar, we run the function IncrementProgressDialog.
The progress bar can be updated by sending one of the two messages, PBM_STEPIT or PBM_SETPOS.
If we want to set a tickle number directly, there should be sent a PBM_SETPOS message with a wParam equal to a tickle number:

C++
SendMessage(hDlg, PBM_SETPOS, nIndex, 0);
C++
prototype number IncrementProgressDialog(string, string);
function number IncrementProgressDialog (szDlg , szMsg)
	HWND hDlgCtrl;
begin
	hDlgCtrl = GetDlgItem(CmdGetHwndDlg(szDlg), CTRL_PROGRESS);
	CtrlSetText(szDlg, CTRL_STATIC, szMsg);
	return SendMessage(hDlgCtrl, PBM_STEPIT, 0, 0);
end;

This script shows how the Progress dialog actually works:

C++
szDlg = "ShowDialog";
szTitle = "Status dialog with progress bar";
if(ShowProgressDialog(szDlg,szTitle,TRUE)) then
	return -1;
endif;
…
//do some action
//increment progress bar and update status message
IncrementProgressDialog(szDlg,"< progress bar status message >");
…
//do some action
//increment progress bar and update status message
IncrementProgressDialog(szDlg, "<progress />");
…
ShowProgressDialog(szDlg,"",FALSE);

ListBox Control

If we try to create a standard ListBox control by an InstallShield wizard, we won’t see the LBS_NOSEL style in the “Other Windows Styles” field. An InstallShield team probably thought of style to be excessive and didn’t include it in a styles list. This style could be helpful if we want to create a read-only list box which only shows the information like a multiline read-only EditBox but with white background color instead of a grey one.

Let’s first define windows API functions and message codes:

C++
prototype HWND User32.CreateWindowExA(int, byref string, byref string, 
			int, int, int, int, int, HWND, HWND, HWND, pointer);
prototype BOOL User32.DestroyWindow(HWND);

#define LBS_NOSEL 		0x4000L
#define LBS_NOTIFY		0x0001L
#define LBS_SORT		0x0002L
#define WS_VSCROLL 	0x00200000L
#define LBS_STANDARD	(LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER)

This function creates a custom ListBox control. It must be called from the DLG_INIT section.

C++
prototype HWND CreateListBoxControl(HWND, int, int, int, int);
function HWND CreateListBoxControl (hwndDlg, nLeft, nTop, nWidth, nHeight)
	HWND hwndLB;
begin
	hwndLB = User32.CreateWindowExA (0,
		LIST_CLASS_NAME, "", WS_TABSTOP| WS_CHILD|LBS_NOSEL|LBS_STANDARD,
		nLeft, nTop, nWidth, nHeight, hwndDlg, NULL, NULL, NULL);

	ShowWindow (hwndLB, SW_SHOW);

	return hwndLB;
end;

This is a complete DLG_INIT section:

C++
case DLG_INIT:
…
	//create custom ListBox control
	hwndLB = CreateListBoxControl (hwndDlg, 170,50,200,80);

	//add text lines to ListBox
	szText = "First line";
	SendMessage(hwndLB, LB_ADDSTRING, 0, &szText);
	szText = "Second line";
	SendMessage(hwndLB, LB_ADDSTRING, 0, &szText);
	szText = "Third line";
	SendMessage(hwndLB, LB_ADDSTRING, 0, &szText);

Before destroying the dialog, we must manually destroy the ListBox window first:

C++
User32.DestroyWindow(hwndLB);

IPAddress Control

This example shows how to use the standard windows IPAddress control.
The IPAddress control is not included in InstallShield toolbar view, so it should be created manually with the CreateWindowExA function.

First let’s define missing definitions of message codes and functions:

C++
prototype HWND User32.CreateWindowExA(int, byref string, byref string, 
			int, int, int, int, int, HWND, HWND, HWND, pointer);
prototype BOOL User32.DestroyWindow(HWND);

#define WS_EX_LEFT		0x00000000L
#define WS_EX_LTRREADING	0x00000000L
#define WS_EX_CLIENTEDGE	0x00000200L

#define WC_IPADDRESSA	"SysIPAddress32"

#define IPM_CLEARADDRESS	(WM_USER+100)
#define IPM_SETADDRESS	(WM_USER+101)
#define IPM_GETADDRESS	(WM_USER+102)

This function is used in the DLG_INIT section. It creates a dialog child window from a SysIPAddress32 windows class and initializes it with a given IP address string.

C++
prototype HWND CreateIPAddressControl(HWND, int, int, int, int, string);
function HWND CreateIPAddressControl (hwndDlg, nLeft, nTop, nWidth, nHeight, szIP)
	HWND hIPControl;
	LIST list;
string	sz1,sz2,sz3,sz4;
 	number n1,n2,n3,n4,nIP;
begin
	hIPControl = User32.CreateWindowExA 
		(WS_EX_LEFT|WS_EX_LTRREADING|WS_EX_CLIENTEDGE,
		WC_IPADDRESSA, "", WS_CHILD|WS_VISIBLE|WS_TABSTOP,
		nLeft, nTop, nWidth, nHeight, hwndDlg, NULL, NULL, NULL);

	if(hIPControl=NULL) then
		return 0;
	endif;

	list = ListCreate(STRINGLIST);
	StrGetTokens(list,szIP,".");
	ListGetFirstString(list,sz1);
	ListGetNextString(list,sz2);
	ListGetNextString(list,sz3);
	ListGetNextString(list,sz4);
	ListDestroy(list);

	StrToNum(n1,sz1);
	StrToNum(n2,sz2);
	StrToNum(n3,sz3);
	StrToNum(n4,sz4);
	nIP = (n1<<24) + (n2<<16) + (n3<<8) + n4;

	SendMessage(hIPControl,IPM_CLEARADDRESS,0,0);
	SendMessage(hIPControl,IPM_SETADDRESS,0,nIP);
	SetFocus(hIPControl);

	return hIPControl;
end;

This function receives an IPAddress control handler and returns the resulting IP address string.

C++
prototype string GetIPAddress(HWND);
function string GetIPAddress(hIPControl)
string	sz1,sz2,sz3,sz4, szIP;
 	number n1,n2,n3,n4,nIP;
begin
    	SendMessage(hIPControl,IPM_GETADDRESS,0,&nIP);
    	n1 = (nIP>>24) & 0xff;
    	n2 = (nIP>>16) & 0xff;
    	n3 = (nIP>>8) & 0xff;
    	n4 = nIP & 0xff;

	NumToStr(sz1,n1);
	NumToStr(sz2,n2);
	NumToStr(sz3,n3);
	NumToStr(sz4,n4);
	szIP = sz1+"."+sz2+"."+sz3+"."+sz4;

	return szIP;
end;

Here, we combine these functions together in an InstallScript environment:

C++
case DLG_INIT:
	//create and initialize IP address window
	hIPControl  = CreateIPAddressControl(hwndDlg, 34,123,160,21,"127.0.0.1");

case NEXT:
    	//retrieve entered IP address
	szIP = GetIPAddress(hIPControl);

Before destroying the dialog manually, we need to destroy the IP address window:

C++
User32.DestroyWindow(hIPControl );

ListView Control in Grid Mode

InstallShield doesn’t have a built in grid control so, in this case, we can use a ListView control in a LVS_REPORT mode for showing a grid table.

Create a ListView control and set its window styles in a control wizard.
All styles except LVS_REPORT are not mandatory and are selected here for convenience.

ListView.JPG

ListBox styleStyle description
LVS_SHOWSELALWAYSA selection, if any, is always shown, even if a control does not have a focus.
LVS_SORTASCENDINGItem indexes are sorted based on an item text in an ascending order.
LVS_EDITLABELSAn Item text can be edited in place.
LVS_NOSORTHEADERColumn headers do not work like buttons. When this style is used in a report view, clicking a column header does not carry out an action such as sorting.
LVS_SINGLESELOnly one item at a time can be selected. By default, multiple items may be selected.

Define message codes which are not defined yet:

C++
#define LVM_SETIMAGELIST       	(LVM_FIRST + 3)
#define LVM_DELETEALLITEMS		(LVM_FIRST + 9)
#define LVM_GETNEXTITEM 		(LVM_FIRST + 12)
#define LVM_GETITEMTEXT		(LVM_FIRST + 45)
#define LVM_SETEXTENDEDLISTVIEWSTYLE	(LVM_FIRST + 54)

#define LVS_EX_FULLROWSELECT	0x00000020
#define LVS_EX_SUBITEMIMAGES    	0x00000002
#define LVS_EX_GRIDLINES        	0x00000001
#define LVS_EX_HEADERDRAGDROP   	0x00000010

#define LR_LOADFROMFILE    		0x00000010

#define LVIF_IMAGE              	0x0002
#define LVIF_STATE              	0x0008

#define ILC_COLOR4              	0x0004

#define IMAGE_ICON         		1
#define LVSIL_SMALL           	1

prototype HWND User32.LoadImage(pointer,pointer,int,int,int,int);
prototype HWND comctl32.ImageList_Create(int,int,int,int,int);
prototype int comctl32.ImageList_ReplaceIcon(HWND,int,HWND);

Define a PSTR structure to reinitialize pointers, like "ptr=&val; val=*ptr" in C/C++:

C++
typedef PSTR
begin
	string str[MAX_PATH];
end;

Here we add a new column to our grid:

C++
prototype number ListViewAddColumn(HWND, number, number, string);
function number ListViewAddColumn(hListWnd, nColID, nColSize, szColName)
	LV_COLUMN lvc;
begin
	lvc.mask 	= LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
	lvc.fmt 		= LVCFMT_LEFT;
    	lvc.iSubItem   	= nColID;
	lvc.cx         	= nColSize;
	lvc.pszText    	= &szColName;

    	return SendMessage(hListWnd, LVM_INSERTCOLUMN, nColID, &lvc);
end;

Here, we add a new line to our grid. Then, at first main column, we set a text, draw an icon and, finally receive an item’s line number.
Text strings in the main items column should be unique. All newly added lines are automatically sorted so, if we want to know a line’s real number, we need to use the return value from this function.

C++
prototype number ListViewAddItem(HWND, number, number,string);
function number ListViewAddItem(hListWnd, nItemID, nImageID, szItemName)
	LVITEM lvit;
begin
	lvit.mask 	= LVIF_TEXT|LVIF_IMAGE;
	lvit.iItem 	= nItemID;
	lvit.iImage 	= nImageID;
	lvit.iSubItem 	= 0;
	lvit.pszText 	= &szItemName;

	return SendMessage(hListWnd, LVM_INSERTITEM, 0, &lvit);
end;

Here, we set a text for secondary columns.
To place a text, we need to specify a main item’s line number and a column number of our subitem.

C++
prototype number ListViewAddSubItem(HWND, number, number, string);
function number ListViewAddSubItem(hListWnd, nItemID, nSubItemID, szSubItemName)
	LVITEM lvit;
begin
	lvit.mask = LVIF_TEXT;
	lvit.iItem = nItemID;
	lvit.iSubItem = nSubItemID;
	lvit.pszText = &szSubItemName;

	return SendMessage(hListWnd, LVM_SETITEM, 0, &lvit);
end;

Here, we retrieve a main item’s text for the selected line.

C++
prototype number ListViewGetSelectedItemText(HWND, byref string);
function number ListViewGetSelectedItemText(hListWnd, szItemName)
	 string szListText[MAX_PATH];
	 LVITEM lvit;
	 number ret;
	 PSTR pointer pstr;
begin
	ret = SendMessage(hListWnd, LVM_GETNEXTITEM, -1, LVIS_SELECTED);
	if(ret > -1) then
		lvit.mask 	= LVIF_TEXT;
		lvit.iItem 	= ret;
		lvit.iSubItem 	= 0;
		lvit.pszText    	= &szListText;
		lvit.cchTextMax	= MAX_PATH;

		SendMessage(hListWnd, LVM_GETITEMTEXT, ret, &lvit);
		pstr = lvit.pszText;

		szItemName 	= pstr->str;
		return ret;
	else
		szItemName 	= "";
		return -1;
	endif;
end;

Create columns and add lines for the ListView control.
Here, we use one icon for the first 10 lines and another icon for the remaining lines.

C++
prototype number ListViewInitialize(HWND);
function number ListViewInitialize(hwndList)
	number ret, nIdx;
	string szIdx;
begin
//LVS_EX_FULLROWSELECT  - When an item is selected, the item and 
//all its subitems are highlighted.
//LVS_EX_GRIDLINES - Displays gridlines around items and subitems.
//LVS_EX_HEADERDRAGDROP - Enables drag-and-drop reordering of columns 
//in a list-view control.
//LVS_EX_SUBITEMIMAGES - Allows images to be displayed for subitems.

	SendMessage (hwndList, LVM_SETEXTENDEDLISTVIEWSTYLE, 0,
		LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_HEADERDRAGDROP);

	SendMessage (hwndList, LVM_DELETEALLITEMS, 0, 0);

	ret = ListViewAddColumn (hwndList,0,80,"Main Column");
	ret = ListViewAddColumn (hwndList,1,80,"Sec Column #1");
	ret = ListViewAddColumn (hwndList,2,80,"Sec Column #2");

	for nIdx = 0 to 19
		NumToStr(szIdx,nIdx);
		if (nIdx < 10) then
			ret = ListViewAddItem(hwndList,0,0,"Key #"+szIdx);
		else
			ret = ListViewAddItem(hwndList,0,1,"Key #"+szIdx);
		endif;
		ListViewAddSubItem(hwndList,ret,1,"Value1 #"+szIdx);
		ListViewAddSubItem(hwndList,ret,2,"Value2 #"+szIdx);
	endfor;

	return 0;
end;

Here, we create and initialize an image list and load there 2 icons.

C++
prototype number ListViewCreateImageList(HWND,string,string);
function number ListViewCreateImageList (hwndList, szImageFile1, szImageFile2)
HWND img1,img2,himl;
begin
	img1 = LoadImage(NULL,&szImageFile1,IMAGE_ICON,16,16,LR_LOADFROMFILE);
	img2 = LoadImage(NULL,& szImageFile2,IMAGE_ICON,16,16,LR_LOADFROMFILE);

	himl = ImageList_Create(16,16,ILC_COLOR4,2,0);
	ImageList_ReplaceIcon(himl, -1, img1);
	ImageList_ReplaceIcon(himl, -1, img2);

	return SendMessage(hwndList, LVM_SETIMAGELIST, LVSIL_SMALL, himl);
end;

This example shows how to use the code below:

C++
case INIT_DLG:
	…
	hwndList = GetDlgItem(hwndDlg,nListID);
	ListViewInitialize(hwndList);
	ListViewCreateImageList(hwndList,SUPPORTDIR^"icon1.ico",SUPPORTDIR^"icon2.ico");

case NEXT:
	ret = ListViewGetSelectedItemText(hwndList,szListText);
	if(ret < 0) then
		//no row is selected
	else
		//print szListText
	endif;

Balloon Tips

The following example shows how to use _ pop-up balloon tips in Edit controls.
Balloon tips may contain a title, message and icon (information, warning, etc…).
In order to use this API functionality, you must have Comclt32.dll of version 6.0 from the latest Microsoft SDK.

This is a definition part.

C++
#define EM_SHOWBALLOONTIP		0x1503
#define EM_HIDEBALLOONTIP		0x1504
#define TTI_NONE                	0
#define TTI_INFO                	1
#define CP_ACP                   	0       		// default to ANSI code page
#define MB_PRECOMPOSED 		0x00000001  	// use precomposed chars

typedef EDITBALLOONTIP
begin
    number cbStruct;
    number pszTitle;
    number pszText;
    number ttiIcon;
end;

Here, we receive a title and message strings, translate them to Unicode and send as a message to an Edit control. The EM_SHOWBALLOONTIP message makes balloon tip to be shown.

C++
prototype number ShowBalloonTip(HWND,string,string);
function number ShowBalloonTip(hWndCtrl, szTitle, szMsg)
	EDITBALLOONTIP baloon;
    	string szBalTitleBuf[MAX_PATH], szBalTextBuf[MAX_PATH];
    	pointer pBalTitleBuf, pBalTextBuf;
begin
	pBalTextBuf = &szBalTextBuf;
	pBalTitleBuf = &szBalTitleBuf;

	MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,&szMsg, 
			StrLengthChars(szMsg)+1, pBalTextBuf, MAX_PATH);
	MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,&szTitle, 
			StrLengthChars(szTitle)+1, pBalTitleBuf, MAX_PATH);

	baloon.cbStruct = SizeOf(baloon);
	baloon.pszText = pBalTextBuf;
	baloon.pszTitle = pBalTitleBuf;
	baloon.ttiIcon = TTI_INFO;

	return SendMessage(hWndCtrl,EM_SHOWBALLOONTIP,0,&baloon);
end;

This function closes the previously shown balloon tip. It may also be closed by simply clicking on it.

C++
prototype number HideBalloonTip(HWND);
function number HideBalloonTip(hWndCtrl)
begin
	return SendMessage(hWndCtrl, EM_HIDEBALLOONTIP, 0, 0);
end;

Custom Event Handling

In the previous chapters, we obtained all missed InstallScript functionality by simply sending windows messages. The receiving of windows messages is a bit more complex task. We are going to solve it by using a windows subclassing mechanism. InstallScript unfortunately doesn’t support function pointers, so we will write the entire message handling code in an external C/C++ DLL and afterwards replace an InstallScript native windows message handling procedure with our C/C++ function.

In our C/C++ code in the first switch/case block, we catch WM_CTLCOLORSTATIC event in order to color read-only edit box to the white color.
In second and third blocks, we try to catch the WM_HELP and WM_SETFOCUS messages for the Edit box and send them back to the InstallScript. Thus we notify the InstallScript control that some action is being happening right now. It will make him to send its own notification back to the WaitOnDialog function.
This trick allows us to run the ShowBalloonTip function from the InstallScript.

Set the DS_CONTEXTHELP style in a dialog styles wizard in order to use the WM_HELP feature.
The following C/C++ code should be compiled to a simple C/C++ DLL without ATL and MFC libraries.

C++
// subclass.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include <stdio.h>

#include <process.h>
static WNDPROC oldstaticproc;
static int nCtrlID;

long __stdcall StaticProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_CTLCOLORSTATIC: //WM_CTLCOLOREDIT:
		if(GetDlgCtrlID((HWND)lParam) == nCtrlID)
		{
			//SetBkMode((HDC)wParam, TRANSPARENT);
			SetBkMode((HDC)wParam, OPAQUE);
			return (INT_PTR)(HBRUSH)GetStockObject(DC_BRUSH);
		}

	case WM_COMMAND:
		if(LOWORD(wParam) == nCtrlID)
		{
			if(HIWORD(wParam) == EN_SETFOCUS)
			{
				SendMessage(hwnd,WM_COMMAND,MAKEWPARAM
					(nCtrlID, EN_CHANGE),
					(LPARAM)GetDlgItem(hwnd, nCtrlID));
			}
			else if(HIWORD(wParam) == EN_KILLFOCUS)
			{
				SendMessage(GetDlgItem(hwnd, nCtrlID),
					EM_HIDEBALLOONTIP,0,0);
			}
		}

	case WM_HELP:
		LPHELPINFO lphlp = (LPHELPINFO)lParam;
		if(wParam == 0 && lphlp != 0)
		{
			if(GetDlgCtrlID((HWND)lphlp->hItemHandle) == nCtrlID)
			{
				SendMessage(hwnd,WM_COMMAND,
					MAKEWPARAM(nCtrlID,EN_CHANGE),
					(LPARAM)GetDlgItem(hwnd, nCtrlID));
			}
		}
	}
	return CallWindowProc(oldstaticproc, hwnd, msg, wParam, lParam);
}

extern "C" __declspec(dllexport) void SubClassWindowProc(HWND hWnd,int argCtrlID)
{
	oldstaticproc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LONG)StaticProc);
	nCtrlID = argCtrlID;
}

This is a prototype definition for an exported DLL method. It should be placed before the WaitOnDialog code block. Pay attention to the “cdecl” parameter.

C++
prototype cdecl subclass.SubClassWindowProc(HWND,int);

Here, we replace the original InstallScript windows messaging procedure with our custom C/C++ function at the initialization time and execute ShowBalloonTip when the Edit control receives focus or when a user clicks on the Edit control with a question mark.

C++
UseDLL(SUPPORTDIR^"subclass.dll");
…
nID = WaitOnDialog(szDlg);
switch(nID)

	case INIT_DLG:
		…
		hwndDlg = CmdGetHwndDlg( szDlg );
		hwndEdit = GetDlgItem(hwndDlg,1303);
		subclass.SubClassWindowProc(hwndDlg,1303);

	case 1303:
		ShowBalloonTip(hwndEdit,"EditBox Title","Tool tip help message");
…
endswitch;

…
UnUseDLL(SUPPORTDIR^"subclass.dll");

Samples

The following code samples contain all the features discussed in the above chapters:

History

  • 28th February, 2011: Initial post
  • 30th April, 2011: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)