在 InstallScript 中编程用户界面






4.56/5 (6投票s)
本文介绍了在纯 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_CLICKED
或 EN_CHANGE
通知,然后手动遍历消息循环,使用 PeekMessage/GetMessage
函数,而不离开 WaitOnDialog
的 switch/case
块。第二种方法更复杂,也更可靠 - 这就是 Windows 子类化方法。我们将在最后一章介绍如何使用它。
带进度条的模态对话框
让我们来看第一个基本示例。我们将创建一个模态对话框,它的功能类似于 InstallShield 的 SdShowMsg
函数,并添加了进度条和状态消息。
首先,您需要创建一个大小与 MessageBox
相同的新对话框,并添加 ProgressBar
和 StaticText
控件来显示状态消息。
我们在资源文件中定义的对话框将类似于 SdShowMsg
对话框的定义。
- 创建一个空对话框,并将其大小调整为
MessageBox
对话框的大小 - 将其资源标识符设置为 = 20009
- 将其样式设置为模态 = TRUE
- 将其其他 Windows 样式设置为如图所示
- 创建一个
StaticText
控件 - 将其控件标识符设置为 = 1303
- 将其样式设置为
- 无前缀 = TRUE
- 无文本换行 = TRUE
- 透明 = FALSE
- 创建一个
ProgressBar
控件 - 将其控件标识符设置为 = 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_STEPIT
或 PBM_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
之外的所有样式都不是强制性的,此处选择它们是为了方便。
列表框样式 | 样式描述 |
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_HELP
和 WM_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 日:文章更新