diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter01-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter01-zh.md new file mode 100644 index 0000000..687f80b --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter01-zh.md @@ -0,0 +1,198 @@ +# 静态框 + + +静态框用来在窗口的特定位置显示文字、数字等信息,还可以用来显示一些静态的图片信息,比如公司徽标、产品商标等等。就像其名称暗示的那样,静态框的行为不能对用户的输入进行动态的响应,它的存在基本上就是为了展示一些信息,而不会接收任何键盘或鼠标输入。__图 1.1__ 给出了静态框控件的典型用途:在对话框中作为其他控件的标签。 + +![静态框控件的典型用途](figures/Part4Chapter01-1.1.jpeg) + +__图 1.1__ 静态框控件的典型用途 + + +以 `CTRL_STATIC` 为控件类名调用 `CreateWindow` 函数,即可创建静态框控件。 + +## 1.1 静态框的类型和风格 + +静态框的风格由静态框种类和一些标志位组成。我们可将静态框控件按功能划分为标准型(只显示文本)、位图型(显示图标或图片),以及特殊类型分组框。下面我们将分别介绍上述不同类型的静态框。 + +### 1.1.1 标准型 + +将静态框控件的风格设定为 `SS_SIMPLE、SS_LEFT`、`SS_CENTER、SS_RIGHT`,以及 `SS_LEFTNOWORDWRAP` 之一,将创建用来显示文字的静态框,其所显示的内容在 `CreateWindow` 函数的 `caption` 参数中进行指定,并且在以后可以用 `SetWindowText` 来改变。 + +通过 `SS_SIMPLE` 风格创建的控件只用来显示单行文本,也就是说,控件文本不会自动换行显示,并且文本永远是左对齐的。 + +通过 `SS_LEFT、SS_CENTER` 或 `SS_RIGHT` 风格创建的静态框可用来显示多行文本,并分别以左对齐、中对齐和右对齐方式显示文本。 + +通过 `SS_LEFTNOWORDWRAP` 风格创建的静态框会扩展文本中的 `TAB` 符,但不做自动换行处理。 + +下面的程序段创建了上述几种类型的静态框: + +```c +CreateWindow (CTRL_STATIC, +"This is a simple static control.", +WS_CHILD | SS_NOTIFY | SS_SIMPLE | WS_VISIBLE | WS_BORDER, +IDC_STATIC1, +10, 10, 180, 20, hWnd, 0); +CreateWindow (CTRL_STATIC, +"This is a left-aligned static control (auto-wrap).", +WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE | WS_BORDER, +IDC_STATIC2, +10, 40, 100, 45, hWnd, 0); +CreateWindow (CTRL_STATIC, +"This is a right-aligned static control (auto-wrap).", +WS_CHILD | SS_NOTIFY | SS_RIGHT | WS_VISIBLE | WS_BORDER, +IDC_STATIC3, +10, 90, 100, 45, hWnd, 0); +CreateWindow (CTRL_STATIC, +"This is a center-aligned static control (auto-wrap).", +WS_CHILD | SS_NOTIFY | SS_CENTER | WS_VISIBLE | WS_BORDER, +IDC_STATIC4, +10, 140, 100, 45, hWnd, 0); +CreateWindow (CTRL_STATIC, +"SS_LEFTNOWORDWRAP: " +"\tTabs are expanded, but words are not wrapped. " +"Text that extends past the end of a line is clipped.", +WS_CHILD | SS_LEFTNOWORDWRAP | WS_VISIBLE | WS_BORDER, +IDC_STATIC, +10, 290, 540, 20, hWnd, 0); +``` + +上述几个控件的显示效果见__图 1.2__。为了能够清楚地看到对齐效果,这些静态框均含有边框。 + +![文本型静态框](figures/Part4Chapter01-1.2.jpeg) +__图 1.2__ 文本型静态框 + + +### 1.1.2 位图型 + +风格设定为 `SS_BITMAP` 或者 `SS_ICON`,这种静态框会显示一幅位图或者图标。对这两类静态框,需要在创建静态框时通过 `dwAddData` 参数设定要显示的位图对象指针或者图标对象句柄。和这两类静态框相关联的风格有 `SS_CENTERIMAGE` 和 `SS_REALSIZEIMAGE`,这两个风格用来控制位图或者图标在控件中的位置。默认情况下,位图和图标要经过适当的缩放充满整个静态框,但使用 `SS_REALSIZEIMAGE` 风格将取消缩放操作,并显示在静态框的左上方,如果在使用 `SS_REALSIZEIMAGE` 的同时使用 `SS_CENTERIMAGE` 风格,则会在控件中部显示位图或图标。 + +下面的程序段创建了一个位图静态框和一个图标静态框,并使用 `SS_REALSIZEIMAGE` 和 `SS_CENTERIMAGE` 风格创建了一个居中显示的位图静态框: + +```c +CreateWindow (CTRL_STATIC, +"", +WS_CHILD | SS_BITMAP | WS_VISIBLE, +IDC_STATIC, +280, 80, 50, 50, hWnd, (DWORD)GetSystemBitmap (SYSBMP_CHECKMARK)); + +CreateWindow (CTRL_STATIC, +"", +WS_CHILD | SS_ICON | WS_VISIBLE, +IDC_STATIC, +280, 20, 50, 50, hWnd, (DWORD)GetLargeSystemIcon (IDI_INFORMATION)); + +CreateWindow (CTRL_STATIC, +"", +WS_CHILD | SS_BITMAP | SS_REALSIZEIMAGE | SS_CENTERIMAGE | WS_VISIBLE, +IDC_STATIC, +280, 140, 50, 50, hWnd, (DWORD)GetSystemBitmap (SYSBMP_CHECKMARK)); +``` + +【注意】许多预定义控件会通过 `CreateWindowEx` 函数的 `dwAddData` 参数传递一些控件的初始化参数,这时,窗口第一附加值在创建控件的过程中用于传递这些参数,但在控件创建之后,应用程序仍可以使用窗口第一附加值来保存私有数据。 + +上述程序段创建的静态框效果见__图 1.3__。 + +![位图型静态框](figures/Part4Chapter01-1.3.jpeg) +图 1.3 位图型静态框 + + +### 1.1.3 分组框 + +将风格设定为 `SS_GROUPBOX` 的静态框为分组框,它是静态框中的特例。分组框是一个矩形框,分组框标题在其顶部显示,分组方块常用来包含其他的控件。分组框内可以创建的控件有:静态框、按钮、简单编辑框、单行编辑框、多行编辑框、列表框、滑块和菜单按钮。 + +下面的程序段创建了一个分组框,其效果见__图 1.4__。 + +```c +CreateWindow (CTRL_STATIC, +"A Group Box", +WS_CHILD | SS_GROUPBOX | WS_VISIBLE, +IDC_STATIC, +350, 10, 200, 100, hWnd, 0); +``` + +![分组静态框](figures/Part4Chapter01-1.4.jpeg) +__图 1.4__ 分组静态框 + +### 1.1.4 其他静态框类型 + +除上述静态框类型之外,还有如下几种不常见的静态框类型: + +- `SS_WHITERECT`:以白色填充静态框矩形。 +- `SS_GRAYRECT`:以灰色填充静态框矩形。 +- `SS_BLACKRECT`:以黑色填充静态框矩形。 +- `SS_GRAYFRAME`:灰色边框。 +- `SS_WHITEFRAME`:白色边框。 +- `SS_BLACKFRAME`:黑色边框。 + +使用这些风格的静态框效果见__图 1.5__。 + +![其他静态框类型](figures/Part4Chapter01-1.5.jpeg) +__图 1.5__ 其他静态框类型 + +## 1.2 静态框消息 + +当静态框类型为位图型时,可通过如下消息获得或者修改静态框的位图: + +- `STM_GETIMAGE`:该消息返回位图的指针或者图标句柄。 +- `STM_SETIMAGE`:通过 `wParam` 参数重新设置位图指针或者图标句柄,并且返回原来的指针。 + +## 1.3 静态框通知码 + +当静态框风格中包含 `SS_NOTIFY` 时,静态框会产生如下两个通知消息: + +- `STN_DBLCLK`:表示用户在静态框内双击了鼠标左键。 +- `STN_CLICKED`:表示用户在静态框内单击了鼠标左键。 + +## 1.4 编程实例 + +__清单 1.1__ 所示的程序代码,创建了一个位图型静态框,并在用户双击该静态框时修改其自身的文本。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `static.c` 文件。__图 1.6__ 是该程序的运行效果。 + +__清单 1.1__ 静态框示例程序 + +```c +#include + +#include +#include +#include +#include +#include + +static void my_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + /* 当用户双击静态框时,调用 SetWindowText 函数改变文本内容 */ + if (nc == STN_DBLCLK) + SetWindowText (hwnd, "I am double-clicked. :)"); +} + +static int StaticDemoWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam) +{ + HWND hwnd; + + switch (message) { + case MSG_CREATE: + /* 创建静态框并设置其通知回调函数 */ + hwnd = CreateWindow (CTRL_STATIC, "Double-click me!", + WS_VISIBLE | SS_CENTER | SS_NOTIFY, + 50, 80, 100, 200, 20, hWnd, 0); + SetNotificationCallback (hwnd, my_notif_proc); + return 0; + + case MSG_DESTROY: + DestroyAllControls (hWnd); + return 0; + + case MSG_CLOSE: + DestroyMainWindow (hWnd); + PostQuitMessage (hWnd); + return 0; + } + + return DefaultMainWinProc(hWnd, message, wParam, lParam); +} + +/* 以下创建主窗口的代码从略 */ +``` + +![静态框示例](figures/Part4Chapter01-1.6.jpeg) +__图 1.6__ 静态框示例 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter02-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter02-zh.md new file mode 100644 index 0000000..8e9b98a --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter02-zh.md @@ -0,0 +1,408 @@ +# 按钮 + + +按钮是除静态框之外使用最为频繁的一种控件。按钮通常用来为用户提供开关选择。MiniGUI 的按钮可划分为普通按钮、复选框和单选钮等几种类型。用户可以通过键盘或者鼠标来选择或者切换按钮的状态。用户的输入将使按钮产生通知消息,应用程序也可以向按钮发送消息以改变按钮的状态。 + +以 `CTRL_BUTTON` 为控件类名调用 `CreateWindow` 函数,即可创建按钮控件。 + +## 1.1 按钮的类型和风格 + +### 1.1.1 普通按钮 + +普通按钮是一个矩形,其中显示了通过 `CreateWindow` 传递的窗口标题。该矩形占用了在 `CreateWindow` 调用中给出的全部高度和宽度,而文字位于矩形的中心。 + +按钮控件主要用来触发一个立即回应的动作,并且不会长久保持开关信息。这种形态的按钮控件有两种窗口风格,分别叫做 `BS_PUSHBUTTON` 和 `BS_DEFPUSHBUTTON`。`BS_DEFPUSHBUTTON` 中的 “DEF” 代表 “默认”。当用来设计对话框时,`BS_PUSHBUTTON` 风格和 `BS_DEFPUSHBUTTON` 风格的作用不同,具有 `BS_DEFPUSHBUTTON` 的按钮将是默认接收 `ENTER` 键输入的按钮,而不管当前的输入焦点处于哪个控件上。但是当用作普通主窗口的控件时,两种型态的按钮作用相同,只是具有 `BS_DEFPUSHBUTTON` 风格的按钮的边框要粗一些。 + +当鼠标光标处在按钮中时,按下鼠标左键将使按钮用三维阴影重画自己,就好像真的被按下一样。放开鼠标按键时,就恢复按钮的原貌,并产生 `BN_CLICKED` 通知,当按钮拥有输入焦点时,在文字的周围就有虚线,按下及释放空格键与按下及释放鼠标按键具有相同的效果。 + +>【提示】本指南对控件行为和表象的描述以默认的经典风格为准。 + +通常情况下,按钮文本会以单行的形式在垂直和水平方向居中显示,不会自动换行。不过,应用程序也可以通过指定 `BS_MULTLINE` 风格来指定显示多行文本。下面的程序段创建了两个普通按钮: + +```c +CreateWindow (CTRL_BUTTON, +"Push Button", +WS_CHILD | BS_PUSHBUTTON | BS_CHECKED | WS_VISIBLE, +IDC_BUTTON, +10, 10, 80, 30, hWnd, 0); + +CreateWindow (CTRL_BUTTON, +"Multiple Lines Push Button", +WS_CHILD | BS_PUSHBUTTON | BS_MULTLINE | WS_VISIBLE, +IDC_BUTTON + 1, +100, 10, 80, 40, hWnd, 0); +``` + +上述代码段建立的普通按钮的显示效果如__图 1.1__ 所示。注意在使用 `BS_MULTILINE` 风格之后,文本将垂直向上对齐。 + +![普通按钮](figures/Part4Chapter02-1.1.jpeg) +__图 1.1__ 普通按钮 + + +另外,也可以在普通按钮上显示位图或图标,这时要使用 `BS_BITMAP` 或者 `BS_ICON` 风格,并通过 `CreateWindow` 函数的 `dwAddData` 参数传递位图对象的指针或图标句柄。默认情况下位图或图标会缩放显示以充满整个按钮窗口范围,使用 `BS_REALSIZEIMAGE` 风格将使位图或图标显示在控件中部,不作任何缩放。下面的代码段建立了一个带位图的按钮,其效果见__图 1.2__。 + +```c +hwnd = CreateWindow (CTRL_BUTTON, +"Close", +WS_CHILD | BS_PUSHBUTTON | BS_BITMAP |BS_REALSIZEIMAGE | BS_NOTIFY | WS_VISIBLE, +IDC_BUTTON + 4, +10, 300, 60, 30, hWnd, (DWORD) GetSystemBitmap (IDI_APPLICATION)); +``` + +![位图按钮](figures/Part4Chapter02-1.2.jpeg) +__图 1.2__ 位图按钮 + + +### 1.1.2 复选框 + +复选框是一个文字方块,文字通常出现在复选框的右边(如果你在建立按钮时指定了 `BS_LEFTTEXT` 风格,那么文字会出现在左边)。复选框通常用于允许用户对选项进行选择的应用程序中。复选框的常用功能如同一个开关:单击一次将显示选中标记,再次单击则会清除选中标记。 + +复选框最常用的两种风格是 `BS_CHECKBOX` 和 `BS_AUTOCHECKBOX`。在使用 `BS_CHECKBOX` 时,应用程序需要自己向该控件发送消息来设定选中标记;而使用 `BS_AUTOCHECKBOX` 风格时,控件会自动在选中和非选中状态之间切换。 + +其余两种复选框风格是 `BS_3STATE` 和 `BS_AUTO3STATE`,正如它们名字所暗示的,这两种风格能显示第三种状态——复选框内是灰色的,这种状态表明该复选框不能被选择或者禁止使用。 + +`BS_3STATE` 和 `BS_AUTO3STATE` 风格之间的区别和上面一样:前者需要应用程序来操作其状态,而后者由控件负责状态的自动切换。 + +默认情况下,复选框沿矩形的左边框对齐,并位于控件窗口范围的顶边和底边之间(垂直居中),在该矩形内的任何地方按下鼠标都会产生通知消息。使用 `BS_LEFTTEXT` 风格将使复选框靠右对齐,并将文本置于复选框的左边。用于文本对齐的风格 `BS_LEFT`、`BS_CENTER`、`BS_RIGHT`、`BS_TOP`、`BS_VCENTER`、`BS_BOTTOM` 等可用于复选框。 + +另外,使用 `BS_PUSHLIKE` 风格将使复选框以普通按钮的形式显示:选中时显示为按下状态,未选中时显示为正常状态。 + +下面的程序段创建了两个复选框,其效果在__图 1.3__ 中。 + +```c +CreateWindow (CTRL_BUTTON, +"Auto 3-state check box", +WS_CHILD | BS_AUTO3STATE | WS_VISIBLE, +IDC_CHECKBOX, +10, 60, 150, 30, hWnd, 0); + +CreateWindow (CTRL_BUTTON, +"Auto check box on left", +WS_CHILD | BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_VISIBLE, +IDC_CHECKBOX + 1, +170, 60, 150, 30, hWnd, 0); +``` + +![复选框按钮](figures/Part4Chapter02-1.3.jpeg) +__图 1.3__ 复选框按钮 + + +### 1.1.3 单选钮 + +单选按钮就像收音机上选台按钮一样,每一个按钮都对应一个频道,而且一次只能有一个按钮被按下。在对话框中,单选按钮组常常用来表示相互排斥的选项。与复选框不同,单选按钮的工作方式不同于开关,也就是说,当第二次按单选按钮时,它的状态会保持不变。 + +单选按钮的形状是一个圆圈,而不是方框,除此之外,它的行为很像复选框。圆圈内的加重圆点表示该单选按钮已经被选中。单选按钮有风格 `BS_RADIOBUTTON` 或 `BS_AUTORADIOBUTTON` 两种,后者会自动显示用户的选择情况,而前者不会。 + +默认情况下,单选按钮沿控件窗口的左边框对齐,并位于控件窗口范围的顶边和底边之间(垂直居中),在该矩形内的任何地方按下鼠标都产生通知消息。使用 `BS_LEFTTEXT` 风格将使单选按钮靠右对齐,并将文本置于按钮的左边。用于文本对齐的风格 `BS_LEFT`、`BS_CENTER`、`BS_RIGHT`、`BS_TOP`、`BS_VCENTER`、`BS_BOTTOM` 等可用于单选按钮。 + +另外,使用 `BS_PUSHLIKE` 风格将使单选按钮以普通按钮的形式显示:选中时显示为按下状态,未选中时显示为正常状态。下面的程序段创建了两个单选按钮,其效果见__图 1.4__。 + +```c +CreateWindow (CTRL_BUTTON, +"Auto Radio Button 2", +WS_CHILD | BS_AUTORADIOBUTTON | WS_VISIBLE, +IDC_RADIOBUTTON + 1, +20, 160, 130, 30, hWnd, 0); + +CreateWindow (CTRL_BUTTON, +"Auto Radio Button 2", +WS_CHILD | BS_AUTORADIOBUTTON | BS_LEFTTEXT | BS_RIGHT | WS_VISIBLE, +IDC_RADIOBUTTON + 4, +180, 160, 140, 30, hWnd, 0); +``` + +![单选按钮](figures/Part4Chapter02-1.4.jpeg) +__图 1.4__ 单选按钮 + + +单选按钮通常成组使用,同一组单选按钮每一刻只能有一个被选中。在创建一组单选按钮时,我们需要设定它们的状态是互斥的,因此,要在创建第一个单选按钮时使用 `WS_GROUP` 风格,以将其设置为该组单选按钮的“打头按钮”。 + +## 1.2 按钮消息 + +应用程序通过给按钮发送消息来实现如下目的: + +- 查询/设置复选框或者单选钮的选中状态:`BM_GETCHECK`、`BM_SETCHECK` +- 查询/设置普通按钮或者复选框的按下或释放状态:`BM_GETSTATE`、`BM_SETSTATE` +- 获取/设置位图按钮上的位图或者图标:`BM_GETIMAGE`、`BM_SETIMAGE` +- 发送 `BM_CLICK` 模拟用户鼠标的单击操作 + +应用程序向复选框或者单选钮发送 `wParam` 等于 `BST_CHECKED` 的 `BM_SETCHECK` 消息来显示其处于选中状态: + +```c +SendMessage (hwndButton, BM_SETCHECK, BST_CHECKED, 0); +``` + +其实 `wParam` 可取的值一共有三个,见__表 1.1__。这些值也是通过 `BM_GETCHECK` 消息返回的选中状态值。 + +__表 1.1__ 复选框和单选钮的选中状态 + +| 状态标识符 | 含义 | +|:----------------------|:-------| +|`BST_UNCHECKED`(0) |未选中 | +|`BST_CHECKED`(1) |已选中 | +|`BST_INDETERMINATE`(2) |不可用状态| + + +我们可以通过给窗口发送 `BM_SETSTATE` 消息来模拟按钮闪动。以下的操作将导致按钮被按下: + +```c +SendMessage (hwndButton, BM_SETSTATE, BST_PUSHED, 0) ; +``` + +下面的调用使按钮恢复正常: + +```c +SendMessage (hwndButton, BM_SETSTATE, 0, 0) ; +``` + +对位图按钮,可使用 `BM_GETIMAGE` 和 `BM_SETIMAGE` 消息获取或设置位图对象或图标句柄: + +```c +int image_type; +PBITMAP btn_bmp; +HICON btn_icon; + +int ret_val = SendMessage (hwndButton, BM_GETIMAGE, (WPARAM)&image_type, 0) ; + +if (image_type == BM_IMAGE_BITMAP) { + /* 该按钮使用的是位图对象 */ + btn_bmp = (PBITMAP) ret_val; +} +else { + /* 该按钮使用的是图标对象 */ + btn_icon = (HICON) ret_val; +} + +/* 将按钮图象设置为位图对象 */ +SendMessage (hwndButton, BM_SETIMAGE, BM_IMAGE_BITMAP, btn_bmp) ; + +/* 将按钮图象设置为图标对象 */ +SendMessage (hwndButton, BM_SETIMAGE, BM_IMAGE_ICON, btn_icon) ; +``` + +另外,我们在应用程序中也可以通过向按钮发送 `BM_CLICK` 消息来模拟用户在按钮上的单击操作。 + +## 1.3 按钮通知码 + +具有 `BS_NOTIFY` 风格的按钮可产生的通知码主要有: + +- `BN_CLICKED`:表明用户单击此按钮。该通知码的值为 0,因此,如果要在按钮的父窗口中处理该按钮发送过来的 `BN_CLICKED` 通知消息,只需判断 `MSG_COMMAND` 消息的 `wParam` 参数是否等于按钮的标识符即可。该通知的产生是默认的,将忽略按钮控件的 `BS_NOTIFY` 风格。 +- `BN_PUSHED`:表明用户将此按钮按下。 +- `BN_UNPUSHED`:表明用户将此按钮释放。 +- `BN_DBLCLK`:表明用户在此按钮上进行了鼠标左键的双击操作。 +- `BN_SETFOCUS`:表明按钮获得了输入焦点。 +- `BN_KILLFOCUS`:表明按钮失去了输入焦点。 + +## 1.4 编程实例 + +通常,应用程序只需处理 `BN_CLICKED` 通知码,对复选框和单选钮,一般设置为自动状态,并在需要时发送 `BM_GETCHECK` 消息来获得选中状态。在对话框中,应用程序还可以使用__表 1.2__ 中的函数来快速获得按钮控件的状态信息。 + +__表 1.2__ 对话框为处理按钮控件而提供的便利函数 + +| 函数名 | 用途 | 备注 | +|:--------------------|:---------------------------------|:----------| +|`CheckDlgButton` |通过按钮标识符来改变按钮的选中状态 | | +|`CheckRadioButton` |通过按钮标识符来改变一组单选钮的选中状态 |确保互斥选中 | +|`IsDlgButtonChecked `|通过标识符判断按钮是否选中 | | + + +__清单 1.1__ 所示的程序代码,给出了一个按钮控件的综合性使用范例。该程序使用一个对话框来询问用户的口味,通过分组单选框来选择喜欢的小吃类型,并通过复选框来选择用户的一些特殊口味。该程序的完整源代码请见本指南示例程序包 `mg-samples` 中的 `button.c` 文件,其运行效果见__图 1.5__。 + +__清单 1.1__ 按钮控件的使用范例 + +```c +#include +#include + +#include +#include +#include +#include +#include + +#define IDC_LAMIAN 101 +#define IDC_CHOUDOUFU 102 +#define IDC_JIANBING 103 +#define IDC_MAHUA 104 +#define IDC_SHUIJIAO 105 + +#define IDC_XIAN 110 +#define IDC_LA 111 + +#define IDC_PROMPT 200 + +static DLGTEMPLATE DlgYourTaste = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 120, 100, 300, 280, + "你喜欢吃哪种风味的小吃", + 0, 0, + 12, NULL, + 0 +}; + +static CTRLDATA CtrlYourTaste[] = +{ + { + "static", + WS_VISIBLE | SS_GROUPBOX, + 16, 10, 130, 160, + IDC_STATIC, + "可选小吃", + 0 + }, + { + "button", + /* 使用 BS_CHECKED,初始时使其选中 */ + WS_VISIBLE | BS_AUTORADIOBUTTON | BS_CHECKED | WS_TABSTOP | WS_GROUP, + 36, 38, 88, 20, + IDC_LAMIAN, + "西北拉面", + 0 + }, + { + "button", + WS_VISIBLE | BS_AUTORADIOBUTTON, + 36, 64, 88, 20, + IDC_CHOUDOUFU, + "长沙臭豆腐", + 0 + }, + { + "button", + WS_VISIBLE | BS_AUTORADIOBUTTON, + 36, 90, 88, 20, + IDC_JIANBING, + "山东煎饼", + 0 + }, + { + "button", + WS_VISIBLE | BS_AUTORADIOBUTTON, + 36, 116, 88, 20, + IDC_MAHUA, + "天津麻花", + 0 + }, + { + "button", + WS_VISIBLE | BS_AUTORADIOBUTTON, + 36, 142, 100, 20, + IDC_SHUIJIAO, + "成都红油水饺", + 0 + }, + { + "static", + WS_VISIBLE | SS_GROUPBOX | WS_GROUP, + 160, 10, 124, 160, + IDC_STATIC, + "口味", + 0 + }, + { + "button", + WS_VISIBLE | BS_AUTOCHECKBOX, + 170, 38, 88, 20, + IDC_XIAN, + "偏咸", + 0 + }, + { + "button", + /* 使用 BS_CHECKED,初始时使其选中 */ + WS_VISIBLE | BS_AUTOCHECKBOX | BS_CHECKED, + 170, 64, 88, 20, + IDC_LA, + "偏辣", + 0 + }, + { + "static", + WS_VISIBLE | SS_LEFT | WS_GROUP, + 16, 180, 360, 20, + IDC_PROMPT, + "西北拉面是面食中的精品,但街上的兰州拉面除外!", + 0 + }, + { + "button", + WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_GROUP, + 80, 220, 95, 28, + IDOK, + "确定", + 0 + }, + { + "button", + WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, + 185, 220, 95, 28, + IDCANCEL, + "取消", + 0 + }, +}; + +static char* prompts [] = { + "西北拉面是面食中的精品,但街上的兰州拉面除外!", + "长沙臭豆腐口味很独特,一般人适应不了。", + "山东煎饼很难嚼 :(", + "天津麻花很脆,很香!", + "成都的红油水饺可真好吃啊!想起来就流口水。", +}; + +static void my_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + /* 用户选择不同的小吃时,在下面的静态框中显示针对这种小吃的提示信息 */ + if (nc == BN_CLICKED) { + SetWindowText (GetDlgItem (GetParent (hwnd), IDC_PROMPT), prompts [id - IDC_LAMIAN]); + } +} + +static int DialogBoxProc2 (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITDIALOG: + { + int i; + /* 为小吃单选钮设定通知回调函数 */ + for (i = IDC_LAMIAN; i <= IDC_SHUIJIAO; i++) + SetNotificationCallback (GetDlgItem (hDlg, i), my_notif_proc); + } + return 1; + + case MSG_COMMAND: + switch (wParam) { + case IDOK: + case IDCANCEL: + EndDialog (hDlg, wParam); + break; + } + break; + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +int MiniGUIMain (int argc, const char* argv[]) +{ + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "button" , 0 , 0); + #endif + + DlgYourTaste.controls = CtrlYourTaste; + + DialogBoxIndirectParam (&DlgYourTaste, HWND_DESKTOP, DialogBoxProc2, 0L); + + return 0; +} + +#ifndef _MGRM_PROCESSES +#include +#endif +``` + +![按钮控件的使用范例](figures/Part4Chapter02-1.5.jpeg) +__图 1.5__ 按钮控件的使用范例 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter03-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter03-zh.md new file mode 100644 index 0000000..6e7351a --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter03-zh.md @@ -0,0 +1,555 @@ +# 列表框 + +列表框通常为用户提供一系列的可选项,这些可选项显示在可滚动的子窗口中,用户可通过键盘及鼠标操作来选中某一项或者多个项,选中的列表项通常高亮显示。列表框的最典型用法就是文件打开对话框,见__图 1.1__。 + +![列表框的典型应用场合:“打开文件”对话框](figures/Part4Chapter03-1.1.jpeg) +__图 1.1__ 列表框的典型应用场合:“打开文件”对话框 + + +以 `CTRL_LISTBOX` 为控件类名调用 `CreateWindow` 函数,即可创建列表框控件。 + +## 1.1 列表框的类型和风格 + +MiniGUI 的列表框控件可划分为三种类型:单选列表框、多选列表框和位图列表框。默认情况下,列表框是单项选择的,用户只能从中选择一个列表项。如果要建立一个可选择多个列表项的列表框,则应使用 `LBS_MULTIPLESEL` 风格。使用该风格时,用户可通过单击某个项的方法选中这个项,再次单击将取消对其的选中。当列表框拥有输入焦点时,也可以使用空格键选择某个项或者取消某个项的选择。多选列表框的运行效果如__图 1.2__ 所示。 + +![多选列表框](figures/Part4Chapter03-1.2.jpeg) +__图 1.2__ 多选列表框 + +除上述两种列表框的基本形态之外,MiniGUI 还提供有一种高级列表框类型。这种列表框中的列表项不仅仅是字符串,还可以用来附带位图或者图标,还可以在列表项旁边显示一个检查框,代表选中或者未选中。要建立这种高级列表框,需用指定 `LBS_USEICON` 或者 `LBS_CHECKBOX` 风格。__图 1.3__ 是这种高级列表框的运行效果。如果希望在用户单击检查框时自动切换选中状态,则可以使用 `LBS_AUTOCHECK` 风格。高级列表框也可以具有 `LBS_MULTIPLESEL` 风格。 + +![高级列表框](figures/Part4Chapter03-1.3.jpeg) +__图 1.3__ 高级列表框 + +除上述用来区别列表框类型的风格之外,还可以在创建列表框时指定其他通用风格。 + +默认状态下,列表框窗口消息处理程序只显示列表条目,它的周围没有任何边界。你可以使用窗口风格 `WS_BORDER` 来加上边框。另外,你可以使用窗口风格 `WS_VSCROLL` 来增加垂直滚动条,以便用鼠标来滚动列表框条目,也可以使用 `WS_HSCROLL` 来增加水平滚动条,可以用来显示超出列表框宽度的条目。 + +缺省的列表框风格不会在用户选中某个列表项时产生通知消息,这样一来,程序必须向列表框发送消息以便了解其中条目的选择状态。所以,列表框控件通常都包括列表框风格 `LBS_NOTIFY`,它可以使列表框控件在用户进行操作时,将一些状态信息及时反馈给应用程序。 + +另外,如果希望列表框控件对列表框中的条目进行排序,那么可以使用另一种常用的风格 `LBS_SORT`。 + +一般情况下,创建列表框控件最常用的风格组合如下: + +```c +(LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER) +``` + +## 1.2 列表框消息 + +### 1.2.1 将字符串加入列表框 + +建立列表框之后,下一步是将字符串放入其中,你可以通过调用 `SendMessage` 为列表框窗口消息处理程序发送消息来做到这一点。字符串通常通过以0开始计数的索引数来引用,其中 0 对应于最顶上的条目。在下面的例子中,`hwndList` 是子窗口列表框控件的句柄,而 `index` 是索引值。在使用 `SendMessage` 传递字符串的情况下,`lParam` 参数是指向以 `NULL` 字符结尾的字符串指针。 + +在大多数例子中,当列表框控件所能存储的内容超过了可用内存空间时,`SendMessage` 将传回 `LB_ERRSPACE`。如果是因为其他原因而出错,那么 `SendMessage` 将传回 `LB_ERR`。如果操作成功,那么 `SendMessage` 将传回 `LB_OKAY`。我们可以通过测试 `SendMessage` 的非零值来判断出这两种错误。 + +如果你采用 `LBS_SORT` 风格,或者仅仅希望将新的字符串追加为列表框的最后一项,那么填入列表框最简单的方法是借助 `LB_ADDSTRING` 消息: + +```c +SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAM)string) ; +``` + +我们也可以使用 `LB_INSERTSTRING` 指定一个索引值,将字符串插入到列表框中的指定位置: + +```c +SendMessage (hwndList, LB_INSERTSTRING, index, (LPARAM)string) ; +``` + +例如,如果 `index` 等于 4,那么 `string` 将变为索引值为 4 的字符串――从顶头开始算起的第 5 个字符串(因为是从 0 开始计数的),位于这个点后面的所有字符串都将向后推移。索引值为 -1 时,将字符串增加在最后。我们也可以对具有 `LBS_SORT` 风格的列表框使用 `LB_INSERTSTRING`,但是这时列表框将会忽略 `index` 的值,而根据排序的结果插入新的项。 + +需要注意的是,在指定了 `LBS_CHECKBOX` 或者 `LBS_USEICON` 风格之后,在向列表框添加条目时,必须使用 `LISTBOXITEMINFO` 结构,而不能直接使用字符串地址,例如: + +```c +HICON hIcon1; /* 声明图标句柄 */ +LISTBOXITEMINFO lbii; /* 声明列表框条目结构体变量 */ + +hIcon1 = LoadIconFromFile (HDC_SCREEN, "res/audio.ico", 1); /* 加载图标 */ + +/* 设定结构信息,并添加条目 */ +lbii.hIcon = hIcon1; +lbii.cmFlag = CMFLAG_CHECKED; +lbii.string = "abcdefg"; +SendMessage (hChildWnd3, LB_ADDSTRING, 0, (LPARAM)&lbii); +``` + +其中,`cmFlag` 的值可以设置为 `CMFLAG_CHECKED`、`CMFLAG_BLANK` 以及 `CMFLAG_PARTCHECKED` 三种,分别表示:选中、未选中和部分选中。 + +我们也可以在高级列表框中显示位图,而不是默认的图标。如果你希望列表项显示的是位图而不是图标,则可以在该标志中包含 `IMGFLAG_BITMAP`,并在 `hIcon` 成员中指定位图对象的指针,如下所示: + +```c +/* 设定结构信息,并添加条目 */ +lbii.hIcon = (DWORD) GetSystemBitmap (SYSBMP_MAXIMIZE); +lbii.cmFlag = CMFLAG_CHECKED | IMGFLAG_BITMAP; +lbii.string = "abcdefg"; +SendMessage (hChildWnd3, LB_ADDSTRING, 0, (LPARAM)&lbii); +``` + +### 1.2.2 删除列表框条目 + +发送 `LB_DELETESTRING` 消息并指定索引值就可以从列表框中删除指定的条目: + +```c +SendMessage (hwndList, LB_DELETESTRING, index, 0) ; +``` + +我们甚至可以使用 `LB_RESETCONTENT` 消息清空列表框中的所有内容: + +```c +SendMessage (hwndList, LB_RESETCONTENT, 0, 0) ; +``` + +### 1.2.3 选择和取得条目 + +发送 `LB_GETCOUNT` 消息可获得列表框中的条目个数: + +```c +count = SendMessage (hwndList, LB_GETCOUNT, 0, 0) ; +``` + +在需要获得某个条目的字符串时,可发送 `LB_GETTEXTLEN` 消息确定列表框中指定条目的字符串长度: + +```c +length = SendMessage (hwndList, LB_GETTEXTLEN, index, 0) ; +``` + +并将该条目复制到文字缓冲区中: + +```c +length = SendMessage (hwndList, LB_GETTEXT, index, (LPARAM)buffer) ; +``` + +在这两种情况下,上述消息返回的 `length` 值是字符串的长度。对以 `NULL` 字符终结的字符串长度来说,`buffer` 必须足够大才行。你可以用 `LB_GETTEXTLEN` 消息返回的字符串长度来分配一些局部内存来存放字符串。 + +如果我们要设置列表框条目的字符串,可发送 `LB_SETTEXT` 消息: + +```c +SendMessage (hwndList, LB_SETTEXT, index, buffer) ; +``` + +对于高级列表框来讲,我们必须使用 `LB_GETITEMDATA` 和 `LB_SETITEMDATA` 才能获得列表框条目的其他信息,比如位图对象或图标句柄、检查框状态等,这些消息也可以用来获取或设置条目的字符串: + +```c +HICON hIcon1; /* 声明图标句柄 */ +LISTBOXITEMINFO lbii; /* 声明列表框条目结构体变量 */ + +hIcon1 = LoadIconFromFile (HDC_SCREEN, "res/audio.ico", 1); /* 加载图标 */ + +/* 设定结构信息,并添加条目 */ +lbii.hIcon = hIcon1; +lbii.cmFlag = CMFLAG_CHECKED; +lbii.string = "new item"; +SendMessage (hChildWnd3, LB_SETITEMDATA, index, (LPARAM)&lbii); +``` + +下面的消息用来检索列表框条目的选中状态,这些消息对单项选择列表框和多项选择列表框具有不同的调用方法。让我们先来看看单项选择列表框。 + +通常,用户会通过鼠标和键盘在列表框中选择条目。但是我们也可以通过程序来控制当前的选中项,这时,需要发送 `LB_SETCURSEL` 消息: + +```c +SendMessage (hwndList, LB_SETCURSEL, index, 0) ; +``` + +反之,我们可以使用 `LB_GETCURSEL` 获得当前选定的索引项: + +```c +index = SendMessage (hwndList, LB_GETCURSEL, 0, 0) ; +``` + +如果没有条目被选中,那么这个消息将返回 `LB_ERR`。 + +对于多项选择列表框来说,使用 `LB_SETCURSEL`、`LB_GETCURSEL` 只能用来设置和获取当前高亮项,无法获得所有具有选中状态的条目。但我们可以使用 `LB_SETSEL` 来设定某特定条目的选择状态,而不影响其他项: + +```c +SendMessage (hwndList, LB_SETSEL, wParam, (LPARAM)index) ; +``` + +`wParam` 参数不为 0 时,选择并加亮某一条目;`wParam` 为 0 时,取消选择。反之,我们可以用 `LB_GETSEL` 消息确定某特定条目的选择状态: + +```c +select = SendMessage (hwndList, LB_GETSEL, index, 0) ; +``` + +其中,如果由 `index` 指定的条目被选中,`select` 为非 0,否则为 0。 + +另外,你还可以使用 `LB_GETSELCOUNT` 消息获得多选列表框中当前被选中的条目个数。然后发送 `LB_GETSELITEMS` 消息获得所有被选中条目的索引值。示例如下: + +```c +int i, sel_count; +int* sel_items; + +sel_count = SendMessage (hwndList, LB_GETSELCOUNT, 0, 0L) ; +if (sel_count == 0) +return; + +sel_items = alloca (sizeof(int)*sel_count); +SendMessage (hwndList, LB_GETSELITEMS, sel_count, sel_items); +for (i = 0; i < sel_count; i++) { + /* sel_items [i] 为选中条目的索引值 */ +} +``` + +### 1.2.4 查找含有字符串的条目 + +```c +index = SendMessage (hwndList, LB_FINDSTRING, (LPARAM)string) ; +``` + +其中,`string` 为希望查找的字符串的指针,该消息返回模糊匹配字符串 `string` 的条目索引值,`LB_ERR` 表示查找失败。如果使用消息 `LB_FINDSTRINGEXACT` 将进行严格精确匹配查找。 + +### 1.2.5 设置和获取某条目的检查框的当前状态 + +```c +status = SendMessage (hwndList, LB_GETCHECKMARK, index, 0) ; +``` + +返回由 `index` 指定索引处条目的检查框的状态。如果没有找到相应条目,则返回 `LB_ERR`。`CMFLAG_CHECKED` 表示该条目的检查框处于选择状态。`CMFLAG_PARTCHECKED` 表示该条目的检查框处于部分选择状态。`CMFLAG_BLANK` 表示该条目的检查框处于未选择状态。 + +```c +ret = SendMessage (hwndList, LB_SETCHECKMARK, index, (LPARAM)status) ; +``` + +设置由 `index` 指定索引处条目的检查框的状态为 `status` 中指定的值。当没有找到 `index` 指定的条目时,返回 `LB_ERR` 表示失败,否则返回 `LB_OKAY` 表示成功。 + +### 1.2.6 设置某列表框条目加粗显示状态 + +```c +ret = SendMessage (hwndList, LB_SETITEMBOLD, index, (LPARAM)status) ; +``` + +设置由 `index` 指定索引处条目的检查框的状态为加粗显示状态。当没有找到 `index` 指定的条目时,返回 `LB_ERR` 表示失败,否则根据 `lParam` 的值判断是否进行加粗设置,如果为 1 则加粗显示,为 0 则正常显示。 + +### 1.2.7 设置和获取某列表框条目的选择状态 + +```c +status = SendMessage (hwndList, LB_GETITEMDISABLE, index, 0) ; +``` + +返回由 `index` 指定索引处条目的检查框是否处于禁止选中状态。如果没有找到相应条目,则返回 `LB_ERR`。1 表示该条目的检查框处于禁止选中状态。0 表示该条目的检查框处于可选择状态。 + +```c +ret = SendMessage (hwndList, LB_SETITEMDISABLE, index, (LPARAM)status) ; +``` + +设置由 `index` 指定索引处条目的检查框的状态为禁止选中状态。当没有找到 `index` 指定的条目时,返回 `LB_ERR` 表示失败,否则根据 `lParam` 的值判断是否进行禁止选中状态设置,如果为 1 则设置为禁止选中状态,为 0 则为可选择状态。 + +### 1.2.8 添加多个列表框条目 + +`LB_MULTIADDITEM` 消息用于向列表框一次添加多个条目。当列表框控件所能存储的内容超过了可用内存空间时,`SendMessage` 将传回 `LB_ERRSPACE`。如果是因为其他原因而出错,那么`SendMessage` 将传回 `LB_ERR`。如果操作成功,那么 `SendMessage` 将传回 `LB_OKAY`。我们可以通过测试 `SendMessage` 的非零值来判断出这两种错误。 + +如果你采用 `LBS_SORT` 风格,或者仅仅希望将新的字符串数组追加到列表框条目中,方法如下: + +```c +int num = 2; +const char text[num][] = {“item1”, “item2”}; + +SendMessage (hwndList, LB_MULTIADDITEM, num, (LPARAM)text); +``` + +其中 `hwndList` 是列表框控件的句柄,`num` 是添加的条目数,`text` 是字符串数组。 + +需要注意的是,在指定了 `LBS_CHECKBOX` 或者 `LBS_USEICON` 风格之后,在向列表框添加多个条目时,必须使用 `LISTBOXITEMINFO` 结构,而不能直接使用字符串数组地址,例如: + +```c +int num = 2; +HICON hIcon1; /* 声明图标句柄 */ +LISTBOXITEMINFO lbii[num]; /* 声明列表框条目结构体变量数组 */ + +hIcon1 = LoadIconFromFile (HDC_SCREEN, "res/audio.ico", 1); /* 加载图标 */ + +/* 设定结构信息,并添加条目 */ +lbii[0].hIcon = hIcon1; +lbii[0].cmFlag = CMFLAG_CHECKED; +lbii[0].string = "item1"; + +lbii[1].hIcon = hIcon1; +lbii[1].cmFlag = CMFLAG_CHECKED; +lbii[1].string = "item2"; + +SendMessage (hwndList, LB_MULTIADDITEM, num, (LPARAM)lbii); +``` + +### 1.2.9 其他消息 + +默认情况下,具有 `LBS_SORT` 风格的列表框在排序时使用标准 C 函数的 `strncmp` 函数排序。但我们可以通过 `LB_SETSTRCMPFUNC` 来重载默认的排序方式,而以自己希望的方式排序。比如: + +```c +static int my_strcmp (const char* s1, const char* s2, size_t n) +{ + int i1 = atoi (s1); + int i2 = atoi (s2); + return (i1 – i2); +} + +SendMessage (hwndList, LB_SETSTRCMPFUNC, 0, (LPARAM)my_strcmp); +``` + +这样,列表框将使用我们自己定义的函数对条目进行排序。上述排序函数可用来对诸如 1、2、3、4、10、20 等条目进行正常的以数值为大小的排序,而默认的排序规则会将上述 6 个数字排序为 1、10、2、20、3、4。一般而言,应用程序要在添加条目之前使用该消息设定新的字符串比较函数。 + +我们还可以为每个列表框条目追加一个附加的 32 位数据,并在适当的时候将这个值取出,这时,我们可以使用 `LB_SETITEMADDDATA` 和 `LB_GETITEMADDDATA` 消息。这两个消息所操作的值对列表框控件来说没有任何意义,它只是负责存储这个值,并在需要时返回这个值。 + +另外,我们还可以使用 `LB_SETITEMHEIGHT` 来消息来设定条目所占的高度,`LB_GETITEMHEIGHT` 返回这个高度。通常情况下,条目的高度取决于控件字体的大小,当控件字体发生变化时(调用 `SetWindowFont` 而改变),条目的高度将发生变化。用户也可以设定一个自己的条目高度。实际的高度将是设定高度和控件字体大小的最大值。 + +## 1.3 列表框通知码 + +具有 `LBS_NOTIFY` 风格的列表框可能产生的通知消息及其含义如__表 1.1__ 所示。 + +__表 1.1__ 列表框通知码 +| 通知码标识符 | 含义 | +|:--------------------|:----| +|`LBN_ERRSPACE` |内存分配失败。| +|`LBN_SELCHANGE` |单项选择列表框的当前选择项发生变化。| +|`LBN_CLICKED` |用户在列表框某条目上单击了鼠标左键。| +|`LBN_DBLCLK` |用户在列表框某条目上双击了鼠标左键。| +|`LBN_SELCANCEL` |用户取消了某个条目的选择。| +|`LBN_SETFOCUS` |列表框获得了输入焦点。| +|`LBN_KILLFOCUS` |列表框失去了输入焦点。| +|`LBN_CLICKCHECKMARK` |用户单击了条目的检查框。| +|`LBN_ENTER` |用户在列表框中按下 `ENTER` 键| + + +只有列表框窗口风格包括 `LBS_NOTIFY` 时,列表框控件才会向父窗口发送上述通知消息。当然,如果你调用 `SetNotificationCallback` 函数设定了列表框控件的通知回调函数,则控件不会向父窗口发送 `MSG_COMMAND` 通知消息,而是会直接调用设定的通知回调函数。 + +`LBN_ERRSPACE` 表示内存分配失败。`LBN_SELCHANGE` 表示目前选中条目已经被改变,这个消息出现在下列情况下:用户在列表框中使用键盘或鼠标改变加亮的条目时,或者用户使用空格键或鼠标切换选择状态时。`LBN_CLICKED` 表示列表框被鼠标点击了。该消息出现在使用鼠标单击某项时。`LBN_DBLCLK` 说明某条目已经被鼠标双击。如果设置了 `LBS_CHECKBOX` 风格,`LBN_CLICKCHECKMARK` 表示鼠标击中了检查方框,如果同时设置了 `LBS_AUTOCHECK` 风格,则检查框会自动在勾选或者空白间切换。 + +根据应用程序的需要,也许要使用 `LBN_SELCHANGE` 或 `LBN_DBLCLK`,也许二者都要使用。程序会收到许多 `LBN_SELCHANGE` 消息,但是 `LBN_DBLCLK` 消息只有当使用者双击鼠标时才会出现。 + +## 1.4 编程实例 + +__清单 1.1__ 所示的程序代码,给出了列表框控件的使用范例。该程序仿照“打开文件”对话框,实现了文件删除功能。初始时,程序列出了当前目录下的所有文件,用户也可以通过目录列表框切换到其他路径。用户可在文件列表框中通过检查框选择多个要删除的文件,当用户按“删除”按钮时,该程序将提示用户。当然,为了保护用户的文件,该程序并未实现真正的删除功能。该程序创建的对话框效果如__图 1.4__ 所示,完整源代码见本指南示例程序包 `mg-samples` 中的 `listbox.c` 文件。 + +__清单 1.1__ 列表框控件的使用范例 + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define IDL_DIR 100 +#define IDL_FILE 110 +#define IDC_PATH 120 + +/* 定义对话框模板 */ +static DLGTEMPLATE DlgDelFiles = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 100, 100, 304, 225, + "删除文件", + 0, 0, + 7, NULL, + 0 +}; + +static CTRLDATA CtrlDelFiles[] = +{ + { + CTRL_STATIC, + WS_VISIBLE | SS_SIMPLE, + 10, 10, 130, 15, + IDC_STATIC, + "目录列表框", + 0 + }, + /* 这个列表框中显示目录项 */ + { + CTRL_LISTBOX, + WS_VISIBLE | WS_VSCROLL | WS_BORDER | LBS_SORT | LBS_NOTIFY, + 10, 30, 130, 100, + IDL_DIR, + "", + 0 + }, + { + CTRL_STATIC, + WS_VISIBLE | SS_SIMPLE, + 150, 10, 130, 15, + IDC_STATIC, + "文件列表框", + 0 + }, + /* 这个列表框中显示文件,前面带一个检查框 */ + { + CTRL_LISTBOX, + WS_VISIBLE | WS_VSCROLL | WS_BORDER | LBS_SORT | LBS_AUTOCHECKBOX, + 150, 30, 130, 100, + IDL_FILE, + "", + 0 + }, + /* 这个静态框用来显示当前路径信息 */ + { + CTRL_STATIC, + WS_VISIBLE | SS_SIMPLE, + 10, 150, 290, 15, + IDC_PATH, + "路径:", + 0 + }, + { + "button", + WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_GROUP, + 10, 170, 130, 25, + IDOK, + "删除", + 0 + }, + { + "button", + WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, + 150, 170, 130, 25, + IDCANCEL, + "取消", + 0 + }, +}; + +/* 这个函数获取当前目录下的所有目录项,分别填到目录列表框和文件列表框中 */ +static void fill_boxes (HWND hDlg, const char* path) +{ + struct dirent* dir_ent; + DIR* dir; + struct stat ftype; + char fullpath [PATH_MAX + 1]; + + SendDlgItemMessage (hDlg, IDL_DIR, LB_RESETCONTENT, 0, (LPARAM)0); + SendDlgItemMessage (hDlg, IDL_FILE, LB_RESETCONTENT, 0, (LPARAM)0); + SetWindowText (GetDlgItem (hDlg, IDC_PATH), path); + + if ((dir = opendir (path)) == NULL) + return; + + while ( (dir_ent = readdir ( dir )) != NULL ) { + + /* Assemble full path name. */ + strncpy (fullpath, path, PATH_MAX); + strcat (fullpath, "/"); + strcat (fullpath, dir_ent->d_name); + + if (stat (fullpath, &ftype) < 0 ) { + continue; + } + + if (S_ISDIR (ftype.st_mode)) + SendDlgItemMessage (hDlg, IDL_DIR, LB_ADDSTRING, 0, (LPARAM)dir_ent->d_name); + else if (S_ISREG (ftype.st_mode)) { + /* 使用检查框的列表框,需要使用下面的结构 */ + LISTBOXITEMINFO lbii; + + lbii.string = dir_ent->d_name; + lbii.cmFlag = CMFLAG_BLANK; + lbii.hIcon = 0; + SendDlgItemMessage (hDlg, IDL_FILE, LB_ADDSTRING, 0, (LPARAM)&lbii); + } + } + + closedir (dir); +} + +static void dir_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + /* 用户双击目录名或者按下 ENTER 键时,进入对应的目录 */ + if (nc == LBN_DBLCLK || nc == LBN_ENTER) { + int cur_sel = SendMessage (hwnd, LB_GETCURSEL, 0, 0L); + if (cur_sel >= 0) { + char cwd [MAX_PATH + 1]; + char dir [MAX_NAME + 1]; + GetWindowText (GetDlgItem (GetParent (hwnd), IDC_PATH), cwd, MAX_PATH); + SendMessage (hwnd, LB_GETTEXT, cur_sel, (LPARAM)dir); + + if (strcmp (dir, ".") == 0) + return; + strcat (cwd, "/"); + strcat (cwd, dir); + /* 重新填充两个列表框 */ + fill_boxes (GetParent (hwnd), cwd); + } + } +} + +static void file_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + /* Do nothing */ +} + +static void prompt (HWND hDlg) +{ + int i; + char files [1024] = "你选择要删除的文件是:\n"; + + /* 获取所有勾选的文件 */ + for (i = 0; i < SendDlgItemMessage (hDlg, IDL_FILE, LB_GETCOUNT, 0, 0L); i++) { + char file [MAX_NAME + 1]; + int status = SendDlgItemMessage (hDlg, IDL_FILE, LB_GETCHECKMARK, i, 0); + if (status == CMFLAG_CHECKED) { + SendDlgItemMessage (hDlg, IDL_FILE, LB_GETTEXT, i, (LPARAM)file); + strcat (files, file); + strcat (files, "\n"); + } + } + + /* 提示用户 */ + MessageBox (hDlg, files, "确认删除", MB_OK | MB_ICONINFORMATION); + + /* 在这里把那些文件真正删除! */ +} + +static int DelFilesBoxProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITDIALOG: + { + char cwd [MAX_PATH + 1]; + SetNotificationCallback (GetDlgItem (hDlg, IDL_DIR), dir_notif_proc); + SetNotificationCallback (GetDlgItem (hDlg, IDL_FILE), file_notif_proc); + fill_boxes (hDlg, getcwd (cwd, MAX_PATH)); + return 1; + } + + case MSG_COMMAND: + switch (wParam) { + case IDOK: + prompt (hDlg); + case IDCANCEL: + EndDialog (hDlg, wParam); + break; + } + break; + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +int MiniGUIMain (int argc, const char* argv[]) +{ + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "listbox" , 0 , 0); + + #endif + + DlgDelFiles.controls = CtrlDelFiles; + + DialogBoxIndirectParam (&DlgDelFiles, HWND_DESKTOP, DelFilesBoxProc, 0L); + + return 0; +} + +#ifndef _MGRM_PROCESSES +#include +#endif +``` + +![“删除文件”对话框](figures/Part4Chapter03-1.4.jpeg) +__图 1.4__ “删除文件”对话框 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter04-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter04-zh.md new file mode 100644 index 0000000..e22b8ce --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter04-zh.md @@ -0,0 +1,510 @@ +# 编辑框 + +编辑框为应用程序提供了接收用户输入和编辑文字的重要途径。相对前面提到的静态框、按钮和列表框等控件来讲,编辑框的用途和行为方式比较单一。它在得到输入焦点时显示一个闪动的插入符,以表明当前的编辑位置;用户键入的字符将插入到插入符所在位置。除此之外,编辑框还提供了诸如删除、移动插入位置和选择文本等编辑功能。 + +MiniGUI 中提供了三种类型的编辑框,分别对应于三种控件类,它们是: + +- 单行编辑框:类名 "sledit"、"edit",标识符 `CTRL_SLEDIT`、`CTRL_EDIT`。它只能处理单行文本,但在 MiniGUI 逻辑字体的帮助下,可以处理任意的多字节字符输入,包括变长字符集。该控件提供了选中、复制和粘贴等编辑功能。 +- 多行编辑框:类名 "textedit"、"mledit"、"medit",标识符 `CTRL_TEXTEDIT`、`CTRL_MLEDIT` 或者 `CTRL_MEDIT`。它用来处理多行文本,亦可处理任意的多字节字符输入,包括变长字符集。该控件提供了选中、复制和粘贴等编辑功能。 +- 单行双向文本编辑框: 类名 "bidisledit",标识符 `CTRL_BIDISLEDIT`。 它只能处理单行文本,该控件除了拥有单行编辑框的功能外,还能够显示和输入双向文本,包括阿拉伯文和希伯来文。该控件提供了选中、复制和粘贴等编辑功能。 + +除上述控件类差别之外,上述三种编辑框控件类的风格、消息以及通知码大体相似,只有少量不同。__图 1.1__ 给出了编辑框的运行效果。 + +![MiniGUI 编辑框](figures/Part4Chapter04-1.1.jpeg) +__图 1.1__ MiniGUI 编辑框 + + +## 1.1 编辑框风格 + +通常,我们在创建编辑框时使用下面的风格组合: + +```c +WS_CHILD | WS_VISIBLE | WS_BORDER +``` + +显然,上述风格定义中没有使用任何编辑框特有的风格。也就是说,我们无需指定编辑框特有的风格就能正常使用编辑框。但编辑框也有一些自己的特有风格,主要包括: +- `ES_UPPERCASE`:可以使编辑框只显示大写字母。 +- `ES_LOWERCASE`:可以使编辑框只显示小写字母。 +- `ES_PASSWORD`:编辑框用来输入密码,但用星号 (`*`) 显示输入的字符。 +- `ES_READONLY`:建立只读编辑框,用户不能修改编辑框中的内容,但插入符仍然可见。 +- `ES_BASELINE`:在编辑框文本下显示虚线。 +- `ES_AUTOWRAP`:用于多行编辑框,当文本超过控件边界时,将自动换行。 +- `ES_LEFT`:指定非多行编辑框的对齐风格,实现文本的左对齐风格。 +- `ES_NOHIDESEL`:编辑框在失去焦点时保持被选择文本的选中状态。 +- `ES_AUTOSELECT`:编辑框在得到焦点时自动选中所有的文本内容(仅针对单行编辑框)。 +- `ES_TITLE`:在编辑框的第一行显示指定的标题,只适用于多行编辑框控件。 +- `ES_TIP`:当编辑框的内容为空时,在其中显示相关的提示信息;只适用于 `SLEDIT` 控件。 +- `ES_CENTER`:指定非多行编辑框的对齐风格,实现文本的居中对齐风格。 +- `ES_RIGHT`:指定非多行编辑框的对齐风格,实现文本的右对齐风格。 + +对多行编辑框,如果希望使用滚动条,则可以指定窗口风格 `WS_HSCROLL` 和 `WS_VSCROLL`。 + +其中适用于多行编辑框的风格有:`ES_UPPERCASE`,`ES_LOWERCASE`,`ES_READONLY`,`ES_BASELINE`,`ES_AUTOWRAP`,`ES_NOHIDESEL`,`ES_TITLE`。 + +其中适用于单行编辑框的风格有:`ES_UPPERCASE`,`ES_LOWERCASE`,`ES_READONLY`,`ES_BASELINE`,`ES_LEFT`,`ES_CENTER`,`ES_RIGHT`,`ES_PASSWORD`,`ES_NOHIDESEL`,`ES_AUTOSELECT`,`ES_TIP`。 + +## 1.2 编辑框消息 + +使用下面的消息,可获得编辑框中的当前文本信息。这些消息可用于上述两种编辑框类型: + +- `MSG_GETTEXTLENGTH`:获取文本的长度,以字节为单位。 +- `MSG_GETTEXT`:获取编辑框中的文本。 +- `MSG_SETTEXT`:设置编辑框中的文本内容。 + +应用程序也可以直接调用下面三个函数来完成相应的工作: + +- `GetWindowTextLength` +- `GetWindowText` +- `SetWindowText` + +实际上,上述三个函数就是对前三个消息的简单封装。我们在前面几章也看到,相同的消息和函数也可以用在静态框及按钮等控件身上。 + +接下来要介绍的几个消息是编辑框特有的。 + +### 1.2.1 获取和设置插入符位置 + +向编辑框发送 `EM_GETCARETPOS` 消息将获得当前的插入符位置: + +```c +int line_pos; +int char_pos; + +SendMessage (hwndEdit, EM_GETCARETPOS, (WPARAM) &line_pos, (LPARAM) &char_pos); +``` + +该消息返回之后,`line_pos` 和 `char_pos` 分别含有插入符所在的行索引值以及在该行的字符位置。对单行编辑框来讲,`line_pos` 永远为零,所以,该消息的 `wParam` 可传零值。 + +注意:对应多行编辑框来说,一行指的是以行结束符(回车换行符)结束的一个字符串行,而不是以 `ES_AUTOWRAP` 绕行风格显示时的一个段落内的一行。MiniGUI 中编辑框的字符位置在显示多字节文本时是以多字节字符(比如说汉字)为单位,而不是以字节为单位。这个定义对于编辑框的其它消息是相同的。 + +应用程序也可通过 `EM_SETCARETPOS` 消息设置插入符的位置: + +```c +int line_pos; +int char_pos; + +SendMessage (hwndEdit, EM_SETCARETPOS, line_pos, char_pos); +``` + +`wParam` 和 `lParam` 参数分别指定行位置和字符位置。 + +### 1.2.2 设置和获取选中的文本 + +`EM_GETSEL` 消息用来获取当前被选中的文本。 + +```c +char buffer[buf_len]; + +SendMessage (hwndEdit, EM_GETSEL, buf_len, (LPARAM) buffer); +``` + +其中 `lParam` 参数指定用来保存所获取文本的字符缓冲区,`wParam` 参数指定该缓冲区的大小。如果指定的缓冲区较小,多余的文本将被截去。 + +`EM_SETSEL` 消息用来设置当前被选中的文本。 + +```c +int line_pos, char_pos; + +SendMessage (hwndEdit, EM_SETSEL, line_pos, char_pos); +``` + +其中 `lParam` 参数指定选择点的行索引值,`wParam` 指定选择点的行内字符位置。发送该消息之后,当前插入点和选择点之间的文本将被选中。 + +`EM_GETSELPOS` 消息用来获取当前的选择点位置。 + +```c +int line_pos; +int char_pos; + +SendMessage (hwndEdit, EM_GETCARETPOS, (WPARAM) &line_pos, (LPARAM) &char_pos); +``` + +`EM_GETSELPOS` 消息的用法和 `EM_GETCARETPOS` 消息类似。 + +`EM_SELECTALL` 消息用来使编辑框所有的文本都被选中,相当于“CTRL+A”操作: + +```c +SendMessage (hwndEdit, EM_SELECTALL, 0, 0); +``` + +### 1.2.3 复制、剪切和粘贴 + +可以通过键盘操作或者发相应消息的方式来对编辑框控件进行复制、剪切和粘贴等编辑操作。编辑框控件的复制等键盘操作: +- `CTRL+C`:把文本从编辑框复制到剪贴板 +- `CTRL+V`:从剪贴板粘贴文本到编辑框 +- `CTRL+X`:把编辑框文本剪切到剪贴板 + +`EM_COPYTOCB` 消息用来把编辑框控件当前选中的文本复制到剪贴板,相当于“CTRL+C”操作: + +```c +SendMessage (hwndEdit, EM_COPYTOCB, 0, 0); +``` + +`EM_CUTTOCB` 消息用来把编辑框中选中的内容剪切到剪贴板中,相当于“CTRL+X”操作: + +CODE{"cpp"}% +SendMessage (hwndEdit, EM_CUTTOCB, 0, 0); +``` + +`EM_INSERTCBTEXT` 消息用来把剪贴板的文本内容复制到编辑框,相当于“CTRL+V”操作: + +```c +SendMessage (hwndEdit, EM_INSERTCBTEXT, 0, 0); +``` + +### 1.2.4 获取和设置行高等属性 + +这里的行高表示回绕显示方式下的一个单行的高度。 + +`EM_GETLINEHEIGHT` 消息用来获取行的高度: + +```c +int line_height; +line_height = SendMessage (hwndEdit, EM_GETLINEHEIGHT, 0, 0); +``` + +`EM_SETLINEHEIGHT` 消息用来设置行的高度,设置成功返回原行高值,失败返回-1: + +```c +int line_height; +SendMessage (hwndEdit, EM_SETLINEHEIGHT, line_height, 0); +``` + +> 注意:最好在设置编辑框的文本前使用EM_SETLINEHEIGHT消息,因为重新设置行高将把编辑框的内容清空。 + +`EM_GETLINECOUNT` 消息用来获取行的数量: + +```c +int line_count; +line_count = SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0); +``` + +这里的行表示回绕显示方式下的一个单行。 + +### 1.2.5 设置文本上限 + +向编辑框发送 `EM_LIMITTEXT` 可设置编辑框控件的文本上限,以字节为单位: + +```c +SendMessage (hwndEdit, EM_LIMITTEXT, 10, 0L); +``` + +上面的消息将使编辑框只能输入总长为 10 字节的字符。 + +### 1.2.6 设置和取消只读状态 + +使用 `EM_SETREADONLY` 消息,并在为 `wParam` 参数传递 `TRUE`,将使编辑框置于只读状态,而为 `wParam` 参数传递 `FALSE`,将使编辑框置于正常编辑状态。 + +### 1.2.7 设置和获取密码字符 + +默认情况下,MiniGUI 使用星号(`*`)来显示密码编辑框中键入的文字,但我们也可以使用 `EM_SETPASSWORDCHAR` 消息来修改密码字符: + +```c +SendMessage (hwndEdit, EM_SETPASSWORDCHAR, ‘%’, 0L); +``` + +上面的消息调用将把密码字符修改为百分号(`%`)。 + +使用 `EM_GETPASSWORDCHAR` 消息将获得当前的密码字符。 + +>【提示】密码字符仅可设置为可显示的ASCII码。 + +`TEXTEDIT` 控件目前没有实现 `EM_SETPASSWORDCHAR` 和 `EM_GETPASSWORDCHAR` 消息。 + +### 1.2.8 设置标题文字和提示文字 + +当 `SLEDIT` 控件具有 `ES_TIP` 风格时,可以使用 `EM_SETTIPTEXT` 消息来设置编辑框的提示文字,使用 `EM_GETTIPTEXT` 消息来获取编辑框的提示文字: + +```c +int len; +char *tip_text; +SendMessage (hwndEdit, EM_SETTIPTEXT, len, (LPARAM)tip_text); +``` + +`lParam` 参数指定提示文字字符串,`wParam` 参数指定字符串的长度;如果 `tip_text` 是以’\0’结束的话,`wParam` 可以设为 -1;如果 `wParam` 为 0,提示文字将为空。`EM_GETTIPTEXT` 消息将返回当前提示文字的字符串: + +```c +int len; +char tip_text[len+1]; +SendMessage (hwndEdit, EM_GETTIPTEXT, len, (LPARAM)tip_text); +``` + +`lParam` 参数指定存储提示文字的字符串缓冲区,`wParam` 参数指定缓冲区能够存放字符串的长度(不包括’\0’)。 + +当 `TEXTEDIT` 控件具有 `ES_TITLE` 风格时,可以使用 `EM_SETTITLETEXT` 消息来设置编辑框的标题文字,使用 `EM_GETTITLETEXT` 消息来获取编辑框的标题文字: + +```c +int len; +char *title_text; +SendMessage (hwndEdit, EM_SETTITLETEXT, len, (LPARAM)title_text); +``` + +`lParam` 参数指定标题文字字符串,`wParam` 参数指定字符串的长度;如果 `title_text` 是以’\0’结束的话,`wParam` 可以设为 -1;如果 `wParam` 为0,标题文字将为空。 `EM_GETTITLETEXT` 消息将返回当前标题文字的字符串: + +```c +int len; +char title_text[len+1]; +SendMessage (hwndEdit, EM_GETTITLETEXT, len, (LPARAM)title_text); +``` + +`lParam` 参数指定存储标题文字的字符串缓冲区,`wParam` 参数指定缓冲区能够存放字符串的长度(不包括’\0’)。 + +### 1.2.9 设置行结束符的显示符号 + +正常情况下,`TEXTEDIT` 编辑框是不把换行符号显示出来的。如果使用 `EM_SETLFDISPCHAR` 消息设置了用于行结束符的显示符号,编辑框将把行结束符显示为所设的显示符号。 + +```c +char disp_char; +SendMessage (hwndEdit, EM_SETLFDISPCHAR, 0, disp_char); +``` + +`lParam` 参数为所要设的行结束符的显示符号。 + +例如,如果要用“*”星号来显示行结束符,可以这样设置: + +```c +SendMessage (hwndEdit, EM_SETLFDISPCHAR, 0, ‘*’); +``` + +### 1.2.10 设置行结束符 + +默认情况下,`TEXTEDIT` 编辑框的换行符号为’\n’。可以使用 `EM_SETLINESEP` 消息改变编辑框使用的换行符号: + +```c +char sep_char; +SendMessage (hwndEdit, EM_SETLINESEP, 0, sep_char); +``` + +`lParam` 参数为所要设的行结束符。 + +例如,如果要用 `TAB` 来标志行的结束,可以这样设置: + +```c +SendMessage (hwndEdit, EM_SETLINESEP, 0, ‘\t’); +``` + +### 1.2.11 获取段落数、指定段落长度和文本 + +`EM_GETNUMOFPARAGRAPHS` 消息用来获取文本的段落数目: + +```c +int num; +num = SendMessage (hwnd, EM_GETNUMOFPARAGRAPHS, 0, 0); +``` + +`EM_GETPARAGRAPHLENGTH` 消息用来获取某个特定段落的长度。获取成功返回指定段落的长度,失败返回 -1: + +```c +int len; +len = SendMessage (hwnd, EM_GETPARAGRAPHLENGTH, idx, 0); +``` + +其中 `wParam` 参数指定要获取文本的段落索引。 + +`EM_GETPARAGRAPHTEXT` 消息用来获取特定段落的文本。为了保证获取文本的字符完整性,会对要拷贝的字节长度进行适当调整: + +```c +TEXTPOSINFO info; +unsigned char buff [32]; + +info.start_pos = 5; +info.copy_len = 10; +info.buff = buff; +info.paragraph_index = -1; + +SendMessage (hwnd, EM_GETPARAGRAPHTEXT, &info, 0); +``` + +`info` 为一个 `TEXTPOSINFO` 类型的结构,指定了与要获取的文本相关的信息。结构定义如下: + +```c +typedef struct _TEXTPOSINFO { + /*The index of paragraph, if value is -1, + *it will take effect on the whole text.*/ + int paragraph_index; + /*The beginning byte position can be copied to the buffer.*/ + int start_pos; + /*The maximal number of bytes can be copied to the buffer.*/ + int copy_len; + /*The pointer to a buffer receives the text. + *Please make sure buffer size is more than the value of copy_len.*/ + char *buff; +}TEXTPOSINFO; +``` + +其中 `start_pos` 成员为获取文本的起始位置;`copy_len` 为获取文本的字节数;`paragraph_index` 为获取文本所在的段落索引(若设置为 -1,将以所有段落为准拷贝所需的字符长度);`buff` 为保存所获取文本的字符缓冲区。 + +## 1.3 编辑框通知码 + +编辑框没有 `ES_NOTIFY` 风格,因此,任意一个编辑框控件均可能产生通知消息,通知码如下所列: + +- `EN_SETFOCUS`:编辑控件已经获得输入焦点 +- `EN_KILLFOCUS`:编辑控件已经失去输入焦点 +- `EN_CHANGE`:编辑控件的内容已经改变 +- `EN_UPDATE`: 编辑控件在接收到 `MSG_SETTEXT`, `EM_RESETCONTENT` 或 `EM_SETLINEHEIGHT` 消息后,内容已经改变 +- `EN_ENTER`:用户在编辑框中按下了 `Enter` 键 +- `EN_MAXTEXT`:编辑控件在插入时超出了限定长度 +- `EN_DBLCLK`:编辑控件被鼠标左键双击 +- `EN_CLICKED`:编辑控件被鼠标左键点击 + +## 1.4 编程实例 + +在前面的章节中,我们已经看到了编辑框的用法。__清单 1.1__ 给出了编辑框的另一个使用实例,该程序将用户在单行编辑框中的输入随用户的编辑复制到自动换行的多行编辑框中。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `edit.c` 文件。该程序的运行效果见__图 1.2__。 + +__清单 1.1__ 编辑框使用实例 + +```c +#include +#include + +#include +#include +#include +#include +#include + +/* 定义对话框模板 */ +static DLGTEMPLATE DlgBoxInputChar = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 0, 0, 400, 220, + #ifdef _LANG_ZHCN + "请键入字母", + #else + "Please input letters", + #endif + 0, 0, + 4, NULL, + 0 +}; + +#define IDC_CHAR 100 +#define IDC_CHARS 110 + +static CTRLDATA CtrlInputChar [] = +{ + { + CTRL_STATIC, + WS_VISIBLE , + 10, 10, 380, 18, + IDC_STATIC, + #ifdef _LANG_ZHCN + "请输入一个字母:", + #else + "Please input a letter:", + #endif + 0 + }, + { + CTRL_SLEDIT, + WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_CENTER, + 10, 40, 80, 25, + IDC_CHAR, + NULL, + 0 + }, + { + CTRL_MLEDIT, + WS_VISIBLE | WS_BORDER | WS_VSCROLL | ES_BASELINE | ES_AUTOWRAP, + 10, 80, 380, 70, + IDC_CHARS, + NULL, + 0 + }, + { + CTRL_BUTTON, + WS_TABSTOP | WS_VISIBLE | BS_DEFPUSHBUTTON, + 170, 160, 60, 25, + IDOK, + #ifdef _LANG_ZHCN + "确定", + #else + "OK", + #endif + 0 + } +}; + +static void my_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + unsigned char buff [256] = {0}; + if (id == IDC_CHAR && nc == EN_CHANGE) { + /* 将用户在单行编辑框中的输入取出(第一个字母),然后插入到多行编辑框中 */ + GetWindowText (hwnd, buff, 4); + /* 将单行编辑框的插入符置于头部,以便覆盖老的字母 */ + SendMessage (hwnd, EM_SETCARETPOS, 0, 0); + SendMessage (GetDlgItem (GetParent (hwnd), IDC_CHARS), MSG_CHAR, buff[0], 0L); + } + else if (id == IDC_CHARS && nc == EN_CHANGE) { + GetWindowText (hwnd, buff, 255); + printf ("String: %s\n", buff); + } +} + +static int InputCharDialogBoxProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + static PLOGFONT my_font; + HWND hwnd; + + switch (message) { + case MSG_INITDIALOG: + my_font = CreateLogFont (NULL, "fmhei", "ISO8859-1", + FONT_WEIGHT_REGULAR, FONT_SLANT_ROMAN, FONT_FLIP_NIL, + FONT_OTHER_NIL, FONT_UNDERLINE_NONE, FONT_STRUCKOUT_NONE, + 20, 0); + hwnd = GetDlgItem (hDlg, IDC_CHAR); + /* 将单行编辑框的字体设置为大字体 */ + SetNotificationCallback (hwnd, my_notif_proc); + /* 模拟 INSERT 键的按下,将单行编辑框的编辑模式设置为覆盖模式 */ + SendMessage (hwnd, MSG_KEYDOWN, SCANCODE_INSERT, 0L); + return 1; + + case MSG_CLOSE: + EndDialog (hDlg, IDCANCEL); + break; + + case MSG_COMMAND: + switch (wParam) { + case IDOK: + case IDCANCEL: + DestroyLogFont (my_font); + EndDialog (hDlg, wParam); + break; + } + break; + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +int MiniGUIMain (int argc, const char* argv[]) +{ + + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "edit" , 0 , 0); + #endif + + #ifdef _LITE_VERSION + if (!InitVectorialFonts ()) { + printf ("InitVectorialFonts: error.\n"); + return 1; + } + #endif + + DlgBoxInputChar.controls = CtrlInputChar; + DialogBoxIndirectParam (&DlgBoxInputChar, HWND_DESKTOP, InputCharDialogBoxProc, 0L); + + #ifdef _LITE_VERSION + TermVectorialFonts (); + #endif + return 0; +} + +#ifndef _LITE_VERSION +#include +#endif + +``` + +![编辑框示例程序的运行效果](figures/Part4Chapter04-1.2.jpeg) +__图 1.2__ 编辑框示例程序的运行效果 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter05-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter05-zh.md new file mode 100644 index 0000000..4a21647 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter05-zh.md @@ -0,0 +1,374 @@ +# 组合框 + +从本质上讲,通常的组合框就是编辑框和列表框的组合。用户可以直接在编辑框中键入文本,也可以从列表框列出的可选项中选择一个已有的条目。MiniGUI 中的组合框可以划分为三种类型:简单组合框、下拉式组合框、旋钮组合框和旋钮数字框。使用下拉式组合框还有一个好处,即能够减少因为使用列表框而带来的对窗口的空间占用。 + +以 `CTRL_COMBOBOX` 为控件类名调用 `CreateWindow` 函数,即可创建组合框控件。 + +## 1.1 组合框的类型和风格 + +### 1.1.1 简单组合框、下拉式组合框以及旋钮组合框 + +将组合框的风格设置为 `CBS_SIMPLE`,即可创建一个简单组合框。简单组合框中的列表框会一直显示在编辑框下面。当用户在列表框中选择某个条目时,就会用该条目的文本填充编辑框,如__图 1.1__ 所示。 + +![简单组合框](figures/Part4Chapter05-1.1.jpeg) +__图 1.1__ 简单组合框 + + +使用组合框风格 `CBS_DROPDOWNLIST`,可创建下拉式组合框。下拉式组合框与简单组合框不同的是,常态下,组合框只在矩形区域内显示一个条目,在条目内容的右边有一个指向下方的图标,当鼠标点击该图标时,会弹出一个列表框来显示更多的内容。__图 1.2__ 给出了下拉式组合框在常态下的效果和下拉之后的效果。 + +![下拉式组合框](figures/Part4Chapter05-1.2.jpeg) +__图 1.2__ 下拉式组合框 + +在调用 `CreateWindow` 函数创建简单组合框和下拉组合框时,应通过 `CreateWindow` 函数的 `dwAddData` 参数传递列表框的高度值,如下所示: + +```c +hwnd4 = CreateWindow (CTRL_COMBOBOX, +"0", +WS_VISIBLE | CBS_SIMPLE | CBS_SORT | WS_TABSTOP, +IDC_BOX4, +10, 100, 180, 24, +parent, 100); /* 指定 dwAddData 为 100,指定简单组合框的列表框高度为 100 */ +``` + +旋钮组合框的行为和上面两类组合框有点较大的差别,但本质上仍然是编辑框和列表框的组合,只是旋钮组合框的列表框永远是隐藏的,我们可通过编辑框旁边的两个箭头按钮来选择列表框中的条目。使用 `CBS_SPINLIST` 类型风格就可以创建旋钮组合框,其效果见__图 1.3__。 + +![旋钮组合框](figures/Part4Chapter05-1.3.jpeg) +__图 1.3__ 旋钮组合框 + +旋钮组合框还具有两种特殊的显示风格(如__图 1.4__ 所示): + +![旋钮组合框的特殊风格(figures/Part4Chapter05-1.4.jpeg) +__图 1.4__ 旋钮组合框的特殊风格 + +- `CBS_SPINARROW_TOPBOTTOM`: 箭头在内容的上下 +- `CBS_SPINARROW_LEFTRIGHT`:箭头在内容的左右 + +因为上述这三种组合框在内部使用了 MiniGUI 预定义的编辑框和列表框,因此,组合框中也可以使用编辑框控件和列表框控件的类似风格: + +- `CBS_READONLY`:使组合框的输入域成为只读区域,对应编辑框的 `ES_READONLY` 风格; +- `CBS_UPPERCASE`:对应编辑框的 `ES_UPPERCASE` 风格,使键入编辑框中的文本自动变成大写。 +- `CBS_LOWERCASE`:对应编辑框的 `ES_LOWERCASE` 风格,使键入编辑框中的文本自动变成小写。 +- `CBS_EDITBASELINE`:对应编辑框的 `ES_BASELINE` 风格,使编辑框带有文本基线。 +- `CBS_SORT`:对应列表框的 `LBS_SORT` 风格。使用该风格的组合框将自动对插入的条目进行排序。 + +>【注意】尽管列表框还可以有其他高级类型,但组合框中的列表框仅仅是一个普通的单选列表框。 + +除上述风格之外,组合框还定义了如下两个风格,可用于所有的组合框类型: + +- `CBS_EDITNOBORDER`:使编辑框不带 `WS_BORDER` 风格,使得输入域不带边框。 +- `CBS_AUTOFOCUS`:组合框在获得输入焦点之后,编辑框将自动获得输入焦点。 + +### 1.1.2 旋钮数字框 + +旋钮数字框在外观上和旋钮组合框类似,但其中显示的是数字而不是列表框中的条目。这种类型的组合框可在许多场合使用。但因为所操作的是数字而不是任意的文本,因此其内部没有使用列表框。使用组合框的风格类型 `CBS_AUTOSPIN` 风格就可以创建旋钮数字框,如__图 1.5__ 所示。 + +![旋钮数字框](figures/Part4Chapter05-1.5.jpeg) +__图 1.5__ 旋钮数字框 + +旋钮数字框只有一个风格,即 `CBS_AUTOLOOP`。使用该风格后,框中的数字将自动循环显示。也就是说,用户在单击向上箭头达到最大值之后,再次单击向上箭头,编辑框中的数字将变成最小值。旋钮数字框默认的最小值和最大值为 0 和 100,每次点击右侧的旋钮,数值的默认增减幅度为 1。 + +默认的最小值和最大值是 0 和 100,每次点击右侧的按钮数值的默认增减幅度为 1,点击 `pagedown` 或 `pageup` 默认的增减幅度为 5。 + +## 1.2 组合框消息 + +### 1.2.1 简单组合框、下拉式组合框以及旋钮组合框的消息 + +因为这三种组合框均含有列表框,因此,用于这三种组合框的消息基本上和列表框消息一一对应: + +- `CB_ADDSTRING`:对应 `LB_ADDSTRING`,用来向内部列表框中添加条目。 +- `CB_INSERTSTRING`:对应 `LB_INSERTSTRING`,用来向内部列表框中插入条目。 +- `CB_DELETESTRING`:对应 `LB_DELETESTRING`,用来从内部列表框中删除条目。 +- `CB_FINDSTRING`:对应 `LB_FINDSTRING`,用于模糊匹配列表框中的条目。 +- `CB_FINDSTRINGEXACT`:对应 `LB_FINDSTRINGEXACT`,用于精确匹配列表框中的条目。 +- `CB_GETCOUNT`:对应 `LB_GETCOUNT`,用于获取内部列表框中的条目个数。 +- `CB_GETCURSEL`:对应 `LB_GETCURSEL`,用于获取内部列表框的当前选中项。 +- `CB_SETCURSEL`:对应 `LB_SETCURSEL`,用于设置内部列表框的选中项。 +- `CB_RESETCONTENT`:对应 `LB_RESETCONTENT`,用于清空内部列表框。 +- `CB_GETITEMADDDATA`:对应 `LB_GETITEMADDDATA`,用于获取内部列表框条目的附加数据。 +- `CB_SETITEMADDDATA`:对应 `LB_SETITEMADDDATA`,用于设置内部列表框条目的附加数据。 +- `CB_GETITEMHEIGHT`:对应 `LB_GETITEMHEIGHT`,用于获取内部列表框条目的高度。 +- `CB_SETITEMHEIGHT`:对应 `LB_SETITEMHEIGHT`,用于设置内部列表框条目的高度。 +- `CB_SETSTRCMPFUNC`:对应 `LB_SETSTRCMPFUNC`,用于设置内部列表框排序用的字符串对比函数。 +- `CB_GETLBTEXT`:对应 `LB_GETTEXT`,用于获取内部列表框条目的文本内容。 +- `CB_GETLBTEXTLEN`:对应 `LB_GETTEXTLEN`,用于获得内部列表框条目的文本长度。 +- `CB_GETCHILDREN`:获得组合框的子控件,`wParam` 返回编辑框控件指针,`lParam` 返回列表框控件指针。 + +组合框也提供了用于内部编辑框的消息: + +- `CB_LIMITTEXT`:对应 `EM_LIMITTEXT` 消息,用于限制内部编辑框的文本长度。 +- `CB_SETEDITSEL`:对应 `EM_SETSEL`,用来设置编辑框选中的文本。 +- `CB_GETEDITSEL`:对应 `EM_GETSEL`,用来获取编辑框选中的文本。 + +关于上述这些消息的具体用法,读者可参阅第 21 章和第 22 章中对列表框消息及编辑框消息的描述。下面的两个消息可用于旋钮组合框: + +- `CB_SPIN`:发送该消息将使旋钮框向前或向后步进,相当于用户单击编辑框旁边的向上或向下箭头(在编辑框中键入向上或向下箭头键,也可取得一样的效果)。`wParam` 控制步进方向,取 0 为向下,取 1 为向上。 +- `CB_FASTSPIN`:发送该消息将使旋钮框快速向前步进,相当于用户在编辑框中键入` PageUp/PageDown` 键。`wParam` 控制步进方向,取 0 为向上,取 1 为向下。 + +下面两个消息可用于下拉式组合框: + +- `CB_GETDROPPEDCONTROLRECT`:获得组合框的下拉列表对应矩形位置。 +- `CB_GETDROPPEDSTATE`:检查组合框的下拉列表是否为显示状态。 + +### 1.2.2 旋钮数字框的消息 + +旋钮数字框可接受的消息如下: + +- `CB_GETSPINRANGE`:获得可取的最大值和最小值,它们分别存储在 `wParam` 参数和 `lParam` 参数指向的地址中。 +- `CB_SETSPINRANGE`:设定可取的最大值和最小值,分别取 `wParam` 参数和 `lParam` 参数的值。 +- `CB_SETSPINVALUE`:参数设置编辑框的当前数值,通过 `wParam` 参数传递要设置的值。 +- `CB_GETSPINVALUE`:该消息返回当前编辑框内的数值。 +- `CB_SPIN`:发送该消息将使旋钮框向前或向后步进,相当于用户单击编辑框旁边的向上或向下箭头(在编辑框中键入向上或向下箭头键,也可取得一样的效果)。`wParam` 控制步进方向,取 1 为向上,取 0 为向下。步进值取决于 `CB_SETSPINPACE` 的设置值。 +- `CB_FASTSPIN`:发送该消息将使旋钮框快速向前步进,相当于用户在编辑框中键入 `PageUp/PageDown` 键。`wParam` 控制步进方向,取 0 为向上,取 1 为向下。步进值取决于 `CB_SETSPINPACE` 的设置值。 +- `CB_GETSPINPACE`:获得步进值(`wParam`)和快速步进值(`lParam`)。 +- `CB_SETSPINPACE`:设置步进值(`wParam`)和快速步进值(`lParam`)。 +- `CB_SETSPINFORMAT`:设定整数的格式化字符串。MiniGUI 在内部使用 `sprintf` 和 `sscanf` 函数在编辑框的文本字符串和整数值之间互相转换。设定格式化字符串之后,MiniGUI 在调用 `sprintf` 和 `sscanf` 函数时将使用这个格式化字符串,使之具有特定的显示格式。 + +## 1.3 组合框通知码 + +当组合框具有 `CBS_NOTIFY` 风格时,将可能产生通知消息。组合框通知消息的通知码基本上是列表框通知码和编辑框通知码的组合,如下所列: + +- `CBN_ERRSPACE`:内存不足 +- `CBN_SELCHANGE`:条目选择变化 +- `CBN_EDITCHANGE`:方框区域的文本发生了变化 +- `CBN_DBLCLK`:用户双击了组合框中的某个条目 +- `CBN_CLICKED`:用户点击了组合框 +- `CBN_SETFOCUS`:组合框获得了输入焦点。如果组合框具有 `CBS_AUTOFOCUS` 风格,则内部编辑框将同时获得输入焦点。 +- `CBN_KILLFOCUS`:组合框失去了输入焦点。 +- `CBN_DROPDOWN`:用户下拉列表框使之显示。当用户点击编辑框旁边的向下箭头按钮或者在编辑框中键入光标控制键,比如向下、向上箭头键,`PageDown` 或者 `PageUp` 等键时,也会下拉并显示列表框。 +- `CBN_CLOSEUP`:下拉的列表框被隐藏(关闭)。 +- `CBN_SELENDOK`:用户从下拉列表框中选择了某个条目。 +- `CBN_SELENDCANCEL`:用户未选择任何条目而关闭下拉列表框。 + +## 1.4 编程实例 + +清单 1.1 的程序给出了组合框的一个编程实例。该程序用旋钮数字框组成了一个时间选择框,然后用下拉列表框提供了要约会的朋友列表。在按“确定”时,程序使用 `MessageBox` 显示用户所做的选择。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `combobox.c` 文件,运行效果见__图 1.6__。 + +__清单 1.1__ 组合框编程实例 + +```c +#include + +#include +#include +#include +#include +#include + +#define IDC_HOUR 100 +#define IDC_MINUTE 110 +#define IDC_SECOND 120 +#define IDL_DAXIA 200 + +#define IDC_PROMPT 300 + +/* 定义对话框模板 */ +static DLGTEMPLATE DlgMyDate = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 100, 100, 304, 135, + "约会大侠", + 0, 0, + 9, NULL, + 0 +}; + +static CTRLDATA CtrlMyDate[] = +{ + { + "static", + WS_CHILD | SS_RIGHT | WS_VISIBLE, + 10, 20, 50, 20, + IDC_STATIC, + "我打算于", + 0 + }, + /* 用来显示小时的旋钮数字框 */ + { + CTRL_COMBOBOX, + WS_CHILD | WS_VISIBLE | + CBS_READONLY | CBS_AUTOSPIN | CBS_AUTOLOOP | CBS_EDITBASELINE, + 60, 18, 40, 20, + IDC_HOUR, + "", + 0 + }, + { + "static", + WS_CHILD | SS_CENTER | WS_VISIBLE, + 100, 20, 20, 20, + IDC_STATIC, + "时", + 0 + }, + /* 用来显示分钟的旋钮数字框 */ + { + CTRL_COMBOBOX, + WS_CHILD | WS_VISIBLE | + CBS_READONLY | CBS_AUTOSPIN | CBS_AUTOLOOP | CBS_EDITBASELINE, + 120, 18, 40, 20, + IDC_MINUTE, + "", + 0 + }, + { + "static", + WS_CHILD | SS_CENTER | WS_VISIBLE, + 160, 20, 30, 20, + IDC_STATIC, + "去找", + 0 + }, + /* 列各位大侠的大名 */ + { + CTRL_COMBOBOX, + WS_VISIBLE | CBS_DROPDOWNLIST | CBS_NOTIFY, + 190, 20, 100, 20, + IDL_DAXIA, + "", + 80 + }, + /* 显示大侠特点 */ + { + "static", + WS_CHILD | SS_RIGHT | WS_VISIBLE, + 10, 50, 280, 20, + IDC_PROMPT, + "This is", + 0 + }, + { + CTRL_BUTTON, + WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_GROUP, + 10, 70, 130, 25, + IDOK, + "确定", + 0 + }, + { + "button", + WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, + 150, 70, 130, 25, + IDCANCEL, + "取消", + 0 + }, +}; + +static const char* daxia [] = +{ + "黄药师", + "欧阳锋", + "段皇爷", + "洪七公", + "周伯通", + "郭靖", + "黄蓉", +}; + +static const char* daxia_char [] = +{ + "怪僻", + "恶毒", + "假慈悲", + "一身正气", + "调皮,不负责任", + "傻乎乎", + "乖巧", +}; + +static void daxia_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + if (nc == CBN_SELCHANGE) { + /* 根据当前选择的大侠,显示对应的性格特点 */ + int cur_sel = SendMessage (hwnd, CB_GETCURSEL, 0, 0); + if (cur_sel >= 0) { + SetWindowText (GetDlgItem (GetParent(hwnd), IDC_PROMPT), daxia_char [cur_sel]); + } + } +} + +static void prompt (HWND hDlg) +{ + char date [1024]; + + /* 总结约会内容 */ + int hour = SendDlgItemMessage(hDlg, IDC_HOUR, CB_GETSPINVALUE, 0, 0); + int min = SendDlgItemMessage(hDlg, IDC_MINUTE, CB_GETSPINVALUE, 0, 0); + int sel = SendDlgItemMessage(hDlg, IDL_DAXIA, CB_GETCURSEL, 0, 0); + + sprintf (date, "你打算于今日 %02d:%02d 去见那个%s的%s", hour, min, + daxia_char [sel], daxia [sel]); + + MessageBox (hDlg, date, "约会内容", MB_OK | MB_ICONINFORMATION); +} + +static int MyDateBoxProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + int i; + switch (message) { + case MSG_INITDIALOG: + /* 设定小时旋钮框的范围在 0~23,数字以 %02d 的格式显示 */ + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINRANGE, 0, 23); + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINFORMAT, 0, (LPARAM)"%02d"); + /* 设定当前值为 20 */ + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINVALUE, 20, 0); + /* 设定步进值和快速步进值均为 1 */ + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINPACE, 1, 1); + + /* 设定小时旋钮框的范围在 0~59,数字以 %02d 的格式显示 */ + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINRANGE, 0, 59); + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINFORMAT, 0, (LPARAM)"%02d"); + /* 设定当前值为 0 */ + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINVALUE, 0, 0); + /* 设定步进值为 1,快速步进值为 2 */ + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINPACE, 1, 2); + + /* 加入各位大侠的名字 */ + for (i = 0; i < 7; i++) { + SendDlgItemMessage(hDlg, IDL_DAXIA, CB_ADDSTRING, 0, (LPARAM)daxia [i]); + } + + /* 设定通知回调函数 */ + SetNotificationCallback (GetDlgItem (hDlg, IDL_DAXIA), daxia_notif_proc); + /* 设定大侠名字和性格特点的初始值 */ + SendDlgItemMessage(hDlg, IDL_DAXIA, CB_SETCURSEL, 0, 0); + SetWindowText (GetDlgItem (hDlg, IDC_PROMPT), daxia_char [0]); + return 1; + + case MSG_COMMAND: + switch (wParam) { + case IDOK: + /* 显示当前选择 */ + prompt (hDlg); + case IDCANCEL: + EndDialog (hDlg, wParam); + break; + } + break; + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +int MiniGUIMain (int argc, const char* argv[]) +{ + #ifdef _ MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "combobox" , 0 , 0); + #endif + + DlgMyDate.controls = CtrlMyDate; + + DialogBoxIndirectParam (&DlgMyDate, HWND_DESKTOP, MyDateBoxProc, 0L); + + return 0; +} + +#ifndef _ MGRM_PROCESSES +#include +#endif +``` + +![组合框示例程序的运行效果](figures/Part4Chapter05-1.6.jpeg) +__图 1.6__ 组合框示例程序的运行效果 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter06-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter06-zh.md new file mode 100644 index 0000000..3b3eef4 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter06-zh.md @@ -0,0 +1,337 @@ +# 24 菜单按钮 + +菜单按钮的功能和普通下拉式组合框的功能基本一样,实质上,在 MiniGUI 早期版本中,菜单按钮就是以组合框的替代品出现的。当然,菜单按钮有很大的限制,比如不能够编辑、不提供列表条目的滚动等等。 + +在外观上,菜单按钮类似一个普通按钮,不同的是在按钮矩形区域的右侧有一个向下的箭头。当用户点击该控件时,就会弹出一个菜单,而用户使用鼠标点击菜单中某一条目时,按钮的内容就变为该条目的内容。如__图 1.1__ 所示。 + +![菜单按钮(左边为平常状态,右边为弹出菜单后的效果)](figures/Part4Chapter06-1.1.jpeg +__图 1.1__ 菜单按钮(左边为平常状态,右边为弹出菜单后的效果) + +以 `CTRL_MENUBUTTON` 为控件类名调用 `CreateWindow` 函数,即可创建菜单按钮。 + +## 1.1 菜单按钮风格 + +菜单按钮有一组自己特有的风格,一般使用如下的方式对风格进行组合。 + +```c +WS_CHILD | WS_VISIBLE | MBS_SORT +``` + +- `MBS_SORT`:对菜单按钮中的条目进行排序显示。 +- `MBS_LEFTARROW`:箭头显示在菜单按钮的左侧。 +- `MBS_NOBUTTON`:不显示按钮。 +- `MBS_ALIGNLEFT`:菜单按钮上的文字向左对齐。 +- `MBS_ALIGNRIGHT`:菜单按钮上的文字向右对齐。 +- `MBS_ALIGNCENTER`:菜单按钮上的文字居中对齐。 + +当菜单条目使用位图的时候,位图的位置不受对齐方式的影响。 + +## 1.2 菜单按钮消息 + +### 1.2.1 向菜单按钮控件添加条目 + +向菜单按钮添加条目时使用 `MBM_ADDITEM` 消息,并传递一个经过初始化的 `MENUBUTTONITEM` 结构,如下所示: + +```c +MENUBUTTONITEM mbi; // 声明一个菜单条目结构体变量 +mbi.text = "item one"; // 设置条目文字 +mbi.bmp = NULL; // 在这里可以指定位图对象 +mbi.data = 0; +pos = SendMessage (hMbtnWnd, MBM_ADDITEM, -1, (LPARAM) &mbi); +``` + +其中,`hMbtnWnd` 是菜单按钮控件的句柄。`Pos` 得到新添加的菜单条目的索引值,当内存空间不足时,则返回 `MB_ERR_SPACE`。 + +### 1.2.2 从菜单按钮控件删除条目 + +从菜单按钮中删除条目,可使用 `MBM_DELITEM` 消息,并指定要删除的菜单项索引号。如下所示: + +```c +SendMessage (hMbtnWnd, MBM_DELITEM, index, 0); +``` + +其中,`index` 为条目的索引值。 + +### 1.2.3 删除菜单中的所有条目 + +和列表框一样,菜单按钮也提供了删除所有条目的消息,即 `MBM_RESETCTRL` 消息。如下所示: + +```c +SendMessage (hMbtnWnd, MBM_RESETCTRL, 0, 0); +``` + +### 1.2.4 设置当前选定条目 + +类似地,用 `MBM_SETCURITEM` 消息设置选中条目,被选中的条目文本将显示在菜单按钮上。如下所示: + +```c +SendMessage (hMbtnWnd, MBM_SETCURITEM, index, 0); +``` + +其中,`index` 为要设定的条目索引值。 + +### 1.2.5 得到当前选定条目 + +用 `MBM_GETCURITEM` 消息可获得当前选中条目的索引号。如下所示: + +```c +index = SendMessage (hMbtnWnd, MBM_GETCURITEM, 0, 0); +``` + +该消息返回当前选定条目的索引值。 + +### 1.2.6 获取或设置菜单项条目数据 + +使用 `MBM_GETITEMDATA` 和 `MBM_SETITEMDATA` 消息可获取或设置菜单项条目的数据。使用这个两个消息时,`wParam` 参数传递要获取或设置的菜单项索引值,`lParam` 参数传递一个指向 `MENUBUTTONITEM` 的结构指针,其中包括菜单项的文本、位图对象以及附加数据。 + +需要注意的是,`MENUBUTTONITEM` 结构中含有一个 `which` 成员,该成员指定了要获取或设置菜单项的哪个数据(文本、位图对象或者附加数据中的一个或多个),通常是下列值的组合: + +- `MB_WHICH_TEXT`:表明要获取或设置菜单项的文本,这时,该结构的 `text` 成员必须指向有效缓冲区。 +- `MB_WHICH_BMP`:表明要获取或设置菜单项的位图对象。 +- `MB_WHICH_ATTDATA`:表明要获取或设置菜单项的附加数据。 + +示例如下: + +```c +MENUBUTTONITEM mbi; + +mbi.which = MB_WHICH_TEXT | MB_WHICH_ATTDATA; +mbi.text = “newtext”; +mbi.data = 1; +SendMessage (menubtn, MBM_SETITEMDATA, 0, (LPARAM) &mbi); +``` + +其中,`menubtn` 是某个菜单按钮的句柄。 + +### 1.2.7 其他消息 + +在使用 `MBS_SORT` 风格时,因为涉及到条目的排序,所以 MiniGUI 也为应用程序提供了 `MBM_SETSTRCMPFUNC` 消息,用来设定一个定制的排序函数。一般而言,应用程序要在添加条目之前使用该消息设定新的字符串比较函数。 + +该消息的用法可参照列表框消息 `LB_SETSTRCMPFUNC` 消息。 + +## 1.3 菜单按钮的通知消息 + +菜单按钮没有 `MBS_NOTIFY` 风格,因此,任意一个菜单按钮控件均可能产生如下的通知消息: + +- `MBN_ERRSPACE`:内存分配失败,存储空间不足。 +- `MBN_SELECTED`:对菜单按钮控件进行了选择。不管前后选择的菜单项是否改变,均会产生该通知消息。 +- `MBN_CHANGED`:菜单按钮控件的选择项发生了变化。 +- `MBN_STARTMENU`:用户激活了菜单按钮的弹出式菜单。 +- `MBN_ENDMENU`:弹出式菜单关闭。 + +## 1.4 编程实例 + +__清单 1.1__ 给出了菜单按钮的使用实例。该程序段是对__清单 1.1__ 程序的一个小改动,即将__清单 1.1__ 程序中的下拉式组合框改成了菜单按钮来实现。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `menubutton.c` 文件,其运行效果见__图 1.2__。 + +__清单 1.1__ 菜单按钮的使用实例 + +```c +/* 定义对话框模板 */ +static DLGTEMPLATE DlgMyDate = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 100, 100, 304, 135, + "约会大侠", + 0, 0, + 9, NULL, + 0 +}; + +static CTRLDATA CtrlMyDate[] = +{ + + ... + + /* 将用于显示大侠名单的组合框换成按钮菜单来实现 */ + { + CTRL_MENUBUTTON, + WS_CHILD | WS_VISIBLE, + 190, 20, 100, 20, + IDL_DAXIA, + "", + 0 + }, + + ... + +}; + +... + +static void daxia_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + if (nc == CBN_SELCHANGE) { + /* 获得选定的大侠,并显示其性格特点 */ + int cur_sel = SendMessage (hwnd, MBM_GETCURITEM, 0, 0); + if (cur_sel >= 0) { + SetWindowText (GetDlgItem (GetParent(hwnd), IDC_PROMPT), daxia_char [cur_sel]); + } + } +} + +static void prompt (HWND hDlg) +{ + char date [1024]; + + int hour = SendDlgItemMessage(hDlg, IDC_HOUR, CB_GETSPINVALUE, 0, 0); + int min = SendDlgItemMessage(hDlg, IDC_MINUTE, CB_GETSPINVALUE, 0, 0); + int sel = SendDlgItemMessage(hDlg, IDL_DAXIA, MBM_GETCURITEM, 0, 0); + + sprintf (date, "你打算于今日 %02d:%02d 去见那个%s的%s", hour, min, + daxia_char [sel], daxia [sel]); + + MessageBox (hDlg, date, "约会内容", MB_OK | MB_ICONINFORMATION); +} + +static int MyDateBoxProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + int i; + switch (message) { + case MSG_INITDIALOG: + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINFORMAT, 0, (LPARAM)"%02d"); + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINRANGE, 0, 23); + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINVALUE, 20, 0); + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINPACE, 1, 1); + + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINFORMAT, 0, (LPARAM)"%02d"); + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINRANGE, 0, 59); + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINVALUE, 0, 0); + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINPACE, 1, 2); + + /* 向菜单按钮中加入各位大侠的名字 */ + for (i = 0; i < 7; i++) { + MENUBUTTONITEM mbi; + mbi.text = daxia[i]; + mbi.bmp = NULL; + mbi.data = 0; + SendDlgItemMessage(hDlg, IDL_DAXIA, MBM_ADDITEM, -1, (LPARAM)&mbi); + } + + /* 设定菜单按钮的通知回调函数 */ + SetNotificationCallback (GetDlgItem (hDlg, IDL_DAXIA), daxia_notif_proc); + SendDlgItemMessage(hDlg, IDL_DAXIA, MBM_SETCURITEM, 0, 0); + SetWindowText (GetDlgItem (hDlg, IDC_PROMPT), daxia_char [0]); + return 1; + + case MSG_COMMAND: + switch (wParam) { + case IDOK: + prompt (hDlg); + case IDCANCEL: + EndDialog (hDlg, wParam); + break; + } + break; + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +}/* 定义对话框模板 */ +static DLGTEMPLATE DlgMyDate = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 100, 100, 304, 135, + "约会大侠", + 0, 0, + 9, NULL, + 0 +}; + +static CTRLDATA CtrlMyDate[] = +{ + + ... + + /* 将用于显示大侠名单的组合框换成按钮菜单来实现 */ + { + CTRL_MENUBUTTON, + WS_CHILD | WS_VISIBLE, + 190, 20, 100, 20, + IDL_DAXIA, + "", + 0 + }, + + ... + +}; + +... + +static void daxia_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + if (nc == CBN_SELCHANGE) { + /* 获得选定的大侠,并显示其性格特点 */ + int cur_sel = SendMessage (hwnd, MBM_GETCURITEM, 0, 0); + if (cur_sel >= 0) { + SetWindowText (GetDlgItem (GetParent(hwnd), IDC_PROMPT), daxia_char [cur_sel]); + } + } +} + +static void prompt (HWND hDlg) +{ + char date [1024]; + + int hour = SendDlgItemMessage(hDlg, IDC_HOUR, CB_GETSPINVALUE, 0, 0); + int min = SendDlgItemMessage(hDlg, IDC_MINUTE, CB_GETSPINVALUE, 0, 0); + int sel = SendDlgItemMessage(hDlg, IDL_DAXIA, MBM_GETCURITEM, 0, 0); + + sprintf (date, "你打算于今日 %02d:%02d 去见那个%s的%s", hour, min, + daxia_char [sel], daxia [sel]); + + MessageBox (hDlg, date, "约会内容", MB_OK | MB_ICONINFORMATION); +} + +static int MyDateBoxProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + int i; + switch (message) { + case MSG_INITDIALOG: + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINFORMAT, 0, (LPARAM)"%02d"); + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINRANGE, 0, 23); + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINVALUE, 20, 0); + SendDlgItemMessage(hDlg, IDC_HOUR, CB_SETSPINPACE, 1, 1); + + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINFORMAT, 0, (LPARAM)"%02d"); + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINRANGE, 0, 59); + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINVALUE, 0, 0); + SendDlgItemMessage(hDlg, IDC_MINUTE, CB_SETSPINPACE, 1, 2); + + /* 向菜单按钮中加入各位大侠的名字 */ + for (i = 0; i < 7; i++) { + MENUBUTTONITEM mbi; + mbi.text = daxia[i]; + mbi.bmp = NULL; + mbi.data = 0; + SendDlgItemMessage(hDlg, IDL_DAXIA, MBM_ADDITEM, -1, (LPARAM)&mbi); + } + + /* 设定菜单按钮的通知回调函数 */ + SetNotificationCallback (GetDlgItem (hDlg, IDL_DAXIA), daxia_notif_proc); + SendDlgItemMessage(hDlg, IDL_DAXIA, MBM_SETCURITEM, 0, 0); + SetWindowText (GetDlgItem (hDlg, IDC_PROMPT), daxia_char [0]); + return 1; + + case MSG_COMMAND: + + int MiniGUIMain (int argc, const char* argv[]) + { + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "menubutton" , 0 , 0); + #endif + + DlgMyDate.controls = CtrlMyDate; + + DialogBoxIndirectParam (&DlgMyDate, HWND_DESKTOP, MyDateBoxProc, 0L); + + return 0; + } + + ... +``` + +![使用菜单按钮](figures/Part4Chapter06-1.2.jpeg) +__图 1.2__ 使用菜单按钮 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter07-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter07-zh.md new file mode 100644 index 0000000..3567862 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter07-zh.md @@ -0,0 +1,244 @@ +# 25 进度条 + +进度条通常用来为用户提示某项任务的完成进度,经常用于文件复制、软件安装等程序中。以 `CTRL_PROGRESSBAR` 为控件类名调用 `CreateWindow` 函数,即可创建进度条。__图 1.1__ 是进度条的典型运行效果。 + +![进度条控件](figures/Part4Chapter07-1.1.jpeg) +__图 1.1__ 进度条控件 + +## 1.1 进度条风格 + +进度条的可用风格不多,只有如下两个: + +- `PBS_NOTIFY`:使用该风格的进度条控件会产生通知消息。 +- `PBS_VERTICAL`:竖直显示进度条,如__图 1.2__ 所示。 + +![竖直进度条](figures/Part4Chapter07-1.2.jpeg) +__图 1.2__ 竖直进度条 + +进度条控件常用的风格组合是: + +```c +WS_CHILD | WS_VISIBLE | PBS_NOTIFY +``` + +## 1.2 进度条消息 + +### 1.2.1 设置进度条的范围 + +默认情况下的进度条范围是 0 到 100,应用程序也可以设定自己的进度条范围,这时可调用 `PBM_SETRANGE` 消息: + +```c +SendMessage (hwndEdit, PBM_SETRANGE, min, max) ; +``` + +>【提示】进度条的范围可以设置为负值。 + +### 1.2.2 设置步进长度 + +我们可以为进度条设置步进长度,然后在每完成一个阶段性任务时,使进度条步进。默认的进度条步进值是 10,发送 `PBM_SETSTEP` 可改变默认值。如下所示: + +```c +SendMessage (hwndEdit, PBM_SETSTEP, 5, 0) ; +``` + +上面的消息调用把进度条步进值修改为 5。 + +>【提示】进度条的步进值可以设置为负值。 + +当进度条的步进值为负值时,需要设置进度条的位置为进度条范围的最大值。这样进度条在显示时才是从进度条范围的最大值减小到进度条范围的最小值。 + +### 1.2.3 设置进度条位置 + +我们也可以随意设置进度条的当前进度——使用 `PBM_SETPOS` 消息: + +```c +SendMessage (hwndEdit, PBM_SETPOS, 50, 0) ; +``` + +上面的消息调用把进度条的当前进度设定为 50。 + +### 1.2.4 在当前进度基础上偏移 + +我们也可以设定新进度在当前进度基础上的偏移量,从而改变进度值: + +```c +SendMessage (hwndEdit, PBM_DELTAPOS, 10, 0) ; +``` + +上面的消息调用将使新进度在当前进度的基础上加 10,即新进度等于当前进度加 10。 + +>【提示】偏移量可以设置为负值。 + +### 1.2.5 使进度条前进一个步进值 + +可发送 `PBM_STEPIT` 使进度步进,新的进度将等于当前进度加步进值的结果。 + +```c +SendMessage (hwndEdit, PBM_STEPIT, 0, 0) ; +``` + +>【注意】当前的进度条控件未提供任何获取当前进度、当前步进值、当前进度范围的消息。 + +## 1.3 进度条通知码 + +如果进度条具有 `PBS_NOTIFY` 风格,则可能产生如下通知消息: +- `PBN_REACHMAX`:已到达最大进度位置。 +- `PBN_REACHMIN`:已到达最小进度位置。 + +## 1.4 编程实例 + +__清单 1.1__ 给出了使用进度条控件的实例。该程序提供了两个函数,调用 `createProgressWin` 函数将创建一个含有进度条的主窗口并返回,我们可以在自己的程序中对这个主窗口中的进度条进行控制,并在完成任务之后调用 `destroyProgressWin` 函数销毁进度主窗口。这两个函数实际来自 MiniGUI 的 `MiniGUIExt` 库。__清单 1.1__ 给出了这两个函数的实现以及调用示例,其运行效果见__图 1.3__。该程序的完整源代码见本指南示例程序包 `mg-samples` 中的 `progressbar.c`。 + +__清单 1.1__ 使用进度条控件的实例 + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static HWND createProgressWin (HWND hParentWnd, char * title, char * label, +int id, int range) +{ + HWND hwnd; + MAINWINCREATE CreateInfo; + int ww, wh; + HWND hStatic, hProgBar; + + /* 根据窗口客户区宽度计算窗口宽度 */ + ww = ClientWidthToWindowWidth (WS_CAPTION | WS_BORDER, 400); + /* 根据窗口客户区高度计算窗口高度 */ + wh = ClientHeightToWindowHeight (WS_CAPTION | WS_BORDER, + (range > 0) ? 70 : 35, FALSE); + + /* 创建主窗口 */ + CreateInfo.dwStyle = WS_ABSSCRPOS | WS_CAPTION | WS_BORDER | WS_VISIBLE; + CreateInfo.dwExStyle = WS_EX_NONE; + CreateInfo.spCaption = title; + CreateInfo.hMenu = 0; + CreateInfo.hCursor = GetSystemCursor(IDC_WAIT); + CreateInfo.hIcon = 0; + /* 该主窗口的窗口过程取默认的主窗口过程 */ + CreateInfo.MainWindowProc = DefaultMainWinProc; + #ifndef _LITE_VERSION + CreateInfo.lx = (GetGDCapability (HDC_SCREEN, GDCAP_MAXX) - ww) >> 1; + CreateInfo.ty = (GetGDCapability (HDC_SCREEN, GDCAP_MAXY) - wh) >> 1; + #else + CreateInfo.lx = g_rcExcluded.left + (RECTW(g_rcExcluded) - ww) >> 1; + CreateInfo.ty = g_rcExcluded.top + (RECTH(g_rcExcluded) - wh) >> 1; + #endif + CreateInfo.rx = CreateInfo.lx + ww; + CreateInfo.by = CreateInfo.ty + wh; + CreateInfo.iBkColor = COLOR_lightgray; + CreateInfo.dwAddData = 0L; + CreateInfo.hHosting = hParentWnd; + + hwnd = CreateMainWindow (&CreateInfo); + if (hwnd == HWND_INVALID) + return hwnd; + + /* 在主窗口中创建提示用静态框控件 */ + hStatic = CreateWindowEx ("static", + label, + WS_VISIBLE | SS_SIMPLE, + WS_EX_USEPARENTCURSOR, + IDC_STATIC, + 10, 10, 380, 16, hwnd, 0); + + /* 在主窗口中创建进度条控件 */ + if (range > 0) { + hProgBar = CreateWindowEx ("progressbar", + NULL, + WS_VISIBLE, + WS_EX_USEPARENTCURSOR, + id, + 10, 30, 380, 30, hwnd, 0); + SendDlgItemMessage (hwnd, id, PBM_SETRANGE, 0, range); + } + else + hProgBar = HWND_INVALID; + + /* 更新控件 */ + UpdateWindow (hwnd, TRUE); + + /* 返回主窗口句柄 */ + return hwnd; +} + +static void destroyProgressWin (HWND hwnd) +{ + /* 销毁控件以及主窗口 */ + DestroyAllControls (hwnd); + DestroyMainWindow (hwnd); + ThrowAwayMessages (hwnd); + MainWindowThreadCleanup (hwnd); +} + +int MiniGUIMain (int argc, const char* argv[]) +{ + int i, sum; + HCURSOR hOldCursor; + HWND hwnd; + + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "progressbar" , 0 , 0); + #endif + + /* 设置“沙漏”鼠标,以表示系统正忙 */ + hOldCursor = SetDefaultCursor (GetSystemCursor (IDC_WAIT)); + + /* 创建进度条窗口,指定进度条控件的标识符和范围值 */ + hwnd = createProgressWin (HWND_DESKTOP, "进度条", + "正在计算,请稍候...", 100, 2000); + + while (HavePendingMessage (hwnd)) { + MSG msg; + GetMessage (&msg, hwnd); + DispatchMessage (&msg); + } + + /* 进入长时计算过程,完成大循环时更新进度条控件的位置 */ + for (i = 0; i < 2000; i++) { + unsigned long j; + + if (i % 100 == 0) { + SendDlgItemMessage (hwnd, 100, PBM_SETPOS, i, 0L); + while (HavePendingMessage (hwnd)) { + MSG msg; + GetMessage (&msg, hwnd); + DispatchMessage (&msg); + } + } + + sum = i*5000; + for (j = 0; j < 500000; j++) + sum *= j; + sum += sum; + } + + /* 销毁进度条窗口 */ + destroyProgressWin (hwnd); + /* 恢复原有鼠标 */ + SetDefaultCursor (hOldCursor); + + return 0; +} + +#ifndef _MGRM_PROCESSES +#include +#endif +``` + +![进度条控件示例程序](figures/Part4Chapter07-1.3.jpeg) +__图 1.3__ 进度条控件示例程序 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter08-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter08-zh.md new file mode 100644 index 0000000..05cc436 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter08-zh.md @@ -0,0 +1,139 @@ +# 滑块 + +滑块通常用于调节亮度、音量等场合。在需要对某一范围的量值进行调节时,就可以使用滑块控件。以 `CTRL_TRACKBAR` 为控件类名调用 `CreateWindow` 函数,即可创建滑块。__图 1.1__ 是滑块控件的典型运行效果。 + +![滑块控件](figures/Part4Chapter08-1.1.jpeg) +__图 1.1__ 滑块控件 + +## 1.1 滑块风格 + +滑块控件的常用风格组合为: + +```c +WS_CHILD | WS_VISIBLE | TBS_NOTIFY +``` + +指定 `TBS_NOTIFY` 风格,可让滑块产生通知消息。 + +默认情况下,滑块是水平的。如果想要创建竖直的滑块,可指定 `TBS_VERTICAL` 风格。__图 1.2__ 中的滑块就是竖直滑块: + +![竖直滑块](figures/Part4Chapter08-1.2.jpeg) +__图 1.2__ 竖直滑块 + +滑块的另外几个风格说明如下: + +- `TBS_TIP`:在滑块两端显示文字说明(如__图 1.1__ 中的“Min”和“Max”那样)。具有这一风格时,滑块控件还将在控件的中部显示当前刻度值。 +- `TBS_NOTICK`:不显示刻度。 +- `TBS_BORDER` 风格可使滑块带有边框,该风格不常用。 + +## 1.2 滑块消息 + +滑块的消息相对简单,总结如下: + +- `TBM_SETRANGE`:通过 `wParam` 和 `lParam` 参数分别设置滑块的最小值和最大值。默认的范围是 0~10。 +- `TBM_GETMIN`:获得滑块的最小值。 +- `TBM_GETMAX`:获得滑块的最大值。 +- `TBM_SETMIN`:设置滑块的最小值。 +- `TBM_SETMAX`:设置滑块的最大值。 +- `TBM_SETLINESIZE`:通过 `wParam` 参数设置滑块的步进值。当用户在滑块拥有输入焦点时按下向上或向下光标键,将使滑块向上或向下移动该步进值。默认的步进值是 1。 +- `TBM_GETLINESIZE`:获得滑块的步进值。 +- `TBM_SETPAGESIZE`:通过 `wParam` 参数设置滑块的快速步进值。当用户在滑块拥有输入焦点时按下 `PageUp` 和 `PageDown` 键,将使滑块分别向上或向下移动该快速步进值。默认的快速步进值是 5。 +- `TBM_GETPAGESIZE`:获得滑块的快速步进值。 +- `TBM_SETPOS`:设置滑块的位置。 +- `TBM_GETPOS`:获得滑块的位置。 +- `TBM_SETTICKFREQ`:设置刻度间距,默认间距是 1。 +- `TBM_GETTICKFREQ`:获得刻度间距。 +- `TBM_SETTIP`:设置最小值及最大值处的文字说明。 +- `TBM_GETTIP`:获取最小值及最大值处的文字说明。 + +## 1.3 滑块通知码 + +当滑块控件具有 `TBS_NOTIFY` 风格时,可能产生如下通知消息: + +- `TBN_CHANGE`:滑块的位置发生了变化。 +- `TBN_REACHMAX`:已到达了上限。 +- `TBN_REACHMIN`:已到达了下限。 + +## 1.4 编程实例 + +__清单 1.1__ 给出了滑块控件的一个示例程序。该程序根据当前滑块的位置在窗口中画对应大小的圆。当用户改变滑块的位置时,圆也会接着更新。该程序的运行效果见__图 1.3__,程序的完整源代码见本指南示例程序包 `mg-samples` 中的 `trackbar.c` 文件。 + +__清单 1.1__ 滑块控件的使用 + +```c +#include +#include +#include + +#include +#include +#include +#include +#include + +static int radius = 10; +static RECT rcCircle = {0, 60, 300, 300}; + +static void my_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + if (nc == TBN_CHANGE) { + + /* 当滑块的的位置发生变化时,记录当前值,并通知主窗口重绘 */ + radius = SendMessage (hwnd, TBM_GETPOS, 0, 0); + InvalidateRect (GetParent (hwnd), &rcCircle, TRUE); + } +} + +static int TrackBarWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam) +{ + HWND hwnd; + switch (message) { + case MSG_CREATE: + /* 创建滑块 */ + hwnd = CreateWindow (CTRL_TRACKBAR, + "", + WS_VISIBLE | TBS_NOTIFY, + 100, + 10, 10, 280, 50, hWnd, 0); + + /* 设置滑块的范围、步进值以及刻度间隔,当前位置等 */ + SendMessage (hwnd, TBM_SETRANGE, 0, 100); + SendMessage (hwnd, TBM_SETLINESIZE, 1, 0); + SendMessage (hwnd, TBM_SETPAGESIZE, 10, 0); + SendMessage (hwnd, TBM_SETTICKFREQ, 10, 0); + SendMessage (hwnd, TBM_SETPOS, radius, 0); + + /* 设置滑块的通知回调函数 */ + SetNotificationCallback (hwnd, my_notif_proc); + break; + + case MSG_PAINT: + { + HDC hdc = BeginPaint (hWnd); + + /* 以当前滑块位置值为半径绘制圆 */ + ClipRectIntersect (hdc, &rcCircle); + Circle (hdc, 140, 120, radius); + + EndPaint (hWnd, hdc); + return 0; + } + + case MSG_DESTROY: + DestroyAllControls (hWnd); + return 0; + + case MSG_CLOSE: + DestroyMainWindow (hWnd); + PostQuitMessage (hWnd); + return 0; + } + + return DefaultMainWinProc(hWnd, message, wParam, lParam); +} + +/* 以下创建主窗口的代码从略 */ +``` + +![滑块控件的使用](figures/Part4Chapter08-1.3.jpeg) +__图 1.3__ 滑块控件的使用 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter09-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter09-zh.md new file mode 100644 index 0000000..21d48ff --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter09-zh.md @@ -0,0 +1,290 @@ +# 工具栏 + +在现代 GUI 应用程序中,工具栏的使用随处可见。MiniGUI 也为应用程序准备了工具栏预定义控件类。实际上,MiniGUI 提供了三种不同的预定义工具栏控件类,分别是 `CTRL_TOOLBAR`、`CTRL_NEWTOOLBAR` 以及 `CTRL_COOLBAR` 控件类。 + +`CTRL_TOOLBAR` 是早期的工具栏控件类,该控件类已废弃,并被 `CTRL_NEWTOOLBAR` 控件类替代。将 `CTRL_TOOLBAR` 控件类包含在 MiniGUI 中,只是为了提供兼容性,新的应用程序不应使用该控件类。本章将介绍 `CTRL_NEWTOOLBAR` 控件类,第 36 章介绍 `CTRL_COOLBAR` 控件类。 + +使用 `CTRL_NEWTOOLBAR` 作为控件类名调用 `CreateWindow` 函数,即可创建工具栏控件,该控件的运行效果见__图 1.1__。 + +![工具栏控件](figures/Part4Chapter09-1.1.jpeg) +__图 1.1__ 工具栏控件 + +## 1.1 创建工具栏控件 + +在创建工具栏控件之前,我们首先要填充一个 `NTBINFO` 结构,并将该结构的指针通过 `CreateWindow` 函数的 `dwAddData` 参数传递给控件类的窗口过程。`NTBINFO` 结构主要用来定义工具栏所用位图的信息,具体如__表 1.1__ 所示。 + +____表 1.1__ `NTBINFO` 结构 + +| 结构成员 | 含义 | 备注 | +|:----------|:------------|:-----| +|`image` | 用来显示工具栏各按钮的位图 | | +|`nr_cells` | 该位图中含有的位图单元个数,也就是说,一共有多少行 | | +|`nr_cols` | 该位图含有多少列,也就是说,每个位图单元含有多少状态 | 取 1 表示只有正常状态;
取2 表示只有正常和高亮状态
;取 3 表示没有灰化状态;
取 4 或者 0 表示含所有 4 个可能状态。| +|`w_cell` | 位图单元的宽度 | 置零时,将以“位图宽度/nr_cols”为公式计算 | +|`h_cell` | 位图单元的高度 | 置零时,将以“位图高度/nr_cells”为公式计算 | + +我们必须将工具栏上各按钮显示的位图(我们称为位图单元)组织在单个位图对象中,而且应组织成类似__图 1.2__ 那样的结构。其中第一列表示的是工具栏按钮可能用到的所有位图单元的正常状态;第二列表示的是工具栏按钮可能用到的所有位图的高亮状态;第三列表示的是按下的状态;第四列表示的是禁止(灰化)的状态。位图的每一行表示单个按钮位图单元的所有状态。 + +![用于工具栏的位图对象](figures/Part4Chapter09-1.2.jpeg) +__图 1.2__ 用于工具栏的位图对象 + +工具栏控件将根据工具栏上各个按钮的状态在位图对象中选择适当的位图单元来显示按钮。 + +## 1.2 工具栏风格 + +工具栏控件类支持如下几种风格: + +- `NTBS_HORIZONTAL`:水平显示工具栏。这是默认风格。 +- `NTBS_VERTICAL`:垂直显示工具栏。如__图 1.3__ 所示。 +- `NTBS_MULTLINE`:工具栏可多行显示。当工具项类型为 `NTBIF_NEWLINE` 时,将另起一行显示其后添加的工具项。如__图 1.3__ 所示。 +- `NTBS_WITHTEXT`:将在按钮下方或者按钮右边显示文本,默认显示在按钮位图的下方。这时,应用程序必须在添加按钮时指定按钮对应的文本。当文字在图标下方显示且按钮处于被激活状态时,按钮图片将突出显示。 +- `NTBS_TEXTRIGHT`:配合 `NTBS_WITHTEXT` 风格使用时,该风格指定将文本显示在按钮位图的右边。__图 1.1__ 中的工具栏就具有 `NTBS_TEXTRIGHT` 风格。当文字在图标右侧且按钮处于被激活状态时,按钮图片和文字都将突出显示。 +- `NTBS_DRAWSTATES`:不使用按钮的高亮、按下以及灰化状态的位图单元,而改用三维风格的边框来表示这些状态。 +- `NTBS_DRAWSEPARATOR`:绘制分隔条。默认情况下,工具栏上用来分隔按钮的分隔条是不会被绘制的,而只会加大两个按钮之间的间距。具有该风格之后,将绘制窄的分隔条。 + +![垂直分行显示的工具栏控件](figures/Part4Chapter09-1.3.jpeg) +__图 1.3__ 垂直分行显示的工具栏控件 + +## 1.3 工具栏消息 + +### 1.3.1 添加工具项 + +向工具栏控件发送 `NTBM_ADDITEM` 消息并传递 `NTBITEMINFO` 结构,可向工具栏中添加一个工具项。__表 1.2__ 给出了 `NTBITEMINFO` 结构各成员的含义。 + +____表 1.2__ `NTBITEMINFO` 结构 + +| 结构成员 | 含义 | 备注 | +|:--------------|:---------|:----| +|`which` | 用于 `NTBM_GETITEM` 和 `NTBM_SETITEM` 消息。| | +|`flags` | 该成员用来指定工具项的类型及状态。类型有:
1. `NTBIF_PUSHBUTTON`:普通的按钮;
2. `NTBIF_CHECKBUTTON`:检查框按钮;
3. `NTBIF_HOTSPOTBUTTON`:定义有热点区域的按钮;
4. `NTBIF_NEWLINE`:在具有` NTBS_MULTILINE` 风格时,表示另起一行显示其它工具项;
5. `NTBIF_SEPARATOR`:分隔条。工具项的状态只有一个,即 `NTBIF_DISABLED`,表示该项被灰化。| 该成员的值应该是类型标志之一与状态标志的或。| +|`id` | 按钮的标识符 。当用户单击某个按钮时,该标识符将作为工具栏通知消息的通知码发送到父窗口或者传递到通知回调函数。| | +|`text` | 当工具栏具有 `NTBS_WITHTEXT` 风格时,该成员用来传递按钮的文本字符串。| | +|`tip` | 目前保留未用。| | +|`bmp_cell` | 指定该按钮使用位图对象中的哪个位图单元,第一个取零。|该按钮将使用第 `bmp_cell` 行的位图单元显示按钮的各个状态。| +|`hotspot_proc` | 如果该按钮是一个定义有热点区域的按钮,则该成员定义用户单击热点时的回调函数。| | +|`rc_hotspot` | 如果该按钮是一个定义有热点区域的按钮,则该成员定义按钮的热点区域矩形,相对于按钮左上角。 | 当用户单击的是该矩形区域时,则看成是激活热点。| +|`add_data` | 工具项的附加数据。| | + +在添加工具项时,将忽略 `which` 成员。下面的代码段说明了如何向工具栏添加普通按钮、初始灰化的按钮、分隔条以及定义有热点区域的按钮: + +```c +HWND ntb1; +NTBINFO ntb_info; +NTBITEMINFO ntbii; +RECT hotspot = {16, 16, 32, 32}; + +/* 填充 NTBINFO 结构 */ +ntb_info.nr_cells = 4; +ntb_info.w_cell = 0; +ntb_info.h_cell = 0; +ntb_info.nr_cols = 0; +ntb_info.image = &bitmap1; + +/* 创建工具栏控件 */ +ntb1 = CreateWindow (CTRL_NEWTOOLBAR, +"", +WS_CHILD | WS_VISIBLE, +IDC_CTRL_NEWTOOLBAR_1, +0, 10, 1024, 0, +hWnd, +(DWORD) &ntb_info); + +/* 添加普通按钮 */ +ntbii.flags = NTBIF_PUSHBUTTON; +ntbii.id = IDC_NTB_TWO; +ntbii.bmp_cell = 1; +SendMessage(ntb1, TBM_ADDITEM, 0, (LPARAM)&ntbii); + +/* 添加灰化的普通按钮 */ +ntbii.flags = NTBIF_PUSHBUTTON | NTBIF_DISABLED; +ntbii.id = IDC_NTB_THREE; +ntbii.bmp_cell = 2; +SendMessage (ntb1, TBM_ADDITEM, 0, (LPARAM)&ntbii); + +/* 添加分隔条 */ +ntbii.flags = NTBIF_SEPARATOR; +ntbii.id = 0; +ntbii.bmp_cell = 0; +ntbii.text = NULL; +SendMessage (ntb1, TBM_ADDITEM, 0, (LPARAM)&ntbii); + +/* 添加定义有热点区域的按钮 */ +ntbii.flags = NTBIF_HOTSPOTBUTTON; +ntbii.id = IDC_NTB_FOUR; +ntbii.bmp_cell = 3; +ntbii.rc_hotspot = hotspot; +ntbii.hotspot_proc = my_hotspot_proc; +SendMessage (ntb1, TBM_ADDITEM, 0, (LPARAM)&ntbii); +``` + +### 1.3.2 获取或设置工具项信息 + +使用 `NTBM_GETITEM` 和 `NTBM_SETITEM` 消息可以获取或设置具有指定标识符的工具项信息。这两个消息的用法和 `NTBM_ADDITEM` 类似,区别是 `wParam` 中保存的是 `item` 的 ID,`lParam` 中保存的是指向一个 `NTBITEMINFO` 结构的指针,其中的 `which` 成员指定了要获取或设置的工具项信息内容。`which` 可以是如下值“或”的结果: +- `MTB_WHICH_FLAGS`:获取或设置工具项的标志,即 `NTBITEMINFO` 结构中的 `flags` 成员。 +- `MTB_WHICH_ID`:获取或设置工具项的标识符。 +- `MTB_WHICH_TEXT`:获取或设置工具项的文本。注意在获取时,必须确保通过 `text` 成员传递足够大的缓冲区。安全起见,应保证该缓冲区的大小为“NTB_TEXT_LEN+1”。 +- `MTB_WHICH_CELL`:获取或设置工具项所使用的位图单元值。 +- `MTB_WHICH_HOTSPOT`:获取或设置工具项的热点矩形。 +- `MTB_WHICH_ADDDATA`:获取或设置工具项的附加数据。 + +为方便起见,MiniGUI 还提供了 `NTBM_ENABLEITEM` 消息,用来使能或者禁止某个具有指定标识符的工具项。如下所示: + +```c +SendMessage (ntb1, NTBM_ENABLEITEM, 100, FALSE); +``` + +上述代码禁止(灰化)了 `ntb1` 工具栏控件中标识符为 100 的工具项。 + +### 1.3.3 设置新的工具项位图 + +应用程序可发送 `NTBM_SETBITMAP` 消息给工具栏控件,以便改变工具栏上的按钮位图。发送该消息时,使用 `lParam` 参数传递一个新的 `NTBINFO` 结构,其中定义了新的工具栏按钮位图。如下代码所示: + +```c +NTBINFO ntbi; +... +SendMessage (ntb, NTBM_SETBITMAP, 0, (LPARAM)&ntbi); +``` + +## 1.4 工具栏通知码 + +工具栏只会在用户单击某个按钮时产生通知消息,工具栏的通知码就是用户单击的按钮的标识符。当用户单击定义有热点区域的按钮时,工具栏控件将直接调用该按钮对应的热点区域回调函数,而不会产生通知消息。 + +## 1.5 编程实例 + +__清单 1.1__ 给出了工具栏控件的编程实例。该程序建立了一个具有三个按钮的工具栏,我们可通过按向左和向右的按钮来控制窗口中圆的位置;另一个按钮仅仅用来演示,不具有任何功能,初始时是灰化的。该程序的运行效果见__图 1.4__,完整源代码可见本指南示例程序包 `mg-samples` 中的 `newtoolbar.c`。 + +__清单 1.1__ 工具栏控件的编程实例 + +```c +#include +#include +#include + +#include +#include +#include +#include +#include + +#define IDC_NTB_LEFT 100 +#define IDC_NTB_RIGHT 110 +#define IDC_NTB_UP 120 + +static int offset = 0; +static RECT rcCircle = {0, 40, 300, 300}; + +static void my_notif_proc (HWND hwnd, int id, int nc, DWORD add_data) +{ + /* 用户按向左和向右按钮时,使窗口中的圆也相应向左和向右移动 */ + if (nc == IDC_NTB_LEFT) { + offset -= 10; + InvalidateRect (GetParent (hwnd), &rcCircle, TRUE); + } + else if (nc == IDC_NTB_RIGHT) { + offset += 10; + InvalidateRect (GetParent (hwnd), &rcCircle, TRUE); + } +} + +static BITMAP ntb_bmp; + +static void create_new_toolbar (HWND hWnd) +{ + HWND ntb; + NTBINFO ntb_info; + NTBITEMINFO ntbii; + gal_pixel pixel; + + ntb_info.nr_cells = 4; + ntb_info.w_cell = 0; + ntb_info.h_cell = 0; + ntb_info.nr_cols = 0; + ntb_info.image = &ntb_bmp; + + /* 创建工具栏控件 */ + ntb = CreateWindow (CTRL_NEWTOOLBAR, + "", + WS_CHILD | WS_VISIBLE, + 100, + 0, 0, 1024, 0, + hWnd, + (DWORD) &ntb_info); + + /* 设置通知回调函数 */ + SetNotificationCallback (ntb, my_notif_proc); + + /* 设置工具栏控件的背景色,使之和按钮位图背景一致 */ + pixel = GetPixelInBitmap (&ntb_bmp, 0, 0); + SetWindowBkColor (ntb, pixel); + InvalidateRect (ntb, NULL, TRUE); + + /* 添加两个普通按钮 */ + memset (&ntbii, 0, sizeof (ntbii)); + ntbii.flags = NTBIF_PUSHBUTTON; + ntbii.id = IDC_NTB_LEFT; + ntbii.bmp_cell = 1; + SendMessage(ntb, TBM_ADDITEM, 0, (LPARAM)&ntbii); + + ntbii.flags = NTBIF_PUSHBUTTON; + ntbii.id = IDC_NTB_RIGHT; + ntbii.bmp_cell = 2; + SendMessage (ntb, TBM_ADDITEM, 0, (LPARAM)&ntbii); + + /* 添加分隔条 */ + ntbii.flags = NTBIF_SEPARATOR; + ntbii.id = 0; + ntbii.bmp_cell = 0; + ntbii.text = NULL; + SendMessage (ntb, TBM_ADDITEM, 0, (LPARAM)&ntbii); + + /* 添加一个初始禁止的按钮 */ + ntbii.flags = NTBIF_PUSHBUTTON | NTBIF_DISABLED; + ntbii.id = IDC_NTB_UP; + ntbii.bmp_cell = 0; + SendMessage (ntb, TBM_ADDITEM, 0, (LPARAM)&ntbii); +} + +static int ToolBarWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_CREATE: + /* 装载工具栏使用的位图对象 */ + if (LoadBitmap (HDC_SCREEN, &ntb_bmp, "new2.jpg")) + return -1; + + create_new_toolbar (hWnd); + break; + + case MSG_PAINT: + { + HDC hdc = BeginPaint (hWnd); + + ClipRectIntersect (hdc, &rcCircle); + + /* 绘制红色的圆 */ + SetBrushColor (hdc, PIXEL_red); + FillCircle (hdc, 140 + offset, 120, 50); + + EndPaint (hWnd, hdc); + return 0; + } + + case MSG_DESTROY: + UnloadBitmap (&ntb_bmp); + DestroyAllControls (hWnd); + return 0; + + case MSG_CLOSE: + DestroyMainWindow (hWnd); + PostQuitMessage (hWnd); + return 0; + } + + return DefaultMainWinProc(hWnd, message, wParam, lParam); +} + +/* 以下创建主窗口的代码从略 */ +``` + +![工具栏控件的使用](figures/Part4Chapter09-1.4.jpeg) +__图 1.4__ 工具栏控件的使用 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter10-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter10-zh.md new file mode 100644 index 0000000..d38fcf2 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter10-zh.md @@ -0,0 +1,461 @@ +# 28 属性表 + + +属性表最常见的用途就是将本该属于不同对话框的交互内容分门别类放在同一对话框中,一方面节省了对话框空间,另一方面也使得交互界面更加容易使用。__图 1.1__ 就是 MiniGUI 属性表控件的一种典型用法。 + +![属性页控件](figures/Part4Chapter10-1.1.jpeg) +__图 1.1__ 属性页控件 + +属性表由一个个的属性页组成,每个属性页有一个凸舌,我们可以单击凸舌,在不同的属性页之间切换。我们可以将属性页理解为一种容器控件,其中可以容纳其他的控件。从应用程序开发者的角度看,我们又可以将属性页理解为对话框中的对话框——每个属性页都有自己的窗口过程,我们通常使用类似建立对话框那样的方法,即定义对话框模板的方法向属性表中添加属性页。 + +在应用程序中,使用 `CTRL_PROPSHEET` 控件类名称调用 `CreateWindow` 函数,即可创建属性页。 + +## 1.1 属性表风格 + +目前属性表有以下四种风格,用来控制属性页凸舌的宽度和显示位置: + +- `PSS_SIMPLE`:所有的属性页凸舌具有相同的宽度。 +- `PSS_COMPACTTAB`:属性页凸舌的宽度取决于属性页标题文本的长度。 +- `PSS_SCROLLABLE`:属性页凸舌的宽度取决于属性页标题文本的长度,当属性页凸舌的数目过多时,将自动出现左右箭头用来调节当前可见的属性页凸舌。 +- `PSS_BOTTOM`:属性页凸舌显示在属性表的下方,可以和上面三种风格同时配合使用。 + +## 1.2 属性表消息 + +### 1.2.1 添加属性页 + +在创建了属性表控件之后,就可以发送 `PSM_ADDPAGE` 消息向属性表中添加属性页。该消息的 `wParam` 用来传递对话框模板,`lParam` 用来传递属性页的窗口过程函数。如下所示: + +```c +HWND pshwnd = GetDlgItem (hDlg, IDC_PROPSHEET); + +/* 准备对话框模板 */ +DlgStructParams.controls = CtrlStructParams; + +/* 添加属性页 */ +SendMessage (pshwnd, PSM_ADDPAGE, +(WPARAM)&DlgStructParams, (LPARAM)PageProc1); +``` + +该消息的返回值是新添加的属性页的索引值,以零为基。 + +### 1.2.2 属性页过程函数 + +和对话框类似,每个属性页有自己的属性页过程函数,用来处理该属性页相关的消息。该过程函数的原型和普通的窗口过程函数一样,但有如下不同: + +- 属性页的过程函数应该为需要进行默认处理的消息调用 `DefaultPageProc` 函数。 +- 属性页过程函数需要处理两个属性页特有的消息:`MSG_INITPAGE` 和 `MSG_SHOWPAGE`。前者类似对话框的 `MSG_INITDIALOG` 消息;后者在属性页被隐藏和重新显示时发送到属性页过程中,`lParam` 参数分别取 `SW_HIDE` 和 `SW_SHOW`。在显示该属性页时,属性页过程函数返回 1 将置第一个具有 `WS_TABSTOP` 的控件具有输入焦点。 +- 给属性表控件发送 `PSM_SHEETCMD` 消息时,属性表控件将向其拥有的所有属性页广播 `MSG_SHEETCMD` 消息。属性页可以在这时检查用户输入的有效性并保存有效输入,如果输入无效或出现其他问题,可以返回 –1 来终止该消息的继续传播。在收到任意一个属性页返回的非零值之后,属性表控件将使 `PSM_SHEETCMD` 消息返回非零值,该值是属性页索引值加一之后的值。这样,我们就可以在属性表所在对话框的处理中了解哪个属性页中含有无效输入,然后终止继续处理,并切换到该属性页。 + +__清单 1.1__ 给出了一个典型的属性页过程函数,以及属性表所在对话框的过程函数。当用户单击了属性表所在对话框的“确定”按钮之后,对话框向属性表控件发送 `PSM_SHEETCMD` 消息,并根据该消息的返回值来决定下一步正常关闭对话框还是切换到某个属性页修正无效输入。属性页的过程函数在收到 `MSG_SHEETCMD` 消息之后,会判断用户的输入是否有效,并相应返回 0 或者 –1。 + +__清单 1.1__ 典型的属性页过程函数以及属性表所在对话框的过程函数 + +```c +static int PageProc1 (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITPAGE: + break; + + case MSG_SHOWPAGE: + return 1; + + case MSG_SHEETCMD: + if (wParam == IDOK) { + char buffer [20]; + GetDlgItemText (hDlg, IDC_EDIT1, buffer, 18); + buffer [18] = '\0'; + + /* 在用户按下属性表所在对话框中的“确定”按钮时,判断用户输入是否有效 */ + if (buffer [0] == '\0') { + MessageBox (hDlg, + "Please input something in the first edit box.", + "Warning!", + MB_OK | MB_ICONEXCLAMATION | MB_BASEDONPARENT); + /* 用户输入无效,返回非零值 */ + return -1; + } + } + return 0; + + case MSG_COMMAND: + switch (wParam) { + case IDOK: + case IDCANCEL: + break; + } + break; + } + + return DefaultPageProc (hDlg, message, wParam, lParam); +} + +static int PropSheetProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITDIALOG: + { + HWND pshwnd = GetDlgItem (hDlg, IDC_PROPSHEET); + + /* 向属性表控件中添加属性页 */ + + DlgStructParams.controls = CtrlStructParams; + SendMessage (pshwnd, PSM_ADDPAGE, + (WPARAM)&DlgStructParams, (LPARAM)PageProc1); + + DlgPassword.controls = CtrlPassword; + SendMessage ( pshwnd, PSM_ADDPAGE, + (WPARAM)&DlgPassword,(LPARAM) PageProc2); + + DlgStartupMode.controls = CtrlStartupMode; + SendMessage ( pshwnd, PSM_ADDPAGE, + (WPARAM)&DlgStartupMode,(LPARAM)PageProc3); + + DlgInitProgress.controls = CtrlInitProgress; + SendMessage ( pshwnd, PSM_ADDPAGE, + (WPARAM)&DlgInitProgress, (LPARAM) PageProc4); + + break; + } + + case MSG_COMMAND: + switch (wParam) + { + case IDC_APPLY: + break; + + case IDOK: + { + /* 向属性表控件发送 PSM_SHEETCMD 消息,通知它“确定”按钮被按下 */ + int index = SendDlgItemMessage (hDlg, IDC_PROPSHEET, + PSM_SHEETCMD, IDOK, 0); + if (index) { + /* 某个属性页返回了非零值,切换到这个属性页提示继续输入 */ + SendDlgItemMessage (hDlg, IDC_PROPSHEET, + PSM_SETACTIVEINDEX, index - 1, 0); + } + else + /* 一切正常,关闭对话框 */ + EndDialog (hDlg, wParam); + + break; + } + case IDCANCEL: + EndDialog (hDlg, wParam); + break; + } + break; + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} +``` + +### 1.2.3 删除属性页 + +要删除某个属性页,只需向属性表控件发送 `PSM_REMOVEPAGE` 消息,并在 `wParam` 中传递要删除的属性页索引即可: + +```c +SendDlgItemMessage (hDlg, IDC_PROPSHEET, PSM_REMOVEPAGE, 0, 0); +``` + +该消息调用将删除属性表中的第一个属性页。 + +>【注意】删除一个属性页可能会改变其他属性页的索引值。 + +### 1.2.4 属性页句柄和索引 + +属性页句柄实际就是属性页中控件父窗口的句柄,也就是属性页过程函数传入的窗口句柄,而这个窗口实质上是属性表控件的一个子窗口。向属性表控件发送 `PSM_GETPAGE` 消息可获得具有某个索引值的属性页的窗口句柄: + +```c +hwnd = SendDlgItemMessage (hDlg, IDC_PROPSHEET, PSM_GETPAGE, index, 0); +``` + +该消息调用将返回索引值为 `index` 的属性页的窗口句柄。而下面的消息调用根据属性页句柄返回属性页索引值: + +```c +index = SendDlgItemMessage (hDlg, IDC_PROPSHEET, PSM_GETPAGEINDEX, hwnd, 0); +``` + +获得属性页的窗口句柄之后,我们可以方便地调用 `CreateWindow` 等函数向其中添加新的控件。当然,在属性页的过程函数中也可以完成类似任务。 + +### 1.2.5 属性页的相关操作 + +MiniGUI 提供了如下消息可用来获得属性页相关信息: + +- `PSM_GETPAGECOUNT`:返回属性页总个数。 +- `PSM_GETTITLELENGTH`:根据 `wParam` 参数传入的属性页索引值获得该属性页标题的长度,类似窗口的 `MSG_GETTEXTLENGTH` 消息。 +- `PSM_GETTITLE`:根据 `wParam` 参数传入的属性页索引值获得该属性页标题,并保存在 `lParam` 参数传递的缓冲区中,类似窗口的 `MSG_GETTEXT` 消息。 +- `PSM_SETTITLE`:根据 `lParam` 参数传入的文本字符串设置由 `wParam` 指定的属性页标题,类似窗口的 `MSG_SETTEXT` 消息。 + +活动属性页是指显示在属性表中的那个属性页,每次只会有一个属性页显示在属性表中。MiniGUI 提供了如下消息用来操作活动属性页: + +- `PSM_GETACTIVEPAGE`:返回活动属性页的窗口句柄。 +- `PSM_GETACTIVEINDEX`:返回活动属性页的索引值。 +- `PSM_SETACTIVEINDEX`:根据 `wParam` 传入的属性页索引值设置活动属性页。 + +## 1.3 属性表通知码 + +目前只有一个属性表控件的通知码: + +- `PSN_ACTIVE_CHANGED`:当属性表中的活动属性页发生变化时,属性表控件将产生该通知消息。 + +## 1.4 编程实例 + +__清单 1.2__ 给出了属性表控件的编程实例。该程序显示了本机的一些系统信息,比如 CPU 类型、内存大小等等。该程序的运行效果见__图 1.2__,完成的源代码见本指南示例程序包 `mg-samples` 中的 `propsheet.c` 程序。 + +__清单 1.2__ 属性表控件的编程实例 + +```c +#include +#include +#include + +#include +#include +#include +#include +#include + +#define PAGE_VERSION 1 +#define PAGE_CPU 2 +#define PAGE_MEMINFO 3 +#define PAGE_PARTITION 4 +#define PAGE_MINIGUI 5 + +#define IDC_PROPSHEET 100 + +#define IDC_SYSINFO 100 + +/* 定义系统信息属性页的模板 */ +static DLGTEMPLATE PageSysInfo = +{ + WS_NONE, + WS_EX_NONE, + 0, 0, 0, 0, + "", + 0, 0, + 1, NULL, + 0 +}; + +/*系统信息属性页中只有一个用来显示信息的静态控件 */ +static CTRLDATA CtrlSysInfo [] = +{ + { + CTRL_STATIC, + WS_VISIBLE | SS_LEFT, + 10, 10, 370, 160, + IDC_SYSINFO, + "测试\n测试\n测试\n测试\n测试\n测试\n", + 0 + } +}; + +/* 从指定文件中读取系统信息 */ +static size_t read_sysinfo (const char* file, char* buff, size_t buf_len) +{ + size_t size; + FILE* fp = fopen (file, "r"); + + if (fp == NULL) return 0; + + size = fread (buff, 1, buf_len, fp); + + fclose (fp); + return size; +} + +#define BUF_LEN 10240 + +/* +* 初始化和刷新时调用该函数刷新对应的窗口。 +* 注意,这个函数被所有的属性页调用。 +*/ +static void get_systeminfo (HWND hDlg) +{ + int type; + HWND hwnd; + char buff [BUF_LEN + 1]; + size_t size = 0; + + /* 根据 type 判断是哪个属性页 */ + type = (int)GetWindowAdditionalData (hDlg); + + /* 获取属性页中静态框的句柄 */ + hwnd = GetDlgItem (hDlg, IDC_SYSINFO); + + buff [BUF_LEN] = 0; + switch (type) { + case PAGE_VERSION: + size = read_sysinfo ("/proc/version", buff, BUF_LEN); + buff [size] = 0; + break; + + case PAGE_CPU: + size = read_sysinfo ("/proc/cpuinfo", buff, BUF_LEN); + buff [size] = 0; + break; + + case PAGE_MEMINFO: + size = read_sysinfo ("/proc/meminfo", buff, BUF_LEN); + buff [size] = 0; + break; + + case PAGE_PARTITION: + size = read_sysinfo ("/proc/partitions", buff, BUF_LEN); + buff [size] = 0; + break; + + case PAGE_MINIGUI: + size = snprintf (buff, BUF_LEN, + "MiniGUI version %d.%d.%d.\n" + "Copyright (C) 1998-2003 Feynman Software and others.\n\n" + "MiniGUI is free software, covered by the GNU General Public License, " + "and you are welcome to change it and/or distribute copies of it “ + “under certain conditions. " + "Please visit\n\n" + "http://www.minigui.org\n\n" + "to know the details.\n\n" + "There is absolutely no warranty for MiniGUI.", + MINIGUI_MAJOR_VERSION, MINIGUI_MINOR_VERSION, MINIGUI_MICRO_VERSION); + break; + } + + if (size) { + SetWindowText (hwnd, buff); + } +} + +/* 所有的属性页使用同一个窗口过程函数 */ +static int SysInfoPageProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITPAGE: + /* 获取属性页中静态框的句柄 */ + get_systeminfo (hDlg); + break; + + case MSG_SHOWPAGE: + return 1; + + case MSG_SHEETCMD: + if (wParam == IDOK) + /* 用户单击对话框中的“刷新”按钮时,将调用该函数刷新 */ + get_systeminfo (hDlg); + return 0; + } + + return DefaultPageProc (hDlg, message, wParam, lParam); +} + +static int PropSheetProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITDIALOG: + { + HWND pshwnd = GetDlgItem (hDlg, IDC_PROPSHEET); + + PageSysInfo.controls = CtrlSysInfo; + + /* 添加属性页,注意每个属性页具有不同的附加数据 */ + + PageSysInfo.caption = "版本信息"; + PageSysInfo.dwAddData = PAGE_VERSION; + SendMessage (pshwnd, PSM_ADDPAGE, (WPARAM)&PageSysInfo, (LPARAM)SysInfoPageProc); + PageSysInfo.caption = "CPU 信息"; + PageSysInfo.dwAddData = PAGE_CPU; + SendMessage (pshwnd, PSM_ADDPAGE, (WPARAM)&PageSysInfo, (LPARAM)SysInfoPageProc); + + PageSysInfo.caption = "内存信息"; + PageSysInfo.dwAddData = PAGE_MEMINFO; + SendMessage (pshwnd, PSM_ADDPAGE, (WPARAM)&PageSysInfo, (LPARAM)SysInfoPageProc); + + PageSysInfo.caption = "分区信息"; + PageSysInfo.dwAddData = PAGE_PARTITION; + SendMessage (pshwnd, PSM_ADDPAGE, (WPARAM)&PageSysInfo, (LPARAM)SysInfoPageProc); + + PageSysInfo.caption = "MiniGUI 信息"; + PageSysInfo.dwAddData = PAGE_MINIGUI; + SendMessage (pshwnd, PSM_ADDPAGE, (WPARAM)&PageSysInfo, (LPARAM)SysInfoPageProc); + break; + } + + case MSG_COMMAND: + switch (wParam) { + case IDOK: + /* 用户按“刷新”按钮时,向所有属性表控件发送 PSM_SHEETCMD 消息 */ + SendDlgItemMessage (hDlg, IDC_PROPSHEET, PSM_SHEETCMD, IDOK, 0); + break; + + case IDCANCEL: + EndDialog (hDlg, wParam); + break; + } + break; + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +/* 主对话框的模板 */ +static DLGTEMPLATE DlgPropSheet = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 0, 0, 410, 275, + "系统信息", + 0, 0, + 3, NULL, + 0 +}; + +/* 该对话框只有三个控件:属性表、“刷新”按钮和“关闭”按钮 */ +static CTRLDATA CtrlPropSheet[] = +{ + { + CTRL_PROPSHEET, + WS_VISIBLE | PSS_COMPACTTAB, + 10, 10, 390, 200, + IDC_PROPSHEET, + "", + 0 + }, + { + CTRL_BUTTON, + WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_GROUP, + 10, 220, 140, 25, + IDOK, + "刷新", + 0 + }, + { + CTRL_BUTTON, + WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, + 260, 220, 140, 25, + IDCANCEL, + "关闭", + 0 + }, +}; + +int MiniGUIMain (int argc, const char* argv[]) +{ + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "propsheet" , 0 , 0); + #endif + + DlgPropSheet.controls = CtrlPropSheet; + + DialogBoxIndirectParam (&DlgPropSheet, HWND_DESKTOP, PropSheetProc, 0L); + + return 0; +} + +#ifndef _MGRM_PROCESSES +#include +#endif +``` + +![属性表控件的使用](figures/Part4Chapter10-1.2.jpeg) +__图 1.2__ 属性表控件的使用 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter11-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter11-zh.md new file mode 100644 index 0000000..3e6d10b --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter11-zh.md @@ -0,0 +1,296 @@ +# 滚动窗口控件 + +滚动窗口(ScrollWnd)控件是一个使用滚动条对内容进行滚动浏览的容器控件,它的基本用途是用来放置别的控件,使用户能够在一个窗口内通过滚动的方法来查看和操作许多控件。当然,滚动窗口还可以用来做很多其它的事情,它的可定制性是很强的。在本章的最后,我们将看到一个用滚动窗口控件作为图片查看器的例子。 + +在应用程序中,使用 `CTRL_SCROLLWND` 控件类名称调用 `CreateWindow` 函数,即可创建滚动窗口控件。 + +## 1.1 可以滚动的窗口 + +滚动窗口控件和下一章讲述的 `ScrollView` 控件都是可以滚动的窗口控件,它们有很多相似之处。一个可以滚动的窗口有一个带滚动条的控件窗口(可视区域)和内容区域构成,如__图 1.1__ 所示。使用滚动条滚动显示内容区域时,内容区域的水平位置值或者垂直位置值将发生变化。内容区域的大小可以由应用程序控制,不过内容区域最小不能小于可视区域。 + +![可以滚动的窗口](figures/Part4Chapter11-1.1.jpeg) +__图 1.1__ 可以滚动的窗口 + +## 1.2 通用的滚动窗口消息 + +滚动窗口和 `ScrollView` 控件都响应一些通用的滚动窗口消息,包括获取和设置滚动窗口的内容范围、设置滚动条的滚动值、获取和设置内容区域的当前位置、获取和设置可视区域的大小等。 + +### 1.2.1 获取和设置内容区域和可视区域的范围 + +`SVM_SETCONTRANGE` 消息用来设置滚动窗口的内容区域的大小。 + +```c +int cont_w, cont_h; +SendMessage (hScrWnd, SVM_SETCONTRANGE, cont_w, cont_h); +``` + +`cont_w` 和 `cont_h` 分别所要设置的内容区域宽度和高度。如果 `cont_w` 或 `cont_h` 为负值,内容区域的宽度或高度将不发生改变;如果所要设置的内容区域宽度或高度值小于可视区域的宽度/高度,设置后的内容区域宽度或高度将等于可视区域的宽度或高度。 + +`SVM_SETCONTWIDTH` 消息和 `SVM_SETCONTHEIGHT` 消息分别设置滚动窗口的宽度和高度。 + +```c +int cont_w, cont_h; +SendMessage (hScrWnd, SVM_SETCONTWIDTH, cont_w, 0); +SendMessage (hScrWnd, SVM_SETCONTHEIGHT, cont_h, 0); +``` + +`SVM_GETCONTWIDTH`、`SVM_GETCONTHEIGHT`、`SVM_GETVISIBLEWIDTH` 和 `SVM_GETVISIBLEHEIGHT` 消息分别用来获取内容区域的宽度和高度、可视区域的宽度和高度。 + +### 1.2.2 获取位置信息和设置当前位置 + +`SVM_GETCONTENTX和SVM_GETCONTENTY` 消息用来获取内容区域的当前位置值。 + +```c +int pos_x = SendMessage (hScrWnd, SVM_GETCONTENTX, 0, 0); +int pos_y = SendMessage (hScrWnd, SVM_GETCONTENTY, 0, 0); +``` + +`SVM_SETCONTPOS` 消息用来设置内容区域的当前位置值,也就是在可视区域中移动内容区域到某个指定位置。 + +```c +int pos_x, pos_y; +SendMessage (hScrWnd, SVM_SETCONTPOS, pos_x, pos_y); +``` + +`SVM_MAKEPOSVISIBLE` 消息用来使内容区域中的某个位置点成为可见。 + +```c +SendMessage (hScrWnd, SVM_MAKEPOSVISIBLE, pos_x, pos_y); +``` + +如果该位置点原来是不可见的,使用 `SVM_MAKEPOSVISIBLE` 消息使之成为可见之后,该位置点将位于可视区域的上边缘(原位置点在可视区域之上)或者下边缘(原位置点在可视区域之下)。 + +### 1.2.3 获取和设置滚动属性 + +`SVM_GETHSCROLLVAL` 和 `SVM_GETVSCROLLVAL` 消息分别用来获取滚动窗口的当前水平和垂直滚动值(点击滚动条箭头的滚动范围值);`SVM_GETHSCROLLPAGEVAL` 和 `SVM_GETVSCROLLPAGEVAL` 消息分别用来获取滚动窗口的当前水平和垂直页滚动值(翻页操作时的滚动范围值)。 + +```c +int val = SendMessage (hScrWnd, SVM_GETHSCROLLVAL, 0, 0); +int val = SendMessage (hScrWnd, SVM_GETVSCROLLVAL, 0, 0); +int val = SendMessage (hScrWnd, SVM_GETHSCROLLPAGEVAL, 0, 0); +int val = SendMessage (hScrWnd, SVM_GETVSCROLLPAGEVAL, 0, 0); +``` + +`SVM_SETSCROLLVAL` 消息用来设置滚动窗口的水平和(或者)垂直滚动值。`wParam` 参数为水平滚动值,`lParam` 为垂直滚动值;如果水平/垂直滚动值为0或者负值的话,滚动窗口的当前的水平/垂直滚动值将不发生变化。 + +```c +int h_val, v_val; +SendMessage (hScrWnd, SVM_SETSCROLLVAL, h_val, v_val); +``` + +`SVM_SETSCROLLPAGEVAL` 消息用来设置滚动窗口的水平和(或者)垂直页滚动值。`wParam` 参数为水平页滚动值,`lParam` 为垂直页滚动值;如果水平/垂直页滚动值为0或者负值的话,滚动窗口的当前的水平/垂直页滚动值将不发生变化。 + +```c +int h_val, v_val; +SendMessage (hScrWnd, SVM_SETSCROLLPAGEVAL, h_val, v_val); +``` + +## 1.3 滚动窗口控件消息 + +### 1.3.1 添加子控件 + +在创建了滚动窗口控件之后,就可以发送 `SVM_ADDCTRLS` 消息往其中添加子控件。该消息的 `wParam` 用来传递控件的个数,`lParam` 用来传递控件数组的指针。 + +```c +CTRLDATA controls[ctrl_nr]; +SendMessage (hScrWnd, SVM_ADDCTRLS, (WPARAM)ctrl_nr, (LPARAM)controls); +``` + +需要注意的是:往滚动窗口控件中添加控件并不会改变滚动窗口内容区域的范围,如果子控件的位置超出了内容区域的当前范围,在内容区域中就看不到该控件。所以,一般在添加子控件之前需要使用 `SVM_SETCONTRANGE` 消息先设置内容区域的范围,使之适合所要添加的控件的显示。 + +除了创建完滚动窗口控件之后发送 `SVM_ADDCTRLS` 消息添加子控件之外,应用程序还可以在使用 `CreateWindow` 函数创建控件时,通过在附加数据项中传递一个 `CONTAINERINFO` 类型的结构指针来使滚动窗口控件创建后自动添加该结构指定的子控件。 + +```c +typedef struct _CONTAINERINFO +{ + WNDPROC user_proc; /** user-defined window procedure of the container */ + + int controlnr; /** number of controls */ + PCTRLDATA controls; /** pointer to control array */ + + DWORD dwAddData; /** additional data */ +} CONTAINERINFO; +typedef CONTAINERINFO* PCONTAINERINFO; +``` + +`controlnr` 项为控件的个数,`controls` 指向一个 `CTRLDATA` 控件数组;位置被占用的控件的附加数据项通过 `CONTAINERINFO` 结构中的 `dwAddData` 项来传递。 + +`SVM_RESETCONTENT` 消息用来重置滚动窗口控件,包括清空其中的子控件和设置内容区域的范围和位置值为默认值。 + +```c +SendMessage (hScrWnd, SVM_RESETCONTENT, 0, 0); +``` + +### 1.3.2 获取子控件的句柄 + +`SVM_GETCTRL` 可以用来获取滚动窗口控件中的子控件的句柄。 + +```c +int id; +HWND hCtrl; +HCtrl = SendMessage (hScrWnd, SVM_GETCTRL, id, 0); +``` + +`SVM_GETFOCUSCHILD` 消息用来获取滚动窗口控件中具有键盘焦点的子控件。 + +```c +HWND hFocusCtrl; +HFocusCtrl = SendMessage (hScrWnd, SVM_GETFOCUSCHILD, 0, 0); +``` + +### 1.3.3 容器(内容)窗口过程 + +滚动窗口中放置子控件的窗口称为容器窗口,也就是内容窗口(区域)。应用程序可以使用 `SVM_SETCONTAINERPROC` 消息来设置新的容器窗口过程,从而达到定制滚动窗口的目的。 + +```c +WNDPROC myproc; +SendMessage (hScrWnd, SVM_SETCONTAINERPROC, 0, (LPARAM)myproc); +``` + +`lParam` 参数为应用程序自定义的容器窗口过程,该窗口过程默认情况下应该返回滚动窗口的缺省容器窗口过程函数 `DefaultContainerProc`。 + +```c +int GUIAPI DefaultContainerProc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam); +``` + +此外,应用程序还可以通过前面提到的 `CONTAINERINFO` 结构的 `user_proc` 项来指定自定义的容器窗口过程。 + +## 1.4 编程实例 + +__清单 1.1__ 中的代码演示了使用滚动窗口控件来构造一个简单的图片查看器的方法。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `scrollwnd.c` 程序。 + +__清单 1.1__ 滚动窗口控件示例程序 + +```c +#define IDC_SCROLLWND 100 +#define ID_ZOOMIN 200 +#define ID_ZOOMOUT 300 + +static HWND hScrollWnd; +static BITMAP bmp_bkgnd; +static float current_scale = 1; + +static int pic_container_proc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + + case MSG_PAINT: + { + HDC hdc = BeginPaint (hWnd); + FillBoxWithBitmap (hdc, 0, 0, current_scale * bmp_bkgnd.bmWidth, + current_scale * bmp_bkgnd.bmHeight, &bmp_bkgnd); + EndPaint (hWnd, hdc); + return 0; + } + + } + + return DefaultContainerProc (hWnd, message, wParam, lParam); +} + +static int +ImageViewerProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + + switch (message) + { + + case MSG_INITDIALOG: + { + hScrollWnd = GetDlgItem (hDlg, IDC_SCROLLWND); + SendMessage (hScrollWnd, SVM_SETCONTAINERPROC, 0, (LPARAM)pic_container_proc); + SendMessage (hScrollWnd, SVM_SETCONTRANGE, bmp_bkgnd.bmWidth, bmp_bkgnd.bmHeight); + + break; + } + + case MSG_COMMAND: + { + int id = LOWORD(wParam); + + if (id == ID_ZOOMIN || id == ID_ZOOMOUT) { + current_scale += (id == ID_ZOOMIN) ? 0.2 : -0.2; + if (current_scale < 0.1) + current_scale = 0.1; + + SendMessage (hScrollWnd, SVM_SETCONTRANGE, + current_scale * bmp_bkgnd.bmWidth, + current_scale * bmp_bkgnd.bmHeight); + InvalidateRect (hScrollWnd, NULL, TRUE); + } + + break; + } + + case MSG_CLOSE: + EndDialog (hDlg, 0); + return 0; + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +static CTRLDATA CtrlViewer[] = +{ + { + "ScrollWnd", + WS_BORDER | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL, + 10, 10, 300, 200, + IDC_SCROLLWND, + "image viewer", + 0 + }, + { + CTRL_BUTTON, + WS_TABSTOP | WS_VISIBLE | BS_DEFPUSHBUTTON, + 20, 220, 60, 25, + ID_ZOOMIN, + "Zoom in", + 0 + }, + { + CTRL_BUTTON, + WS_TABSTOP | WS_VISIBLE | BS_PUSHBUTTON, + 220, 220, 60, 25, + ID_ZOOMOUT, + "Zoom out", + 0 + } +}; + +static DLGTEMPLATE DlgViewer = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 0, 0, 350, 280, + "Image Viewer", + 0, 0, + TABLESIZE(CtrlViewer), CtrlViewer, + 0 +}; + +int MiniGUIMain (int argc, const char* argv[]) +{ + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "scrollwnd" , 0 , 0); + #endif + + if (LoadBitmap (HDC_SCREEN, &bmp_bkgnd, "bkgnd.jpg")) + return 1; + + DialogBoxIndirectParam (&DlgViewer, HWND_DESKTOP, ImageViewerProc, 0L); + + UnloadBitmap (&bmp_bkgnd); + return 0; +} + +#ifndef _ MGRM_PROCESSES +#include +#endif +``` + +这个简单的图片查看器可以通过滚动条来滚动查看图片,还可以进行放大和缩小。图片查看器通过 `SVM_SETCONTAINERPROC` 消息设置了新的容器窗口过程函数,并在 `MSG_PAINT` 消息中绘制图片。程序的运行效果如__图 1.2__ 所示。 + +![一个简单的图片查看器](figures/Part4Chapter11-1.2.jpeg) +__图 1.2__ 一个简单的图片查看器 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter12-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter12-zh.md new file mode 100644 index 0000000..2fdb23e --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter12-zh.md @@ -0,0 +1,403 @@ +# 滚动型控件 + +滚动型(ScrollView)控件也是一个滚动窗口控件,和 `ScrollWnd` 控件的不同之处是滚动型控件中滚动显示的是列表项而不是控件。 + +滚动型的主要用途是显示和处理列表项,这点和 `Listbox` 及 `Listview` 控件类似。不过,滚动型中列表项的高度是可以由用户指定的,不同列表项可以有不同的高度。最重要的是,滚动型中列表项的内容绘制完全是由应用程序自己确定的。总的来说,滚动型是一个可定制性很强的控件,给予了应用程序很大的自由,使用滚动型可以完成许多 `Listbox` 和 `Listview` 控件不能胜任的工作。 + +## 1.1 控件风格 + +具有 `SVS_AUTOSORT` 风格的滚动型控件将对列表项进行自动排序,前提是已经使用 `SVM_SETITEMCMP` 消息设置了滚动型控件的列表项比较函数。 + +```c +SVM_SETITEMCMP myItemCmp; +SendMessage (hScrWnd, SVM_SETITEMCMP, 0, (LPARAM)myItemCmp); +``` + +`myItemCmp` 为应用程序指定的列表项比较函数。 + +滚动型控件的列表项比较函数是一个 `SVM_SETITEMCMP` 类型的函数,原型如下: + +```c +typedef int (*SVITEM_CMP) (HSVITEM hsvi1, HSVITEM hsvi2); +``` + +`hsvi1` 和 `hsvi2` 为所要比较的两个列表项的句柄。如果比较函数返回负值,`hsvi` 代表的列表项将被排序在 `hsvi2` 代表的列表项之前。 + +此外,还可以对不具有 `SVS_AUTOSORT` 风格的滚动型控件使用 `SVM_SORTITEMS` 消息来对列表项进行一次性的排序。 + +```c +SVM_SETITEMCMP myItemCmp; +SendMessage (hScrWnd, SVM_SORTITEMS, 0, (LPARAM)myItemCmp); +``` + +`myItemCmp` 为应用程序指定的排序时使用的列表项比较函数。 + +## 1.2 滚动型控件消息 + +除了和 `ScrollWnd` 控件一样响应一些通用的滚动窗口消息之外,滚动型控件的相关消息主要用于列表项的添加、删除和访问等方面。 + +### 1.2.1 列表项的内容显示 + +滚动型控件的列表项内容显示完全是由应用程序自己确定的,所以,在使用列表项之前,必须首先指定列表项内容的显示方法。`SVM_SETITEMDRAW` 消息用来设置列表项的绘制函数。 + +```c +SVITEM_DRAWFUNC myDrawItem; +SendMessage (hScrWnd, SVM_SETITEMDRAW, 0, (LPARAM)myDrawItem); +``` + +列表项内容绘制函数是一个 `SVITEM_DRAWFUNC` 类型的函数,原型如下: + +```c +typedef void (*SVITEM_DRAWFUNC) (HWND hWnd, HSVITEM hsvi, HDC hdc, RECT *rcDraw); +``` + +传给绘制函数的参数分别为滚动型控件窗口句柄 `hWnd`、列表项句柄 `hsvi`、图形设备上下文 `hdc` 和列表项绘制矩形区域。 + +列表项绘制函数可以根据滚动型控件的实际用途在指定的矩形区域中绘制自定义的内容,可以是文本,也可以是图片,一切均由应用程序自己决定。 + +### 1.2.2 列表项操作函数的设置 + +`SVM_SETITEMOPS` 消息可以用来设置列表项相关操作的一些回调函数,包括初始化、绘制和结束函数。 + +```c +SVITEMOPS myops; +SendMessage (hScrWnd, SVM_SETITEMOPS, 0, (LPARAM)&myops); +``` + +myops为一个SVITEMOPS类型的结构,指定了滚动型控件针对列表项的相关操作函数。如下: + +```c +typedef struct _svitem_operations +{ + SVITEM_INITFUNC initItem; /** called when an ScrollView item is created */ + SVITEM_DESTROYFUNC destroyItem; /** called when an item is destroied */ + SVITEM_DRAWFUNC drawItem; /** call this to draw an item */ +} SVITEMOPS; +``` + +`initItem` 是创建列表项时调用的初始化函数,原型如下: + +```c +typedef int (*SVITEM_INITFUNC) (HWND hWnd, HSVITEM hsvi); +``` + +传递的参数为控件窗口的句柄hWnd和所创建的列表项的句柄。可以使用该函数在创建列表项时进行一些相关的初始化工作。 + +`destroyItem` 是销毁列表项时调用的销毁函数,原型如下: + +```c +typedef void (*SVITEM_DESTROYFUNC) (HWND hWnd, HSVITEM hsvi); +``` + +传递的参数为控件窗口的句柄 `hWnd` 和所创建的列表项的句柄。可以使用该函数在销毁列表项时进行一些相关的清理工作,例如释放相关的资源。 + +`drawItem` 指定列表项的绘制函数,它的作用和使用 `SVM_SETITEMDRAW` 消息设置绘制函数是完全一样的。 + +### 1.2.3 列表项的操作 + +`SVM_ADDITEM和SVM_DELITEM` 消息分别用来添加和删除一个列表项。 + +```c +int idx; +HSVITEM hsvi; +SVITEMINFO svii; +Idx = SendMessage (hScrWnd, SVM_ADDITEM, (WPARAM)&hsvi, (LPARAM)&svii); +``` + +`svii` 是一个 `SVITEMINFO` 类型的结构,如下: + +```c +typedef struct _SCROLLVIEWITEMINFO +{ + int nItem; /** index of item */ + int nItemHeight; /** height of an item */ + DWORD addData; /** item additional data */ +} SVITEMINFO; +``` + +`nItem` 项为列表项的添加位置,如果 `nItem` 为负值,列表项将被添加到末尾。`nItemHeight` 为列表项的高度,`addData` 为列表项的附加数据值。 + +`hsvi` 用来存放所添加的列表项的句柄值,该句柄可以用来访问列表项。`SVM_ADDITEM` 消息返回所添加列表项的实际索引值。 + +`SVM_DELITEM` 消息用来删除一个列表项。 + +```c +int idx; +HSVITEM hsvi; +SendMessage (hScrWnd, SVM_DELITEM, idx, hsvi); +``` + +`hsvi` 指定所要删除的列表项的句柄。如果 `hsvi` 为 0,`idx` 指定所要删除的列表项的索引值。 + +`SVM_REFRESHITEM` 消息用来刷新一个列表项区域。 + +```c +int idx; +HSVITEM hsvi; +SendMessage (hScrWnd, SVM_REFRESHITEM, idx, hsvi); +``` + +`hsvi` 指定所要刷新的列表项的句柄。如果 `hsvi` 为 0,`idx` 指定所要刷新的列表项的索引值。 + +`SVM_GETITEMADDDATA` 消息用来获取列表项的附加数据。 + +```c +SendMessage (hScrWnd, SVM_GETITEMADDDATA, idx, hsvi); +``` + +`hsvi` 指定所要访问的列表项的句柄。如果 `hsvi` 为 0,`idx` 指定所要访问的列表项的索引值。 + +`SVM_SETITEMADDDATA` 消息用来设置列表项的附加数据。 + +```c +int idx; +DWORD addData; +SendMessage (hScrWnd, SVM_SETITEMADDDATA, idx, addData); +``` + +`idx` 指定所要访问的列表项的索引值,`addData` 为所要设置的附加数据。 + +`SVM_GETITEMCOUNT` 消息用来获取当前列表项的数量。 + +```c +int count = SendMessage (hScrWnd, SVM_GETITEMCOUNT, 0, 0); +``` + +`SVM_RESETCONTENT` 消息用来删除掉控件中所有的列表项。 + +```c +SendMessage (hScrWnd, SVM_RESETCONTENT, 0, 0); +``` + +### 1.2.4 获取和设置当前高亮项 + +滚动型控件具有一个高亮列表项属性,也就是说,列表项中仅有(如果有的话)一个列表项是当前高亮的列表项。应用程序可以设置和获取当前高亮的列表项。 + +需要注意的是:高亮只是滚动型控件的一个属性,某个列表项是当前的高亮项并不代表该列表项在显示上一定有什么特殊之处(如高亮显示),这完全是由应用程序来自己决定的。 + +`SVM_SETCURSEL` 消息用来设置控件的高亮列表项。 + +```c +SendMessage (hScrWnd, SVM_SETCURSEL, idx, bVisible); +``` + +`idx` 指定所要设置为高亮的列表项的索引值,`bVisible` 如果为 `TRUE`,该列表项将成为可见项。 + +`SVM_GETCURSEL` 消息用来获取控件的当前高亮列表项。 + +```c +int hilighted_idx = SendMessage (hScrWnd, SVM_GETCURSEL, 0, 0); +``` + +`SVM_GETCURSEL` 消息的返回值为当前高亮列表项的索引值。 + +### 1.2.5 列表项的选择和显示 + +滚动型控件的列表项除了有高亮属性之外,还有选中属性。高亮是唯一的,选中不是唯一的,也就是说,滚动型控件的列表项可以被多选。应用程序可以设置列表项的选中状态。 + +和高亮属性一样,我们同样要注意:选中只是列表项的一个状态,某个列表项是选中的项并不代表该列表项在显示上一定有什么特殊之处(如高亮显示),这也完全是由应用程序来决定的。 + +```c +SendMessage (hScrWnd, SVM_SELECTITEM, idx, bSel); +``` + +`idx` 指定所要设置的列表项的索引值。`bSel` 如果为 `TRUE`,该列表项将被设置为选中;反之为非选中。 + +`SVM_SHOWITEM` 消息用来显示一个列表项。 + +```c +SendMessage (hScrWnd, SVM_SHOWITEM, idx, hsvi); +``` + +`hsvi` 为所要显示的列表项的句柄。`idx` 指定所要显示的列表项的索引值,`idx` 只有在 `hsvi` 为 0 时起作用。 + +`SVM_CHOOSEITEM` 消息是 `SVM_SELECTITEM` 和 `SVM_SHOWITEM` 消息的组合,用来选中一个列表项并使之可见。 + +```c +SendMessage (hScrWnd, SVM_CHOOSEITEM, idx, hsvi); +``` + +`hsvi` 为所要选择和显示的列表项的句柄。`idx` 指定所要选择和显示的列表项的索引值,`idx` 只有在 `hsvi` 为 0 时起作用。 + +### 1.2.6 显示的优化 + +在使用 `SVM_ADDITEM` 消息或者 `SVM_DELITEM` 消息一次性增加或者删除很多列表项时,可以使用 `MSG_FREEZE` 消息进行一定的优化。用法是在操作之前冻结控件,操作之后解冻。 + +`MSG_FREEZE` 消息的 `wParam` 参数如果为 `TRUE` 则是冻结,反之为解冻。 + +### 1.2.7 设置可见区域的范围 + +滚动型控件的窗口并不全是可视区域,还包括边缘(margin)区域,如__图 1.1__ 所示 + +![滚动型的可视区域](figures/Part4Chapter12-1.1.jpeg) +__图 1.1__ 滚动型的可视区域 + +`SVM_SETMARGINS` 消息可以对滚动型控件的边缘范围进行设置。 + +```c +RECT rcMargin; +SendMessage (hScrWnd, SVM_SETMARGINS, 0, (LPARAM)&rcMargin); +``` + +`rcMargin` 中的 `left`、`top`、`right` 和 `bottom` 项分别为所要设置的左、上、右和下边缘的大小,如果设置的某个边缘值为负值,对应的设置将不起作用。 + +`SVM_GETMARGIN` S消息可以获取滚动型控件的边缘范围值。 + +```c +RECT rcMargin; +SendMessage (hScrWnd, SVM_GETMARGINS, 0, (LPARAM)&rcMargin); +``` + +`SVM_GETLEFTMARGIN`、`SVM_GETTOPMARGIN`、`SVM_GETRIGHTMARGIN` 和 `SVM_GETBOTTOMMARGIN` 消息分别用来获取左、上、右和下边缘值。 + +## 1.3 控件通知码 + +滚动型控件在响应用户点击等操作和发生某些状态改变时会产生通知消息,包括: + +- `SVN_SELCHANGED`:当前高亮列表项发生改变 +- `SVN_CLICKED`:用户点击列表项 +- `SVN_SELCHANGING`:当前高亮列表项正发生改变 + +应用程序需要使用 `SetNotificationCallback` 函数注册一个通知消息处理函数,在该函数中对收到的各个通知码进行应用程序所需的处理。 + +`SVN_CLICKED` 和 `SVN_SELCHANGED` 通知消息处理函数传递的附加数据为被点击或者当前高亮的列表项句柄。 + +`SVN_SELCHANGING` 通知消息处理函数传递的附加数据为先前高亮的列表项句柄。 + +## 1.4 编程实例 + +__清单 1.1__ 中的代码演示了使用滚动型控件来构造一个简单的联系人列表程序的方法。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `scrollview.c` 程序。 + +__清单 1.1__ 滚动型控件示例程序 + +```c +#define IDC_SCROLLVIEW 100 +#define IDC_BT 200 +#define IDC_BT2 300 +#define IDC_BT3 400 +#define IDC_BT4 500 + +static HWND hScrollView; + +static const char *people[] = +{ + "Peter Wang", + "Michael Li", + "Eric Liang", + "Hellen Zhang", + "Tomas Zhao", + "William Sun", + "Alex Zhang" +}; + +static void myDrawItem (HWND hWnd, HSVITEM hsvi, HDC hdc, RECT *rcDraw) +{ + const char *name = (const char*)ScrollView_get_item_adddata (hsvi); + + SetBkMode (hdc, BM_TRANSPARENT); + SetTextColor (hdc, PIXEL_black); + + if (ScrollView_is_item_hilight(hWnd, hsvi)) { + SetBrushColor (hdc, PIXEL_blue); + FillBox (hdc, rcDraw->left+1, rcDraw->top+1, RECTWP(rcDraw)-2, RECTHP(rcDraw)-1); + SetBkColor (hdc, PIXEL_blue); + SetTextColor (hdc, PIXEL_lightwhite); + } + + Rectangle (hdc, rcDraw->left, rcDraw->top, rcDraw->right - 1, rcDraw->bottom); + TextOut (hdc, rcDraw->left + 3, rcDraw->top + 2, name); +} + +static int myCmpItem (HSVITEM hsvi1, HSVITEM hsvi2) +{ + const char *name1 = (const char*)ScrollView_get_item_adddata (hsvi1); + const char *name2 = (const char*)ScrollView_get_item_adddata (hsvi2); + + return strcmp (name1, name2); +} + +static int +BookProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + + switch (message) + { + + case MSG_INITDIALOG: + { + SVITEMINFO svii; + static int i = 0; + + hScrollView = GetDlgItem (hDlg, IDC_SCROLLVIEW); + SetWindowBkColor (hScrollView, PIXEL_lightwhite); + + SendMessage (hScrollView, SVM_SETITEMCMP, 0, (LPARAM)myCmpItem); + SendMessage (hScrollView, SVM_SETITEMDRAW, 0, (LPARAM)myDrawItem); + + for (i = 0; i < TABLESIZE(people); i++) { + svii.nItemHeight = 32; + svii.addData = (DWORD)people[i]; + svii.nItem = i; + SendMessage (hScrollView, SVM_ADDITEM, 0, (LPARAM)&svii); + } + break; + } + + case MSG_COMMAND: + { + int id = LOWORD (wParam); + int code = HIWORD (wParam); + + switch (id) { + case IDC_SCROLLVIEW: + if (code == SVN_CLICKED) { + int sel; + sel = SendMessage (hScrollView, SVM_GETCURSEL, 0, 0); + InvalidateRect (hScrollView, NULL, TRUE); + } + break; + + } + break; + } + + case MSG_CLOSE: + { + EndDialog (hDlg, 0); + return 0; + } + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +static CTRLDATA CtrlBook[] = +{ + { + "ScrollView", + WS_BORDER | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | + SVS_AUTOSORT, + 10, 10, 320, 150, + IDC_SCROLLVIEW, + "", + 0 + }, +}; + +static DLGTEMPLATE DlgBook = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 0, 0, 350, 200, + "My Friends", + 0, 0, + TABLESIZE(CtrlBook), NULL, + 0 +}; +``` + +该程序把联系人以列表的形式显示出来,并且按名字进行了排序。 + +![联系人列表](figures/Part4Chapter12-1.2.jpeg) +__图 1.2__ 联系人列表 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter13-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter13-zh.md new file mode 100644 index 0000000..b87e8ca --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter13-zh.md @@ -0,0 +1,311 @@ +# 31 树型控件 + +树型控件(treeview)以树型的方式显示一系列的分层次的项,每个项(子项)可以包括一个或多个子项。每项或子项包括文字标题和可选的图标,用户可以通过点击该项来展开或折叠该项中的子项。树型控件比较适合用来表示具有从属关系的对象,例如,文件、目录结构,或者某一机构的组织情况。 + +使用 `CTRL_TREEVIEW` 作为控件类名称,可以通过 `CreateWindow` 函数调用创建树型控件。 + +我们在创建树型控件之后,可以通过发送相应的消息来添加、删除、设置、获取和查找节点项。 + +## 1.1 树型控件风格 + +树型控件的风格控制控件的外观。你可以在创建控件时指定最初的控件风格,还可以在之后使用 `GetWindowStyle` 获取其风格和用 `SetWindowStyle` 设置新的风格。 + +带有 `TVS_WITHICON` 风格的树型控件使用图标来显示每项的折叠和展开状态,相应的图标可以在创建节点项时指定。如果没有为某个节点项指定特定的图标的话,树型控件将使用 MiniGUI 配置文件 `MiniGUI.cfg` 中指定的 “treefold” 和 “treeunfold” 图标。没有 `TVS_WITHICON` 风格的树型控件使用一个带方框的 “+” 号来表示一个折叠的节点项,用带方框的 “-” 号来表示展开的节点项。 + +`TVS_SORT` 风格的树型控件将对节点项进行自动排序。 + +带有 `TVS_NOTIFY` 风格的树型控件将在响应用户操作时产生相应的通知消息和通知码。 + +>【注意】在 MiniGUI 3.0 中,`TreeView` 控件的 `TVS_ICONFORSELECT` 风格被取消;使用此风格对 `TreeView` 控件无任何影响。 + +## 1.2 树型控件消息 + +### 1.2.1 节点项的创建和删除 + +树型控件由根节点和一系列的子节点构成。我们可以在使用 `CreateWindow` 函数创建树型控件时,通过该函数的 `dwAddData` 参数把一个 `TVITEMINFO` 类型的结构指针传递给树型控件来指定根节点的属性。具体例子可参见 31.4 中的编程实例。`TVITEMINFO` 结构中包含了(根)节点的属性信息。 + +```c +typedef struct _TVITEMINFO +{ + /* 该项的文字标题 */ + char *text; + + /* 该项的状态标志 */ + DWORD dwFlags; + + /* 折叠项的图标句柄 */ + HICON hIconFold; + /* 展开项的图标句柄 */ + HICON hIconUnfold; + + /* 该项的附加数据 */ + DWORD dwAddData; +} TVITEMINFO; + +``` + +`text` 为节点的标题,如果创建树型控件时不指定根节点的属性,根节点的标题将为“root”。 + +`dwFlags` 为节点项的状态标志,`TVIF_SELECTED` 表明该节点是被选择的项,`TVIF_FOLD` 表明该节点项初始是折叠的。在添加节点时,只有 `TVIF_FOLD` 标志是可用的。 + +`hIconFold` 和 `hIconUnfold` 分别是节点折叠和展开时所使用的用户自定义图标句柄。使用这两项只有在树型控件为 `TVS_WITHICON` 风格时才是有意义的。 + +`TVM_ADDITEM`(`TVM_INSERTITEM`)消息往树型控件中插入一个节点项: + +```c +TVITEMINFO tvItemInfo; +GHANDLE item; +item = SendMessage (hTrvWnd, TVM_ADDITEM, 0, (LPARAM) &tvItemInfo); +``` + +`item` 为 `SendMessage` 函数返回的代表所添加节点的句柄值,之后我们需要使用该句柄来操作该节点项。 + +`TVM_DELTREE` 消息删除一个节点及其所有子项(包括子项的子项): + +```c +SendMessage (hTrvWnd, TVM_DELTREE, (WPARAM)item, 0); +``` + +`item` 是一个 `GHANDLE` 类型的句柄值,该值应该是使用 `TVM_ADDITEM` 消息添加该节点时 `SendMessage` 函数返回的句柄值。 + +### 1.2.2 节点项属性的设置和获取 + +`TVM_GETITEMINFO` 消息用来获取某个节点项的属性信息: + +```c +TVITEMINFO tvii; +GHANDLE item; +SendMessage (hTrvWnd, TVM_GETITEMINFO, (WPARAM)item, (LPARAM)&tvii); +``` + +`item` 是所要获取信息的节点项的句柄,`tvii` 结构用来存放所获取的节点项属性信息。使用时我们要注意,`tvii` 结构中的 `text` 所指向的字符缓冲区应该足够大。 + +`TVM_SETITEMINFO` 消息用来设置某个节点项的属性: + +```c +TVITEMINFO tvii; +GHANDLE item; +SendMessage (hTrvWnd, TVM_SETITEMINFO, (WPARAM)item, (LPARAM)&tvii); +``` + +`item` 是所要设置的节点项的句柄,`tvii` 结构用包含了所要设置的节点项属性信息。 + +`TVM_GETITEMTEXT` 消息获取某个节点项的文字标题: + +```c +char *buffer; +SendMessage (hTrvWnd, TVM_GETITEMTEXT, (WPARAM)item, (LPARAM)buffer); +``` + +`buffer` 字符缓冲区应该足够大以存放节点项的文字标题。 + +节点项的文字标题的长度可以用 `TVM_GETITEMTEXTLEN` 消息来获取: + +```c +int len; +len = SendMessage (hTrvWnd, TVM_GETITEMTEXTLEN, (WPARAM)item, 0); +``` + +### 1.2.3 选择和查找节点项 + +`TVM_SETSELITEM` 消息用来选择某个节点项: + +```c +GHANDLE item; +SendMessage (hTrvWnd, TVM_SETSELITEM, (WPARAM)item, 0); +``` + +`item` 为所要选择的节点项句柄。 + +`TVM_GETSELITEM` 消息获取当前被选择的节点项: + +```c +GHANDLE item; +item = SendMessage (hTrvWnd, TVM_GETSELITEM, 0, 0); +``` + +`item` 为当前被选择的节点项的句柄。 + +`TVM_GETROOT` 消息用来获取树型控件的根节点: + +```c +GHANDLE rootItem; +rootItem = SendMessage (hTrvWnd, TVM_GETROOT, 0, 0); +``` + +`TVM_GETRELATEDITEM` 消息用来获取指定节点的相关节点项: + +```c +GHANDLE item; +int related; +GHANDLE relItem; +relItem = SendMessage (hTrvWnd, TVM_GETRELATEDITEM, related, (LPARAM)item); +``` + +`item` 为指定的节点,`related` 可以是如下值: + +- `TVIR_PARENT`:获取 `item` 节点的父节点 +- `TVIR_FIRSTCHILD`:获取 `item` 节点的第一个子节点 +- `TVIR_NEXTSIBLING`:获取 `item` 节点的下一个兄弟节点 +- `TVIR_PREVSIBLING`:获取 `item` 节点的前一个兄弟节点 + +`SendMessage` 函数返回所获取的节点项的句柄值。 + +`TVM_SEARCHITEM` 消息用来查找某个特定的节点项: + +```c +GHANDLE itemRoot; +const char *text; +GHANDLE found; +found = SendMessage (hTrvWnd, TVM_SEARCHITEM, (WPARAM) itemRoot, (LPARAM) text); +``` + +`itemRoot` 所指定的节点树就是查找的范围,`text` 所指的字符串就是查找的内容。如果查找成功,`SendMessage` 函数将返回查找到的节点项的句柄。如果失败则返回0。 + +`TVM_FINDCHILD` 消息用来查找节点项的特定子节点: + +```c +GHANDLE itemParent; +const char *text; +GHANDLE found; +found = SendMessage (hTrvWnd, TVM_FINDCHILD, (WPARAM) itemParent, (LPARAM) text); +``` + +`itemParent` 所指定的节点的子节点是所要查找的节点范围,`text` 所指的字符串就是查找的内容。如果查找成功,`SendMessage` 函数将返回查找到的节点项的句柄。如果失败则返回0。 + +`TVM_FINDCHILD` 和 `TVM_SEARCHITEM` 消息的不同之处在于:`TVM_FINDCHILD` 只在子节点中查找,而 `TVM_SEARCHITEM` 在整个节点树中查找。 + +### 1.2.4 比较和排序 + +`TVS_SORT` 风格的树型控件对节点项进行自动排序。应用程序在使用 `TVM_ADDITEM` 消息添加节点项时,如果控件没有 `TVS_SORT` 风格,各项按添加的先后顺序排列;如果有 `TVS_SORT` 风格,各项按字符串比较次序排列。 + +字符串的排列次序由树型控件的字符串比较函数确定。初始的字符串比较函数为 `strncmp`,应用程序可以通过 `TVM_SETSTRCMPFUNC` 消息来设置新的树型控件字符串比较函数: + +```c +SendMessage (hTrvWnd, TVM_SETSTRCMPFUNC, 0, (LPARAM)str_cmp); +``` + +`str_cmp` 为 `STRCMP` 类型的函数指针: + +```c +typedef int (*STRCMP) (const char* s1, const char* s2, size_t n); +``` + +该字符串比较函数比较字符串 s1 和 s2 的最多 n 个字符,并根据比较结果返回一个小于 0、等于 0 或大于 0 的整数。 + +## 1.3 树型控件的通知码 + +树型控件在响应用户点击等操作和发生某些状态改变时会产生通知消息,包括: + +- `TVN_SELCHANGE`:当前选择的节点项发生改变 +- `TVN_DBLCLK`:用户双击节点项 +- `TVN_SETFOCUS`:树型控件获得焦点 +- `TVN_KILLFOCUS`:树型控件失去焦点 +- `TVN_CLICKED`:用户单击节点项 +- `TVN_ENTER`:用户按下回车键 +- `TVN_FOLDED`:节点项被折叠 +- `TVN_UNFOLDED`:节点项被展开 + +如果应用程序需要了解树型控件产生的通知码的话,需要使用 `SetNotificationCallback` 函数注册一个通知消息处理函数,在该函数中对收到的各个通知码进行应用程序所需的处理。 + +## 1.4 编程实例 + +__清单 1.1__ 中的代码演示了树型控件的使用。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `treeview.c` 程序。 + +__清单 1.1__ 树型控件示例程序 + +```c +#define IDC_TREEVIEW 100 + +#define CHAPTER_NUM 5 + +/* 定义树型控件条目使用的文本 */ +static const char *chapter[] = +{ + "第十六章 树型控件", + "第十七章 列表型控件", + "第十八章 月历控件", + "第十九章 旋钮控件", + "第二十章 酷工具栏控件", +}; + +/* 定义树型控件条目使用的文本 */ +static const char *section[] = +{ + "控件风格", + "控件消息", + "控件通知码" +}; + +static int BookProc(HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITDIALOG: + { + TVITEMINFO tvItemInfo; + int item; + int i, j; + + /* 向树型控件中添加条目 */ + for (i = 0; i < CHAPTER_NUM; i++) { + tvItemInfo.text = (char*)chapter[i]; + item = SendMessage (GetDlgItem(hDlg, IDC_TREEVIEW), TVM_ADDITEM, + 0, (LPARAM)&tvItemInfo); + /* 向每个条目中添加子条目 */ + for (j = 0; j < 3; j++) { + tvItemInfo.text = (char*)section[j]; + SendMessage (GetDlgItem(hDlg, IDC_TREEVIEW), TVM_ADDITEM, + item, (LPARAM)&tvItemInfo); + } + } + } + break; + + case MSG_CLOSE: + EndDialog (hDlg, 0); + return 0; + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +static TVITEMINFO bookInfo = +{ + "MiniGUI编程指南" +}; + +/* 对话框模板 */ +static DLGTEMPLATE DlgBook = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 100, 100, 320, 240, + #ifdef _LANG_ZHCN + "目录", + #else + "Book Content", + #endif + 0, 0, + 1, NULL, + 0 +}; + +/* 这个对话框中只有一个控件:树型控件 */ +static CTRLDATA CtrlBook[] = +{ + { + CTRL_TREEVIEW, + WS_BORDER | WS_CHILD | WS_VISIBLE | + WS_VSCROLL | WS_HSCROLL, + 10, 10, 280, 180, + IDC_TREEVIEW, + "treeview control", + (DWORD)&bookInfo + } +}; +``` + +![书目录的树型显示](figures/Part4Chapter13-1.1.jpeg) +__图 1.1__ 书目录的树型显示 + +`treeview.c` 程序用树型控件来显示书目录结构。程序在用对话框模版创建对话框时指定树型控件数据结构 `CTRLDATA的dwAddData` 项为 `&bookInfo`,`bookInfo` 是一个 `TVITEMINFO` 类型的结构,其中给出了节点的标题为“MiniGUI编程指南”,因此所创建的树型控件的根节点标题就是“MiniGUI编程指南”。 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter14-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter14-zh.md new file mode 100644 index 0000000..84605d3 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter14-zh.md @@ -0,0 +1,671 @@ +# 列表型控件 + +列表型控件(listview)以列表的方式显示一系列的数据项(列表项),每个列表项的内容可以由一个或多个子项构成,不同列表项的相同类型子项以列的方式组织,列表型控件的表头(header)内容通常反映了列表项不同子项的意义。外观上,列表型控件就是一个包括表头部分和列表项部分的矩形框。可以通过拖动表头来调整列表型控件中各个子项的列宽,列表中显示不下的内容可以通过滚动条来滚动显示。 + +对于包括多个属性的数据项而言,列表型控件是一种方便和有效的数据项排列和展示工具。例如,列表型控件经常用来作为文件浏览框,它可以在一个区域内显示包括文件名、文件类型、大小和修改日期在内的诸多文件属性。 + +你可以通过调用 `CreateWindow` 函数,使用控件类名称 `CTRL_LISTVIEW`,来创建一个列表型控件。应用程序通常通过向一个列表型控件发送消息来增加、删除、排列和操作列表项。和别的控件一样,列表型控件在响应用户点击等操作时会产生通知消息。 + +## 1.1 列表型控件风格 + +默认状态下,列表型控件窗口只显示表头和列表项,显示区域的周围没有边界。你可以在以 `CreateWindow` 函数创建控件时使用窗口风格标识号 `WS_BORDER` 来给列表型控件加上边界。另外,还可以使用窗口风格 `WS_VSCROLL` 和 `WS_HSCROLL` 来增加垂直和水平滚动条,以便用鼠标来滚动显示列表型控件中的各项内容。 + +`LVS_TREEVIEW` 风格的列表型控件支持以树型的方式来显示列表项,也就是说,该风格的列表型控件合并了普通列表型控件和树型控件的功能。 + +`LVS_UPNOTIFY` 风格指定列表型控件的在响应用户鼠标点击操作时的响应方式。默认情况下,如果没有指定 `LVS_UPNOTIFY` 风格,列表型控件将在鼠标按下时发出通知消息;如果指定了该风格,控件将在鼠标抬起时发出通知消息。 + +## 1.2 列表型控件消息 + +### 1.2.1 列的操作 + +在创建一个列表型控件之后,下一步通常需要往该控件中添加一列或依次添加多列,这是由应用程序向控件发送 `LVM_ADDCOLUMN` 消息来完成的: + +```c +LVCOLUMN p; +SendMessage (hwndListView, LVM_ADDCOLUMN, 0, (LPARAM)&p) ; +``` + +其中,`p` 是一个 `LVCOLUMN` 结构,其中包含了列表型控件中新增的列的相关信息。`LVCOLUMN` 结构定义以及各项意义如下: + +```c +typedef struct _LVCOLUMN +{ + /* 新增列的位置 */ + int nCols; + /* 列宽 */ + int width; + /* 列的标题 */ + char *pszHeadText; + /* 列标题的最大长度 */ + int nTextMax; + /* 列表头的图象 */ + DWORD image; + /* 用于列排序的比较函数 */ + PFNLVCOMPARE pfnCompare; + /* 列标志 */ + DWORD colFlags; +} LVCOLUMN; +typedef LVCOLUMN *PLVCOLUMN; +``` + +`LVCOLUMN` 结构用于创建或操作列表型控件的列,和 `LVM_ADDCOLUMN`、`LVM_GETCOLUMN`、`LVM_SETCOLUMN` 及 `LVM_MODIFYHEAD` 等消息一块使用。 + +在使用于 `LVM_ADDCOLUMN` 消息时,`LVCOLUMN` 结构中需要至少给出 `pszHeadText` 项的值,也就是列的标题,其它项可以置为 0,这时列表型控件将采用这些项的缺省值。 + +`nCols` 项是一个整数值,指明新增列是第几列,列次序从 1 开始。如果 `nCols` 值为 0 或超出列次序的范围,新增的列将添加为列表型控件的最后一列。`width` 项为新增列的列宽度,如果不指定该值或为 0,新增列的宽度将采用缺省值。`nTextMax` 项在用于添加列时可以忽略。 + +`image` 项是一个位图或图标句柄,如果指定该项值的话,所指的图像将显示在列的头部。该项目前还没有起作用。 + +`pfnCompare` 指向一个 `PFNLVCOMPARE` 类型的函数,该函数就是新增列所附的比较函数。当用户点击该列的标题时,列表型控件将根据该比较函数来确定各个列表项的次序。如果不指定列的比较函数的话,列表型控件将采用默认的字符串比较函数。 + +```c +typedef int (*PFNLVCOMPARE) (int nItem1, int nItem2, PLVSORTDATA sortData); +``` + +`nItem1` 和 `nItem2` 为整数值,是所比较的两个列表项的索引值。`sortData` 项目前没有意义。比较函数根据传递给它的两个列表项索引值来确定比较结果,比较的依据通常和比较函数所附的列的含义相关,而该含义是由应用程序定义的。比较时很可能还需要除列表项索引值之外的其它数据,一种常用和可行的做法是:在添加列表项时添上对比较函数有用的附加数据项,然后在比较函数中获取该项,进行处理。 + +`colFlags` 项为列的标志,目前有标题对齐标志:`LVCF_LEFTALIGN`、`LVCF_RIGHTALIGN` 和 `LVCF_CENTERALIGN`,分别表示列中文字左对齐、右对齐和居中对齐。 + +在创建列之后,还可以通过 `LVM_SETCOLUMN` 消息来设置和修改列的各项属性: + +```c +LVCOLUMN p; +SendMessage (hwndListView, LVM_SETCOLUMN, 0, (LPARAM)&p); +``` + +`p` 也是一个 `LVCOLUMN` 结构,各项意义及要求和 `LVM_ADDCOLUMN` 消息中的参数 `p` 相同。 + +LVM_MODIFYHEAD 消息是 LVM_SETCOLUMN 的简化,可以用来设置列表头的标题: + +```c +LVCOLUMN p; +SendMessage (hwndListView, LVM_MODIFYHEAD, 0, (LPARAM)&p) ; +``` + +`p` 同样是一个 `LVCOLUMN` 结构,不过只需给出 `nCols` 和 `pszHeadText` 项的值。 + +`LVM_GETCOLUMN` 消息用来获取列表型控件中某一列的属性: + +```c +LVCOLUMN p; +int nCols; +SendMessage (hwndListView, LVM_GETCOLUMN, nCols, (LPARAM)&p) ; +``` + +`nCols` 是所要获取信息的列的整数索引值,`p` 是一个 `LVCOLUMN` 结构,该结构用来存放所获取的列相关属性。 + +`LVM_GETCOLUMNWIDTH` 消息用来获取某列的宽度: + +```c +int width; +int nCols; +width = SendMessage (hwndListView, LVM_GETCOLUMNWIDTH, nCols, 0) ; +``` + +`nCols` 是所要获取信息的列的整数索引值,`SendMessage` 函数的返回值就是列的宽度,出错的话则返回 -1。 + +`LVM_GETCOLUMNCOUNT` 消息用来获取列表型控件中列的数量: + +```c +int count; +count = SendMessage (hwndListView, LVM_GETCOLUMNCOUNT, 0, 0) ; +``` + +`SendMessage` 函数的返回值就是列的数量。 + +`LVM_DELCOLUMN` 消息用来删除列表型控件中的一列: + +```c +int nCols; +SendMessage (hwndListView, LVM_DELCOLUMN, nCols, 0) ; +``` + +`nCols` 为所要删除的列的索引值。 + +`LVM_SETHEADHEIGHT` 用来设置列表头的高度: + +```c +int newHeight; +SendMessage (hwndListView, LVM_SETHEADHEIGHT, newHeight, 0) ; +``` + +`newHeight` 为新的表头高度值。 + +### 1.2.2 列表项操作 + +列表型控件由许多纵向排列的列表项组成,每个列表项由列分为多个子项,列表项可以包含特定的应用程序定义的附加数据。应用程序可以通过发送相应的消息来添加、修改和设置、删除列表项或获取列表项的属性信息。 + +在使用 `CreateWindow` 函数创建一个列表型控件之后,控件中还没有任何条目,需要通过 `LVM_ADDITEM` 消息来往列表型控件中添加列表项: + +```c +HLVITEM hItem; +HLVITEM hParent; +LVITEM lvItem; +hItem = SendMessage (hwndListView, LVM_ADDITEM, hParent, (LPARAM)&lvItem) ; +``` + +`hParent` 指定了新增列表项的父节点,`hParent` 为 0 表示把该节点添加到根节点下(最顶层)。如果该控件是普通的列表型控件,`hParent` 为 0 即可。 + +`lvItem` 是一个 `LVITEM` 类型的结构,其中包含了列表型控件中新增的列表项的相关信息。`LVITEM` 结构定义以及各项意义如下: + +```c +typedef struct _LVCOLUMN +{ + /* 新增列的位置 */ + int nCols; + /* 列宽 */ + int width; + /* 列的标题 */ + char *pszHeadText; + /* 列标题的最大长度 */ + int nTextMax; + /* 列表头的图象 */ + DWORD image; + /* 用于列排序的比较函数 */ + PFNLVCOMPARE pfnCompare; + /* 列标志 */ + DWORD colFlags; +} LVCOLUMN; +typedef LVCOLUMN *PLVCOLUMN; +``` + +`nItem` 为新增列表项的位置值,如果该值超出索引值范围的话,新增的项将被添加到列表的最后。如果 `LVM_ADDITEM` 消息的 `wParam` 参数指定了新添节点的父节点,`nItem` 项所表示的位置值指的是新添节点在父节点中的位置。`itemData` 项用于保存列表项的附加数据,该数据的含义由应用程序定义。 + +`LVM_ADDITEM` 消息的返回值为新增列表项的句柄,该句柄可以在其它访问列表型的消息中使用。 + +通过 `LVM_ADDITEM` 消息新增的列表项中还没有内容,需要用 `LVM_FILLSUBITEM` 或 `LVM_SETSUBITEM` 消息来设置列表项中各个子项的内容。 + +`LVM_GETITEM` 消息用来获取一个列表项的信息: + +```c +LVITEM lvItem; +HLVITEM hItem; +SendMessage (hwndListView, LVM_GETITEM, hItem, (LPARAM)&lvItem) ; +``` + +`hItem` 为目标列表型的句柄。`lvItem` 是 `LVITEM` 类型的结构,该结构用来保存所获取的列表项信息;如果 `hItem` 为 0,`lvItem` 结构的 `nItem` 域应该预设为所要获取的列表项的索引值。 + +`LVM_GETITEMCOUNT` 消息用来获取列表型控件的列表项数量: + +```c +int count; +count = SendMessage (hwndListView, LVM_GETITEMCOUNT, 0, 0) ; +``` + +`SendMessage` 函数的返回值就是列表型控件的列表项数量。 + +`LVM_GETITEMADDDATA` 消息用来获取列表项的附加数据: + +```c +DWORD addData; +int nItem; +HLVITEM hItem; +addData = SendMessage (hwndListView, LVM_GETITEMADDDATA, nItem, hItem) ; +``` + +`hItem` 为所要获取的列表项的句柄;如果 `hItem` 为 0,`nItem` 指定所要获取的列表项的索引值。`SendMessage` 函数返回列表项的附加数据。 + +`LVM_SETITEMADDDATA` 消息设置列表项的附加数据: + +```c +HLVITEM hItem; +DWORD addData; +SendMessage (hwndListView, LVM_GETITEMADDDATA, hItem, (LPARAM)addData) ; +``` + +`hItem` 为所要设置的列表项的句柄。`addData` 为附加数据。如果设置成功,`SendMessage` 返回 `LV_OKAY`,否则返回 `LV_ERR`。 + +`LVM_SETITEMHEIGHT` 消息可以用来设置一个列表型控件的列表项高度。如果不设置的话,列表型控件的列表项高度将采用缺省值。 + +```c +HLVITEM hItem; +int newHeight; +SendMessage (hwndListView, LVM_SETITEMHEIGHT, hItem, newHeight) ; +``` + +`hItem` 为所要设置的列表项的句柄。`newHeight` 为列表项新的高度值。如果设置成功,`SendMessage` 函数返回 `TRUE`,否则返回 `FALSE`。 + +`LVM_DELITEM` 消息用来在列表型控件中删除一个列表项,`LVM_DELALLITEM` 消息删除所有的列表项: + +```c +HLVITEM hItem; +int nItem; +SendMessage (hwndListView, LVM_DELITEM, nItem, hItem) ; +SendMessage (hwndListView, LVM_DELALLITEM, 0, 0) ; +``` + +`hItem` 为所要删除的列表项的句柄;如果 `hItem` 为 0,`nItem` 指定所要删除的列表项的索引值。 + +每个列表项包括一个或多个子项,子项的数目和列表型控件的列数相同。一个子项中包括字符串和图像,可以使用 `LVM_SETSUBITEM`、`LVM_SETSUBITEMTEXT` 和 `LVM_SETSUBITEMCOLOR` 和 `LVM_GETSUBITEMTEXT` 等消息来获取和设置子项的属性。 + +`LVM_SETSUBITEM`(`LVM_FILLSUBITEM`)消息用来设置子项的各项属性: + +```c +LVSUBITEM subItem; +HLVITEM hItem; +SendMessage (hwndListView, LVM_SETSUBITEM, hItem, (LPARAM)&subItem) ; +``` + +`hItem` 为所要设置的列表项的句柄。`subItem` 是 `LVSUBITEM` 类型的结构,其中包含了创建一个子项所需的相关信息。 + +```c +typedef struct _LVSUBITEM +{ + /* 子项的标志 */ + DWORD flags; + /* 子项的垂直索引值 */ + int nItem; + /* 子项的水平索引值 */ + int subItem; + /* 子项的文字内容 */ + char *pszText; + /* 子项的文字长度 */ + int nTextMax; + /* 子项的文字颜色 */ + int nTextColor; + /* 子项的图像 */ + DWORD image; +} LVSUBITEM; +typedef LVSUBITEM *PLVSUBITEM; +``` + +`flags` 为子项的标志值,目前包括:`LVFLAG_BITMAP` 和 `LVFLAG_ICON`,如果子项中要显示位图或图标的话,应该把相应的标志置上,如:`flags |= LVFLAG_BITMAP`。 + +`nItem` 和 `subItem` 分别为所设置子项的垂直索引值和水平索引值,也就是行和列的位置;如果 `LVM_SETSUBITEM` 消息的 `wParam` 参数指定了目标列表项的句柄的话,`nItem` 将被忽略。`pszText` 为子项中所要显示的文字内容。`nTextMax` 为子项的最大文字长度,用于 `LVM_SETSUBITEM` 消息时可以忽略。`LVSUBITEM` 结构用于获取子项信息时,`pszText` 指向存放文字内容的缓存区,`nTextMax` 指明该缓冲区的大小。 + +`nTextColor` 指定子项的文字颜色,我们也可以另外使用 `LVM_SETSUBITEMCOLOR` 消息来设置子项的文字颜色。`image` 指定要在子项中显示的位图或图标,该值只有在 `flags` 项置上 `LVFLAG_BITMAP` 或 `LVFLAG_ICON` 标志时才起作用。 + +`LVM_GETSUBITEMTEXT` 和 `LVM_SETSUBITEMTEXT` 消息分别用来获取和设置子项的文字内容,`LVM_GETSUBITEMLEN` 消息获取子项字符串的长度: + +```c +LVSUBITEM subItem; +HLVITEM hItem; +int len; + +SendMessage (hwndListView, LVM_GETSUBITEMTEXT, hItem, (LPARAM)&subItem) ; +SendMessage (hwndListView, LVM_SETSUBITEMTEXT, hItem, (LPARAM)&subItem) ; +len = SendMessage (hwndListView, LVM_GETSUBITEMLEN, hItem, (LPARAM)&subItem) ; +``` + +### 1.2.3 选择、显示和查找列表项 + +`LVM_SELECTITEM` 消息用来选择一个列表项,被选中的项将高亮显示。需要注意的是,被选中的项并不一定是可见的。 + +```c +int nItem; +HLVITEM hItem; + +SendMessage (hwndListView, LVM_SELECTITEM, nItem, hItem) ; +``` + +`hItem` 为所要选择的列表项的句柄;如果 `hItem` 为 0,`nItem` 指定所要选择的列表项的索引值。 + +`LVM_GETSELECTEDITEM` 消息用来确定当前被选中的列表项: + +```c +HLVITEM hItemSelected; +hItemSelected = SendMessage (hwndListView, LVM_GETSELECTEDITEM, 0, 0) ; +``` + +`SendMessage` 函数返回列表型控件当前被选中的列表项的句柄。如果没有被选中的项,则返回0。 + +`LVM_SHOWITEM` 消息使一个列表项在列表型控件中成为可见的条目。使一个列表项可见并不会使之被选中。 + +```c +HLVITEM hItem; +int nItem; +SendMessage (hwndListView, LVM_SHOWITEM, nItem, hItem) ; +``` + +`hItem` 为所要显示的列表项的句柄。如果 `hItem` 为 0,`nItem` 指定所要显示的列表项的索引值。如果所要显示的条目原来是不可见的或不完全可见的,那么在使用 `LVM_SHOWITEM` 消息之后,它将成为可见区域中的第一个或最后一个可见条目,而且是完全可见的。 + +`LVM_CHOOSEITEM` 是 `LVM_SELECTIEM` 和 `LVM_SHOWITEM` 功能的组合,它使一个列表项被选中而且成为可见的项。 + +```c +int nItem; +HHLVITEM hItem; +SendMessage (hwndListView, LVM_CHOOSEITEM, nItem, hItem) ; +``` + +`hItem` 为所要选择和显示的列表项的句柄;如果 `hItem` 为 0,`nItem` 指定所要选择和显示的列表项的索引值。 + +`LVM_FINDITEM` 消息用于在列表型控件中查找一个特定的列表项。如果查找成功的话,`SendMessage` 返回查找到的列表项的句柄。 + +```c +HLVITEM hFound; +HLVITEM hParent; +LVFINDINFO findInfo; +hFound = SendMessage (hwndListView, LVM_FINDITEM, hParent, (LPARAM)&findInfo) ; +``` + +`hParent` 指定查找的目标节点树的根节点。`findInfo` 是 `LVFINDINFO` 类型的结构,其中包含了查找时所需要的信息。 + +```c +typedef struct _LVFINDINFO +{ + /* 查找标志 */ + DWORD flags; + /* 查找的开始索引 */ + int iStart; + /* pszInfo项包含几列的文字内容 */ + int nCols; + /* 查找的多个子项文字内容 */ + char **pszInfo; + /* 列表项附加数据 */ + DWORD addData; + + /** The found item's row, reserved */ + int nItem; + /** The found subitem's column, reserved */ + int nSubitem; + +} LVFINDINFO; +typedef LVFINDINFO *PLVFINDINFO; + +``` + +`flags` 项为查找标志,可以是 `LVFF_TEXT` 和(或)`LVFF_ADDDATA`,表示根据列表项的子项文字和(或)附加数据查找。如果 `LVM_FINDITEM` 消息的 `wParam` 参数指定的查找根节点 `hParent` 为 0,`iStart` 为查找的开始索引值,如果是 0 的话就从头查找。 + +`pszInfo` 指向所要查找的多个字符串,`nCols` 的值表示匹配的列表项中前 `nCols` 列子项的文字内容要和 `pszInfo` 中的字符串一致。如果根据附加数据查找的话,`addData` 项应包含所要查找的附加数据。 + +### 1.2.4 比较和排序 + +普通列表型控件具有比较和排序功能。列表型控件在使用 `LVM_ADDITEM` 消息添加列表项之后,各项是按添加的先后顺序和添加时指定的索引排列的。在用户点击列表型控件的表头也就是列标题时,控件将根据该列所附的比较函数确定列表项的次序,并进行排序。我们在前面已经说过,在使用 `LVM_ADDCOLUMN` 消息添加列时,可以指定所添加列的比较函数;此后还可以通过 `LVM_SETCOLUMN` 函数来设置新的比较函数。 + +我们还可以通过向列表型控件发送 `LVM_SORTIEMS` 消息来使之对列表项进行排序: + +```c +SendMessage (hwndListView, LVM_SORTITEMS, 0, (LPARAM)pfnCompare) ; +``` + +`pfnCompare` 指向一个 `PFNLVCOMPARE` 类型的函数,该函数就是列表项排序此时所依据的比较函数,由应用程序定义。 + +此外,我们还可以通过发送 `LVM_COLSORT` 消息来使列表型控件依据某列来进行比较排序: + +```c +int nCol; +SendMessage (hwndListView, LVM_COLSORT, nCol, 0) ; +``` + +`nCol` 参数是指定排序时所依据的列索引,列表型控件将依据该参数所指定的列所附的比较函数进行比较和排序。 + +在没有指定比较函数时,列表型控件使用默认字符串比较函数进行排序。初始的字符串比较函数为 `strcasecmp`,我们可以通过 `LVM_SETSTRCMPFUNC` 消息来设置自定义的字符串比较函数。 + +```c +SendMessage (hwndListView, LVM_SETSTRCMPFUNC, 0, (LPARAM)pfnStrCmp) ; +``` + +`pfnStrCmp` 为 `STRCMP` 类型的函数指针: + +```c +typedef int (*STRCMP) (const char* s1, const char* s2, size_t n); +``` + +该字符串比较函数比较字符串 s1 和 s2 的最多 n 个字符,并根据比较结果返回一个小于 0、等于 0 或大于 0 的整数。 + +### 1.2.5 树型节点的操作 + +我们可以对具有 `LVS_TREEVIEW` 风格的列表型控件进行一些树型节点的操作,包括获取相关节点和折叠一个节点。 + +`LVM_GETRELATEDITEM` 消息用来获取一个节点的相关树型节点,如父节点,兄弟节点和第一个子节点等。 + +```c +int related; +HLVITEM hItem; +HLVITEM hRelatedItem; +hRelatedItem = SendMessage (hwndListView, LVM_GETRELATEDITEM, related, hItem) ; +``` + +`related` 指定相关节点和目标节点的关系,包括: + +- `LVIR_PARENT`:获取父节点 +- `LVIR_FIRSTCHILD`:获取第一个子节点 +- `LVIR_NEXTSIBLING`:获取下一个兄弟节点 +- `LVIR_PREVSIBLING`:获取前一个兄弟节点 + +`hItem` 为目标节点的句柄。`LVM_GETRELATEDITEM` 消息返回所获取到的相关节点的句柄。 + +`LVM_FOLDITEM` 消息用来折叠或者展开一个包含子节点的节点项。 + +```c +HLVITEM hItem; +BOOL bFold; +SendMessage (hwndListView, LVM_FOLDITEM, bFold, hItem) ; +``` + +`bFold` 为 `TRUE` 的话折叠节点项,否则展开节点项。`hItem` 为目标节点的句柄。 + +## 1.3 其它消息的处理 + +当用户按上下箭头键时,当前被选中的列表项将发生变化,前移或后移一项,而且新的选中项将变为可见(如果它原来不可见的话)。当用户按上下翻页键时,列表项将进行翻页,幅度和点击滚动条翻页是一样的,前一页的最后一项成为后一页的第一项。如果按下 `HOME` 键,第一个列表项将被选中且变为可见;如果按下 `END` 键,最后一个列表项将被选中且成为可见。 + +## 1.4 列表型控件通知码 + +列表型控件在响应用户点击等操作和发生一些状态改变时会产生通知消息,包括: + +- `LVN_ITEMRDOWN`:用户鼠标右键在列表项上按下 +- `LVN_ITEMRUP`:用户鼠标右键在列表项上抬起 +- `LVN_HEADRDOWN`:用户鼠标右键在表头上按下 +- `LVN_HEADRUP`:用户鼠标右键在表头上抬起 +- `LVN_KEYDOWN`:键按下 +- `LVN_ITEMDBCLK`:用户双击某个列表项 +- `LVN_ITEMCLK`:用户单击某个列表项(保留) +- `LVN_SELCHANGE`:当前选择的列表项改变 +- `LVN_FOLDED`:用户鼠标点击某个列表项,使之折叠 +- `LVN_UNFOLDED`:用户鼠标点击某个列表项,使之展开 + +当用户鼠标右键在列表项上按下时,该项将被选中,并且产生 `LVN_SELCHANGE` 和 `LVN_ITEMRDOWN` 两个通知码。 + +如果应用程序需要了解列表型控件产生的通知码的话,需要使用 `SetNotificationCallback` 函数注册一个通知消息处理函数,在该函数中对收到的各个通知码进行应用程序所需的处理。 + +## 1.5 编程实例 + +__清单 1.1__ 中的代码演示了列表型控件的使用。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `listview.c` 程序。 + +__清单 1.1__ 列表型控件示例程序 + +```c +#define IDC_LISTVIEW 10 +#define IDC_CTRL1 20 +#define IDC_CTRL2 30 + + +#define SUB_NUM 3 + +static char * caption [] = +{ + "姓名", "语文", "数学", "英语" +}; + +#define COL_NR TABLESIZE(caption) + +static char *classes [] = +{ + "1班", "2班", "3班" +}; + +typedef struct _SCORE +{ + char *name; + int scr[SUB_NUM]; +} SCORE; + +static SCORE scores[] = +{ + {"小明", {81, 96, 75}}, + {"小强", {98, 62, 84}}, + {"小亮", {79, 88, 89}}, + {"小力", {79, 88, 89}}, +}; +#define SCORE_NUM TABLESIZE(scores) + +static GHANDLE add_class_item (HWND hlist, PLVITEM lvItem, GHANDLE classent) +{ + LVSUBITEM subdata; + GHANDLE item = SendMessage (hlist, LVM_ADDITEM, classent, (LPARAM)lvItem); + + subdata.nItem = lvItem->nItem; + subdata.subItem = 0; + subdata.pszText = classes[lvItem->nItem];; + subdata.nTextColor = 0; + subdata.flags = 0; + subdata.image = 0; + SendMessage (hlist, LVM_SETSUBITEM, item, (LPARAM) & subdata); + + return item; +} + +static GHANDLE add_score_item (HWND hlist, PLVITEM lvItem, GHANDLE classent) +{ + char buff[20]; + LVSUBITEM subdata; + GHANDLE item = SendMessage (hlist, LVM_ADDITEM, classent, (LPARAM)lvItem); + int i = lvItem->nItem; + int j; + + subdata.flags = 0; + subdata.image = 0; + subdata.nItem = lvItem->nItem; + + for (j = 0; j < 4; j ++) { + + subdata.subItem = j; + if (j == 0) { + subdata.pszText = scores[i].name; + subdata.nTextColor = 0; + } + else { + sprintf (buff, "%d", scores[i].scr[j-1]); + subdata.pszText = buff; + if (scores[i].scr[j-1] > 90) + subdata.nTextColor = PIXEL_red; + else + subdata.nTextColor = 0; + } + SendMessage (hlist, LVM_SETSUBITEM, item, (LPARAM) & subdata); + + } + + return item; +} + +static int +ScoreProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + HWND hListView; + hListView = GetDlgItem (hDlg, IDC_LISTVIEW); + + switch (message) + { + + case MSG_INITDIALOG: + { + int i, j; + LVITEM item; + LVCOLUMN lvcol; + GHANDLE hitem; + + for (i = 0; i < COL_NR; i++) { + lvcol.nCols = i; + lvcol.pszHeadText = caption[i]; + lvcol.width = 120; + lvcol.pfnCompare = NULL; + lvcol.colFlags = 0; + SendMessage (hListView, LVM_ADDCOLUMN, 0, (LPARAM) &lvcol); + } + + item.nItemHeight = 25; + + SendMessage (hListView, MSG_FREEZECTRL, TRUE, 0); + hitem = 0; + for (i = 0; i < 3; i++) { + item.nItem = i; + hitem = add_class_item (hListView, &item, 0); + + for (j = 0; j < SCORE_NUM; j++) { + item.nItem = j; + add_score_item (hListView, &item, hitem); + } + + } + + SendMessage (hListView, MSG_FREEZECTRL, FALSE, 0); + break; + } + + case MSG_COMMAND: + { + int id = LOWORD (wParam); + int i, j; + + if (id == IDC_CTRL2) { + float average = 0; + char buff[20]; + for (i = 0; i < SCORE_NUM; i++) { + for (j = 0; j < SUB_NUM; j++) { + average += scores[i].scr[j]; + } + } + average = average / (SCORE_NUM * SUB_NUM); + + sprintf (buff, "%4.1f", average); + SendDlgItemMessage (hDlg, IDC_CTRL1, MSG_SETTEXT, 0, (LPARAM)buff); + } + break; + } + + case MSG_CLOSE: + { + EndDialog (hDlg, 0); + break; + } + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +static CTRLDATA CtrlScore[] = +{ + { + "button", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + 80, 260, 80, 20, + IDC_CTRL2, + "求总平均分", + 0 + }, + { + "edit", + WS_CHILD | WS_VISIBLE | WS_BORDER, + 10, 260, 50, 20, + IDC_CTRL1, + "", + 0 + }, + { + "listview", + WS_BORDER | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | LVS_TREEVIEW, + 10, 10, 320, 220, + IDC_LISTVIEW, + "score table", + 0 + }, +}; + +static DLGTEMPLATE DlgScore = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 0, 0, 480, 340, + "求平均分", + 0, 0, + 0, NULL, + 0 +}; +``` + +![列表型控件的使用](figures/Part4Chapter14-1.1.jpeg) +__图 1.1__ 列表型控件的使用 + +`listview.c` 程序在对话框中创建一个列表型控件用于显示学生的各门功课分数,点击下面的按钮可以求学生各门功课的总平均分。 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter15-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter15-zh.md new file mode 100644 index 0000000..e8d7a09 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter15-zh.md @@ -0,0 +1,259 @@ +# 月历控件 + +月历控件(monthcalendar)提供一个类似日历的用户界面,使用户可以方便的选择和设置日期。应用程序可以通过向月历控件发送消息来获取和设置日期。 + +你可以通过调用`CreateWindow`函数,使用控件类名称 `CTRL_MONTHCALENDAR`,来创建一个月历控件。 + +## 1.1 月历控件风格 + +月历控件可以用中文或英文等多种格式显示星期和月份等日期信息,这可以通过指定控件的风格为 `MCS_CHN`、`MCS_ENG_L` 或 `MCS_ENG_S` 来完成。如果月历控件风格中包括 `MCS_CHN` 的话,控件以中文显示日期信息;如果包括 `MCS_ENG_L`,以英文显示日期信息;如果包括 `MCS_ENG_S` 的话,将以简写的英文格式显示。 + +如果风格中包括 `MCS_NOTIFY` 的话,月历控件将在响应用户操作时等情况下产生相应的通知消息。 + +## 1.2 月历控件消息 + +### 1.2.1 获取日期 + +`MCM_GETCURDAY` 消息用来获取当前选择的日期中是当月中的第几天: + +```c +int day; +day = SendMessage (hwndMonthcal, MCM_GETCURDAY, 0, 0) ; +``` + +`SendMessage` 函数返回值就是当前的天数。 + +`MCM_GETCURMONTH` 消息用来获取当前选择的日期中的月份值: + +```c +int month; +month = SendMessage (hwndMonthcal, MCM_GETCURMONTH, 0, 0) ; +``` + +`SendMessage` 函数返回值就是当前的月份。 + +`MCM_GETCURYEAR` 消息用来获取当前选择日期中的年份: + +```c +int year; +year = SendMessage (hwndMonthcal, MCM_GETCURYEAR, 0, 0) ; +``` + +`SendMessage` 函数返回值就是当前的年份。 + +`MCM_GETCURMONLEN` 消息用来获取当前月份的长度(该月有几天): + +```c +int monthlen; +monthlen = SendMessage (hwndMonthcal, MCM_GETCURMONLEN, 0, 0) ; +``` + +`SendMessage` 函数返回值就是当前月份的长度。 + +`MCM_GETFIRSTWEEKDAY` 消息用来确定当前月份中的第一天是星期几: + +```c +int weekday; +weekday = SendMessage (hwndMonthcal, MCM_GETFIRSTWEEKDAY, 0, 0) ; +``` + +`SendMessage` 函数返回值就是当前月份第一天的星期号,从 0 到 6,0 表示星期天。 + +`MCM_GETCURDATE` 消息获取月历控件中当前选择的日期: + +```c +SYSTEMTIME systime; +SendMessage (hwndMonthcal, MCM_GETCURDATE, 0, (LPARAM)&systime) ; +``` + +`systime` 是一个 `SYSTEMTIME` 类型的结构,存放获取的年、月、日和星期几等日期信息。该结构还用于 `MCM_GETTODAY` 等消息,其结构定义如下: + +```c +typedef struct _SYSTEMTIME +{ + /* 年 */ + int year; + /* 月 */ + int month; + /* 日 */ + int day; + /* 星期几 */ + int weekday; +} SYSTEMTIME; +typedef SYSTEMTIME *PSYSTEMTIME; +``` + +`MCM_GETTODAY` 消息获取“今天”的日期: + +```c +SYSTEMTIME systime; +SendMessage (hwndMonthcal, MCM_GETTODAY, 0, (LPARAM)&systime) ; +``` + +`systime` 也是一个 `SYSTEMTIME` 类型的结构。 + +### 1.2.2 设置日期 + +需要注意的是,在 Linux 系统中,设置日期可能需要特殊用户身份(如 root)。 + +`MCM_SETCURDAY` 消息设置当前选择的“天”,`MCM_SETCURMONTH` 消息设置当前的月,`MCM_SETCURYEAR` 消息设置当前的年: + +```c +int day; +int month; +int year; +SendMessage (hwndMonthcal, MCM_SETCURDAY, day, 0) ; +SendMessage (hwndMonthcal, MCM_SETCURMONTH, month, 0) ; +SendMessage (hwndMonthcal, MCM_SETCURYEAR, year, 0) ; +``` + +`day`、`month` 和 `year` 分别指定新的天、月和年,如果这些值在合理的值范围之外,控件将采用最接近的一天、月或年。 + +`MCM_SETCURDATE` 消息设置当前选择的日期: + +```c +SYSTEMTIME systime; +SendMessage (hwndMonthcal, MCM_SETCURDATE, 0, (LPARAM)&systime) ; +``` + +`MCM_SETTODAY` 把“今天”设为当前选择的日期: + +```c +SendMessage (hwndMonthcal, MCM_SETTODAY, 0, 0) ; +``` + +### 1.2.3 调整颜色 + +应用程序可以通过 `MCM_GETCOLOR` 和 `MCM_SETCOLOR` 消息来获取和改变月历控件中各部分的颜色设置: + +```c +MCCOLORINFO color; +SendMessage (hwndMonthcal, MCM_GETCOLOR, 0, (LPARAM)&color) ; +SendMessage (hwndMonthcal, MCM_SETCOLOR, 0, (LPARAM)&color) ; +``` + +`color` 是一个 `MCCOLORINFO` 类型的结构,用于保存颜色信息。 + +```c +typedef struct _MCCOLORINFO +{ + /* 标题的背景色 */ + int clr_titlebk; + /* 标题的文字颜色 */ + int clr_titletext; + /* 年和月箭头的颜色 */ + int clr_arrow; + /* 箭头高亮时背景色 */ + int clr_arrowHibk; + + /* 星期标题背景色 */ + int clr_weekcaptbk; + /* 星期标题文字颜色 */ + int clr_weekcapttext; + + /* 天数部分背景色 */ + int clr_daybk; + /* 天数部分高亮时背景色 */ + int clr_dayHibk; + /* 天数部分文字颜色 */ + int clr_daytext; + /* 非当前月部分天数文字颜色 */ + int clr_trailingtext; + /* 高亮的文字颜色 */ + int clr_dayHitext; +} MCCOLORINFO; +``` + +### 1.2.4 控件大小 + +为了能够正常显示其中的内容,月历控件有一个窗口最小限制值,`MCM_GETMINREQRECTW` 消息和 `MCM_GETMINREQRECTH` 消息分别用来获取最小宽度和最小高度值: + +```c +int minw, minh; +minw = SendMessage (hwndMonthcal, MCM_GETMINREQRECTW, 0, 0) ; +minh = SendMessage (hwndMonthcal, MCM_GETMINREQRECTH, 0, 0) ; +``` + +`SendMessage` 函数的返回值就是最小宽度和高度值。 + +## 1.3 月历控件通知码 + +当用户点击月历控件并造成当前日期发生改变时,控件将产生 `MCN_DATECHANGE` 通知码。 + +## 1.4 编程实例 + +__清单 1.1__ 中的代码演示了月历控件的使用。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `monthcal.c` 程序。 + +__清单 1.1__ 月历控件示例程序 + +```c +#define IDC_MC 100 +#define IDC_OK 200 + +/* 对话框模板:只有两个控件:月历控件和“确定”按钮 */ +static CTRLDATA CtrlTime[]= +{ + { + "monthcalendar", + WS_CHILD | WS_VISIBLE | MCS_NOTIFY | MCS_CHN, + 10, 10, 240, 180, + IDC_MC, + "", + 0 + }, + { + "button", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + 260, 180, 50, 22, + IDC_OK, + "确定", + 0 + } +}; + +static DLGTEMPLATE DlgTime = +{ + WS_VISIBLE | WS_CAPTION | WS_BORDER, + WS_EX_NONE, + 0, 0, 320, 240, + "约会时间", + 0, 0, + 2, CtrlTime, + 0 +}; + +static int TimeWinProc(HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_INITDIALOG: + break; + + case MSG_COMMAND: + { + int id = LOWORD(wParam); + if (id == IDC_OK) { + char info[100]; + SYSTEMTIME date; + /* 获取月历控件中的当前日期 */ + SendMessage (GetDlgItem(hDlg, IDC_MC), MCM_GETCURDATE, 0, (LPARAM)&date); + sprintf (info, "你定于%d年%d月%d日会见总统!", + date.year, date.month, date.day); + MessageBox (hDlg, info, "约会", MB_OK | MB_ICONINFORMATION); + EndDialog (hDlg, 0); + } + } + break; + + case MSG_CLOSE: + { + EndDialog (hDlg, 0); + } + return 0; + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} +``` + +![月历控件的使用](figures/Part4Chapter15-1.1.jpeg) +__图 1.1__ 月历控件的使用 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter16-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter16-zh.md new file mode 100644 index 0000000..6a99c52 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter16-zh.md @@ -0,0 +1,219 @@ +# 旋钮控件 + +本章描述的旋钮控件(spinbox)使用户可以从一组预定义的值中进行选择。旋钮控件的界面包括上下两个箭头,用户通过点击箭头进行滚动选择。 + +你可以通过调用 `CreateWindow` 函数,使用控件类名称 `CTRL_SPINBOX` 来创建一个旋钮控件。 + +需要注意的是,旋钮控件的窗口大小尺寸是固定的,也就是说,你在使用 `CreateWindow` 函数时传递的窗口宽度和高度参数将不起作用。 + +## 1.1 旋钮控件风格 + +旋钮控件目前具有的唯一风格是 `SPS_AUTOSCROLL`。该风格的旋钮控件可以自动判断旋钮控件目前的滚动状态,在滚动到最大值和最小值时分别把向上和向下箭头禁止掉(变灰)。没有该风格的旋钮控件的滚动状态由应用程序掌握。 + +## 1.2 旋钮控件消息 + +### 1.2.1 设置和获取位置属性 + +我们通常在创建旋钮控件之后,通过向它发送 `SPM_SETINFO` 消息来设置控件的属性和状态。当然在使用过程中,我们还可以使用该消息来重新设置控件的属性。 + +```c +SPININFO spinfo; +SendMessage (hwndSpinBox, SPM_SETINFO, 0, (LPARAM)&spinfo) ; +``` + +上述代码中,`spinfo` 是一个 `SPININFO` 类型的结构,其定义如下: + +```c +typedef struct _SPININFO +{ + /* 最大位置值 */ + int max; + /* 最小位置值 */ + int min; + /* 当前位置值 */ + int cur; +} SPININFO; +typedef SPININFO *PSPININFO; +``` + +`SPININFO` 结构中的各项分别给出了旋钮控件的最大位置值、最小位置值和当前位置值。对于具有 `SPS_AUTOSCROLL` 风格的旋钮控件而言,必须满足如下条件:最大位置值 >= 当前位置值 >= 最小位置值。 + +`SPM_GETINFO` 消息用来获取旋钮控件的属性。 + +```c +SPININFO spinfo; +SendMessage (hwndSpinBox, SPM_GETINFO, 0, (LPARAM)&spinfo) ; +``` + +`spinfo` 结构用来存放所获取的属性值。 + +`SPM_SETCUR` 消息用来设置旋钮控件的当前位置值。 + +```c +int cur; +SendMessage (hwndSpinBox, SPM_SETCUR, cur, 0) ; +``` + +`cur` 值就是所要设置的旋钮控件当前位置值。具有 `SPS_AUTOSCROLL` 风格的旋钮控件 `cur` 值应在最大值和最小值之间,否则将设置失败,`SendMessage` 返回 -1。 + +`SPM_GETCUR` 消息获取当前的位置值。 + +```c +int cur; +cur = SendMessage (hwndSpinBox, SPM_GETCUR, 0, 0) ; +``` + +### 1.2.2 禁止和恢复 + +`SPM_DISABLEDOWN`、`SPM_ENABLEDOWN`、`SPM_DISABLEUP` 和 `SPM_ENABLEUP` 分别用来禁止和恢复上下箭头的滚动能力,而不管这时旋钮控件的当前位置值是否达到最大或最小。这几个消息仅对没有 `SPS_AUTOSCROLL` 风格的旋钮控件有效,具有 `SPS_AUTOSCROLL` 风格的旋钮控件的箭头的滚动能力和状态是由控件自己控制的。 + +```c +SendMessage (hwndSpinBox, SPM_DISABLEDOWN, 0, 0) ; +SendMessage (hwndSpinBox, SPM_ENABLEDOWN, 0, 0) ; +SendMessage (hwndSpinBox, SPM_DISABLEUP, 0, 0) ; +SendMessage (hwndSpinBox, SPM_ENABLEUP, 0, 0) ; +``` + +### 1.2.3 目标窗口 + +`SPM_SETTARGET` 消息设置旋钮控件的目标窗口。 + +```c +HWND hTarget; +SendMessage (hwndSpinBox, SPM_SETTARGET, 0, (LPARAM)hTarget) ; +``` + +用户点击旋钮控件的上下箭头时,旋钮控件将向它的目标窗口发送 `MSG_KEYDOWN` 和 `MSG_KEYUP` 消息,`wParam` 参数为 `SCANCODE_CURSORBLOCKUP`(点击上箭头时)或 `SCANCODE_CURSORBLOCKDOWN`(点击下箭头时),`lParam` 参数将设置 `KS_SPINPOST` 标志位,指明该消息来自旋钮控件。 + +`SPM_GETTARGET` 消息获取旋钮控件的目标窗口。 + +```c +HWND hTarget; +hTarget = SendMessage (hwndSpinBox, SPM_SETTARGET, 0, 0) ; +``` + +## 1.3 旋钮控件通知码 + +旋钮控件在大于等于最大位置时将产生 `SPN_REACHMAX`,小于等于最小位置时将产生 `SPN_REACHMIN` 通知码。 + +## 1.4 编程实例 + +__清单 1.1__ 中的代码演示了旋钮控件的使用。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `spinbox.c` 程序。 + +__清单 1.1__ 旋钮控件示例程序 + +```c +#define IDC_SPIN 10 +#define IDC_CTRL1 20 +#define IDC_CTRL2 30 +#define IDC_CTRL3 40 +#define IDC_CTRL4 50 + +static int +SpinProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + SPININFO spinfo; + HWND hSpin = GetDlgItem (hDlg, IDC_SPIN); + + switch (message) { + case MSG_INITDIALOG: + { + /* 设定旋钮控件范围和当前值 */ + spinfo.min = 1; + spinfo.max = 10; + spinfo.cur = 1; + SendMessage (hSpin, SPM_SETTARGET, 0, (LPARAM)hDlg); + SendMessage (hSpin, SPM_SETINFO, 0, (LPARAM)&spinfo); + } + break; + + case MSG_KEYDOWN: + { + /* 处理按键消息,包括来自按钮控件的模拟按键消息 */ + if (wParam == SCANCODE_CURSORBLOCKUP || + wParam == SCANCODE_CURSORBLOCKDOWN) { + if (!(lParam & KS_SPINPOST)) { + int cur; + cur = SendMessage (hSpin, SPM_GETCUR, 0, 0); + if (wParam == SCANCODE_CURSORBLOCKUP) + cur --; + else + cur ++; + SendMessage (hSpin, SPM_SETCUR, cur, 0); + } + /* 重绘窗口 */ + InvalidateRect (hDlg, NULL, TRUE); + } + } + break; + + case MSG_PAINT: + { + HDC hdc; + int x, y, w, h; + int cur; + + cur = SendMessage (hSpin, SPM_GETCUR, 0, (LPARAM)&spinfo); + x = 10; + y = cur*10; + w = 60; + h = 10; + if (y < 10) + y = 10; + else if (y > 100) + y = 100; + + /* 绘制窗口,反映当前的旋钮控件位置 */ + hdc = BeginPaint (hDlg); + MoveTo (hdc, 2, 10); + LineTo (hdc, 100, 10); + Rectangle (hdc, x, y, x+w, y+h); + SetBrushColor (hdc, PIXEL_black); + FillBox (hdc, x, y, w, h); + MoveTo (hdc, 2, 110); + LineTo (hdc, 100, 110); + EndPaint (hDlg, hdc); + } + break; + + case MSG_CLOSE: + { + EndDialog (hDlg, 0); + } + break; + + } + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +/* 对话框模板 */ +static DLGTEMPLATE DlgSpin = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 100, 100, 320, 240, + "spinbox and black block", + 0, 0, + 1, NULL, + 0 +}; + +/* 该对话框只有一个控件:旋钮控件 */ +static CTRLDATA CtrlSpin[] = +{ + { + CTRL_SPINBOX, + SPS_AUTOSCROLL | WS_BORDER | WS_CHILD | WS_VISIBLE, + 200, 120, 0, 0, + IDC_SPIN, + "", + 0 + } +}; +``` + +![旋钮控件的使用](figures/Part4Chapter16-1.1.jpeg) +__图 1.1__ 旋钮控件的使用 + +`spinbox.c` 程序在对话框中创建了一个具有 `SPS_AUTOSCROLL` 风格的旋钮控件,用户可以通过点击该旋钮控件的上下箭头来操纵左上方的黑方块在上下两根黑线间移动。 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter17-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter17-zh.md new file mode 100644 index 0000000..ed08d35 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter17-zh.md @@ -0,0 +1,176 @@ +# 酷工具栏 + +酷工具栏(coolbar)是一个可以显示一排文字或图标按钮的工具栏。它很简单,易于使用。 + +你可以通过调用 `CreateWindow` 函数,使用控件类名称 CTRL_COOLBAR 来创建一个酷工具栏控件。 + +## 1.1 酷工具栏风格 + +`CBS_BMP_16X16` 和 `CBS_BMP_32X32` 风格的酷工具栏的按钮项分别显示 16x16 和 32x32 的位图;`CBS_BMP_CUSTOM` 风格的酷工具栏的按钮项使用自定义大小的位图,对于这个风格的控件,使用 `CreateWindow` 创建时需要通过 `dwAddData` 参数把位图的高度和宽度传递给控件,如下: + +```c +CreateWindowEx (CTRL_COOLBAR, ..., MAKELONG (item_width, item_height))); +``` + +`CBS_USEBKBMP` 风格的酷工具栏有背景位图,创建控件时需要把位图文件的路径通过 `CreateWindow` 函数的 `spCaption` 参数传递给控件。 + +```c +CreateWindowEx (CTRL_COOLBAR, “res/bk.bmp”, ...); +``` + +建立酷工具栏不接受指定的高度值。 + +## 1.2 酷工具栏消息 + +在创建酷工具栏之后,我们需要使用 `CBM_ADDITEM` 消息来往工具栏中添加按钮项。 + +```c +COOLBARITEMINFO itemInfo; +SendMessage (hwndCoolBar, CBM_ADDITEM, 0, (LPARAM)&itemInfo) ; +``` + +`itemInfo` 是一个 `COOLBARITEMINFO` 类型的结构。 + +```c +typedef struct _COOLBARITEMINFO +{ + /* 保留 */ + int insPos; + /* 按钮项id */ + int id; + /* 按钮项类型 */ + int ItemType; + /* 按钮使用的位图 */ + PBITMAP Bmp; + /* 按钮提示文字 */ + const char *ItemHint; + /* 按钮标题 */ + const char *Caption; + /* 按钮项的附加数据 */ + DWORD dwAddData; +} COOLBARITEMINFO; +``` + +`id` 项为工具栏中各按钮项的 `id` 值。用户点击按钮项时,酷工具栏将产生通知消息,`wParam` 参数的高字节部分(`HIWORD`)就是相应按钮项的 `id` 值,`wParam` 的低字节部分(`LOWORD`)为工具栏控件本身的控件标识符。 + +`ItemType` 指定按钮项的类型,值可以是 `TYPE_BARITEM`、`TYPE_BMPITEM` 和 `TYPE_TEXTITEM`。`TYPE_BARITEM` 类型的按钮项为垂直分隔线;`TYPE_BMPITEM` 类型的按钮项为位图按钮;`TYPE_TEXTITEM` 类型的按钮项为文字按钮。 + +如果按钮项的类型为 `TYPE_BMPITEM `的话,`Bmp` 位图句柄指定该按钮项所使用的位图。`ItemHint` 为鼠标移动到按钮项之上时所显示的提示文字;按钮项的类型为 `TPYE_TEXTITEM` 时,`Caption` 中应该存放按钮项所显示的文字字符串。 + +`dwAddData` 为按钮项的附加数据。 + +`CBM_ENABLE` 消息禁止或恢复某个按钮项。 + +```c +int id; +BOOL beEnabled; +SendMessage (hwndCoolBar, CBM_ENABLE, id, beEnabled) ; +``` + +`id` 为所要设置的按钮项的标示符值,`beEnabled` 为 `TRUE` 时恢复,为 `FALSE` 时禁止。 + +## 1.3 编程实例 + +__清单 1.1__ 中的代码演示了酷工具栏控件的使用。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `coolbar.c` 程序。 + +__清单 1.1__ 酷工具栏示例程序 + +```c +#define ITEM_NUM 10 + +/* 要在酷工具栏上显示的文本 */ +static const char* caption[] = +{ + "0", "1", "2", "3", "4", "5","6", "7", "8", "9" +}; + +/* 提示窗口中的文本 */ +static const char* hint[] = +{ + "数字 0", "数字 1", "数字 2", "数字 3", "数字 4", + "数字 5", "数字 6", "数字 7", "数字 8", "数字 9" +}; + +/* 创建酷工具栏,并添加工具项 */ +static void create_coolbar (HWND hWnd) +{ + HWND cb; + COOLBARITEMINFO item; + int i; + + cb = CreateWindow (CTRL_COOLBAR, + "", + WS_CHILD | WS_VISIBLE | WS_BORDER, + 100, + 10, 100, 100, 20, + hWnd, + 0); + + item.ItemType = TYPE_TEXTITEM; + item.Bmp = NULL; + item.dwAddData = 0; + for (i = 0; i < ITEM_NUM; i++) { + item.insPos = i; + item.id = i; + item.Caption = caption[i]; + item.ItemHint = hint[i]; + SendMessage (cb, CBM_ADDITEM, 0, (LPARAM)&item); + } +} + +static int CoolbarWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam) +{ + static HWND ed; + + switch (message) { + case MSG_CREATE: + /* 创建编辑框,用来反馈用户对酷工具栏的操作 */ + ed = CreateWindow (CTRL_EDIT, + "", + WS_CHILD | WS_VISIBLE | WS_BORDER, + 200, + 10, 10, 100, 20, + hWnd, + 0); + + create_coolbar (hWnd); + break; + + case MSG_COMMAND: + { + int id = LOWORD (wParam); + int code = HIWORD (wParam); + + if (id == 100) { + static char buffer[100]; + char buf[2]; + + /* 根据用户按下的工具项将适当的字符写入编辑框 */ + sprintf (buf, "%d", code); + SendMessage (ed, MSG_GETTEXT, 90, (LPARAM)buffer); + strcat (buffer, buf); + SendMessage (ed, MSG_SETTEXT, 0, (LPARAM)buffer); + } + } + break; + + case MSG_DESTROY: + DestroyAllControls (hWnd); + return 0; + + case MSG_CLOSE: + DestroyMainWindow (hWnd); + PostQuitMessage (hWnd); + return 0; + } + + return DefaultMainWinProc(hWnd, message, wParam, lParam); +} + +/* 以下创建主窗口的代码省略 */ +``` + +![酷工具栏控件的使用](figures/Part4Chapter17-1.1.jpeg) +__图 1.1__ 酷工具栏控件的使用 + +`coolbar.c` 程序在对话框中创建了一个由“0-9”数字组成的酷工具栏,点击该工具栏的按钮时,对应的数字将输入到上面的编辑框中。 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter18-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter18-zh.md new file mode 100644 index 0000000..35a46d8 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter18-zh.md @@ -0,0 +1,183 @@ +# 动画控件 + +动画控件(animation)是一个可以显示动画的控件;它很简单,易于使用。 + +你可以通过调用 `CreateWindow` 函数,使用控件类名称 `CTRL_ANIMATION` 来创建一个动画控件。 + +## 1.1 `ANIMATION` 对象 + +在创建动画控件之前,你必须首先创建一个 `ANIMATION` 对象。该对象其实是一个链表结构,链表的每个节点表示动画对象的一帧图象。ANIMATION 对象由下面两个结构表示: + +```c +/** Animation frame structure. */ +typedef struct _ANIMATIONFRAME +{ + /** The disposal method (from GIF89a specification): + * Indicates the way in which the graphic is to be treated after being displayed. + * - 0\n No disposal specified. The decoder is not required to take any action. + * - 1\n Do not dispose. The graphic is to be left in place. + * - 2\n Restore to background color. The area used by the frame must be restored to + * the background color. + * - 3\n Restore to previous. The decoder is required to restore the area overwritten by + * the frmae with what was there prior to rendering the frame. + */ + int disposal; + /** The x-coordinate of top-left corner of the frame in whole animation screen. */ + int off_x; + /** The y-coordinate of top-left corner of the frame in whole animation screen. */ + int off_y; + /** The width of the frame. */ + unsigned int width; + /** The height of the frame. */ + unsigned int height; + + /** The time of the frame will be display, in the unit of animation time_unit. */ + unsigned int delay_time; + #ifdef _USE_NEWGAL + /** The memdc compatible with the gif image. */ + HDC mem_dc; + /** The bits of the mem_dc, should be freed after deleting the mem_dc. */ + Uint8* bits; + #else + /** The bitmap of the frame. */ + BITMAP bmp; + #endif + + /** The next frame */ + struct _ANIMATIONFRAME* next; + /** The previous frame */ + struct _ANIMATIONFRAME* prev; +} ANIMATIONFRAME; + +/** Animation structure */ +typedef struct _ANIMATION +{ + /** The width of the animation. */ + unsigned int width; + /** The height of the animation. */ + unsigned int height; + + /** The background color */ + RGB bk; + + /** The number of all frames. */ + int nr_frames; + /** + * The unit of the time will be used count the delay time of every frame. + * The default is 1, equal to 10ms. + */ + int time_unit; + /** Pointer to the animation frame.*/ + ANIMATIONFRAME* frames; +} ANIMATION; +``` + +`ANIMATION` 结构描述的是动画对象的全局属性,包括动画的宽度和高度,动画帧的个数,用来表示延迟的时间单位(取 1 时表示 10ms),以及指向动画帧链表的头指针。 + +`ANIMATIONFRAME` 结构表示单个的动画帧,包括有如下信息: + +- 当前动画帧在全局动画中的偏移量及帧的宽度及高度。因为一幅动画帧相对于上一帧可能只会修改部分图象信息,因此,在帧结构中仅包含需要修改的部分将大大降低帧的数据量。 +- 当前帧的延迟时间。以 `ANIMATION` 对象中的 `time_unit` 为单位计算的当前帧播放时间。 +- 当前帧的图象信息。当使用 `NEWGAL` 接口时,该图象用内存 `DC` 表示;否则用 `BITMAP` 对象表示。 + +应用程序可以自行构建 `ANIMATION` 对象,亦可调用下面的函数直接从 `GIF98a` 的图象文件中创建 `ANIMATION` 对象: + +```c +ANIMATION* CreateAnimationFromGIF89a (HDC hdc, MG_RWops* area); +ANIMATION* CreateAnimationFromGIF89aFile (HDC hdc, const char* file); +ANIMATION* CreateAnimationFromGIF89aMem (HDC hdc, const void* mem, int size); +``` + +上述函数将表示 `GIF89a` 数据的数据源(`area`)中读取动画 `GIF` 的图象信息,然后创建一个 `ANIMATION `对象。 + +应用程序创建了 `ANIMATION` 对象之后,既可以自行显示,亦可创建动画控件显示动画。在调用 `CreateWindow` 函数创建动画控件时,可将创建好的 `ANIMATION` 对象传递给动画控件,动画控件将使用该 `ANIMATION` 对象自动播放动画。下面的代码段从一个 `gif` 文件中创建了 `ANIMATION` 对象,然后利用该对象建立了动画控件: + +```c +ANIMATION* anim = CreateAnimationFromGIF89aFile (HDC_SCREEN, "banner.gif"); +if (anim == NULL) +return 1; + +CreateWindow (CTRL_ANIMATION, +"", +WS_VISIBLE | ANS_AUTOLOOP, +100, +10, 10, 300, 200, hWnd, (DWORD)anim); +``` + +注意在调用 `CreateWindow` 函数时,可将 `ANIMATION` 对象指针通过 `dwAddData` 参数传入动画控件。 + +## 1.2 动画控件风格 + +目前,动画控件的风格有如下三个: + +- `ANS_AUTOLOOP`:使用该风格之后,动画控件将自动重复播放动画。 +- `ANS_SCALED`:根据控件大小缩放动画对象。 +- `ANS_FITTOANI`:根据动画对象大小调整控件尺寸。 + +## 1.3 动画控件消息 + +动画控件的消息也非常简单,目前有如下几个消息,可用来控制动画控件的播放行为: + +- `ANM_SETANIMATION`:设置 `ANIMATION` 对象。 +- `ANM_GETANIMATION`:获取当前的 `ANIMATION` 对象。 +- `ANM_STARTPLAY`:开始播放。在发送 `ANM_STARTPLAY` 消息给动画控件之前,动画控件将仅仅显示 `ANIMATION` 对象的第一帧图象;只有发送了 `ANM_STARTPLAY` 消息之后,动画控件才会按 `ANIMATION` 对象中的信息播放动画。 +- `ANM_PAUSE_RESUME`:暂停/继续播放。用来暂停动画的播放(正在播放时),或者用来继续动画的播放(已被暂停时)。 +- `ANM_STOPPLAY`:停止动画的播放。动画控件将返回到 `ANIMATION` 的第一帧图象。 + +## 1.4 编程实例 + +__清单 1.1__ 中的代码演示了动画控件的使用。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `animation.c` 程序。 + +__清单 1.1__ 动画控件的使用 + +```c +static int AnimationWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case MSG_CREATE: + { + ANIMATION* anim = CreateAnimationFromGIF89aFile (HDC_SCREEN, "banner.gif"); + if (anim == NULL) + return 1; + + SetWindowAdditionalData (hWnd, (DWORD) anim); + CreateWindow (CTRL_ANIMATION, + "", + WS_VISIBLE | ANS_AUTOLOOP, + 100, + 10, 10, 300, 200, hWnd, (DWORD)anim); + SendMessage (GetDlgItem (hWnd, 100), ANM_STARTPLAY, 0, 0); + + CreateWindow (CTRL_ANIMATION, + "", + WS_VISIBLE | ANS_AUTOLOOP, + 200, + 10, 210, 300, 200, hWnd, (DWORD)anim); + break; + } + + case MSG_LBUTTONDOWN: + SendMessage (GetDlgItem (hWnd, 200), ANM_STARTPLAY, 0, 0); + break; + + case MSG_DESTROY: + DestroyAnimation ((ANIMATION*)GetWindowAdditionalData (hWnd), TRUE); + DestroyAllControls (hWnd); + return 0; + + case MSG_CLOSE: + DestroyMainWindow (hWnd); + PostQuitMessage (hWnd); + return 0; + } + + return DefaultMainWinProc(hWnd, message, wParam, lParam); +} + +/* 以下创建主窗口的代码省略 */ +``` + +![动画控件的使用](figures/Part4Chapter18-1.1.jpeg) +__图 1.1__ 动画控件的使用 + +__清单 1.1__ 中的程序在创建主窗口时装载了当前目录下的 `banner.gif` 文件,并创建了对应的 `ANIMATION` 对象。之后,创建了两个动画控件。第一个动画控件在创建后立即开始播放,第二个动画控件只在用户单击窗口时开始播放。__图 1.1__ 是该示例程序的运行效果图。其中,第一个动画控件显示的是 `banner.gif` 文件的第二帧,第二个动画控件显示的第一帧。 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter19-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter19-zh.md new file mode 100644 index 0000000..b8c4076 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter19-zh.md @@ -0,0 +1,612 @@ +# 网格控件 + +网格控件(gridview)以表格的方式显示一系列的数据项(单元格),每个单元格的内容相互独立,网格控件的表头(header)(包括一列的头和一行的头)内容通常反映了表格一行或者一列的意义。外观上,网格控件就是一个包括表头部分的单元格部分的矩形框。可以通过拖动表头来调整网格控件中行的高度或者列的宽度,表格中显示不下的内容可以通过滚动条来滚动显示。 + +网格控件是一种方便和有效的数据项排列和展示工具。适合处理具有不同属性的大量数据,如实验数据或是账目表格等。 + +你可以通过调用 `CreateWindow` 函数,使用控件类名称 `CTRL_GRIDVIEW` 来创建一个网格控件。应用程序通常通过向一个网格控件发送消息来增加、删除和操作表格项。和别的控件一样,网格控件在响应用户点击等操作时会产生通知消息。 + +## 1.1 网格控件风格 + +默认状态下,网格控件窗口只显示表头和单元格,显示区域的周围没有边界。你可以在以 `CreateWindow` 函数创建控件时使用窗口风格 `WS_BORDER` 来给网格控件加上边界。另外,还可以使用窗口风格 `WS_VSCROLL` 和 `WS_HSCROLL` 来增加垂直和水平滚动条。以便用鼠标来滚动显示网格控件中的各项内容。 + +## 1.2 网格控件消息 + +在创建网格的时候,通过设置一个结构 `GRIDVIEWDATA`,并将这个结构作为参数传递进去。这个结构定义以及各项意义如下: + +```c +typedef struct _GRIDVIEWDATA +{ + /** 网格控件的行数 */ + int nr_rows; + /** 网格控件的列数 */ + int nr_cols; + /** 网格控件中各行之间的高度*/ + int row_height; + /** 网格控件中各列之间的宽度 */ + int col_width; +} GRIDVIEWDATA; +``` + +### 1.2.1 列的操作 + +在生成网格控件以后,如果需要更多的列, 需要往控件中增加列,增加一列由应用程序向控件发送 `GRIDM_ADDCOLUMN` 消息来完成。 + +```c +int index; +GRIDCELLDATA celldata; +GRIDCELLDATAHEADER cellheader; +SendMessage(hWndGrid, GRIDM_ADDCOLUMN, index, &celldata); +``` + +上面的代码中,`celldata` 是一个 `GRIDCELLDATA` 结构,其中包含了网格控件中新增加的列的相关信息。`GRIDCELLDATA` 结构定义及各项意义如下: + +```c +typedef struct _GRIDCELLDATA +{ + /** mask of properties, can be OR'ed with following values: + * 设置/获取网格的类型(必需) + * - GVITEM_STYLE\n + * 设置/获取网格的前景色 + * - GVITEM_FGCOLOR\n + * 设置/获取网格的背景 + * - GVITEM_BGCOLOR\n + * 设置/获取网格的字体 + * - GVITEM_FONT\n + * 设置/获取网格的icon + * - GVITEM_IMAGE\n + * 设置/获取网格的对齐方式 + * - GVITEM_ALLCONTENT\n + * 设置/获取网格的主要内容 + * - GVITEM_MAINCONTENT\n + * 设置/获取网格的全部内容 + * - GVITEM_ALLCONTENT\n + */ + DWORD mask; + /** 网格的类型 */ + DWORD style; + /** 文本颜色 */ + gal_pixel color_fg; + /** 背景色*/ + gal_pixel color_bg; + /** 文本字体 */ + PLOGFONT font; + /** 单元格上显示的icon */ + PBITMAP image; + /** 根据不同类型,需要传入不同的内容 */ + void* content; +}GRIDCELLDATA; +``` + +上述结构中的 `content` 项是指向另外一个结构 `GRIDCELLDATAHEADER` 的地址。这个结构的定义以及各项意义如下: + +```c +typedef struct _GRIDCELLDATAHEADER +{ + /** 列的宽度或行的高度 */ + int size; + /** 行头或列头上的标题 */ + char* buff; + /** 标题的长度 */ + int len_buff; +}GRIDCELLDATAHEADER; +``` + +增加一列的时候,设置 `GRIDCELLDATAHEADE` 结构成员 `size` 为新增加列的宽度,成员 `buff` 为列头的标题,成员 `len_buff` 为标题的长度。增加一行的操作与此类似,不同的是成员 `size` 为新增加行的高度。 + +`GRIDCELLDATA` 结构用于设置网格控件中行,列以及单元格的属性,许多消息都需要用到这个结构体。如 `GRIDM_SETCELLPROPERTY`、`GRIDM_GETCELLPROPERTY`、`GRIDM_ADDROW`,以及 `GRIDM_ADDCOLUMN` 等。 + +`style` 为单元格的类型,每次设置的时候需要指明是下列这几种类型中的一种:`GV_TYPE_HEADER`, `GV_TYPE_TEXT`,`GV_TYPE_NUMBER`,`GV_TYPE_SELECTION`,`GV_TYPE_CHECKBOX`。它还可以和单元格风格类型同时使用,比如 `GVS_READONLY` 等。 + +`content` 还可以指向其它结构,分别有 `GRIDCELLDATATEXT`(文字单元格)、`GRIDCELLDATANUMBER`(数字单元格)、`GRIDCELLDATASELECTION`(组合选择框单元格)、`GRIDCELLDATACHECKBOX`(选择框单元格)。这些结构的定义及各项意义如下: + +```c +typedef struct _GRIDCELLDATATEXT +{ + /** 单元格上显示的文本 */ + char* buff; + /** 文本的长度 */ + int len_buff; +}GRIDCELLDATATEXT; + +typedef struct _GRIDCELLDATANUMBER +{ + /** 单元格上的数字*/ + double number; + /** 数字的显示格式,如”%2f”等 */ + char* format; + /** 显示格式字符串的长度 */ + int len_format; +}GRIDCELLDATANUMBER; + +typedef struct _GRIDCELLDATASELECTION +{ + /** 当前选中的索引 */ + int cur_index; + /** 需要显示的字符串,如"Yes\nNo"*/ + char* selections; + /** 显示字符串的长度,如上面字符串的长度为7 */ + int len_sel; +}GRIDCELLDATASELECTION; + +typedef struct _GRIDCELLDATACHECKBOX +{ + /** 选择框是否被选中 */ + BOOL checked; + /** 选择框后面所跟文字 */ + char* text; + /** 所跟文字的长度 */ + int len_text; +}GRIDCELLDATACHECKBOX; +``` + +`GRIDM_SETCOLWIDTH` 可以设置控件列的宽度: + +```c +int index; +int width; +SendMessage (hwndGrid, GRIDM_SETCOLWIDTH, index, width) ; +``` + +其中,`index` 是要设置的列的整数索引值,`width` 是要设置的宽度。 + +`GRIDM_GETCOLWIDTH` 可以获取控件列的宽度: + +```c +int width; +int index; +width = SendMessage (hwndGrid, GRIDM_GETCOLWIDTH, 0, index); +``` + +其中,`index` 是要获取的列的整数索引值,`SendMessage` 函数的返回值就是列的宽度。出错的话则返回 -1。 + +`GRIDM_ADDCOLUMN` 消息用来增加网格控件的一列: + +```c +int index; +GRIDCELLDATA* celldata; +SendMessage (hwndGrid, GRIDM_ADDCOLUMN, index, celldata) ; +``` + +其中,`index` 是要增加的列的前一列的整数索引值,`celldata` 是一个 `GRIDCELLDATA` 结构的指针,用来设置新增列的初始值。 + +`GRIDM_DELCOLUMN` 消息用来删除网格控件中的一列: + +```c +int index; +SendMessage (hwndGrid, GRIDM_DELCOLUMN, 0, index) ; +``` + +其中,`index` 为所要删除的列的索引值。 + +`GRIDM_GETCOLCOUNT` 消息用来获取网格控件中列的数量。 + +```c +int count; +count = SendMessage (hwndGrid, GRIDM_GETCOLCOUNT, 0, 0) ; +``` + +`SendMessage` 函数的返回值就是列的数量。出错则返回 -1。 + +### 1.2.2 行的操作 + +有关行的操作与列的操作相似。 + +`GRIDM_SETROWHEIGHT` 可以设置控件行的高度: + +```c +int index; +int height; +SendMessage (hwndGrid, GRIDM_SETROWHEIGHT, index, height) ; +``` + +其中,`index` 是要设置的行的整数索引值,`height` 是要设置的高度。 + +`GRIDM_GETROWHEIGHT` 可以获取控件行的高度: + +```c +int height; +int index; +height = SendMessage (hwndGrid, GRIDM_GETROWHEIGHT, 0, index); +``` + +其中,`index` 是要获取的行的整数索引值,`SendMessage` 函数的返回值就是行的高度。出错的话则返回 -1。 + +`GRIDM_ADDROW` 消息用来往网格控件中添加一行: + +```c +int index; +GRIDCELLDATA* celldata; +SendMessage (hwndGrid, GRIDM_ADDROW, index, celldata) ; +``` + +其中,`index` 是要增加的行的前一行的整数索引值,`celldata` 是一个 `GRIDCELLDATA` 结构的指针,用来设置新增行的初始值。 + +`GRIDM_DELROW` 消息用来删除网格控件中的一行: + +```c +int index; +SendMessage (hwndGrid, GRIDM_DELROW, 0, index) ; +``` + +其中,`index` 为所要删除的行的索引值。 + +`GRIDM_GETROWCOUNT` 消息用来获取网格控件中行的数量。 + +```c +int count; +count = SendMessage (hwndGrid, GRIDM_ GETROWCOUNT, 0, 0) ; +``` + +`SendMessage` 函数的返回值就是行的数量。 + +### 1.2.3 单元格的操作 + +`GRIDM_SETCELLPROPERTY` 消息用来设置一个或多个单元格。 + +```c +GRIDCELLS* cells; +GRIDCELLDATA* celldata; +SendMessage (hwndGrid, GRIDM_SETCELLPROPERTY, cells, celldata) ; +``` + +其中,`cells` 是一个指向 `GRIDCELLS` 结构的指针,用来表示所要设置的单元格的范围。`GRIDCELLS` 结构定义以及各项意义如下: + +```c +typedef struct _GRIDCELLS +{ + /** 所选单元格的起始行 */ + int row; + /** 所选单元格的起始列 */ + int column; + /** 所选单元格范围所跨的列数 */ + int width; + /** 所选单元格范围所跨的行数 */ + int height; +}GRIDCELLS; +``` + +`SendMessage` 函数成功设置好指定单元格中的内容后返回 `GRID_OKAY`,如果失败则返回 `GRID_ERR`。 + +`GRIDM_GETCELLPROPERTY` 消息用来获得单元格的属性。 + +```c +GRIDCELLS* cells; +GRIDCELLDATA* celldata; +SendMessage (hwndGrid, GRIDM_GETCELLPROPERTY, &cells, celldata) ; +``` + +其中,`cells` 为具体的某一个单元格,注意它不能是多个单元格。`SendMessage` 函数成功获取指定单元格中的内容后返回 `GRID_OKAY`,`celldata` 结构中含有所要获得的单元格的信息。如果失败则返回 `GRID_ERR`。 + +另外还有针对不同类型的单元格的消息,如 `GRIDM_SETNUMFORMAT` 消息用来设置数字单元格(`GRIDCELLDATANUMBER`)的数字格式。 + +```c +GRIDCELLS* cells; +char* format = “%3.2f”; +SendMessage (hwndGrid, GRIDM_SETNUMFORMAT, cells, format); +``` + +其中,`cells` 表示所要设置的单元格,`format` 表示所有设置的数字格式。 + +对于所有类型的单元格,`GRIDM_SETSELECTED` 消息设置高亮的单元格: + +```c +GRIDCELLS* cells; +SendMessage (hwndGrid, GRIDM_SETSELECTED, 0, cells); +``` + +其中,`cells` 表示所要设置高亮的单元格,`SendMessage` 函数成功设置高亮单元格后返回 `GRID_OKAY`,如果失败则返回 `GRID_ERR`。 + +`GRIDM_GETSELECTED` 消息用来得到所有高亮的单元格: + +```c +GRIDCELLS* cells; +SendMessage (hwndGrid, GRIDM_GETSELECTED, 0, cells); +``` + +`SendMessage` 函数返回后 `cells` 包含所有高亮的单元格。 + +## 1.3 其它消息的处理 + +当用户按上下左右箭头键时, 当前被选中的单元格将发生变化,而且新的选中项将变为可见(如果它原来不可见的话)。当用户按上下翻页键(`PAGEUP`、`PAGEDOWN`)时,列表项将进行翻页,幅度和点击滚动条翻页是一样的,前一页的最后一项成为后一页的第一项。如果按下 `HOME` 键,列中的第一个单元格将被选中且变为可见。如果按下 `END` 键,最后一个单元格将被选中且成为可见。以上各键在 `SHIFT` 键同时按下时将执行高亮相关区域的操作。当单元格被双击时,或者在单元格选中状态下键入字符时可以编辑单元格中的内容。 + +网格控件还具有将某些单元格(源单元格)和另一些单元格(目标单元格)进行关联起来的操作,然后目标单元格将会在源单元格的数据改变时根据用户给定的数据操作函数更新其自身内容。进行该项工作的结构体如下: + +```c +typedef struct _GRIDCELLDEPENDENCE +{ + /* 源单元格 */ + GRIDCELLS source; + /* 目标单元格 */ + GRIDCELLS target; + /* 数据操作函数 */ + GRIDCELLEVALCALLBACK callback; + /* 附加信息 */ + DWORD dwAddData; +}GRIDCELLDEPENDENCE; + +/* 数据操作函数的原型 */ +typedef int (*GRIDCELLEVALCALLBACK)(GRIDCELLS* target, GRIDCELLS* source, DWORD dwAddData); +``` + +`GRIDM_ADDDEPENDENCE` 消息用来往网格控件中添加一个单元格关联(注意源单元格和目标单元格必须不相交,并且目标单元格和控件中已有的单元格关联中的其它目标单元格也必须不相关)。 + +```c +GRIDCELLDEPENDENCE* dependece; +SendMessage (hwndGrid, GRIDM_ADDDEPENDENCE, 0, dependence); +``` + +加入成功后返回该关联的索引,否则返回 `GRID_ERR`。 + +`GRIDM_DELDEPENDENCE` 消息用来删除网格控件中已有的一个单元格关联。 + +```c +int dependence_id; +SendMessage (hwndGrid, GRIDM_DELDEPENDENCE, 0, dependence_id); +``` + +其中,`dependence_id` 表示所要删除的单元格关联的索引值。删除成功后返回 `GRID_OKAY`,如果失败则返回 `GRID_ERR`。 + +## 1.4 网格控件通知码 + +网格控件在响应用户点击等操作和发生一些状态改变时产生通知码,包括: + +- `GRIDN_HEADLDOWN`:用户鼠标左键在表头上按下 +- `GRIDN_HEADLUP`:用户鼠标左键在表头上抬起 +- `GRIDN_KEYDOWN`:键按下 +- `GRIDN_CELLDBCLK`:用户双击某个单元格 +- `GRIDN_CELLCLK`:用户单击某个单元格 +- `GRIDN_FOCUSCHANGED`:当前选择的单元格改变 +- `GRIDN_CELLTEXTCHANGED`:单元格内容改变 + +当用户鼠标左键在单元格上按下时,该格将被选中,并且产生 `GRIDN_FOCUSCHANGED` 和 `GRIDN_CELLCLK` 两个通知码。 + +如果应用程序需要了解网格控件产生的通知码的话,需要使用 `SetNotificationCallback` 函数注册一个通知消息处理函数,在该函数中对收到的各个通知码进行应用程序所需的处理。 + +## 1.5 编程实例 + +__清单 1.1__ 中的代码演示了网格控件的使用。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `gridview.c` 程序。 + +__清单 1.1__ 网格控件示例程序 + +```c +int ww = 800; +int wh = 600; + +enum { + IDC_GRIDVIEW, +}; + +static HWND hGVWnd; + +static char* colnames[] = {"语文", "数学", "英语", "总分"}; +static char* scores[] = {"小明", "小强","小亮", "小力", "平均分"}; + +int total(GRIDCELLS* target, GRIDCELLS* source, DWORD dwAddData) +{ + int i, j; + double value = 0; + GRIDCELLDATA data; + GRIDCELLS cells; + GRIDCELLDATANUMBER num; + memset(&data, 0, sizeof(data)); + memset(&num, 0, sizeof(num)); + data.mask = GVITEM_MAINCONTENT|GVITEM_STYLE; + data.content = # + data.style = GV_TYPE_NUMBER; + cells.width = 1; + cells.height = 1; + for(i = 0; iwidth; i++) + { + cells.column = source->column + i; + for (j = 0; jheight; j++) + { + cells.row = source->row + j; + SendMessage(hGVWnd, GRIDM_GETCELLPROPERTY, (WPARAM)&cells, (LPARAM)&data); + value += num.number; + } + } + num.number = value; + num.len_format = -1; + cells.row = target->row; + cells.column = target->column; + SendMessage(hGVWnd, GRIDM_SETCELLPROPERTY, (WPARAM)&cells, (LPARAM)&data); + + return 0; +} + +int averge(GRIDCELLS* target, GRIDCELLS* source, DWORD dwAddData) +{ + int i, j; + int count = 0; + double value = 0; + GRIDCELLDATA data; + GRIDCELLS cells; + GRIDCELLDATANUMBER num; + memset(&data, 0, sizeof(data)); + memset(&num, 0, sizeof(num)); + data.content = # + data.style = GV_TYPE_NUMBER; + cells.width = 1; + cells.height = 1; + for(i = 0; iwidth; i++) + { + cells.column = source->column + i; + for (j = 0; jheight; j++) + { + data.content = # + data.style = GV_TYPE_NUMBER; + cells.row = source->row + j; + SendMessage(hGVWnd, GRIDM_GETCELLPROPERTY, (WPARAM)&cells, (LPARAM)&data); + value += num.number; + count++; + } + } + data.mask = GVITEM_MAINCONTENT; + num.number = value/count; + cells.row = target->row; + cells.column = target->column; + SendMessage(hGVWnd, GRIDM_SETCELLPROPERTY, (WPARAM)&cells, (LPARAM)&data); + + return 0; + return 0; +} + +static int +ControlTestWinProc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case MSG_CREATE: + { + GRIDVIEWDATA gvdata; + gvdata.nr_rows = 10; + gvdata.nr_cols = 10; + gvdata.row_height = 30; + gvdata.col_width = 60; + hGVWnd = CreateWindowEx (CTRL_GRIDVIEW, "Grid View", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | + WS_HSCROLL | WS_BORDER, WS_EX_NONE, IDC_GRIDVIEW, 20, 20, 600, + 300, hWnd, (DWORD)&gvdata); + int i; + GRIDCELLS cellsel; + GRIDCELLDEPENDENCE dep; + GRIDCELLDATA celldata; + GRIDCELLDATAHEADER header; + GRIDCELLDATANUMBER cellnum; + memset(&header, 0, sizeof(header)); + memset(&celldata, 0, sizeof(celldata)); + file://设置列表头的属性 + for (i = 1; i<= 3; i++) + { + header.buff = colnames[i-1]; + header.len_buff = -1; + celldata.content = &header; + celldata.mask = GVITEM_MAINCONTENT; + celldata.style = GV_TYPE_HEADER; + cellsel.row = 0; + cellsel.column = i; + cellsel.width = 1; + cellsel.height = 1; + SendMessage(hGVWnd, GRIDM_SETCELLPROPERTY, (WPARAM)&cellsel, (LPARAM)&celldata); + + } + + file://设置行表头的属性 + memset(&header, 0, sizeof(header)); + memset(&celldata, 0, sizeof(celldata)); + for (i = 1; i<= 4; i++) + { + header.buff = scores[i-1]; + celldata.content = &header; + celldata.mask = GVITEM_MAINCONTENT; + celldata.style = GV_TYPE_HEADER; + cellsel.row = i; + cellsel.column = 0; + cellsel.width = 1; + cellsel.height = 1; + SendMessage(hGVWnd, GRIDM_SETCELLPROPERTY, (WPARAM)&cellsel, (LPARAM)&celldata); + + } + + file://设置单元格的属性 + memset(&celldata, 0, sizeof(celldata)); + memset(&cellnum, 0, sizeof(cellnum)); + cellnum.number = 50; + cellnum.format = NULL; + celldata.content = &cellnum; + celldata.mask = GVITEM_MAINCONTENT; + celldata.style = GV_TYPE_NUMBER; + cellsel.row = 1; + cellsel.column = 1; + cellsel.width = 3; + cellsel.height = 4; + SendMessage(hGVWnd, GRIDM_SETCELLPROPERTY, (WPARAM)&cellsel, (LPARAM)&celldata); + + file://增加一列的操作 + memset(&header, 0, sizeof(header)); + memset(&celldata, 0, sizeof(celldata)); + header.buff = "总分"; + header.size = -1; + celldata.mask = GVITEM_MAINCONTENT; + celldata.content = &header; + celldata.style = GV_TYPE_HEADER; + SendMessage(hGVWnd, GRIDM_ADDCOLUMN, 3, (LPARAM)&celldata); + + file://增加一行的操作 + memset(&header, 0, sizeof(header)); + memset(&celldata, 0, sizeof(celldata)); + header.buff = "平均分"; + header.size = -1; + celldata.mask = GVITEM_MAINCONTENT; + celldata.content = &header; + celldata.style = GV_TYPE_HEADER; + SendMessage(hGVWnd, GRIDM_ADDROW, 4, (LPARAM)&celldata); + + memset(&celldata, 0, sizeof(celldata)); + memset(&cellnum, 0, sizeof(cellnum)); + cellnum.number = 0; + cellnum.format = NULL; + celldata.content = &cellnum; + celldata.mask = GVITEM_MAINCONTENT; + celldata.style = GV_TYPE_NUMBER; + cellsel.row = 1; + cellsel.column = 4; + cellsel.width = 1; + cellsel.height = 4; + SendMessage(hGVWnd, GRIDM_SETCELLPROPERTY, (WPARAM)&cellsel, (LPARAM)&celldata); + + cellsel.row = 5; + cellsel.column = 1; + cellsel.width = 4; + cellsel.height = 1; + SendMessage(hGVWnd, GRIDM_SETCELLPROPERTY, (WPARAM)&cellsel, (LPARAM)&celldata); + + + // 给这个单元格设置求和函数. + memset(&dep, 0, sizeof(dep)); + dep.callback = total; + for (i = 1; i<= 4; i++) + { + dep.source.row = i; + dep.source.column = 1; + dep.source.width = 3; + dep.source.height = 1; + dep.target.row = i; + dep.target.column = 4; + dep.target.width = 1; + dep.target.height = 1; + SendMessage(hGVWnd, GRIDM_ADDDEPENDENCE, 0, (LPARAM)&dep); + } + + dep.callback = averge; + // 给这个单元格设置求平均值函数. + for (i = 1; i<= 4; i++) + { + dep.source.row = 1; + dep.source.column = i; + dep.source.width = 1; + dep.source.height = 4; + dep.target.row = 5; + dep.target.column = i; + dep.target.width = 1; + dep.target.height = 1; + SendMessage(hGVWnd, GRIDM_ADDDEPENDENCE, 0, (LPARAM)&dep); + } + + return 0; + } + case MSG_COMMAND: + break; + case MSG_CLOSE: + DestroyMainWindow (hWnd); + MainWindowCleanup (hWnd); + return 0; + + } + return DefaultMainWinProc (hWnd, message, wParam, lParam); +} +``` + +![网格控件的使用](figures/Part4Chapter19-1.1.jpeg) +__图 1.1__ 网格控件的使用 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter20-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter20-zh.md new file mode 100644 index 0000000..d3ca95c --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter20-zh.md @@ -0,0 +1,327 @@ +# 图标型控件 + +图标型(IconView)控件提供一个以图标加标签文字的方式供用户浏览条目的界面。这些图标项显示在可滚动的子窗口中,用户可通过键盘及鼠标操作来选中某一项或者多个项,选中的图标项通常高亮显示。图标型控件的典型用法是作为桌面图标的容器和目录下文件的显示。 + +使用 `CTRL_ICONVIEW` 作为控件类名称,可以通过 `CreateWindow` 函数调用创建图标型控件。 + +我们在创建图标型控件之后,可以通过发送相应的消息来添加、删除、设置图标尺寸(必须在添加该图标之前)和获取图标标签文字等。 + +## 1.1 图标型控件风格 + +默认状态下,图标型控件窗口只显示图标和其标签文字,显示区域的周围没有边界。你可以在以 `CreateWindow` 函数创建控件时使用窗口风格标识号 `WS_BORDER` 来为图标型控件加上边界。另外,还可以使用窗口风格 `WS_VSCROLL` 和 `WS_HSCROLL` 来增加垂直和水平滚动条,以便用鼠标来滚动显示列表型控件中的各项内容。 + +图标型控件是基于 `ScrollView`(滚动型)控件开发的,它保留了 `ScrollView` 控件的风格。 + +## 1.2 图标型控件消息 + +### 1.2.1 图标项的操作 + +在创建一个图标型控件之后,下一步通常需要往该控件中添加图标项,这是由应用程序向控件发送 `IVM_ADDITEM` 消息来完成的。 + +```c +IVITEMINFO ivii; +SendMessage (hIconView, IVM_ADDITEM, 0, (LPARAM)&ivii) ; +``` + +其中,`ivii` 是一个 `IVITEMINFO` 结构,用来表示所要设置的图标项的信息。`IVITEMINFO` 结构的定义以及各项意义如下: + +```c +typedef struct _IVITEMINFO +{ + /* 图标项的索引值 */ + int nItem; + /* 图标项的图标 */ + PBITMAP bmp; + /* 图标项的标签文字 */ + const char *label; + /* 图标项的附加信息 */ + DWORD addData; + /* 保留 */ + DWORD dwFlags; +} IVITEMINFO; +``` + +图标项的索引值表示了该项在父窗口的位置。添加成功后返回该图标项的句柄,否则返回 0。 + +在添加图标项之前可以指定图标项的宽度和高度,所有的图标项都将以这个宽高度来显示。这是由 `IVM_SETITEMSIZE` 来完成的: + +```c +int width; +int height; +SendMessage (hIconView, IVM_SETITEMSIZE, width, height) ; +``` + +其中,`width` 是要设置的宽度,`height` 是要设置的高度。 + +因为图标型控件是基于 `ScrollView` 控件的,因此,图标型控件的其余各消息基本上和 `ScrollView` 的消息一一对应: + +- `IVM_RESETCONTENT`:对应 `SVM_RESETCONTENT`,用于清空图标型控件中的图标项。 +- `IVM_DELITEM`:对应 `SVM_DELITEM`,用于删除图标型控件中的图标项。 +- `IVM_SETITEMDRAW`:对应 `SVM_SETITEMDRAW`,用于设置图标项的绘制函数。 +- `IVM_SETCONTWIDTH`:对应 `SVM_SETCONTWIDTH`,用于设置滚动窗口的宽度。 +- `IVM_SETCONTHEIGHT`:对应 `SVM_SETCONTHEIGHT`,用于设置滚动窗口的高度。 +- `IVM_SETITEMOPS`:对应 `SVM_ SETITEMOPS`,用于设置图标项相关操作的一些回调函数。 +- `IVM_GETMARGINS`:对应 `SVM_GETMARGINS`,用于获取图标型控件的边缘范围值。 +- `IVM_SETMARGINS`:对应 `SVM_SETMARGINS`,用于设置图标型控件的边缘范围值。 +- `IVM_GETLEFTMARGIN`、`IVM_GETTOPMARGIN`、`IVM_GETRIGHTMARGIN` 和 `IVM_GETBOTTOMMARGIN` 分别对应 `SVM_GETLEFTMARGIN`、`SVM_GETTOPMARGIN`、`SVM_GETRIGHTMARGIN`、`SVM_GETBOTTOMMARGIN` 用于获取图标型控件中的左、上、右、下边缘值。 +- `IVM_GETCONTWIDTH`、`IVM_GETCONTHEIGHT`、`IVM_GETVISIBLEWIDTH` 和 `IVM_GETVISIBLEHEIGHT` 分别对应 `SVM_GETCONTWIDTH`、 +`SVM_GETCONTHEIGHT`、`SVM_GETVISIBLEWIDTH` 和 `SVM_GETVISIBLEHEIGHT`,用来获取内容区域的宽度和高度、可视区域的宽度和高度。 +- `IVM_SETCONTRANGE`:对应 `SVM_SETCONTRANGE`,用于设置滚动窗口的内容区域的大小。 +- `IVM_GETCONTENTX` 和 `IVM_GETCONTENTY` 分别对应 `SVM_GETCONTENTX` 和 `SVM_GETCONTENTY`,用于获取内容区域的当前位置值。 +- `IVM_SETCONTPOS`:对应 `SVM_SETCONTPOS`,用于设置内容区域的当前位置值,也就是在可视区域中移动内容区域到某个指定位置。 +- `IVM_GETCURSEL` 和 `IVM_SETCURSEL` 分别对应 `SVM_GETCURSEL` 和 `SVM_SETCURSEL`,用于获取和设置控件的当前高亮图标项。 +- `IVM_SELECTITEM`:对应 `SVM_SELECTITEM`,用于选择一个列表项,被选中的项将高亮显示。 +- `IVM_SHOWITEM`:对应 `SVM_SHOWITEM`,用于显示一个图标项。 +- `IVM_CHOOSEITEM`:对应 `SVM_CHOOSEITEM`,是 `IVM_SELECTITEM` 和 `IVM_SHOWITEM` 消息的组合,用来选中一个图标项并使之可见。 +- `IVM_SETITEMINIT`:对应 `SVM_SETITEMINIT`,用于设置图标项的初始操作。 +- `IVM_SETITEMDESTROY`:对应 `SVM_SETITEMDESTROY`,用于设置图标项的销毁操作。 +- `IVM_SETITEMCMP`:对应 `SVM_SETITEMCMP`,用于设置图标型控件图标项的比较函数。 +- `IVM_MAKEPOSVISIBLE`:对应 `SVM_MAKEPOSVISIBLE`,用于使内容区域中的某个位置点成为可见。 +- `IVM_GETHSCROLLVAL` 和 `IVM_GETVSCROLLVAL` 分别对应 `SVM_GETHSCROLLVAL` 和 `SVM_GETVSCROLLVAL`,用来获取滚动窗口的当前水平和垂直滚动值(点击滚动条箭头的滚动范围大小)。 +- `IVM_GETHSCROLLPAGEVAL` 和 `IVM_GETVSCROLLPAGEVAL` 分别对应 `SVM_GETHSCROLLPAGEVAL` 和 `SVM_GETVSCROLLPAGEVAL`,用来获取滚动窗口的当前水平和垂直页滚动值(翻页操作时的滚动范围大小)。 +- `IVM_SETSCROLLVAL`:对应 `SVM_SETSCROLLVAL`,用于设置滚动窗口的水平和(或者)垂直滚动值。 +- `IVM_SETSCROLLPAGEVAL`:对应 `SVM_SETSCROLLPAGEVAL`,用于设置滚动窗口的水平和(或者)垂直页滚动值。 +- `IVM_SORTITEMS`:对应 `SVM_SORTITEMS`,用于对图标项进行一次性的排序。 +- `IVM_GETITEMCOUNT`:对应 `SVM_GETITEMCOUNT`,用于获取当前图标项的数量。 +- `IVM_GETITEMADDDATA`:对应 `SVM_GETITEMADDDATA`,用于获取当前图标项的附加信息。 +- `IVM_SETITEMADDDATA`:对应 `SVM_SETITEMADDDATA`,用于设置当前图标项的附加信息。 +- `IVM_REFRESHITEM`:对应 `SVM_REFRESHITEM`,用于刷新一个图标项区域。 +- `IVM_GETFIRSTVISIBLEITEM`:对应 `SVM_GETFIRSTVISIBLEITEM`,用于获取第一个可见的图标项。 + +## 1.3 控件通知码 + +图标型控件在响应用户点击等操作和发生某些状态改变时会产生通知消息,包括: + +- `LVN_SELCHANGE`:对应 `SVN_SELCHANGE`,当前高亮图表项发生改变 +- `LVN_CLICKED`:对应 `SVN_CLICKED`,用户点击图标项 + +应用程序需要使用 `SetNotificationCallback` 函数注册一个通知消息处理函数,在该函数中对收到的各个通知码进行应用程序所需的处理。 + +`LVN_CLICKED` 和 `LVN_SELCHANGE` 通知消息处理函数传递的附加数据为被点击或者当前高亮的图标项句柄。 + +## 1.4 编程实例 + +__清单 1.1__ 中的代码演示了使用图标型控件来构造一个简单的图标项浏览窗口。该程序的完整源代码可见本指南示例程序包 `mg-samples` 中的 `iconview.c` 程序。 + +__清单 1.1__ 图标型控件示例程序 + +```c +#define IDC_ICONVIEW 100 +#define IDC_BT 200 +#define IDC_BT2 300 +#define IDC_BT3 400 +#define IDC_BT4 500 + +#define IDC_ADD 600 +#define IDC_DELETE 601 + +static HWND hIconView; + +static BITMAP myicons [12]; + +static const char* iconfiles[12] = +{ + "./res/acroread.png", + "./res/icons.png", + "./res/looknfeel.png", + "./res/package_games.png", + "./res/tux.png", + "./res/xemacs.png", + "./res/gimp.png", + "./res/kpilot.png", + "./res/multimedia.png", + "./res/realplayer.png", + "./res/usb.png", + "./res/xmms.png" +}; + +static const char *iconlabels[12] = +{ + "acroread", + "icons", + "looknfeel", + "games", + "tux", + "xemacs", + "gimp", + "kpilot", + "multimedia", + "realplayer", + "usb", + "xmms" +}; + +static void myDrawItem (HWND hWnd, GHANDLE hsvi, HDC hdc, RECT *rcDraw) +{ + const PBITMAP pbmp = (PBITMAP)iconview_get_item_bitmap (hsvi); + const char *label = (const char*)iconview_get_item_label (hsvi); + + SetBkMode (hdc, BM_TRANSPARENT); + SetTextColor (hdc, PIXEL_black); + + if (iconview_is_item_hilight(hWnd, hsvi)) { + SetBrushColor (hdc, PIXEL_blue); + } + else { + SetBrushColor (hdc, PIXEL_lightwhite); + } + FillBox (hdc, rcDraw->left, rcDraw->top, RECTWP(rcDraw), RECTHP(rcDraw)); + SetBkColor (hdc, PIXEL_blue); + + if (label) { + RECT rcTxt = *rcDraw; + rcTxt.top = rcTxt.bottom - GetWindowFont (hWnd)->size * 2; + rcTxt.left = rcTxt.left - (GetWindowFont (hWnd)->size) + 2; + + DrawText (hdc, label, -1, &rcTxt, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + } + FillBoxWithBitmap (hdc, rcDraw->left, rcDraw->top, 0, 0, pbmp); +} + +static int +BookProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + + case MSG_INITDIALOG: + { + IVITEMINFO ivii; + static int i = 0, j = 0; + + hIconView = GetDlgItem (hDlg, IDC_ICONVIEW); + SetWindowBkColor (hIconView, PIXEL_lightwhite); + //SendMessage (hIconView, IVM_SETITEMDRAW, 0, (LPARAM)myDrawItem); + SendMessage (hIconView, IVM_SETITEMSIZE, 55, 65); + //SendMessage (hIconView, IVM_SETITEMSIZE, 35, 35); + for (j = 0; j < 3; j ++) { + for (i = 0; i < TABLESIZE(myicons); i++) { + memset (&ivii, 0, sizeof(IVITEMINFO)); + ivii.bmp = &myicons[i]; + ivii.nItem = 12 * j + i; + ivii.label = iconlabels[i]; + ivii.addData = (DWORD)iconlabels[i]; + SendMessage (hIconView, IVM_ADDITEM, 0, (LPARAM)&ivii); + } + } + break; + } + + case MSG_COMMAND: + { + int id = LOWORD (wParam); + int code = HIWORD (wParam); + + switch (id) { + case IDC_ICONVIEW: + if (code == IVN_CLICKED) { + int sel; + sel = SendMessage (hIconView, IVM_GETCURSEL, 0, 0); + printf ("clicking %d\n", sel); + } + break; + case IDC_ADD: + { + IVITEMINFO ivii; + char buff [10]; + int idx; + int count = SendMessage (hIconView, IVM_GETITEMCOUNT, 0, 0); + + sprintf (buff, "NewIcon%i", count); + memset (&ivii, 0, sizeof (IVITEMINFO)); + ivii.bmp = &myicons [0]; + ivii.nItem = count; + ivii.label = buff; + ivii.addData = (DWORD)"NewIcon"; + + idx = SendMessage (hIconView, IVM_ADDITEM, 0, (LPARAM)&ivii); + SendMessage (hIconView, IVM_SETCURSEL, idx, 1); + break; + } + + case IDC_DELETE: + { + int sel = SendMessage (hIconView, IVM_GETCURSEL, 0, 0); + int count = SendMessage (hIconView, IVM_GETITEMCOUNT, 0, 0); + char *label = NULL; + + if (sel >= 0){ + label = (char *) SendMessage (hIconView, IVM_GETITEMADDDATA, sel, 0); + if (label && strlen (label)) + printf ("delelete item:%s\n", label); + SendMessage (hIconView, IVM_DELITEM, sel, 0); + if (sel == count - 1) + sel --; + SendMessage (hIconView, IVM_SETCURSEL, sel, 1); + } + break; + } + + } /* end command switch */ + break; + } + + case MSG_KEYDOWN: + if (wParam == SCANCODE_REMOVE) { + int cursel = SendMessage (hIconView, IVM_GETCURSEL, 0, 0); + + if (cursel >= 0){ + SendMessage (hIconView, IVM_DELITEM, cursel, 0); + SendMessage (hIconView, IVM_SETCURSEL, cursel, 0); + } + } + break; + + case MSG_CLOSE: + { + EndDialog (hDlg, 0); + return 0; + } + + } /* end switch */ + + return DefaultDialogProc (hDlg, message, wParam, lParam); +} + +static CTRLDATA CtrlBook[] = +{ + { + CTRL_ICONVIEW, + WS_BORDER | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL, + 10, 10, 290, 300, + IDC_ICONVIEW, + "", + 0 + }, + { + CTRL_BUTTON, + WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP, + 90, 330, 50, 30, + IDC_ADD, + "Add", + 0 + }, + { + CTRL_BUTTON, + WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, + 170, 330, 50, 30, + IDC_DELETE, + "Delete", + 0 + } +}; + +static DLGTEMPLATE DlgIcon = +{ + WS_BORDER | WS_CAPTION, + WS_EX_NONE, + 0, 0, 310, 400, + "My Friends", + 0, 0, + TABLESIZE(CtrlBook), CtrlBook, + 0 +}; +``` + +![图标型控件](figures/Part4Chapter20-1.1.jpeg) +__图 1.1__ 图标型控件 diff --git a/programming-guide-zh/MiniGUIProgGuidePart6Chapter21-zh.md b/programming-guide-zh/MiniGUIProgGuidePart6Chapter21-zh.md new file mode 100644 index 0000000..29490a2 --- /dev/null +++ b/programming-guide-zh/MiniGUIProgGuidePart6Chapter21-zh.md @@ -0,0 +1,928 @@ +# 独立滚动条控件 + +## 1.1 滚动条的定义 + +- 滚动条控件的组成部分有箭头(arrow)、滚槽(shaft)、游标(thumb)三部分组成。有的滚动条没有箭头,有的没有滚槽和游标。如图: + +__图 1.1__ 滚动条结构图 +![滚动条结构图](figures/Part4Chapter21-1.1.gif) + +- 滚动条是矩形的,当鼠标击中滚动条,滚动条发送消息给父窗口,父窗口显示更新的窗口内容并更新游标的位置。当鼠标点击箭头时,滚动条也会发消息给父窗口,更新父窗口的内容和游标的位置。 + +## 1.2 滚动条的形态 + +滚动条有两种形态:一种是以主窗口或者其他控件的一部分出现,通过风格 `WS_HSCROLL`、`WS_VSCROLL` 来指定是否拥有水平、垂直滚动条。主窗口或者控件可以同时拥有这两种风格;另一种是独立控件,控件类名是 `CTRL_SCROLLBAR`,应用程序在创建滚动条控件时,通过 `SBS_HORZ` 或者 `SBS_VERT` 来确定是创建水平滚动条还是垂直滚动条,这两种风格只能选择其一。前者不是本章要介绍的内容,但是毕竟二者有密切的联系,因此,此处作一下必要的介绍。 + +## 1.3 独立滚动条的风格 + +独立滚动条拥有如下的风格: + +__表 1.1__ 独立滚动条风格 + +| 风格标识符 | 意义 | +|:---------------------|:-------| +|`SBS_HORZ` | 创建一个水平滚动条。当没有指定 `SBS_BOTTOMALIGN` 或 `SBS_TOPALIGN`,滚动条的范围根据 `CreateWindowEx2` 参数 x,y,w,h 决定。 | +|`SBS_VERT` | 创建一个垂直滚动条。当没有指定 `SBS_LEFTALIGN` 或 `SBS_RIGHTALIGN`,滚动条的范围根据 `CreateWindowEx2` 参数 x,y,w,h 决定。 | +|`SBS_BOTTOMALIGN` | 与 `SBS_HORZ` 一起使用。放置水平滚动条在 `CreateWindowEx2` 指定范围的底部。| +|`SBS_TOPALIGN` | 与 `SBS_HORZ` 一起使用。放置水平滚动条在 `CreateWindowEx2` 指定范围的顶部。 | +|`SBS_LEFTALIGN` | 与 `SBS_VERT` 一起使用。放置垂直滚动条在 `CreateWindowEx2` 指定范围的左边。| +|`SBS_RIGHTALIGN` | 与 `SBS_VERT` 一起使用,放置垂直滚动条在 `CreateWindowEx2` 指定范围的右边。| +|`SBS_NOARROWS` | 没有箭头,不能与 `SBS_NOSHAFT` 一起使用 | +|`SBS_NOSHAFT` | 没有 `shaft`,不能与 `SBS_NOARROWS` 一起使用 | +|`SBS_FIXEDBARLEN` | 水平滚动条的 `thumb` 长度固定,或者垂直滚动条的 `thumb` 长度固定 | +|`SBS_NOTNOTIFYPARENT` | 向父窗口的通知发送方式不是发送通知码,而是发送消息;默认发送通知码 | + +## 1.4 独立滚动条的消息 + +应用程序可以向滚动条控件发送下面的消息来进行相应的处理: + +- 获取/设置滚动条的数据信息:`SBM_GETSCROLLINFO`、`SBM_SETSCROLLINFO` +- 获取/设置当前 `thumb` 的位置: `SBM_GETPOS` 、 `SBM_SETPOS` +- 获取/设置滚动范围 `range`: `SBM_GETRANGE` 、 `SBM_SETRANGE` +- 设置滚动范围 `range` 并且立即重绘: `SBM_SETRANGEREDRAW` +- 启用/禁止箭头 `arrow`: `SBM_ENABLE_ARROW` + +### 1.4.1 获取滚动条信息 + +应用程序向滚动条发送 `wParam` 参数为 `SCROLLINFO` *指针的 `SBM_GETSCROLLINFO` 消息获取滚动条控件的最大值、最小值、滚动条所含的页数、当前位置这些信息。所获取到的信息将存放在 `wParam` 所指向的内存里面返回。`SCROLLINFO` 结构体如下: + +```c +/* +* Scroll bar information structure. +*/ +typedef struct _SCROLLINFO +{ + /** Size of the structrue in bytes */ + UINT cbSize; + /** + * A flag indicates which fields contain valid values, + * can be OR'ed value of the following values: + * - SIF_RANGE\n + * Retrives or sets the range of the scroll bar. + * - SIF_PAGE\n + * Retrives or sets the page size of the scroll bar. + * - SIF_POS\n + * Retrives or sets the position of the scroll bar. + * - SIF_DISABLENOSCROLL\n + * Hides the scroll when disabled, not implemented so far. + */ + UINT fMask; + /** The minimum position value of the scroll bar */ + int nMin; + /** The maximum position value of the scroll bar */ + int nMax; + /** The page size of the scroll bar */ + UINT nPage; + /** The position value of the scroll bar */ + int nPos; +} SCROLLINFO, *PSCROLLINFO; +``` + +通过 `SBM_GETSCROLLINFO` 消息所能获得的信息是由 `SBM_GETSCROLLINFO` 结构体的fMask 来指定的,`fMas` 可取如下表的值: + +__表 1.2__ 滚动条信息列表 + +| 信息标识符 | 意义 | +|:-----------|:------| +|`SIF_RANGE` | 获取滚动条的取值范围| +|`SIF_PAGE` | 获取滚动条的页数| +|`SIF_POS` | 获取滚动条的当前位置| +|`SIF_ALL` | 获取上面所有的信息| + +下面的示例是获取滚动条控件的所有信息 + +```c +SCROLLINFO scinfo = {0}; +scinfo.fMask = SIF_ALL; +SendMessage (hwnd_scrollbar, SBM_GETSCROLLINFO, (wParam)&scinfo, 0); +``` + +### 1.4.2 设置滚动条信息 + +向滚动条控件发送 `SBM_SETSCROLLINFO` 消息进行滚动条信息设置。参数 `wParam` 为 `SCROLLINFO` 结构体指针,存放有需要设置的滚动条信息。参数 `lParam` 用来确定是否立即重绘,`FALSE` 为不重绘, `TRUE` 则立即重绘。 + +下面的示例是对滚动条的信息进行设置,并且不立即进行重绘: + +```c +SCROLLINFO scinfo = {0}; +scinfo.fMask = SIF_ALL; +scinfo.nMin = 0; +scinfo.nMax = 10; +scinfo.nPage = 2; +scinfo.nPos = 0; +BOOL redraw = FALSE; +SendMessage (hwnd_scrollbar, SBM_SETCROLLINFO, (wParam)&scinfo, redraw); +``` + +### 1.4.3 获取当前游标的位置 + +向滚动条发送 `SBM_GETPOS` 消息,就可以获取当前游标所在位置。下面是示例代码: + +```c +int pos = SendMessage (hwnd_scrollbar, SBM_GETPOS, 0, 0); +``` + +### 1.4.4 设置当前游标的位置 + +向滚动条发送 `SBM_SETPOS` 消息,设置滚动条游标当前的位置。目的位置放在参数 `wParam` 中, `lParam` 表示是否立即重绘,`TRUE` 立即重绘,`FALSE` 不重绘。 + +```c +int pos = 10; +SendMessage (hwnd_scrollbar, SBM_SETPOS, pos, TRUE); +``` + +### 1.4.5 获取滚动条滚动范围 + +通过 `SBM_GETRANGE` 消息可以获取滚动条的滚动范围。参数 `wParam` 得到最小范围,`lParam` 得到最大范围。 + +```c +int min, max; +SendMessage (hwnd_scrollbar, SBM_GETRANGE, &min, &max); +``` + +### 1.4.6 设置滚动条滚动范围 + +通过 `SBM_SETRANGE` 消息可以设置滚动条的滚动范围。参数 `wParam` 是设置的最小范围,`lParam` 是设置的最大范围,此消息不会引起滚动条立即重绘。 + +下面的代码设置滚动条的滚动范围在 0 至 100 之间,但是需要在别的消息或者事件引起界面重绘时,你才能看到滚动条的变化。 + +```c +SendMessage (hwnd_scrollbar, SBM_SETRANGE, 0, 100); +``` + +### 1.4.7 设置滚动条滚动范围,并立即重绘 + +如果需要设置滚动条滚动范围并且要立即重绘的话,则需要向它发送 `SBM_SETRANGEREDRAW` 消息。参数 `wParam` 是设置的最小范围,`lParam` 是设置的最大范围。 + +下面的代码设置滚动条的滚动范围在 0 至 100 之间,并且立即重绘。 + +```c +SendMessage (hwnd_scrollbar, SBM_SETRANGEREDRAW, 0, 100); +``` + +### 1.4.8 启用或者禁用滚动条箭头 + +通过 `SBM_ENABLE_ARROW` 消息可以启用或者禁止滚动条箭头。所谓禁止就是不能向箭头指向的方向滚动。参数 `wParam` 可以取值如下,`lParam` 为 `TRUE` 表示启用,`FALSE` 表示禁用。 + +__表 1.3__ 箭头标识表 + +| 箭头标识符 | 意义 | +|:---------------|:------| +|`SB_ARROW_LTUP` | 代表水平滚动条的左方向键或者垂直滚动条的上方向键 | +|`SB_ARROW_BTDN` | 代表水平滚动条的右方向键或者垂直滚动条的下方向键 | +|`SB_ARROW_BOTH` | 所有方向键 | + + +下面的代码将禁用滚动条所有的箭头 + +```c +SendMessage (hwnd_scrollbar, SBM_ENABLE_ARROW, SB_ARROW_BOTH, FALSE); +``` + +## 1.5 滚动条可配置的属性 + +滚动条有如下的属性可以通过 `GetWindowElementAttr`、`SetWindowElementAttr`、`GetWindowElementPixelEx`、`SetWindowElementPixelEx` 这些函数来获取与设置。下表是可配置的属性列表及说明: + +__表 1.4__ 通知码列表 + +| 属性标识符 | 说明 | +|:----------------------|:-----| +|`WE_MAINC_THREED_BODY` | 绘制滚动条滚槽的颜色以及游标的颜色 | +|`WE_FGC_THREED_BODY` | 绘制箭头的颜色 | +|`WE_FGC_DISABLED_ITEM` | 绘制无效箭头的颜色 | +|`WE_METRICS_SCROLLBAR` | 滚动条的尺寸,即水平滚动条的高度、垂直滚动条的宽度 | + + +下面的代码演示了对上面属性的操作: + +```c +DWORD color_3d, fgc_3d, fgc_dis; +gal_pixel old_brush_color; + +//获取绘制滚动条各个部分的颜色属性值 +color_3d = GetWindowElementAttr(hWnd, WE_MAINC_THREED_BODY); +fgc_3d = GetWindowElementAttr(hWnd, WE_FGC_THREED_BODY); +fgc_dis = GetWindowElementAttr(hWnd, WE_FGC_DISABLED_ITEM); + +//绘制滚动条滚槽 +old_brush_color = SetBrushColor(hdc, +RGBA2Pixel(hdc,GetRValue(color_3d), +GetGValue(color_3d), GetBValue(color_3d), +GetAValue(color_3d))); +FillBox(hdc, rect.left, rect.top, RECTW(rect), RECTH(rect)); +SetBrushColor(hdc, old_brush_color); + +//绘制滚动条箭头 +draw_3dbox(hdc, &rect, color_3d, +bn_status | LFRDR_3DBOX_THICKFRAME | LFRDR_3DBOX_FILLED); + +if(sb_status & SBS_DISABLED_LTUP || sb_status & SBS_DISABLED) +draw_arrow(hWnd, hdc, &rect, fgc_dis, LFRDR_ARROW_LEFT); +else +draw_arrow(hWnd, hdc, &rect, fgc_3d, LFRDR_ARROW_LEFT); +``` + +## 1.6 滚动条通知码 + +滚动条所拥有的通知码如下表: + +__表 1.5__ 通知码列表 + +| 通知码标识符 | 意义 | +|:------------------|:----| +|`SB_LINEUP` | 垂直滚动条向上滚一行 | +|`SB_LINEDOWN` | 垂直滚动条向下滚一行 | +|`SB_PAGEUP` | 垂直滚动条向上滚一页 | +|`SB_PAGEDOWN` | 垂直滚动条向下滚一页 | +|`SB_LINELEFT` | 水平滚动条向左滚一列 | +|`SB_LINERIGHT` | 水平滚动条向右滚一列 | +|`SB_PAGELEFT` | 水平滚动条向左滚一列页 | +|`SB_PAGERIGHT` | 水平滚动条向右滚一页 | +|`SB_THUMBPOSITION` | 当游标被鼠标左键按住拖动,然后释放,此时的游标位置将由此通知码传给父窗口 | +|`SB_THUMBTRACK` | 当游标被鼠标左键按住,在拖动游标的过程中,游标的位置将由此通知码不断的传给父窗口 | +|`SB_TOP` | 游标到了水平滚动条的最左边或者是垂直滚动条的最上边,即到了滚动条的最小值 | +|`SB_BOTTOM` | 游标到了水平滚动条的最右边或者是垂直滚动条的最下边,即到了滚动条的最大值| + +- 当滚动条指定了风格 `SBS_NOTNOTIFYPARENT` 时,水平滚动条的父窗口将会收到 `MSG_HSCROLL` 消息,垂直滚动条的父窗口将会收到 `MSG_VSCROLL` 消息, 参数 `wParam` 为通知码 `id`, 当 `id` 为 `SB_THUMBPOSITION`,`SB_THUMBTRACK` 时,`lParam` 为游标的当前位置 `curPos`,其他情况下 `lParam` 没有意义。 +- 当滚动条没有指定风格 `SBS_NOTNOTIFYPARENT` 时,滚动条的父窗口将会收到通知码,参数 `wParam` 包含控件 `ID` 和通知码。当通知码为 `SB_THUMBPOSITION`,`SB_THUMBTRACK` 时,父窗口可以通过发送 `SBM_GETPOS` 来获取游标的当前位置 `curPos`。当然,如果你调用 `SetNotificationCallback` 函数设定了滚动条控件的通知回调函数,则控件不会向父窗口发送 `MSG_COMMAND `通知消息,而是会直接调用设定的通知回调函数。 + +### 1.6.1 通知消息的触发 + +滚动条可以接收鼠标和键盘事件,并根据不同的情况触发不同的通知消息。 + +- 当鼠标点击滚动条不同部位时,会触发不同的通知消息。另外,要注意的是,用鼠标左键拖动游标的过程中,滚动条会不断发送 `SB_THUMBTRACK` 通知消息,鼠标左键释放后会发送 `SB_THUMBPOSITIO` 消息。滚动条各个部位所触发的消息,请见下图: + +__图 1.2__ 鼠标触发的通知消息 +![鼠标触发的通知消息](figures/Part4Chapter21-1.2.gif) + +- 键盘按键会触发相应的通知消息,具体如下表所示: + +__表 1.6__ 通知码列表 + +| 按键 | 通知码 | +|:-------------|:------| +|`PAGEUP` 键 | 水平滚动条发送 `SB_PAGELEFT` ;垂直滚动条发送 `SB_PAGEUP` | +|`PAGEDOWN` 键 | 水平滚动条发送 `SB_PAGERIGHT` ;垂直滚动条发送 `SB_PAGEDOWN` | +|上方向键 | 垂直滚动条发送 `SB_LINEUP` | +|左方向键 | 水平滚动条发送 `SB_LINELEFT` | +|下方向键 | 垂直滚动条发送 `SB_LINEDOWN` | +|右方向键 | 水平滚动条发送 `SB_LINERIGHT` | +|`HOME` 键 | `SB_TOP` | +|`END` 键 | `SB_BOTTOM` | + +## 1.7 编程示例 + +下面的代码给出了各种样式的滚动条的创建示例。通过鼠标或者按键,你可以对滚动条进行点击、拖动等操作。为了使演示更生动,本示例还在界面的右边放置了一个圆圈和方块,随着你控制滚动条上下或者左右滚动,圆圈会变大变小,方块会上下移动。示例的效果如__图 1.3__ 所示,代码摘自示例程序包 `mg-samples` 中的 `scrollbar_ctrl.c` 文件,完整代码请见此文件。 + +__清单 1.1__ 滚动条示例代码 + +```c +#include +#include + +#include +#include +#include +#include +#include + +#ifdef _LANG_ZHCN +#include "scrollbar_ctrl_res_cn.h" +#elif defined _LANG_ZHTW +#include "scrollbar_ctrl_res_tw.h" +#else +#include "scrollbar_ctrl_res_en.h" +#endif + +/** define scrollbar data */ +#define SB_MAX 20 +#define SB_MIN 0 +#define SB_PAGE 6 + +/** define circle data */ +#define CC_CENTER_X 400 +#define CC_CENTER_Y 100 +#define CC_RADIUS_MAX 50 +#define CC_RADIUS_MIN 0 + +/** define box data */ +#define BOX_WIDTH 40 +#define BOX_HEIGHT 20 +#define BOX_Y_MAX 300 +#define BOX_Y_MIN 200 + +/** x position of separator */ +#define SEP 300 + +/** radius of circle */ +int _radius; + +/** y position of box */ +int _box_pos_y; + +/** invalidate rect */ +RECT _rect_invalid; + +static int ScrollbarProc(HWND hwnd, int message, WPARAM wParam, LPARAM lParam) +{ + /** IDC of SCROLLBAR control */ + static int _my_scroll_idc = 100; + + /** scrollbar handle of main window */ + static HWND hwnd_sb_main; + + /** scrollbar handle of control */ + HWND hwnd_sb_ctrl; + + switch (message) + { + case MSG_CREATE: + { + _rect_invalid.left = SEP; + _rect_invalid.top = 0; + _rect_invalid.right = g_rcScr.right; + _rect_invalid.bottom = g_rcScr.bottom; + + SCROLLINFO scinfo = {0}; + scinfo.fMask = SIF_ALL; + scinfo.nMin = SB_MIN; + scinfo.nMax = SB_MAX; + scinfo.nPage = SB_PAGE; + scinfo.nPos = SB_MIN; + + calc_circle_pos (scinfo.nPos); + calc_box_pos (scinfo.nPos); + + /** classic VSCROLL with SBS_NOTNOTIFYPARENT */ + + hwnd_sb_main = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | + SBS_VERT | SBS_LEFTALIGN + | SBS_NOTNOTIFYPARENT + , + 0, + ++_my_scroll_idc, + 20, 50, 20, 150, hwnd, + "classic", 0, + 0); + + SendMessage (hwnd_sb_main, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_main, MSG_SETFOCUS, 0, 0); + + /** flat VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | + SBS_VERT | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 43, 50, 20, 150, hwnd, + "flat", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** fashion VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | + SBS_VERT | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 66, 50, 20, 150, hwnd, + "fashion", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** tiny VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | + SBS_VERT | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 92, 50, 20, 150, hwnd, + "tiny", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** classic NOSHAFT VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOSHAFT | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 120, 50, 20, 34, hwnd, + "classic", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** flat NOSHAFT VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOSHAFT | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 140, 50, 20, 34, hwnd, + "flat", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** fashion NOSHAFT VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOSHAFT | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 160, 50, 20, 34, hwnd, + "fashion", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** tiny NOSHAFT VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOSHAFT | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 184, 50, 20, 34, hwnd, + "tiny", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** classic NOARROW VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOARROW | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 210, 50, 20, 150, hwnd, + "classic", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** flat NOARROW VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOARROW | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 232, 50, 20, 150, hwnd, + "flat", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** fashion NOARROW VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOARROW | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 254, 50, 20, 150, hwnd, + "fashion", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** tiny NOARROW VSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_VERT | SBS_NOARROW | SBS_LEFTALIGN + , + 0, + ++_my_scroll_idc, + 276, 50, 20, 150, hwnd, + "tiny", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** classic HSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_HORZ + | SBS_TOPALIGN + , + 0, + ++_my_scroll_idc, + 20, 220, 150, 20, hwnd, + "classic", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** flat HSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_HORZ + | SBS_TOPALIGN + , + 0, + ++_my_scroll_idc, + 20, 240, 150, 20, hwnd, + "flat", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** fashion HSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_HORZ + | SBS_TOPALIGN + , + 0, + ++_my_scroll_idc, + 20, 260, 150, 20, hwnd, + "fashion", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + + /** tiny HSCROLL */ + + hwnd_sb_ctrl = CreateWindowEx2 (CTRL_SCROLLBAR, "", + WS_VISIBLE | SBS_HORZ + | SBS_TOPALIGN + , + 0, + ++_my_scroll_idc, + 20, 280, 150, 20, hwnd, + "tiny", 0, + 0); + + SendMessage (hwnd_sb_ctrl, SBM_SETSCROLLINFO, (WPARAM)&scinfo, TRUE); + SendMessage (hwnd_sb_ctrl, MSG_SETFOCUS, 0, 0); + } + break; + + case MSG_COMMAND: + { + int code = HIWORD(wParam); + HWND scroll = (HWND)lParam; + int pos = 0; + + switch (code) + { + case SB_LINELEFT: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + SendMessage (scroll, SBM_SETPOS, --pos, TRUE); + } + + break; + + case SB_LINERIGHT: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + SendMessage (scroll, SBM_SETPOS, ++pos, TRUE); + } + break; + + case SB_PAGELEFT: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + pos -= SB_PAGE; + SendMessage (scroll, SBM_SETPOS, pos, TRUE); + } + break; + + case SB_PAGERIGHT: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + pos += SB_PAGE; + SendMessage (scroll, SBM_SETPOS, pos, TRUE); + } + break; + + case SB_LINEUP: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + SendMessage (scroll, SBM_SETPOS, --pos, TRUE); + } + + break; + + case SB_LINEDOWN: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + SendMessage (scroll, SBM_SETPOS, ++pos, TRUE); + } + break; + + case SB_PAGEUP: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + pos -= SB_PAGE; + SendMessage (scroll, SBM_SETPOS, pos, TRUE); + } + break; + + case SB_PAGEDOWN: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + + pos += SB_PAGE; + SendMessage (scroll, SBM_SETPOS, pos, TRUE); + } + break; + + case SB_THUMBPOSITION: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + } + break; + + case SB_THUMBTRACK: + { + pos = SendMessage (scroll, SBM_GETPOS, 0, 0); + } + break; + + case SB_TOP: + { + pos = SB_MIN; + } + break; + + case SB_BOTTOM: + { + pos = SB_MAX; + } + break; + + default: + break; + } + + draw_shape (hwnd, pos); + } + break; + + case MSG_HSCROLL: + { + int pos = 0; + switch (wParam) + { + case SB_LINELEFT: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + SendMessage (hwnd_sb_main, SBM_SETPOS, --pos, TRUE); + } + + break; + + case SB_LINERIGHT: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + SendMessage (hwnd_sb_main, SBM_SETPOS, ++pos, TRUE); + } + break; + + case SB_PAGELEFT: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + pos -= SB_PAGE; + SendMessage (hwnd_sb_main, SBM_SETPOS, pos, TRUE); + } + break; + + case SB_PAGERIGHT: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + pos += SB_PAGE; + SendMessage (hwnd_sb_main, SBM_SETPOS, pos, TRUE); + } + break; + case SB_THUMBPOSITION: + { + pos = (int)lParam; + } + break; + + case SB_THUMBTRACK: + { + pos = (int)lParam; + } + break; + + case SB_TOP: + { + pos = SB_MIN; + } + break; + + case SB_BOTTOM: + { + pos = SB_MAX; + } + break; + + default: + break; + } + draw_shape (hwnd, pos); + } + break; + + case MSG_VSCROLL: + { + int pos = 0; + switch (wParam) + { + case SB_LINEUP: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + SendMessage (hwnd_sb_main, SBM_SETPOS, --pos, TRUE); + + } + + break; + + case SB_LINEDOWN: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + SendMessage (hwnd_sb_main, SBM_SETPOS, ++pos, TRUE); + } + break; + + case SB_PAGEUP: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + pos -= SB_PAGE; + SendMessage (hwnd_sb_main, SBM_SETPOS, pos, TRUE); + } + break; + + case SB_PAGEDOWN: + { + pos = SendMessage (hwnd_sb_main, SBM_GETPOS, 0, 0); + + pos += SB_PAGE; + SendMessage (hwnd_sb_main, SBM_SETPOS, pos, TRUE); + } + break; + case SB_THUMBPOSITION: + { + pos = (int)lParam; + } + break; + + case SB_THUMBTRACK: + { + pos = (int)lParam; + } + break; + + case SB_TOP: + { + pos = SB_MIN; + } + break; + + case SB_BOTTOM: + { + pos = SB_MAX; + } + break; + default: + break; + } + draw_shape (hwnd, pos); + } + break; + + case MSG_PAINT: + { + HDC hdc = BeginPaint(hwnd); + + /** separator */ + MoveTo (hdc, SEP, 0); + LineTo (hdc, SEP, g_rcScr.bottom); + + /** circle */ + Circle (hdc, CC_CENTER_X, CC_CENTER_Y, _radius); + + /** top and bottom line of box */ + MoveTo (hdc, SEP + 20, BOX_Y_MIN); + LineTo (hdc, SEP + 20 + BOX_WIDTH + 40, BOX_Y_MIN); + + MoveTo (hdc, SEP + 20, BOX_Y_MAX); + LineTo (hdc, SEP + 20 + BOX_WIDTH + 40, BOX_Y_MAX); + + /** box */ + SetBrushColor (hdc, PIXEL_black); + FillBox (hdc, SEP + 40, _box_pos_y, BOX_WIDTH, BOX_HEIGHT); + + EndPaint (hwnd, hdc); + } + break; + + case MSG_CLOSE: + { + DestroyMainWindow (hwnd); + PostQuitMessage (hwnd); + return 0; + } + } + + return DefaultMainWinProc(hwnd, message, wParam, lParam); +} + +int MiniGUIMain (int argc, const char* argv[]) +{ + MSG Msg; + HWND hMainWnd; + MAINWINCREATE CreateInfo; + + #ifdef _MGRM_PROCESSES + JoinLayer(NAME_DEF_LAYER , "scrollbar" , 0 , 0); + #endif + + CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION; + CreateInfo.dwExStyle = WS_EX_NONE; + CreateInfo.spCaption = SCB_ST_CAP; + CreateInfo.hMenu = 0; + CreateInfo.hCursor = GetSystemCursor(0); + CreateInfo.hIcon = 0; + CreateInfo.MainWindowProc = ScrollbarProc; + CreateInfo.lx = 0; + CreateInfo.ty = 0; + CreateInfo.rx = g_rcScr.right; + CreateInfo.by = g_rcScr.bottom; + CreateInfo.iBkColor = COLOR_lightwhite; + CreateInfo.dwAddData = 0; + CreateInfo.hHosting = HWND_DESKTOP; + + hMainWnd = CreateMainWindowEx (&CreateInfo, "flat", 0, 0, 0); + + if (hMainWnd == HWND_INVALID) + { + return -1; + } + + ShowWindow(hMainWnd, SW_SHOWNORMAL); + + while (GetMessage(&Msg, hMainWnd)) { + TranslateMessage(&Msg); + DispatchMessage(&Msg); + } + + MainWindowThreadCleanup (hMainWnd); + + return 0; +} + +#ifdef _MGRM_THREADS +#include +#endif +``` + +__图 1.3__ 滚动条控件 +![滚动条控件](figures/Part4Chapter21-1.3.png)