65.9K
CodeProject 正在变化。 阅读更多。
Home

在 InstallScript 中编程用户界面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (6投票s)

2011年2月28日

CPOL

8分钟阅读

viewsIcon

63344

downloadIcon

1205

本文介绍了在纯 InstallScript 项目中进行用户界面编程的高级技术。

目录

引言

InstallScript 语言通常基于 C/C++,而 InstallScript GUI 编程则以 Win32 API 为基础。尽管如此,InstallScript 仍然缺乏 Win32 API 的许多高级功能。很多时候,一些功能被制作人员故意删除,以便让“菜鸟”更容易理解和使用,尽管语言语法理论上可以支持。

InstallScript 控件向导遵循 MSI 标准,缺少一些标准的 Windows 控件,例如 IP 地址控件、动画控件等。
现有的 InstallShield 控件也缺少一些 Win32 API 中存在的控件样式,例如 LBS_NOSEL - 一个 ListView 控件样式(我们将在未来的章节中详细介绍)。
所有这些缺失的控件都可以通过 CreateWindowsEx 手动创建,并通过标准的 Windows 消息机制进行管理。

捕获 Windows 通知是另一个问题。
WaitOnDialog 函数用于捕获对话框事件。在 DLG_MSG_ALL 模式下,它只将大约 10% 经过筛选的、基于 WM_COMMAND 的控件通知传递给用户函数。其余 90% 的 Windows 消息可能被认为对于“菜鸟”来说是过多的。不允许为静态控件(图标、图像和文本标签)设置任何事件,因为它们在资源文件中被自动设置为禁用状态,并且无法从向导中设置其状态。
这种方法对于 Basic MSI 项目来说可以接受,但在纯 InstallScript 环境中则不行。原因是 MSI 技术最初只打算支持有限数量的 Windows 功能,而 InstallScript 则是一个功能齐全的脚本语言,实际上与 MSI 几乎没有共同之处。

下面显示的事件是我在 DLG_MSG_ALL 模式下能够捕获到的所有事件

按钮、复选框、单选按钮 ComboBox ListBox  
BN_SETFOCUS CBN_DROPDOWN LBN_SELCHANGE LVN_COLUMNCLICK
BN_KILLFOCUS CBN_SELENDOK LBN_DBLCLK LVN_HOTTRACK
BN_CLICKED CBN_CLOSEUP    
BN_DOUBLECLICKED CBN_SELENDCANCEL ListView 树视图
    LVN_ITEMCHANGING TVN_SELCHANGINGA
  编辑框 LVN_ITEMCHANGED TVN_SELCHANGEDA
  EN_UPDATE LVN_DELETEALLITEMS NM_DBLCLK
  EN_CHANGE LVN_INSERTITEM  

内置 InstallScript 函数未处理的 Windows 消息可以通过两种方式进行拦截。第一种方法是使用常规的内置 InstallScript 机制 WaitOnDialog 捕获 BN_CLICKEDEN_CHANGE 通知,然后手动遍历消息循环,使用 PeekMessage/GetMessage 函数,而不离开 WaitOnDialogswitch/case 块。第二种方法更复杂,也更可靠 - 这就是 Windows 子类化方法。我们将在最后一章介绍如何使用它。

带进度条的模态对话框

让我们来看第一个基本示例。我们将创建一个模态对话框,它的功能类似于 InstallShield 的 SdShowMsg 函数,并添加了进度条和状态消息。

首先,您需要创建一个大小与 MessageBox 相同的新对话框,并添加 ProgressBarStaticText 控件来显示状态消息。

我们在资源文件中定义的对话框将类似于 SdShowMsg 对话框的定义。

  1. 创建一个空对话框,并将其大小调整为 MessageBox 对话框的大小
  2. 将其资源标识符设置为 = 20009
  3. 将其样式设置为模态 = TRUE
  4. 将其其他 Windows 样式设置为如图所示

    MsgDialog.JPG

  5. 创建一个 StaticText 控件
  6. 将其控件标识符设置为 = 1303
  7. 将其样式设置为
    1. 无前缀 = TRUE
    2. 无文本换行 = TRUE
    3. 透明 = FALSE
  8. 创建一个 ProgressBar 控件
  9. 将其控件标识符设置为 = 1301

在 InstallScript 头文件中定义对话框和控件 ID

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

定义进度条消息代码(WM_USER 已由 InstallShield 定义)和 Win32 API 函数

#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);

这是 ShowProgressDialog 的主函数。通过执行此函数并设置 bShow=TRUE 来创建对话框。当函数第二次调用时,使用相同的对话框名称和 bShow=FALSE,对话框将被销毁。
进度条的刻度范围是从 0 到 100,但您可以根据需要更改此设置。

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;

每次我们想增加进度条时,就调用 IncrementProgressDialog 函数。
进度条可以通过发送 PBM_STEPITPBM_SETPOS 消息来更新。
如果我们想直接设置刻度值,应该发送一个 PBM_SETPOS 消息,并将 wParam 设置为刻度值。

SendMessage(hDlg, PBM_SETPOS, nIndex, 0);
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;

此脚本展示了进度对话框的实际工作原理

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);

列表框控件

如果我们尝试通过 InstallShield 向导创建一个标准的 ListBox 控件,我们在“其他 Windows 样式”字段中将看不到 LBS_NOSEL 样式。InstallShield 团队可能认为此样式是多余的,未将其包含在样式列表中。如果您想创建一个只读列表框,用于显示信息,就像多行只读 EditBox 一样,但背景颜色为白色而不是灰色,那么此样式会很有帮助。

我们首先定义 Windows API 函数和消息代码

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)

此函数创建一个自定义 ListBox 控件。它必须在 DLG_INIT 部分调用。

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;

这是完整的 DLG_INIT 部分

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);

在销毁对话框之前,我们必须先手动销毁 ListBox 窗口

User32.DestroyWindow(hwndLB);

IP地址控件

此示例演示了如何使用标准的 Windows IPAddress 控件。
IPAddress 控件未包含在 InstallShield 工具栏视图中,因此需要使用 CreateWindowExA 函数手动创建。

首先,让我们定义缺失的消息代码和函数定义

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)

此函数在 DLG_INIT 部分使用。它从 SysIPAddress32 Windows 类创建一个对话框子窗口,并使用给定的 IP 地址字符串进行初始化。

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;

此函数接收一个 IPAddress 控件句柄,并返回结果 IP 地址字符串。

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;

在这里,我们将这些函数组合在 InstallScript 环境中

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);

在手动销毁对话框之前,我们需要销毁 IP 地址窗口

User32.DestroyWindow(hIPControl );

网格模式下的列表视图控件

InstallShield 没有内置的网格控件,因此在这种情况下,我们可以使用 ListView 控件的 LVS_REPORT 模式来显示网格表。

在控件向导中创建一个 ListView 控件并设置其窗口样式。
除了 LVS_REPORT 之外的所有样式都不是强制性的,此处选择它们是为了方便。

ListView.JPG

列表框样式 样式描述
LVS_SHOWSELALWAYS 即使控件没有焦点,也会始终显示选择(如果有)。
LVS_SORTASCENDING 项目索引根据项目文本按升序排序。
LVS_EDITLABELS 项目文本可以原地编辑。
LVS_NOSORTHEADER 列标题不充当按钮。当此样式在报表视图中使用时,单击列标题不会执行排序等操作。
LVS_SINGLESEL 一次只能选择一个项目。默认情况下,可以多选。

定义尚未定义的消息代码

#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);

定义一个 PSTR 结构来重新初始化指针,例如 C/C++ 中的“ptr=&val; val=*ptr

typedef PSTR
begin
	string str[MAX_PATH];
end;

在这里,我们向网格添加一列

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;

在这里,我们向网格添加一行。然后,在第一个主列中,我们设置文本,绘制图标,最后获取项目的行号。
主项目列中的文本字符串应该是唯一的。所有新添加的行都会自动排序,所以如果我们想知道行的实际编号,我们就需要使用此函数的返回值。

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;

在这里,我们为主列设置文本。
要放置文本,我们需要指定主项目的行号和子项目的列号。

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;

在这里,我们检索选定行的主项目文本。

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;

创建列并为 ListView 控件添加行。
在这里,我们为前 10 行使用一个图标,为其余行使用另一个图标。

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;

在这里,我们创建并初始化一个图像列表,并将 2 个图标加载到其中。

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;

此示例展示了如何使用下面的代码

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;

气球提示

以下示例展示了如何使用 _ 在编辑控件中显示弹出气球提示。
气球提示可以包含标题、消息和图标(信息、警告等…)。
要使用此 API 功能,您必须拥有最新 Microsoft SDK 中的 Comclt32.dll 版本 6.0。

这是定义部分。

#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;

在这里,我们接收标题和消息 strings,将它们翻译成 Unicode,并作为消息发送到编辑控件。EM_SHOWBALLOONTIP 消息会显示气球提示。

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;

此函数关闭之前显示的气球提示。它也可以通过单击它来关闭。

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

自定义事件处理

在前面的章节中,我们通过发送 Windows 消息获得了所有缺失的 InstallScript 功能。接收 Windows 消息是一个稍微复杂一些的任务。我们将通过使用 Windows 子类化机制来解决这个问题。InstallScript 不支持函数指针,因此我们将把所有的消息处理代码写在一个外部 C/C++ DLL 中,然后用我们的 C/C++ 函数替换 InstallScript 原生的 Windows 消息处理过程。

在我们的 C/C++ 代码中,第一个 switch/case 块中,我们捕获 WM_CTLCOLORSTATIC 事件,以便将只读编辑框设置为白色。
在第二个和第三个块中,我们尝试捕获编辑框的 WM_HELPWM_SETFOCUS 消息,并将它们发送回 InstallScript。这样,我们就通知 InstallScript 控件有某个操作正在发生。这将促使它将自己的通知发送回 WaitOnDialog 函数。
这个技巧允许我们从 InstallScript 中运行 ShowBalloonTip 函数。

在对话框样式向导中设置 DS_CONTEXTHELP 样式,以便使用 WM_HELP 功能。
以下 C/C++ 代码应编译成一个简单的 C/C++ DLL,不使用 ATL 和 MFC 库。

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

这是导出 DLL 方法的原型定义。它应该放在 WaitOnDialog 代码块之前。请注意“cdecl”参数。

prototype cdecl subclass.SubClassWindowProc(HWND,int);

在这里,我们在初始化时用我们自定义的 C/C++ 函数替换原始的 InstallScript Windows 消息过程,并在编辑控件获得焦点或用户单击带有问号的编辑控件时执行 ShowBalloonTip

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”);

示例

下面的代码示例包含上面章节中讨论的所有功能

历史

  • 2011 年 2 月 28 日:初始发布
  • 2011 年 4 月 30 日:文章更新
© . All rights reserved.