Contents
I once wrote a utility application that needed to handle a multitude of parameters associated with a remote device. I used property sheet style tab pages with individual fields for each property and the number of control fields multiplied until the application teetered under the weight of all those windows. Since then, I have become a big fan of Datagrid
and Propertygrid
style UIs. Ideally, at any one time you have one window displaying data and another to edit the data, yet maintain the illusion of a rich interface with many controls well organized. I wanted to have such an interface for my Win32 projects, something easy to use, ultra light weight, and professional looking.
Before starting to write this Propertygrid
, I did a little background research to see what kinds of solutions others had come up with. I noticed a promising property Listbox
by Noel Ramathal [^] as well as another by Runming Yan [^] that appears to be based upon Noel's work. I started from these examples but strove for something that had a similar look and feel to the Visual Studio Propertygrid
as well as the one used in the Pelles C IDE. In addition to this, I wanted to write this Propertygrid
as a message based custom Win32/64 control.
To begin, include the Propertygrid
control's header file in the project:
#include "propertyGrid.h"
This Propertygrid
control is a message based, custom control, and as such must be initialized before use. One way to handle this is to call the initializer in the WinMain()
method of the application just after the call to InitCommonControlsEx()
.
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
INITCOMMONCONTROLSEX icc;
WNDCLASSEX wcx;
ghInstance = hInstance;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icc);
InitPropertyGrid(hInstance);
To make things simple though, I combined this step in the control's pseudo constructor. It is called only once, the first time a new Propertygrid
control is instantiated.
HWND New_PropertyGrid(HWND hParent, DWORD dwID)
{
static ATOM aPropertyGrid = 0;
static HWND hPropertyGrid;
HINSTANCE hinst = (HINSTANCE)GetWindowLongPtr(hParent, GWLP_HINSTANCE);
if (!aPropertyGrid)
aPropertyGrid = InitPropertyGrid(hinst);
hPropertyGrid = CreateWindowEx(0, g_szClassName, _T(""),
WS_CHILD, 0, 0, 0, 0, hParent, (HMENU)dwID, hinst, NULL);
return hPropertyGrid;
}
Next declare a Propertygrid
item and then initialize it. An item must be part of a group or catalog as identified by the lpszCatalog parameter. Here is a snippet that demonstrates how properties are loaded into the grid.
BOOL Main_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
HWND hPropGrid = GetDlgItem(hwnd,IDC_PG);
PROPGRIDITEM Item;
PropGrid_ItemInit(Item);
Item.lpszCatalog = _T("Edit, Static, and Combos"); Item.lpszPropName = _T("Edit Field"); Item.lpCurValue = (LPARAM) gDemoData.strProp1; Item.lpszPropDesc = _T("This field is editable"); Item.iItemType = PIT_EDIT;
PropGrid_AddItem(hPropGrid, &Item);
PropGrid_ShowToolTips(hPropGrid,TRUE); PropGrid_ExpandAllCatalogs(hPropGrid);
return TRUE;
}
The following figure identifies the various fields of the Propertygrid
that are populated by these PROPGRIDITEM struct fields.
Here is another snippet showing a file dialog item:
PROPGRIDFDITEM ItemFd = {0};
ItemFd.lpszDlgTitle = _T("Choose File"); ItemFd.lpszFilePath = gDemoData.strProp8; ItemFd.lpszFilter = _T("Text files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0");
ItemFd.lpszDefExt = _T("txt");
Item.lpszPropName = _T("Choose file"); Item.lpCurValue = (LPARAM) &ItemFd;
Item.iItemType = PIT_FILE;
PropGrid_AddItem(hPropGrid, &Item);
In addition to declaring a Propertygrid
item and then initializing it. It is also necessary to declare and initialize a PROPGRIDFDITEM. The following figure identifies the various fields of the file dialog popup that are populated by these PROPGRIDFDITEM struct fields.
And finally a snippet showing an editable combo item:
_tmemset(gDemoData.strProp3,_T('\0'),NELEMS(gDemoData.strProp3));
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);
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);
In addition to declaring a Propertygrid
item and then initializing it. It is also necessary to populate the lpszzCmbItems parameter. In the demo, I want to be able to add items to this list so I created the buffer via malloc()
. The following figure shows the dropdown populated by the list.
Application property values can be updated dynamically, this is the technique used by the demo application in the WM_NOTIFY
handler.
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;
Each time an item value changes, the Propertygrid
sends a notification to the control's parent. If dynamic updates are not necessary, then ignore the notifications and simply request the data when desired.
These examples show some of the ways that the control can be simply implemented in a Win32 project. To demonstrate the class in a useful context, I put together a demo that includes code for each supported item type.
What follows is a programming reference for the Propertygrid
control class.
The PROPGRIDITEM
structure specifies or receives attributes for a Propertygrid
item.
typedef struct tagPROPGRIDITEM {
LPTSTR lpszCatalog;
LPTSTR lpszPropName;
LPTSTR lpszzCmbItems;
LPTSTR lpszPropDesc;
LPARAM lpCurValue;
LPVOID lpUserData;
INT iItemType;
} PROPGRIDITEM, *LPPROPGRIDITEM;
lpszCatalog
: The catalog (group) name lpszPropName
: The property (item) name lpszzCmbItems
: A double null
terminated list of strings (combo items). This field is only valid for items of type PIT_COMBO
and PIT_EDITCOMBO
lpszPropDesc
: The property (item) description iItemType
: The property (item) type identifier. The value may be one of the following:
PIT_EDIT
- Property item type: Edit PIT_COMBO
- Property item type: Dropdown list PIT_EDITCOMBO
- Property item type: Dropdown(editable) PIT_CHECKCOMBO
- Property item type: Dropdown checked list PIT_STATIC
- Property item type: Not editable text PIT_COLOR
- Property item type: Color PIT_FONT
- Property item type: Font PIT_FILE
- Property item type: File select dialog PIT_FOLDER
- Property item type: Folder select dialog PIT_CHECK
- Property item type: Boolean PIT_IP
- Property item type: IP Address PIT_DATE
- Property item type: Date PIT_TIME
- Property item type: Time PIT_DATETIME
- Property item type: Date & Time PIT_CATALOG
- Property item type: Catalog
lpCurValue
: The property (item) value. The data type depends on the item type as follows:
PIT_EDIT
, PIT_COMBO
, PIT_EDITCOMBO
, PIT_STATIC
and PIT_FOLDER
- Text PIT_COLOR
- A COLORREF
value PIT_FONT
- Pointer to a PROPGRIDFONTITEM
struct PIT_FILE
- Pointer to a PROPGRIDFDITEM
struct PIT_CHECK
- A BOOL
value PIT_IP
- A DWORD
value PIT_DATE
, PIT_TIME
and PIT_DATETIME
- Pointer to a SYSTEMTIME
struct
The PROPGRIDFONTITEM
structure specifies or receives attributes for a Propertygrid
item of type PIT_FONT
.
typedef struct tagPROPGRIDFONTITEM {
LOGFONT logFont;
COLORREF crFont;
} PROPGRIDFONTITEM, *LPPROPGRIDFONTITEM;
logFont
: Logical font struct crFont
: Text color
The PROPGRIDFDITEM
structure specifies or receives attributes for a Propertygrid
item of type PIT_FILE
.
typedef struct tagPROPGRIDFDITEM {
LPTSTR lpszDlgTitle;
LPTSTR lpszFilePath;
LPTSTR lpszFilter;
LPTSTR lpszDefExt;
} PROPGRIDFDITEM, *LPPROPGRIDFDITEM;
lpszDlgTitle
: The font dialog title lpszFilePath
: Initial path lpszFilter
: A double null
terminated list of strings (filter items) lpszDefExt
: The default extension
Configure the control to do what you want using Windows messages. To make this easy and as a way of documenting the messages, I created macros for each message. If you prefer to call SendMessage()
or PostMessage()
explicitly, please refer to the macro defs in the header for usage.
Add an item to a Propertygrid
. Items are appended to their respective catalogs.
INT PropGrid_AddItem(
HWND hwndCtl
LPPROPGRIDITEM lpItem
);
Deletes the item at the specified location in a Propertygrid
.
INT PropGrid_DeleteItem(
HWND hwndCtl
INT index
);
Enables or disables a Propertygrid
control.
VOID PropGrid_Enable(
HWND hwndCtl
BOOL fEnable
);
Gets the number of items in a Propertygrid
.
INT PropGrid_GetCount(
HWND hwndCtl
);
Gets the index of the currently selected item in a Propertygrid
.
INT PropGrid_GetCurSel(
HWND hwndCtl
);
Gets the width that a Propertygrid
can be scrolled horizontally (the scrollable width).
INT PropGrid_GetHorizontalExtent(
HWND hwndCtl
);
Gets the PROPGRIDITEM associated with the specified Propertygrid
item.
LPPROPGRIDITEM PropGrid_GetItemData(
HWND hwndCtl
INT index
);
Retrieves the height of all items in a Propertygrid
.
INT PropGrid_GetItemHeight(
HWND hwndCtl
);
Gets the dimensions of the rectangle that bounds a Propertygrid
item as it is currently displayed in the Propertygrid
.
INT PropGrid_GetItemRect(
HWND hwndCtl
INT index
RECT* lprc
);
Gets the selection state of an item.
INT PropGrid_GetSel(
HWND hwndCtl
INT index
);
Removes all items from a Propertygrid
.
INT PropGrid_ResetContent(
HWND hwndCtl
);
Sets the currently selected item in a Propertygrid
.
INT PropGrid_SetCurSel(
HWND hwndCtl
INT index
);
Set the width by which a Propertygrid
can be scrolled horizontally (the scrollable width). If the width of the Propertygrid
is smaller than this value, the horizontal scroll bar horizontally scrolls items in the Propertygrid
. If the width of the Propertygrid
is equal to or greater than this value, the horizontal scroll bar is hidden.
VOID PropGrid_SetHorizontalExtent(
HWND hwndCtl
INT cxExtent
);
Sets the PROPGRIDITEM associated with the specified Propertygrid
item.
INT PropGrid_SetItemData(
HWND hwndCtl
INT index
LPPROPGRIDITEM data
);
Sets the height of all items in a Propertygrid
.
INT PropGrid_SetItemHeight(
HWND hwndCtl
INT cy
);
Expand certain specified catalogs in a Propertygrid
.
VOID PropGrid_ExpandCatalogs(
HWND hwndCtl
LPTSTR lpszzCatalogs
);
Expand all catalogs in a Propertygrid
.
VOID PropGrid_ExpandAllCatalogs(
HWND hwndCtl
);
Collapse certain specified catalogs in a Propertygrid
.
VOID PropGrid_CollapseCatalogs(
HWND hwndCtl
LPTSTR lpszzCatalogs
);
Collapse all catalogs in a Propertygrid
.
VOID PropGrid_CollapseAllCatalogs(
HWND hwndCtl
);
Show or hide tooltips in the Propertygrid
.
VOID PropGrid_ShowToolTips(
HWND hwndCtl
BOOL fShow
);
Show or hide the property description pane in the Propertygrid
.
VOID PropGrid_ShowPropertyDescriptions(
HWND hwndCtl
BOOL fShow
);
PropGrid_SetFlatStyleChecks
Sets the appearance of the checkboxes.
VOID PropGrid_SetFlatStyleChecks(
HWND hwndCtl
BOOL fFlat
);
Initialize an item struct
.
VOID PropGrid_ItemInit(
PROPGRIDITEM pgi
);
The Propertygrid
control provides notifications via WM_NOTIFY
. The lParam
parameter of these notification messages points to an NMPROPGRID
structure.
The NMPROPGRID
structure contains information about a Propertygrid
control notification message.
typedef struct tagNMPROPGRID {
NMHDR hdr;
INT iIndex;
} NMPROPGRID, *LPNMPROPGRID;
The PGN_PROPERTYCHANGE
notification message notifies a Propertygrid
control's parent window that an item's data has changed. This notification message is sent in the form of a WM_NOTIFY
message.
PGN_PROPERTYCHANGE
pnm = (NMPROPGRID *) lParam;
During a recent break in the school year, two of my kids decided that they wanted to spend a day with Dad at work to see all of the cool stuff he gets to do at the office. I agreed to take them on separate days and planned work related activities on those days that I thought would interest each boy. At lunch time, I took them to an interesting exhibit hosted by a firm located in a business park near work, The Craftsmanship Museum [^].
One of the things that impressed me about this exhibit was the attention to detail and many hours of patient work the hobbyists invested in the miniature replicas and indeed, most of the mechanical engines run. The skill and craftsmanship invested in the projects are evident even at a casual glance.
Something I learned there was that for a mechanical engine to run well (or at all for that matter) parts have to be machined to exact tolerances and tolerances stack up. Some of the most reliable engine designs tend to be simple but well thought out.
Ideally, with a Propertygrid
, you have only 2 windows to work with, one to display data and one to edit data. Initially I used a link list to store pointers to all item data and the Listbox
displayed the catalogs and the visible data. Later as I began to flesh things out, I realized that I kept adding features to the internal data structure that mimicked Listbox
features such as indexing. At that point I substituted my list with a second, minimal instance of the windows Listbox
class and simplified the code considerably. I realized that I could transparently support a greater subset of existing Listbox
messages giving the user greater flexibility with reduced development effort.
This Propertygrid
uses seven different windows controls to edit properties. I took a cue from the Listview
control which creates an Editbox
only when an item is being edited and then destroys it when the edit is done. Consequently the Propertygrid
has only one instance of an editor most of the time. The exceptions being the date and time field with two editors, or the static and check box fields which do not require any editor. This cuts out some overhead when it comes to managing the editors and makes for a cleaner more elegant implementation in the code.
In addition to the Listbox
es and the editors, there are two optional components - a static
description field and tooltips. These are created if the user configures the Propertygrid
to display them.
There are a lot of things I won't cover here that I cover in some detail in previous articles. For those interested in how I approach the overall structure of a custom control, as well as the basics of subclassing windows, check out Win32 SDK Data Grid View Made Easy [^]. The tips I want to share here, with a few exceptions, have to do with aspects of drawing the control(s).
Most of the look and feel of the grid is achieved by owner drawing a Listbox
. One mistake that I have seen developers make when owner drawing something is not making use of the stock objects (pens and brushes) and system colors. The following is an example of how NOT to draw your control.
VOID Grid_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDIS)
{
SetBkMode(lpDIS->hDC, TRANSPARENT);
FillRect(lpDIS->hDC, &rectPart1,
CreateSolidBrush(nIndex ==
(UINT)ListBox_GetCurSel(lpDIS->hwndItem) ?
RGB(0,0,255) : RGB(255,255,255)));
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));
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));
What's wrong with the above code snippet? A little test will show the error quickly enough. With the application running in the debugger, I change the display properties of my desktop to Plum...
Yuck! That's not what I wanted. Let's draw it the right way and let the user decide how the control should look.
VOID Grid_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDIS)
{
SetBkMode(lpDIS->hDC, TRANSPARENT);
FillRect(lpDIS->hDC, &rectPart1,
GetSysColorBrush(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ?
COLOR_HIGHLIGHT : COLOR_WINDOW));
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));
Plum perfect!
They said it couldn't be done...and yet that date-time picker appears to be nearly flat, actually it is nearly invisible, but how?
The various editors are subclassed primarily to gain access to keypress events but why not override WM_PAINT
as well? That's exactly what I did, and with the exception of the Combobox
es, a single method worked for everything.
BOOL Editor_OnPaint(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc = GetWindowDC(hwnd);
RECT rect;
CallWindowProc((WNDPROC)GetProp(hwnd, TEXT("Wprc")), hwnd, msg, wParam, lParam);
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;
}
Here I call the method from the DatePicker_Proc()
static LRESULT CALLBACK DatePicker_Proc
(HWND hDate, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND hGParent = GetParent(GetParent(hDate));
Control_GetInstanceData(hGParent, &g_lpInst);
if (WM_DESTROY == msg) {
SetWindowLongPtr(hDate, GWLP_WNDPROC, (DWORD)GetProp(hDate, TEXT("Wprc")));
RemoveProp(hDate, TEXT("Wprc"));
return 0;
}
else if (WM_PAINT == msg) {
return Editor_OnPaint(hDate, msg, wParam, lParam);
}
As I said, the Combobox
es were a bit different but the applied principle is the same. Here's how it's done for the Combobox
.
void ComboBox_OnPaint(HWND hwnd)
{
FORWARD_WM_PAINT(hwnd, DefProc);
HDC hdc = GetWindowDC(hwnd);
RECT rect;
GetClientRect(hwnd, &rect);
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);
}
The check boxes are not controls at all but are drawn using DrawFrameControl()
which draws a bitmap representation of a classic styled checkbox like this:, yet with three additional lines of code I turn it into a nice flat checkbox like this:. Here's how it's done.
DrawFrameControl(lpDIS->hDC, &rect3, DFC_BUTTON, DFCS_BUTTONCHECK |
(_tcsicmp(pItem->lpszCurValue, CHECKED) == 0 ? DFCS_CHECKED : 0));
FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_BTNFACE));
InflateRect(&rect3, -1, -1);
FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_WINDOW));
No bitmap or resources necessary, just drew the little box like so (rect2
defines a square with an odd number of pixels on a side).
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); if (pItem->fCollapsed) DrawLine(lpDIS->hDC, ptCtr.x, rect2.top, ptCtr.x, rect2.bottom);
Subclassing an edit control is fairly straight forward. It consists of only a single window, and its messages are all routed through the same proc. Subclassing a Combobox
or IPedit
is another matter entirely. Keyboard messages get routed through child controls and the parent control's proc will never see them if we don't subclass the children too. If only there were an easy way to subclass those children... Well there is. When a control is created, right off it sends a WM_COMMAND
message to its parent usually with an EN_SETFOCUS
notification code if the child is an edit or an LBN_SETFOCUS
for a Listbox
. We don't care about the notification, we want the child's handle the first time, to subclass it.
static LRESULT CALLBACK IpEdit_Proc
(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
else {
if (WM_PAINT == msg) {
return Editor_OnPaint(hwnd, msg, wParam, lParam);
}
else if (WM_COMMAND == msg)
{
HWND hwndCtl = GET_WM_COMMAND_HWND(wParam, lParam);
{
WNDPROC lpfn = (WNDPROC)GetProp(hwndCtl, TEXT("Wprc"));
if (NULL == lpfn)
{
SetProp(hwndCtl, TEXT("Wprc"),
(HANDLE)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC));
SubclassWindow(hwndCtl, IpEdit_Proc);
}
}
}
}
return CallWindowProc((WNDPROC)GetProp(hwnd, TEXT("Wprc")),
hwnd, msg, wParam, lParam);
}
I subclassed the edit controls to the same proc as their parent. In doing so, I need to take care to differentiate between child and parent when messages route through this proc.
It turns out that a Listbox
control created with the LBS_OWNERDRAWVARIABLE
style does not handle the mouse wheel correctly. The scroll effect is very jumpy; so bad in fact, that if you want to use that style, it is advisable to intercept WM_MOUSEWHEEL
to either disable it or write your own handler.
The Listbox
doesn't have a scrollbar component, instead it draws a scroll bar in the non-client area of the control probably using DrawFrameControl()
. Consequently one cannot subclass the scrollbar in order to detect mouse events. The following snippet demonstrates one way to work around this and detect begin and end scroll.
static LRESULT CALLBACK ListBox_Proc(HWND hList, UINT msg,
WPARAM wParam, LPARAM lParam)
{
HWND hParent = GetParent(hList);
Control_GetInstanceData(hParent, &g_lpInst);
switch (msg)
{
case WM_MBUTTONDOWN:
case WM_NCLBUTTONDOWN:
ListBox_OnBeginScroll(hList);
g_lpInst->fScrolling = TRUE;
break;
case WM_SETCURSOR:
if (g_lpInst->fScrolling)
{
ListBox_OnEndScroll(hList);
g_lpInst->fScrolling = FALSE;
}
break;
I documented this source with Doxygen [^] comments for those that might find it helpful or useful. Your feedback is appreciated.
- April 30, 2010: Version 1.0.0.0
- August 3, 2010: Version 1.1.0.0 - Fixed Bug with
PropGrid_GetItemData()
where an item of type PIT_FILE
returned an empty string instead of the file path - October 28, 2010: Version 1.2.0.0 - Several bug fixes and improvements (annotated in the source code)
- December 09, 2010: Version 1.3.0.0 - Improved tabbing behavior with the control, the
lpCurValue
member of the PROPGRIDITEM
returned by PropGrid_GetItemData()
for an item of type PIT_FILE
is now a pointer to a PROPGRIDFDITEM
struct in accordance with the documentation instead of just the file path string. - November 11, 2013: Version 1.4.0.0 - Modified how the editors were drawn in order to maintain consistent look and feel between XP and Win7.
- November 16, 2013: Version 1.5.0.0 - Fixed bug condition under X64 operation.
- November 27, 2013: Version 1.6.0.0 - Fixed some bugs related to data persistance if the grid is resized during a field edit. Fixed a bug in the date field.
- September 14, 2014: Version 1.7.0.0 - Some minor bug fixes.
- February 27, 2016: Version 1.8.0.0 - Added PG_FLATCHECKS message and PropGrid_SetFlatStyleChecks() macro.
- March 30, 2016: Version 1.9.0.0 - Some bug fixes related to the IP address field. Added MSVC project download.
- November 18, 2018: Version 2.0.0.0 - Fixed crash issue related to PropGrid_ResetContent(). Fixed the firing of WM_NOTIFY messages, limiting them to once per field edit. Added some requested features - factory created window now default visible, added scrollbar to drop downs, keyboard shortcuts, etc...
- November 21, 2018: Version 2.1.0.0 - Encorporated two more bug fixes and support for user data member in the item struct. Thanks to Jakob for contributing these fixes/features.
- May 4, 2021: Version 2.2.0.0 - rewrote the section of code related to the comboboxes adding a checked combobox. Improved keyboard and tabbing related to the comboboxes, added bug fix suggestions, and cleaned up unnecessary comments.
- May 9, 2021: Version 2.3.0.0 - fixed bug preventing editor window from hiding during mouse wheel scroll in windows 10.
- Nov 23, 2021: Version 2.4.0.0 - Added missing GetProp to ComboList_OnRButtonDown