使Win32 SDK PropertyGrid变得简单
内容 介绍使用propertygrid公共数据结构的背景 PROPGRIDITEM 成员 PROPGRIDFONTITEM 成员 PROPGRIDFDITEM 成员 消息和宏 propgrid_additem propgrid_deleteitem propgrid_enable propgrid_getcount propgrid_getcursel propgrid_gethorizontalextent propgrid_getitemdata propgrid_getitemheight propgrid_getitemrect propgrid_getsel propgrid_resetcontent propgrid_setcursel propgrid_sethorizontalextent propgrid_setitemdata propgrid_setitemheight propgrid_expandcatalogs propgrid_expandallcatalogs propgrid_collapsecatalogs propgrid_collapseallcatalogs propgrid_showtooltips propgrid_showpropertydescriptions propgrid_setflatstylechecks propgrid_iteminit 通知 NMPROPGRID PGN_PROPERTYCHANGE 针对win32 /64 SDK开发人员的设计注意事项提示和技巧 所有者绘制提示无边框控件简单复选框目录切换子类组合控件鼠标滚轮bug检测开始和结束滚动事件在列表框 最后的评论历史 介绍 我曾经编写过一个实用程序,它需要处理与远程设备相关的大量参数。我使用属性表样式标签页,每个属性都有单独的字段,控制字段的数量成倍增加,直到应用程序在所有这些窗口的重压下摇摇欲坠。从那时起,我就成了Datagrid和Propertygrid风格ui的忠实粉丝。理想情况下,在任何时候,您都有一个显示数据的窗口和另一个编辑数据的窗口,但同时保持了许多组织良好的控件的丰富界面的假象。我想为我的Win32项目有这样一个界面,一些容易使用,超轻的重量,和专业的外观。 背景 在开始编写Propertygrid之前,我做了一点背景研究,看看其他人提出了什么类型的解决方案。我注意到Noel Ramathal的一个很有前途的属性列表框,以及Runming Yan的一个似乎基于Noel作品的列表框。我从这些示例开始,但努力寻找一些与Visual Studio Propertygrid以及Pelles C IDE中使用的外观和感觉类似的东西。除此之外,我还想将Propertygrid编写为基于消息的自定义Win32/64控件。 使用Propertygrid 首先,在项目中包含Propertygrid控件的头文件: 隐藏,复制Code
#include "propertyGrid.h"
此Propertygrid控件是基于消息的自定义控件,因此在使用之前必须初始化。处理这个问题的一种方法是在调用InitCommonControlsEx()之后调用应用程序的WinMain()方法中的初始化器。 隐藏,复制Code
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { INITCOMMONCONTROLSEX icc; WNDCLASSEX wcx; ghInstance = hInstance; /* Initialize common controls. Also needed for MANIFEST's */ icc.dwSize = sizeof(icc); icc.dwICC = ICC_WIN95_CLASSES/*|ICC_COOL_CLASSES|ICC_DATE_CLASSES| ICC_PAGESCROLLER_CLASS|ICC_USEREX_CLASSES*/; InitCommonControlsEx(&icc); InitPropertyGrid(hInstance);
为了使事情变得简单,我在控件的pseudo构造函数中合并了这个步骤。它只被调用一次,即在第一次实例化一个新的Propertygrid控件时。 隐藏,复制Code
HWND New_PropertyGrid(HWND hParent, DWORD dwID) { static ATOM aPropertyGrid = 0; static HWND hPropertyGrid; HINSTANCE hinst = (HINSTANCE)GetWindowLongPtr(hParent, GWLP_HINSTANCE); //Only need to register the property grid once if (!aPropertyGrid) aPropertyGrid = InitPropertyGrid(hinst); hPropertyGrid = CreateWindowEx(0, g_szClassName, _T(""), WS_CHILD, 0, 0, 0, 0, hParent, (HMENU)dwID, hinst, NULL); return hPropertyGrid; }
接下来声明一个Propertygrid项,然后初始化它。项必须是lpszCatalog参数标识的组或目录的一部分。下面的代码片段演示了如何将属性加载到网格中。 隐藏,复制Code
BOOL Main_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { HWND hPropGrid = GetDlgItem(hwnd,IDC_PG); PROPGRIDITEM Item; //Initialize Item in order to prevent unassigned pointers PropGrid_ItemInit(Item); Item.lpszCatalog = _T("Edit, Static, and Combos"); //Static text Item.lpszPropName = _T("Edit Field"); //Static text Item.lpCurValue = (LPARAM) gDemoData.strProp1; //Depends on ItemType Item.lpszPropDesc = _T("This field is editable"); //Static text Item.iItemType = PIT_EDIT; PropGrid_AddItem(hPropGrid, &Item); // //Add other items // PropGrid_ShowToolTips(hPropGrid,TRUE); //Show Tool Tips (Default = no tool tips) PropGrid_ExpandAllCatalogs(hPropGrid); //Load all properties in display return TRUE; }
下图标识了由这些PROPGRIDITEM结构字段填充的Propertygrid的各个字段。 下面是另一个显示文件对话框项的代码片段: 隐藏,复制Code
//Declare and initialize a prop grid file dialog item PROPGRIDFDITEM ItemFd = {0}; ItemFd.lpszDlgTitle = _T("Choose File"); //Static text ItemFd.lpszFilePath = gDemoData.strProp8; //Text // Standard file dialog filter string array (a double null terminated string) ItemFd.lpszFilter = _T("Text files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0"); ItemFd.lpszDefExt = _T("txt"); //Static text //Prop grid item current value takes a pointer to a prop grid file dialog item struct Item.lpszPropName = _T("Choose file"); //Static text Item.lpCurValue = (LPARAM) &ItemFd; Item.iItemType = PIT_FILE; PropGrid_AddItem(hPropGrid, &Item);
除了声明Propertygrid项然后初始化它之外。声明和初始化PROPGRIDFDITEM也是必要的。下图标识了由这些PROPGRIDFDITEM结构字段填充的文件对话框弹出的各个字段。 最后一个片段显示了一个可编辑的组合项: 隐藏,复制Code
//Initialize Demo Data _tmemset(gDemoData.strProp3,_T('\0'),NELEMS(gDemoData.strProp3)); // Combo choices string array (a double null terminated string) TCHAR szzChoices[] = _T("Robert\0Wally\0Mike\0Vickie\0Leah\0Arthur\0"); gDemoData.dwChoicesCount = NELEMS(szzChoices); gDemoData.szzChoices = (LPTSTR) malloc(gDemoData.dwChoicesCount * sizeof(TCHAR)); _tmemmove(gDemoData.szzChoices, szzChoices, gDemoData.dwChoicesCount); // // Skip some stuff // Item.lpszPropName = _T("Editable Combo Field"); Item.lpCurValue = (LPARAM) gDemoData.strProp3; Item.lpszzCmbItems = gDemoData.szzChoices; Item.lpszPropDesc = _T("Press F4 to view drop down."); Item.iItemType = PIT_EDITCOMBO; PropGrid_AddItem(hPropGrid, &Item);
除了声明Propertygrid项然后初始化它之外。还需要填充lpszzCmbItems参数。在演示中,我希望能够向这个列表中添加项目,因此我通过malloc()创建了缓冲区。下图显示了由列表填充的下拉列表。 应用程序属性值可以动态更新,这是演示应用程序在WM_NOTIFY处理程序中使用的技术。 隐藏,复制Code
static BOOL Main_OnNotify(HWND hwnd, INT id, LPNMHDR pnm) { if(IDC_PG == id) { LPNMPROPGRID lpnmp = (LPNMPROPGRID)pnm; switch(lpnmp->iIndex) { case 0: _stprintf(gDemoData.strProp1, NELEMS(gDemoData.strProp1), #ifdef _UNICODE _T("%ls"), #else _T("%s"), #endif (LPTSTR)(PropGrid_GetItemData (pnm->hwndFrom,lpnmp->iIndex)->lpCurValue)); break; // // Other cases follow //
每当项值发生更改时,Propertygrid都会向控件的父控件发送通知。如果不需要动态更新,则忽略通知,只需在需要时请求数据。 这些示例展示了在Win32项目中简单实现控件的一些方法。为了在有用的上下文中演示这个类,我制作了一个演示,其中包括每种受支持项类型的代码。 下面是Propertygrid控件类的编程引用。 公共数据结构 PROPGRIDITEM PROPGRIDITEM结构指定或接收Propertygrid项的属性。 隐藏,复制Code
typedef struct tagPROPGRIDITEM { LPTSTR lpszCatalog; LPTSTR lpszPropName; LPTSTR lpszzCmbItems; LPTSTR lpszPropDesc; LPARAM lpCurValue; INT iItemType; } PROPGRIDITEM, *LPPROPGRIDITEM;
成员 lpszCatalog:目录(组)名称属性(项目)名称lpszzcmbitems:一个双空终止字符串列表(项目组合)。该字段仅对类型为PIT_COMBO和pit_editcombo lpszpropdesc的项目有效:属性(项目)描述iitemtype:属性(项目)类型标识符。值可以是下列值之一: PIT_EDIT -房地产项目类型:编辑pit_combo -房地产项目类型:下拉列表pit_editcombo -房地产项目类型:下拉(可编辑)pit_static -房地产项目类型:没有可编辑的文本pit_color——房地产项目类型:颜色pit_font -房地产项目类型:字体pit_file -房地产项目类型:文件选择对话框pit_folder -房地产项目类型:文件夹选择对话框pit_check -房地产项目类型:布尔pit_ip -房地产项目类型:IP地址pit_date -房地产项目类型:Date pit_time -属性项类型:Date &属性项类型:Catalog lpCurValue:属性(项目)值。数据类型取决于项目类型,如下所示: PIT_EDIT、PIT_COMBO PIT_EDITCOMBO, PIT_STATIC PIT_FOLDER -文本pit_color pit_font COLORREF价值指向PROPGRIDFONTITEM struct pit_file——指针PROPGRIDFDITEM struct pit_check -一个BOOL值pit_ip DWORD值pit_date, PIT_TIME和PIT_DATETIME——指向SYSTEMTIME结构体的指针 PROPGRIDFONTITEM PROPGRIDFONTITEM结构指定或接收类型为PIT_FONT的Propertygrid项的属性。 隐藏,复制Code
typedef struct tagPROPGRIDFONTITEM { LOGFONT logFont; COLORREF crFont; } PROPGRIDFONTITEM, *LPPROPGRIDFONTITEM;
成员 逻辑字体结构:文字颜色 PROPGRIDFDITEM PROPGRIDFDITEM结构指定或接收类型为PIT_FILE的Propertygrid项的属性。 隐藏,复制Code
typedef struct tagPROPGRIDFDITEM { LPTSTR lpszDlgTitle; LPTSTR lpszFilePath; LPTSTR lpszFilter; LPTSTR lpszDefExt; } PROPGRIDFDITEM, *LPPROPGRIDFDITEM;
成员 lpszDlgTitle:字体对话框标题lpszfilepath:初始路径lpszfilter:双空终止字符串(过滤项)列表lpszdefext:默认扩展名 消息和宏 配置控件以使用Windows messages执行所需操作。为了简化此操作并作为记录消息的一种方法,我为每个消息创建了宏。如果您喜欢显式地调用SendMessage()或PostMessage(),请参考头中的宏defs来使用。 PropGrid_AddItem 向Propertygrid添加项。项目被附加到它们各自的目录中。 隐藏,复制Code
INT PropGrid_AddItem( HWND hwndCtl LPPROPGRIDITEM lpItem ); /*Parameters hwndCtl Handle to the Propertygrid control. lpItem Pointer to a Propertygrid item. Return Values The zero-based index of the item in the grid. If an error occurs, the return value is LB_ERR. If there is insufficient space to store the new string, the return value is LB_ERRSPACE.*/
PropGrid_DeleteItem 删除Propertygrid中指定位置的项。 隐藏,复制Code
INT PropGrid_DeleteItem( HWND hwndCtl INT index ); /*Parameters hwndCtl Handle to the Propertygrid control. index The zero-based index of the item to delete. Return Values A count of the items remaining in the grid. The return value is LB_ERR if the index parameter specifies an index greater than the number of items in the list.*/
PropGrid_Enable 启用或禁用Propertygrid控件。 隐藏,复制Code
VOID PropGrid_Enable( HWND hwndCtl BOOL fEnable ); /*Parameters hwndCtl Handle to the Propertygrid control. fEnable TRUE to enable the control, or FALSE to disable it. Return Values No return value.*/
PropGrid_GetCount 获取Propertygrid中的项数。 隐藏,复制Code
INT PropGrid_GetCount( HWND hwndCtl ); /*Parameters hwndCtl Handle to the Propertygrid control. Return Values The number of items.*/
PropGrid_GetCurSel 获取Propertygrid中当前选定项的索引。 隐藏,复制Code
INT PropGrid_GetCurSel( HWND hwndCtl ); /*Parameters hwndCtl Handle to the Propertygrid control. Return Values The zero-based index of the selected item. If there is no selection, the return value is LB_ERR.*/
PropGrid_GetHorizontalExtent 获取可水平滚动Propertygrid的宽度(可滚动宽度)。 隐藏,复制Code
INT PropGrid_GetHorizontalExtent( HWND hwndCtl ); /*Parameters hwndCtl Handle to the Propertygrid control. Return Values The scrollable width, in pixels, of the Propertygrid.*/
PropGrid_GetItemData 获取与指定的Propertygrid项关联的PROPGRIDITEM。 隐藏,复制Code
LPPROPGRIDITEM PropGrid_GetItemData( HWND hwndCtl INT index ); /*Parameters hwndCtl Handle to the Propertygrid control. index The zero-based index of the item. Return Values A pointer to a PROPGRIDITEM object.*/
PropGrid_GetItemHeight 检索Propertygrid中所有项的高度。 隐藏,复制Code
INT PropGrid_GetItemHeight( HWND hwndCtl ); /*Parameters hwndCtl Handle to the Propertygrid control. Return Values The height, in pixels, of the items, or LB_ERR if an error occurs.*/
PropGrid_GetItemRect 获取当前在Propertygrid中显示的限定Propertygrid项的矩形的尺寸。 隐藏,复制Code
INT PropGrid_GetItemRect( HWND hwndCtl INT index RECT* lprc ); /*Parameters hwndCtl Handle to the Propertygrid control. index The zero-based index of the item in the Propertygrid. lprc A pointer to a RECT structure that receives the client coordinates for the item in the Propertygrid. Return Values If an error occurs, the return value is LB_ERR.*/
PropGrid_GetSel 获取项的选择状态。 隐藏,复制Code
INT PropGrid_GetSel( HWND hwndCtl INT index ); /*Parameters hwndCtl Handle to the Propertygrid control. index The zero-based index of the item. Return Values If the item is selected, the return value is greater than zero; otherwise, it is zero. If an error occurs, the return value is LB_ERR.*/
PropGrid_ResetContent 从Propertygrid中删除所有项。 隐藏,复制Code
INT PropGrid_ResetContent( HWND hwndCtl ); /*Parameters hwndCtl Handle to the Propertygrid control. Return Values The return value is not meaningful.*/
PropGrid_SetCurSel 在Propertygrid中设置当前选定的项。 隐藏,复制Code
INT PropGrid_SetCurSel( HWND hwndCtl INT index ); /*Parameters hwndCtl Handle to the Propertygrid control. index The zero-based index of the item to select, or -1 to clear the selection. Return Values If an error occurs, the return value is LB_ERR. If the index parameter is -1, the return value is LB_ERR even though no error occurred.*/
PropGrid_SetHorizontalExtent 设置可以水平滚动Propertygrid的宽度(可滚动宽度)。如果Propertygrid的宽度小于此值,则水平滚动条将水平滚动Propertygrid中的项目。如果Propertygrid的宽度等于或大于此值,则将隐藏水平滚动条。 隐藏,复制Code
VOID PropGrid_SetHorizontalExtent( HWND hwndCtl INT cxExtent ); /*Parameters hwndCtl Handle to the Propertygrid control. cxExtent The number of pixels by which the grid can be scrolled. Return Values No return value.*/
PropGrid_SetItemData 设置与指定的Propertygrid项关联的PROPGRIDITEM。 隐藏,复制Code
INT PropGrid_SetItemData( HWND hwndCtl INT index LPPROPGRIDITEM data ); /*Parameters hwndCtl Handle to the Propertygrid control. index The zero-based index of the item. data The item data to set. Return Values If an error occurs, the return value is LB_ERR.*/
PropGrid_SetItemHeight 设置Propertygrid中所有项的高度。 隐藏,复制Code
INT PropGrid_SetItemHeight( HWND hwndCtl INT cy ); /*Parameters hwndCtl Handle to the Propertygrid control. cy The height of the items, in pixels. Return Values If the height is invalid, the return value is LB_ERR.*/
PropGrid_ExpandCatalogs 在Propertygrid中展开某些指定的目录。 隐藏,复制Code
VOID PropGrid_ExpandCatalogs( HWND hwndCtl LPTSTR lpszzCatalogs ); /*Parameters hwndCtl Handle to the Propertygrid control. lpszzCatalogs The list of catalog names each terminated by a null (\0). Return Values No return value.*/
PropGrid_ExpandAllCatalogs 展开Propertygrid中的所有目录。 隐藏,复制Code
VOID PropGrid_ExpandAllCatalogs( HWND hwndCtl ); /*Parameters hwndCtl Handle to the Propertygrid control. Return Values No return value.*/
PropGrid_CollapseCatalogs 在Propertygrid中折叠某些指定的目录。 隐藏,复制Code
VOID PropGrid_CollapseCatalogs( HWND hwndCtl LPTSTR lpszzCatalogs ); /*Parameters hwndCtl Handle to the Propertygrid control. lpszzCatalogs The list of catalog names each terminated by a null (\0). Return Values No return value.*/
PropGrid_CollapseAllCatalogs 在Propertygrid中折叠所有目录。 隐藏,复制Code
VOID PropGrid_CollapseAllCatalogs( HWND hwndCtl ); /*Parameters hwndCtl Handle to the Propertygrid control. Return Values No return value.*/
PropGrid_ShowToolTips 在Propertygrid中显示或隐藏工具提示。 隐藏,复制Code
VOID PropGrid_ShowToolTips( HWND hwndCtl BOOL fShow ); /*Parameters hwndCtl Handle to the Propertygrid control. fShow TRUE for tooltips; FALSE do not show tooltips. Return Values No return value.*/
PropGrid_ShowPropertyDescriptions 在Propertygrid中显示或隐藏属性描述窗格。 隐藏,复制Code
VOID PropGrid_ShowPropertyDescriptions( HWND hwndCtl BOOL fShow ); /*Parameters hwndCtl Handle to the Propertygrid control. fShow TRUE for descriptions; FALSE do not show description pane Return Values No return value.*/
,PropGrid_SetFlatStyleChecks 设置复选框的外观。 隐藏,复制Code
VOID PropGrid_SetFlatStyleChecks( HWND hwndCtl BOOL fFlat ); /*Parameters hwndCtl Handle to the Propertygrid control. fFlat TRUE for flat checkboxes, or FALSE for standard checkboxes. Return Values No return value.*/
道具Grid_ItemInit 初始化项目结构。 隐藏,复制Code
VOID PropGrid_ItemInit( PROPGRIDITEM pgi ); /*Parameters pgi The newly declared PROPGRIDITEM struct. Return Values No return value.*/
通知 Propertygrid控件通过WM_NOTIFY提供通知。这些通知消息的lParam参数指向NMPROPGRID结构。 NMPROPGRID NMPROPGRID结构包含有关Propertygrid控件通知消息的信息。 隐藏,复制Code
typedef struct tagNMPROPGRID { NMHDR hdr; INT iIndex; } NMPROPGRID, *LPNMPROPGRID; /*Members hdr Specifies an NMHDR structure. The code member of the NMHDR structure contains the following notification code that identifies the message being sent: PGN_PROPERTYCHANGE. iIndex Index of a Propertygrid item. Remarks The address of this structure is specified as the lParam parameter of the WM_NOTIFY message for all Propertygrid control notification messages.*/
PGN_PROPERTYCHANGE PGN_PROPERTYCHANGE通知消息通知Propertygrid控件的父窗口,项目的数据已经更改。这个通知消息以WM_NOTIFY消息的形式发送。 隐藏,复制Code
PGN_PROPERTYCHANGE pnm = (NMPROPGRID *) lParam; /*Parameters pnm Pointer to an NMPROPGRID structure that specifies an item index of the Propertygrid item that has changed. Return Values No return value.*/
设计注意事项 在最近一个学年的休息期间,我的两个孩子决定他们想要花一天的时间在父亲的工作,看看他在办公室做的所有很酷的事情。我同意在不同的日子带他们去,并在那些我认为会让每个孩子感兴趣的日子里计划一些与工作相关的活动。午饭时间,我带他们去看了一个有趣的展览,这个展览是由一家公司举办的,它位于工坊附近的一个商业园区——工艺博物馆。 这个展览给我留下深刻印象的一件事是对细节的关注和爱好者在微型复制品上投入的许多小时的耐心工作,事实上,大部分机械引擎都在运转。在项目中投入的技能和工艺即使是不经意的一瞥也很明显。 我学到的一些东西是,一个机械发动机运行良好(或在所有的那件事),零件必须加工精确的公差和公差堆叠。一些最可靠的发动机设计往往简单但经过深思熟虑。 理想情况下,使用Propertygrid,您只有两个窗口可以使用,一个用于显示数据,另一个用于编辑数据。最初,我使用一个链接列表来存储指向所有项数据的指针,列表框显示目录和可见数据。后来,当我开始充实内容时,我意识到我一直在向内部数据结构中添加一些模仿列表框特性的特性,比如索引。这时,我用windows Listbox类的第二个最小实例替换了我的列表,并大大简化了代码。我意识到我可以透明地支持现有列表框消息的更大子集,从而在减少开发工作量的同时为用户提供更大的灵活性。 这个Propertygrid使用7个不同的窗口控件来编辑属性。我从Listview控件获得了一个提示,它只在一个项目被编辑时创建一个编辑框,然后在编辑完成时销毁它。因此,Propertygrid大多数时候只有一个编辑器实例。例外情况是带有两个编辑器的日期和时间字段,或者不需要任何编辑器的静态和复选框字段。这减少了管理编辑器时的一些开销,并使代码实现更干净、更优雅。 除了列表框和编辑器之外,还有两个可选组件——静态描述字段和工具提示。如果用户配置Propertygrid以显示它们,就会创建这些属性。 针对win32 /64 SDK开发人员的提示和技巧 有很多东西我不会在这里讨论,但我将在前面的文章中详细介绍。对于那些对我如何处理自定义控件的总体结构以及子类化窗口的基础感兴趣的人,请查看Win32 SDK Data Grid View Made Easy[^]。除了少数例外,我想在这里分享的技巧都与绘制控件有关。 老板画技巧 网格的大部分外观和感觉是通过所有者绘制一个列表框来实现的。我看到开发人员在画东西时犯的一个错误是没有使用库存对象(钢笔和画笔)和系统颜色。下面是一个如何不绘制控件的示例。 隐藏,复制Code
VOID Grid_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDIS) { // // Skip stuff // SetBkMode(lpDIS->hDC, TRANSPARENT); FillRect(lpDIS->hDC, &rectPart1, CreateSolidBrush(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ? RGB(0,0,255) : RGB(255,255,255)));//Blue and White //Write the property name oldFont = SelectObject(lpDIS->hDC, Font_SetBold(lpDIS->hwndItem, FALSE)); SetTextColor(lpDIS->hDC, nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ? RGB(255,255,255) : RGB(0,0,0));//White and Black DrawText(lpDIS->hDC, pItem->lpszPropName, _tcslen(pItem->lpszPropName), MAKE_PRECT(rectPart1.left + 3, rectPart1.top + 3, rectPart1.right - 3, rectPart1.bottom + 3), DT_NOCLIP | DT_LEFT | DT_SINGLELINE); DrawBorder(lpDIS->hDC, &rectPart1, BF_TOPRIGHT, RGB(192,192,192));//Shade of Grey
上面的代码片段有什么问题?一个小测试就可以很快地显示错误。随着应用程序在调试器中运行,我将桌面的显示属性更改为Plum… 讨厌的东西!那不是我想要的。让我们以正确的方式绘制它,并让用户决定控件的外观。 隐藏,复制Code
VOID Grid_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDIS) { // // Skip stuff // SetBkMode(lpDIS->hDC, TRANSPARENT); FillRect(lpDIS->hDC, &rectPart1, GetSysColorBrush(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ? COLOR_HIGHLIGHT : COLOR_WINDOW)); //Write the property name oldFont = SelectObject(lpDIS->hDC, Font_SetBold(lpDIS->hwndItem, FALSE)); SetTextColor(lpDIS->hDC, GetSysColor(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); DrawText(lpDIS->hDC, pItem->lpszPropName, _tcslen(pItem->lpszPropName), MAKE_PRECT(rectPart1.left + 3, rectPart1.top + 3, rectPart1.right - 3, rectPart1.bottom + 3), DT_NOCLIP | DT_LEFT | DT_SINGLELINE); DrawBorder(lpDIS->hDC, &rectPart1, BF_TOPRIGHT, GetSysColor(COLOR_BTNFACE));
李子完美! 无边界的控制 他们说这是不可能的……然而,这个日期时间选择器看起来几乎是平的,实际上它几乎是不可见的,但是它是如何做到的呢? 各种编辑器的子类主要获得访问按键事件,但为什么不重写WM_PAINT以及?这正是我所做的,除了Comboboxes之外,一个方法可以解决所有问题。 隐藏,复制Code
BOOL Editor_OnPaint(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc = GetWindowDC(hwnd); RECT rect; // First let the system do its thing CallWindowProc((WNDPROC)GetProp(hwnd, TEXT("Wprc")), hwnd, msg, wParam, lParam); // Next obliterate the border GetWindowRect(hwnd, &rect); MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT) &rect.left, 2); rect.top += 2; rect.left += 2; DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW)); rect.top += 1; rect.left += 1; rect.bottom += 1; rect.right += 1; DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW)); ReleaseDC(hwnd, hdc); return TRUE; }
这里,我从DatePicker_Proc()调用方法 隐藏,复制Code
static LRESULT CALLBACK DatePicker_Proc (HWND hDate, UINT msg, WPARAM wParam, LPARAM lParam) { HWND hGParent = GetParent(GetParent(hDate)); // Note: Instance data is attached to datepicker's grandparent Control_GetInstanceData(hGParent, &g_lpInst); if (WM_DESTROY == msg) // Unsubclass the control { SetWindowLongPtr(hDate, GWLP_WNDPROC, (DWORD)GetProp(hDate, TEXT("Wprc"))); RemoveProp(hDate, TEXT("Wprc")); return 0; } else if (WM_PAINT == msg) // Obliterate border { return Editor_OnPaint(hDate, msg, wParam, lParam); } // // Process other messages //
正如我所说的,组合框有一点不同,但应用的原则是相同的。下面是Combobox的操作。 隐藏,复制Code
else if (WM_PAINT == msg) // Obliterate border (differs from standard method) { // First let the system do its thing CallWindowProc((WNDPROC)GetProp(hwnd, TEXT("Wprc")), hwnd, msg, wParam, lParam); // Next obliterate the border HDC hdc = GetWindowDC(hwnd); RECT rect; GetClientRect(hwnd, &rect); rect.bottom -= 2; DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW)); rect.top += 1; rect.left += 1; DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW)); ReleaseDC(hwnd, hdc); return TRUE; }
简单的复选框 复选框根本不是控件,而是使用DrawFrameControl()绘制的,它绘制了一些一个经典样式的复选框的映射表示如下:,但是通过额外的三行代码,我将它变成一个漂亮的平面复选框,如下:。这是怎么做的。 隐藏,复制Code
DrawFrameControl(lpDIS->hDC, &rect3, DFC_BUTTON, DFCS_BUTTONCHECK | (_tcsicmp(pItem->lpszCurValue, CHECKED) == 0 ? DFCS_CHECKED : 0)); //Make border thin FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_BTNFACE)); InflateRect(&rect3, -1, -1); FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_WINDOW));
目录切换 不需要位图或资源,只需要像这样画出这个小框(rect2定义了一个边上像素为奇数的正方形)。 隐藏,复制Code
FillRect(lpDIS->hDC, &rect2, GetSysColorBrush(COLOR_WINDOW)); FrameRect(lpDIS->hDC, &rect2, GetStockObject(BLACK_BRUSH)); POINT ptCtr; ptCtr.x = (LONG) (rect2.left + (WIDTH(rect2) * 0.5)); ptCtr.y = (LONG) (rect2.top + (HEIGHT(rect2) * 0.5)); InflateRect(&rect2, -2, -2); DrawLine(lpDIS->hDC, rect2.left, ptCtr.y, rect2.right, ptCtr.y); //Make a - if (pItem->fCollapsed) //Convert to + DrawLine(lpDIS->hDC, ptCtr.x, rect2.top, ptCtr.x, rect2.bottom);
对复合控件进行子类化 编辑控件的子类化是相当直接的。它只包含一个窗口,并且它的消息都是通过同一个程序路由的。键盘消息通过子控件路由,如果我们不子类化子控件,父控件的proc将永远看不到它们。如果有一种简单的方法来子类化这些孩子…也有。当一个控件被创建时,它立即发送一个WM_COMMAND消息给它的父控件,通常是EN_SETFOCUS通知代码,如果子控件是一个编辑或LBN_SETFOCUS为一个列表框。我们不关心通知,我们想要孩子的句柄第一次子类化它。 隐藏,收缩,复制Code
static LRESULT CALLBACK IpEdit_Proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // // Skip a bunch of stuff // else //Handle messages (events) in the parent ipedit control { if (WM_PAINT == msg) // Obliterate border { return Editor_OnPaint(hwnd, msg, wParam, lParam); } else if (WM_COMMAND == msg) { // Each of the control's edit fields posts notifications on showing // the first time they do so we'll grab and subclass them. HWND hwndCtl = GET_WM_COMMAND_HWND(wParam, lParam); { WNDPROC lpfn = (WNDPROC)GetProp(hwndCtl, TEXT("Wprc")); if (NULL == lpfn) { //Subclass child and save the OldProc SetProp(hwndCtl, TEXT("Wprc"), (HANDLE)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC)); SubclassWindow(hwndCtl, IpEdit_Proc); } } } } return CallWindowProc((WNDPROC)GetProp(hwnd, TEXT("Wprc")), hwnd, msg, wParam, lParam); }
我将编辑控件的子类化到与它们的父类相同的proc中。在此过程中,当消息通过此过程路由时,我需要注意区分子进程和父进程。 隐藏,收缩,复制Code
static LRESULT CALLBACK ComboBox_Proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static TCHAR buf[MAX_PATH]; HWND hGParent = GetParent(GetParent(hwnd)); // Note: Instance data is attached to combo's grandparent // or the edit field's greatgrandparent GetClassName(hwnd, buf, NELEMS(buf)); BOOL fEdit = (0 == _tcsicmp(buf, WC_EDIT)); if (fEdit) Control_GetInstanceData(GetParent(hGParent), &g_lpInst); else Control_GetInstanceData(hGParent, &g_lpInst); if (WM_DESTROY == msg) //Unsubclass the combobox or child edit control { SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD)GetProp(hwnd, TEXT("Wprc"))); RemoveProp(hwnd, TEXT("Wprc")); return 0; } // // Skip some stuff // else if (WM_COMMAND == msg) { // The editable combo's edit box posts a notification on loading // the first time it does so we'll grab and subclass it. HWND hwndCtl = GET_WM_COMMAND_HWND(wParam, lParam); { WNDPROC lpfn = (WNDPROC)GetProp(hwndCtl, TEXT("Wprc")); if (NULL == lpfn) { // Do not subclass the drop down list GetClassName(hwndCtl, buf, NELEMS(buf)); if (0 == _tcsicmp(buf, WC_EDIT)) { //Subclass edit and save the old proc SetProp(hwndCtl, TEXT("Wprc"), (HANDLE)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC)); SubclassWindow(hwndCtl, ComboBox_Proc); } } } } // // Skip some more stuff // return CallWindowProc((WNDPROC)GetProp(hwnd, TEXT("Wprc")), hwnd, msg, wParam, lParam); }
在最初使用这种方法制作组合框时,我遇到了一个问题。我无意中子类化了下拉列表。要明白,它的父元素实际上并不是组合框!这样做的原因是下拉列表将被父组合框的客户端矩形剪切。列表框发送WM_COMMAND通知给组合框,就好像它是父控件一样。但是如果使用GetParent(),返回的HWND是桌面。这意味着对Control_GetInstanceData()的调用将返回NULL,因为它无法找到与Propertygrid的这个实例一起存储的属性。 鼠标滚轮的错误 事实证明,使用LBS_OWNERDRAWVARIABLE样式创建的Listbox控件不能正确处理鼠标滚轮。滚动效果是非常跳跃的;如此糟糕的事实,如果你想要使用那种风格,它是明智的拦截WM_MOUSEWHEEL禁用它或写你自己的处理程序。 检测列表框中的开始和结束滚动事件 Listbox没有滚动条组件,相反,它可能使用DrawFrameControl()在控件的非客户端区域绘制滚动条。因此,不能将滚动条子类化来检测鼠标事件。下面的代码片段演示了一种解决此问题并检测开始和结束滚动的方法。 隐藏,收缩,复制Code
static LRESULT CALLBACK ListBox_Proc(HWND hList, UINT msg, WPARAM wParam, LPARAM lParam) { HWND hParent = GetParent(hList); // Note: Instance data is attached to listbox's parent Control_GetInstanceData(hParent, &g_lpInst); switch (msg) { // // Skip stuff // case WM_MBUTTONDOWN: case WM_NCLBUTTONDOWN: //The listbox doesn't have a scrollbar component, it draws a scroll // bar in the non-client area of the control. A mouse click in the // non-client area then, equals clicking on a scroll bar. A click // on the middle mouse button equals pan, we'll handle that as if // it were a scroll event. ListBox_OnBeginScroll(hList); g_lpInst->fScrolling = TRUE; break; case WM_SETCURSOR: //Whenever the mouse leaves the non-client area of a listbox, it // fires a WM_SETCURSOR message. The same happens when the middle // mouse button is released. We can use this behavior to detect the // completion of a scrolling operation. if (g_lpInst->fScrolling) { ListBox_OnEndScroll(hList); g_lpInst->fScrolling = FALSE; } break; // // more stuff //
最后的评论 我用Doxygen[^]注释为那些可能觉得有用或有用的人记录了这个源代码。感谢您的反馈。 历史 2010年4月30日:版本1.0.0.0 8月3日2010:版本1.1.0.0 -固定错误PropGrid_GetItemData()的一个项目类型PIT_FILE返回一个空字符串,而不是文件路径的10月28日2010:版本1.2.0.0——一些Bug修复和改进(源代码注释)09年12月,2010:版本1.3.0.0—改进了控件的选项卡行为,PropGrid_GetItemData()为类型为PIT_FILE的项返回的PROPGRIDITEM的lpCurValue成员现在是根据文档指向PROPGRIDFDITEM结构的指针,而不仅仅是文件路径字符串。2013年11月11日:版本1.4.0.0——修改了编辑器的绘制方式,以便维护。XP和Win7之间一致的外观和感觉2013年11月16日:1.5.0.0版本-修正了X64操作下的错误情况。2013年11月27日:1.6.0.0版本——修正了一些在字段编辑时调整网格大小导致数据持久化的错误。修正了日期字段的错误。2014年9月14日:1.7.0.0版本-修正了一些小错误。2016年2月27日:版本1.8.0.0—添加了pg_flatcheck消息和PropGrid_SetFlatStyleChecks()宏。2016年3月30日:版本1.9.0.0修复了一些与IP地址字段有关的错误。添加MSVC项目下载。2018年11月18日:2.0.0.0版本-修复了PropGrid_ResetContent(). 修正了WM_NOTIFY消息的触发,限制它们每字段编辑一次。增加了一些要求的功能-工厂创建的窗口现在默认可见,增加了滚动条下拉菜单,键盘快捷键,等等…2018年11月21日:2.1.0.0版本——修正了两个bug,并支持项目结构中的用户数据成员。感谢Jakob提供了这些补丁/特性。 本文转载于:http://www.diyabc.com/frontweb/news287.html