Windows 编程/用户界面控件
一些预定义的窗口类旨在用作用户界面控件。它们通常被称为“标准 Windows 控件”和“通用控件”。
这些 UI 控件的使用应在面向任务的类别中进行记录,而不是以面向 API 的方式进行记录。
标准 Windows 控件在 USER32.DLL (旧的 USER.EXE) 中实现,后来 (Windows XP+) 由 COMCTL32.DLL 用于 Luna 外观和感觉。
这些始终存在。不需要初始化。适用于 Windows 2,ListBox 和 ComboBox 适用于 Windows 3。
静态控件是与用户没有交互的“控件”。
静态控件是一个子窗口,它显示文本、位图图像或图标。静态控件不能被选中,也不会从键盘获得焦点。但是,它们可以通过使用SS_NOTIFY接收鼠标输入。这将允许控件通过其父级的WM_NOTIFY事件检测单击和双击。
标签应放置在其所指控件的前面。这样做时,按 Alt+热键将自动将焦点移动到下一个可能的控件,无需任何代码行。
标签也可以用于在单行编辑控件的右侧显示适当的物理单位。由于此类文本从不充当标签,因此逻辑顺序 (Z 顺序) 不重要。可以使用样式位 SS_NOPREFIX 来避免与安培符号 (&) 字符相关的問題。
- 创建标签
标签静态控件应是另一个窗口的子窗口,可以是主窗口或子窗口。要创建它,您只需使用 STATIC 类和您选择的参数调用 CreateWindow()。以下是创建它的基本代码。
/*Create a Static Label control*/ hwndLabel = CreateWindow( TEXT("STATIC"), /*The name of the static control's class*/ TEXT("Label 1"), /*Label's Text*/ WS_CHILD | WS_VISIBLE | SS_LEFT, /*Styles (continued)*/ 0, /*X co-ordinates*/ 0, /*Y co-ordinates*/ 50, /*Width*/ 25, /*Height*/ hwnd, /*Parent HWND*/ (HMENU) ID_MYSTATIC, /*The Label's ID*/ hInstance, /*The HINSTANCE of your program*/ NULL); /*Parameters for main window*/
使用标签
- 设置标签的文本
要设置标签 (静态控件) 的文本,请使用带有 WM_SETTEXT 消息的 SendMessage() 函数。
/*Setting the Label's text /*You may need to cast the text as (LPARAM)*/ SendMessage( hwndLabel , /*HWND Label*/ WM_SETTEXT, /*UINT Message*/ NULL, /*WPARAM Unused*/ (LPARAM) TEXT("Hello")); /*LPARAM Text*/
使用其他样式位,可以显示位图或图标 - 当源图像放置在同一个资源中时,无需任何代码行。
图元文件支持是在 Windows 95 中添加的。
静态控件可用于绘制分组矩形或分隔线。
按钮是具有简单用户界面的控件。
每个人都应该熟悉 Windows 按钮。它只是一个带有文本的凸起的正方形,当您单击它时通常会发生一些事情。
与所有 Windows 控件一样,按钮是您的窗口的子窗口,可以是主窗口或另一个子窗口。因此,要将其实现到您的程序中,您只需调用 CreateWindow() 函数。以下是一行代码来创建一个按钮。
- 创建按钮
// create button and store the handle HWND hwndButton = CreateWindow (TEXT("button"), // The class name required is button TEXT("Push Button"), // the caption of the button WS_CHILD |WS_VISIBLE | BS_PUSHBUTTON, // the styles 0,0, // the left and top co-ordinates 100,300, // width and height hwnd, // parent window handle (HMENU)ID_MYBUTTON, // the ID of your button hInstance, // the instance of your application NULL) ; // extra bits you don't really need
这将为您创建按钮,但它在单击时不会执行任何操作。为此,您需要进入 Windows 过程函数并处理 WM_COMMAND 事件。在 WM_COMMAND 事件中,wparam 的低位字是导致事件的子窗口的 ID。因此,要确保您收到了来自按钮的消息,请确保 ID 相同。
- 测试按钮 ID
// compare button ID to message ID if(ID_MYBUTTON == LOWORD(wparam)) { /* it's your button so do some work */ }
所以现在您知道您的按钮已被按下,您需要找出发生了什么,所以我们处理存储在 wparam 的高位字中的通知代码。您需要关注的通知代码是 BN_CLICKED。
- 测试通知
// compare Notification to message Notification if(BN_CLICKED == HIWORD(wparam)) { /* the button has been clicked do some stuff */ }
最后,您可能还需要知道的是 lparam 包含被按下的按钮的句柄。
这种类型可以实现任何东西。在许多情况下,此类按钮被禁用 (WS_DISABLED,即没有用户输入),并用于在对话框中绘制其他任何内容。在这种情况下,它充当实际绘制的方便占位符,因为对话框的像素大小以及控件的大小会根据用户的 dpi 设置和当前字体度量而变化。
从 Windows Vista 开始引入,此按钮具有一个额外的组合框下拉字段,通常用于在按下之前更改此按钮的行为。
复选框
[edit | edit source]复选框可以单独使用,也可以组合成一个组。在一个组中,可以进行多选。此外,还可以使用三态复选框。此状态应用于“不知道”或“以下选择不同”等情况,例如在 Windows 设置控制面板应用程序中。
为了正确实现复选框,应在编译时遵循以下规则,通常使用资源编辑器
- 所有复选框都应设置 BS_AUTOCHECKBOX 样式位,否则程序逻辑必须处理点击事件
- 单独的复选框或组中的第一个复选框应设置 WS_GROUP 样式位
- 其他复选框不应设置 WS_GROUP 样式位
- 复选框后的下一个控件必须设置 WS_GROUP。否则,向右/向下的箭头键将不会循环焦点,而是会进入下一个控件(可能是编辑控件)
- 复选框组必须连续排序
- 设置 WS_TABSTOP 不是必需的。通常,每个复选框都设置了此样式位。
如果这样做了,箭头键将按预期移动焦点,空格键将切换其状态,无需编写任何代码。
复选框组不应用于超过 7 个选项。如果可用选项更多,或在编译时未知选项数量,则应使用复选框列表。
单选按钮
[edit | edit source]单选按钮始终在一个至少包含 2 个按钮的组中,通常是 3 到 7 个选项。它们相互排斥,一次只能选择(选中)一个。尽管可以这样做,但程序逻辑应避免出现没有选中或选中多个单选按钮的情况。当所有按钮都设置了 BS_AUTORADIOBUTTON 样式位时,Windows(更确切地说,是在消息循环中处理的 IsDialogMessage() 函数,就像调用 DialogBox() 时一样)会自动确保这种行为,无需编写任何代码。
- 所有按钮都设置了 BS_AUTORADIOBUTTON 样式位
- 组中的第一个单选按钮具有 WS_GROUP 样式
- 其他单选按钮不具有 WS_GROUP 样式
- 单选按钮在对话框模板或 CreateWindowEx() 调用(即 Z 顺序)中以连续顺序排列。
单选按钮不应设置 WS_TABSTOP 样式位。Windows 会自动将 Tab 键的焦点设置为选中的单选按钮。
单选按钮不应用于超过 7 个选项。(这超出了良好 GUI 设计的“3 到 7 个选项”规则。)如果可用选项更多,或在编译时未知选项数量,则应使用组合框。
单选按钮组后的下一个控件应设置 WS_GROUP 位。否则,向右/向下的箭头键将无法按预期工作:焦点将跳到下一个控件,而不是在单选按钮组中循环。
示例代码
[edit | edit source]这就是使用 API 在 Win32 应用程序中实现按钮所需的一切
- 单选按钮
HWND hRadio =CreateWindow(TEXT("button"), TEXT("&Red"), WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 20, 155, 100, 30, hDlg, (HMENU)ID_RED, GetModuleHandle(NULL), NULL);
顺便说一句,几乎没有人使用 CreateWindow 创建单选按钮。通常将其放置在 RC 文件中,使用以下行
AUTORADIOBUTTON "&title",id,left,top,width,height[,morestyles]
窗口标题中的一个和号将创建一个热键功能。CreateWindow() 使用像素坐标,而 RC 文件条目使用对话框基单位来表示这四个度量值。
分组框
[edit | edit source]这不是一个真正的按钮,没有任何交互。它的类名也是“BUTTON”。此元素通常用于视觉上对单选按钮进行分组。它不会像 .NET 对应元素那样创建子元素。
分组框应放置在其视觉内容之前。如果这样做,按下 Alt+热键将自动将焦点移动到下一个可用的控件,无需编写任何代码。
滚动条
[edit | edit source]虽然滚动条可以附加到任何窗口或控件(并且本身不是带句柄的窗口),但滚动条控件是一个真正的带句柄的窗口。这些可以创建为水平方向 (SB_HORZ) 或垂直方向 (SB_VERT)。
在通用控件进度条和轨道条出现之前,有人“误用”了此控件来设置扬声器的音量或显示一个漫长过程的进度。现在,单个滚动条绝不会用于此类目的。
但是,滚动条对于与水平滚动条共享状态栏是必要的,就像我们从 Acrobat Reader 或某些办公软件中看到的那样。因为标准滚动条始终与其窗口一样长,不会更短。
编辑控件
[edit | edit source]编辑控件是文本编辑和显示的标准基本对象(通常称为文本框)。
编辑控件具有相当多的样式。
单行编辑
[edit | edit source]单行编辑控件通常用于输入简短描述、文件名、参数和数字。尽管支持 WS_VSCROLL 样式并显示一个小小的垂直滚动条,就像一个上下控件一样,但除此之外什么都不会发生。通常,使用 ES_AUTOHSCROLL 来在文本不适合给定空间时水平移动内容。字体可以以编程方式更改,但不能混合字体和/或颜色。为此,可以使用 RichEdit 控件。
多行编辑
[edit | edit source]多行编辑看起来像 Notepad.exe。实际上,记事本只是一个带有框架窗口的编辑控件,该框架窗口支持菜单、加载/保存、调整大小等功能。其他所有功能,甚至包括上下文菜单、Unicode 和从右到左支持,都已经内置在该控件中。
// create a text box and store the handle HWND hwndText = CreateWindow( TEXT("edit"), // The class name required is edit TEXT(""), // Default text. WS_VISIBLE | WS_CHILD | WS_BORDER | WS_HSCROLL | WS_VSCROLL| ES_MULTILINE | ES_AUTOHSCROLL, // the styles 0,0, // the left and top co-ordinates 100,300, // width and height hwnd, // parent window handle (HMENU)ID_MYTEXT, // the ID of your editbox hInstance, // the instance of your application NULL ); // extra bits you dont really need
以下是在示例中使用的样式
WS_BORDER | 文本区域周围的细边框。 |
WS_HSCROLL | 显示水平滚动条。 |
WS_VSCROLL | 显示垂直滚动条。 |
ES_MULTILINE | 这是一个多行文本框。 |
ES_AUTOHSCROLL | 不换行。 |
// Set the text. SendMessage(hwndText, WM_SETTEXT, 0, (LPARAM)"Hello"); // Get the text. LRESULT iTextSize = SendMessage(hwndText, EM_GETLIMITTEXT, 0, 0); char *szText = new char[iTextSize]; SendMessage(hwndText, WM_GETTEXT, iTextSize, (LPARAM)szText);
多行编辑需要“\r\n”作为行分隔符,不能使用其他字符。否则,在某些 Windows 版本上可能会出现奇怪的行为。与之配套的父窗口对话框样式 DS_LOCALEDIT 对 Win32 来说是多余的。无法直接访问内部文本缓冲区(EM_GETHANDLE 不起作用),因此您必须使用 GetWindowText() 和一个足够大的缓冲区来获取副本。
与其他所有控件相比,编辑控件的行为有所不同,因为它们在以编程方式更改其内容时会使用 EN_CHANGE 和 EN_UPDATE 通知父级。当多个编辑具有循环依赖关系时,很容易发生无限循环。有一些解决方法可以解决这个问题
- 在 EN_CHANGE 上,使用一些小的延迟调用 SetTimer。在 SetWindowText/SetDlgItemText/SetDlgItemInt 之后,在 WM_TIMER 处理程序被调用之前,调用 KillTimer 来删除计时器。在计时器例程中处理实际更改。如果 OnChange 处理很长,但应避免按下 ENTER 键,这将非常有用。例如,设置无线电接收机的天线位置,您可以在其中输入任意经度或步进电机位置。
- 在 EN_CHANGE 上,检查 EM_GETMODIFY。在 SetWindowText 之前清除修改标志。
- 使用一个全局变量 (“bool NoEditChange”),您可以在 EN_CHANGE 上进行检查。在 SetWindowText 之前将其设置为 true,并在完成时设置为 false。就像往常一样,对话框是完全单线程的,这里不会发生竞争条件。
列表框
[edit | edit source]列表框可以包含制表符,因此可以显示某种程度上带有制表符的条目。此控件并不常用,除了在类似电子表格的应用程序中。它的主要目的是提供下拉组合框的弹出列表框。
组合框
[edit | edit source]此控件有三种外观。
简单组合框(不可编辑)
[edit | edit source]这种类型很少使用,不值得解释。
不可编辑的下拉组合框
[edit | edit source]当用户需要从一些选项中选择时使用此控件。
在创建时,使用较大的高度。它设置下拉列表框的最大高度。如果可用选项较少,Windows 会自动缩短其长度,但永远不会扩展。并且在此处滚动很烦人。(注意,Windows 3.x 不会缩短。)剩余控件的高度为 13 个对话框单位。(当您将其与类似的 Edit 控件组合时,您需要数字 13。)
// create a combo box and store the handle HWND hwndCombo = CreateWindow (TEXT("combobox"), // The class name required is combobox TEXT(""), // not used, ignored. WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, // the styles 0,0, // the left and top co-ordinates 100,300, // width and height hwnd, // parent window handle (HMENU)ID_MYCOMBO, // the ID of your combobox hInstance, // the instance of your application NULL) ; // extra bits you dont really need
实际小部件的显示高度将根据字体自动更改。“未使用的”部分高度将用于下拉菜单的大小。
例如,300 像素高度中只有 33 像素用于所选项目。当单击向下按钮时,菜单的高度将为 267 像素。
还有其他组合框样式:CBS_SIMPLE(类似于列表框)和 CBS_DROPDOWN(类似于 CBS_DROPDOWNLIST,但所选字段可编辑)。
// Add a list of strings to the combo box. SendMessage( hwndCombo, // The handle of the combo box CB_ADDSTRING, // Tells the combo box to append this string to its list 0, // Not used, ignored. (LPARAM) "Item A" // The string to add. ); SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item B"); SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item C"); SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item D"); SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item E"); // Select the default item to be "Item C". SendMessage( hwndCombo, // The handle of the combo b, CB_SETCURSEL, // Tells the combo box to select the specified index 2, // The index of the item to select (starting at zero) 0 // Not used, ignored. );
可编辑下拉组合框
[edit | edit source]当用户可以输入单行文本或可以选取一些预定义字符串时使用它。一个典型的例子是选择带有历史功能的 URL 或数字。或者选择端口地址,允许输入一个尚未知道的端口地址。
通用控件
[edit | edit source]通用控件大约在 Windows 3.11 中引入,并在功能和外观上不断扩展。请注意,当使用 Windows XP Luna 样式或更新版本时,上面的标准控件会自动由 comctl32.dll 的代码进行子分类。清单资源控制此行为。
通用控件 DLL 的版本和功能很大程度上取决于安装的 Internet Explorer 版本。
头文件和链接要求
[edit | edit source]使用通用控件需要链接到 comctl32.lib 并包含 commctrl.h。虽然某些控件默认存在,但其他控件需要使用 InitCommonControlsEx() 进行初始化。它取决于操作系统,因此最好始终使用正确的位调用 InitCommonControlsEx(),以满足您的应用程序需要的控件。
函数 InitCommonControls() 不会做任何事情,但它确保当您不链接到任何其他通用控件库函数(如 CreateToolBar())时加载库。当然,Windows 3.11 类是在 DLL 加载时注册的。
通用控件通常用作常规对话框元素。对话框资源模板大约在 Windows 2.0 中引入,如果这些不是 STATIC、BUTTON、EDIT、LISTBOX、COMBOBOX 或 SCROLLBAR(上面的标准控件),则为用户类名保留空间。因此,示例可能会显示如何使用 CreateWindowEx() 创建子窗口,但这很不常见,因为将它们包含到对话框模板中要容易得多。
工具栏
[edit | edit source]引入:Windows 3.11
case WM_VSCROLL: { int scrollMsg = (int)LOWORD(wParam); int pos = (short int)HIWORD(wParam); SCROLLINFO scrollInfo; GetScrollInfo(hTextOutput, SB_VERT, &scrollInfo); switch (scrollMsg) { case SB_LINEUP: { if (pos > 0) { pos--; } } break; case SB_LINEDOWN: { if (pos < scrollInfo.nMax) { pos++; } } break; } scrollInfo.fMask = SIF_POS; scrollInfo.nPos = pos; SetScrollInfo (hTextOutput, SB_VERT, &scrollInfo, TRUE); }
状态栏(状态栏)
[edit | edit source]请注意,CalcEffectiveClientRect() 的文档大多是错误的:数组的第一个 UINT/BOOL 对根本没有使用。
日期/时间选择器
[edit | edit source]引入:Windows 95
IP 地址输入字段
[edit | edit source]向上/向下控件
[edit | edit source]它主要附加到一个编辑窗口,因此它看起来像一个带有微小垂直滚动条的编辑窗口。事实上,滚动条的技巧在 Windows 3.x 的时代被广泛使用。但现在它看起来比这个控件更糟糕。
向上/向下控件自动执行整数计数和限制监视。它们可以自动适应其“伙伴”窗口,因此不需要放置代码。编辑控件会自动向水平方向缩小,就好像添加了垂直滚动条一样。
可能的伙伴窗口是单行编辑、进度条和滑块。
不幸的是,Win32 上/下控件不适用于十进制数,与 .NET 对等项相反。因此,您必须编写一些无聊的代码来实现 .NET 行为。
带有向上/向下控件的编辑应该具有 14 个对话框基本单位的高度,以获得最佳外观。不幸的是,当使用标准 Windows 方案时,文本内容看起来有点过高(即错位)。
选项卡控件
[edit | edit source]此控件很少直接使用。属性表大量使用它。
当选项卡周围需要更多控件时,需要选项卡控件。属性表只能在底部有一些按钮。当真正使用时,每个选项卡应该控制一个子对话框(设置 WS_CHILD 样式),然后子对话框是父对话框的子级(不是选项卡控件的子级!)。资源应该仔细规划,因此不需要放置代码。虽然属性表在第一次调用时会延迟初始化每个子级(以节省时间),但这取决于您是否复制此行为或在显示时初始化所有子级。单击选项卡应该 ShowWindow(...,SW_HIDE) 当前子对话框,并 ShowWindow(...,SW_SHOW) 所需的子对话框。
子对话框是在属性表(Win95)中引入的,而不是在选项卡控件(Win32s)中引入的。这些具有以下行为
- 当子对话框消失时,其所有控件都会消失——即真正的层次结构
- 其子级的选项卡顺序将被处理,就好像它们在父对话框中一样
- 嵌套子级不受官方支持
- 每个子级都有自己的对话框过程
- 对话框字体必须与父对话框相同
工具提示
[edit | edit source]气球式工具提示是在 Windows XP 中引入的。因此,大多数程序仍然使用标准的黑色黄色矩形工具提示。
列表视图、树视图
[edit | edit source]Windows 资源管理器是这两个控件的完美示例。左侧窗格通常显示目录树,右侧窗格显示文件列表。此外,桌面本身(非常类似于)列表视图。
static const INITCOMMONCONTROLSEX icc={sizeof(icc),ICC_TREEVIEW_CLASSES}; InitCommonControlsEx(&icc);
组合框 Ex
[edit | edit source]引入:Windows 95。
额外功能
- 文本左侧的图像
- 所选项目的不同图像(但很少使用)
注意事项
- 初始化繁琐
- 不要忘记 InitCommonControlsEx!
- 更大的二进制资源
选中列表框
[edit | edit source]进度条
[edit | edit source]它是什么
[edit | edit source]一个标准的条形图,显示项目的进度。显示已完成数量与总数量的图形表示。
示例代码
[edit | edit source]- 创建进度条
进度条控件应该是另一个窗口的子窗口,可以是主窗口或子窗口。要创建它,您只需使用 PROGRESS_CLASS 类和您选择的参数调用 CreateWindow() 函数。以下是创建它的基本代码。
/*Create a Progress Bar*/ HWND hwndProgress = CreateWindow( PROGRESS_CLASS, /*The name of the progress class*/ NULL, /*Caption Text*/ WS_CHILD | WS_VISIBLE, /*Styles*/ 0, /*X co-ordinates*/ 0, /*Y co-ordinates*/ 200, /*Width*/ 30, /*Height*/ hwnd, /*Parent HWND*/ (HMENU) ID_MYPROGRESS, /*The Progress Bar's ID*/ hInstance, /*The HINSTANCE of your program*/ NULL); /*Parameters for main window*/
- 使用进度条
- 改变位置
有三种方法可以递增/递减进度条。PBM_DELTAPOS 以给定数字递增。PBM_SETPOS 设置特定位置。PBM_SETSTEP 设置递增/递减数字,而 PBM_STEPIT 以该数字递增/递减。
- 增量位置
- PBM_DELTAPOS 使用作为 wparam 给出的数字推进进度条。
/*Advance progress bar by 25 units*/ SendMessage( hwndProgress , /*HWND*/ /*Progress Bar*/ PBM_DELTAPOS, /*UINT*/ /*Message*/ 25, /*WPARAM*/ /*Units*/ NULL) /*LPARAM*/ /*Unused*/
- 设置位置
- PBM_SETPOS 将进度条推进到 WPARAM 中指定的特定位置。
/*Advances progress bar to specified position (50)*/ SendMessage( hwndProgress , /*HWND*/ /*Progress Bar*/ PBM_SETPOS, /*UINT*/ /*Message*/ 50, /*WPARAM*/ /*Units*/ NULL) /*LPARAM*/ /*Unused*/
- 步进位置
- PBM_SETSTEP 指定要步进的单位数量。PBM_STEPIT 以 PBM_SETSTEP 给出的单位数量(默认值为 10 个单位)递增。
/*Advances progress bar by specified units*/ /*If progress bar is stepped when at the progress bar's maximum, /* the progress bar will go back to its starting position*/ /*Set the step*/ SendMessage( hwndProgress , /*HWND*/ /*Progress Bar*/ PBM_SETSTEP, /*UINT*/ /*Message*/ 1, /*WPARAM*/ /*Amount to step by*/ NULL) /*LPARAM*/ /*Unused*/ /*Step*/ SendMessage( hwndProgress , /*HWND*/ /*Progress Bar*/ PBM_STEPIT, /*UINT*/ /*Message*/ NULL, /*WPARAM*/ /*Unused*/ NULL) /*LPARAM*/ /*Unused*/