Contents
This is the third in a series of articles concerning Windows SDK development that I have written over the years. The first two are:
- Win32 SDK C Tab Control Made Easy [^]
- Win32 SDK C Autocomplete Combobox Made Easy [^]
Someone once asked me why I would want to code a control in C when I could utilize the object oriented power of C++. Indeed, the data grid view presented here is nothing new and there are quite a few projects out there, most of them MFC based, that are quite good. The answer to that question has more to do with my coding interests and personal, professional development than anything else.
I primarily develop automated test applications used in engineering and manufacturing. I started out coding engineering applications in VB.NET using vendor authored frameworks that wrapped pre-existing COM based frameworks that in turn wrapped C based IO communications frameworks. I was able to quickly throw together an application that worked but that was somewhat slow and bloated. In addition to this, I wanted to do things from the outset that the vendor supplied frameworks didn't make provision for, and many examples that I found were written in C and utilized the C based IO layer directly.
In my quest to learn enough C to adapt the code examples to my needs, I discovered BCX - The Basic to C Translator [^] a tool that allowed me to take understandable snippets of basic and convert them to C so that I could, for the first time, see beneath the abstraction layer of the higher language. This method of self education was particularly effective because my level of interest and concentration are peaked when trying to solve a particular problem -- something that does not necessarily happen when reading a textbook or technical publication.
In order to cement my understanding of C, I wrote some windows based applications using the Pelles C [^] compiler and IDE. At this point, I began to understand how Windows worked; especially messaging, callbacks, pointers, and function pointers. This in turn helped me understand how VB.NET and C# worked under the hood.
That, in a nutshell, is my journey. I now primarily code in C#, I like the syntax, and most code examples (VB.NET, JAVA, C++, MFC) can be adapted to my projects with little effort. That said, some of the more interesting challenges and lessons along the way come out of the C based projects I've written.
The Windows SDK provides a small selection of basic tools that handle the majority of user interface tasks but there is often a need for something extra. The fact that there are many examples out there of grid type controls attests to this. It would have been nice if Microsoft had included a grid in its standard collection of SDK widgets but in all fairness there is no implementation of an editable grid anywhere in the operating system. I wanted a message based custom grid control but I didn't want to write and draw the entire thing myself so I looked around at some of the MFC based grid projects out there and got some ideas for this custom control.
This is the easy part. First include the custom control's header file in the project:
#include "DataGridView.h"
This Data Grid View is a message based, custom control and as such must be initialized before use. In the WinMain()
method of the demo, call the control's initializer 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);
InitDataGridView(hInstance);
In the resource dialog Editor, place a custom control holder and give it the Windows class name for this control.
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.
COLORREF DataGridView_GetEditorBkColor(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the editor's back color.
BOOL DataGridView_SetEditorBkColor(
HWND hwnd
COLORREF clrBk
);
Parameters
hwnd
Handle to the data grid view control.
clrBk
Background color to set.
Return Values
Returns TRUE if successful or FALSE otherwise.
COLORREF DataGridView_GetEditorTxtColor(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the editor's text color.
BOOL DataGridView_SetEditorTxtColor(
HWND hwnd
COLORREF clrTxt
);
Parameters
hwnd
Handle to the data grid view control.
clrTxt
Editor's new text color.
Return Values
Returns TRUE if successful or FALSE otherwise.
HWND DataGridView_GetListViewControl(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the handle to the list view control if successful or NULL otherwise.
HWND DataGridView_GetEditControl(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the handle to the edit control if successful or NULL otherwise.
int DataGridView_GetColumnCount(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the number of columns.
int DataGridView_GetRowCount(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the number of rows.
BOOL DataGridView_SetResizableHeader(
HWND hwnd
BOOL fResizable
);
Parameters
hwnd
Handle to the data grid view control.
fResizable
Default TRUE columns may be resized. Otherwise column widths frozen.
Return Values
Returns TRUE if successful or FALSE otherwise.
COLORREF DataGridView_GetBkColor(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the data grid view's back color.
BOOL DataGridView_SetBkColor(
HWND hwnd
COLORREF clrBk
);
Parameters
hwnd
Handle to the data grid view control.
clrBk
Background color to set or the CLR_NONE value for no background color.
Return Values
Returns TRUE if successful or FALSE otherwise.
COLORREF DataGridView_GetTextColor(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the data grid view's text color.
BOOL DataGridView_SetTextColor(
HWND hwnd
COLORREF clrTxt
);
Parameters
hwnd
Handle to the data grid view control.
clrTxt
Data grid view's new text color.
Return Values
Returns TRUE if successful or FALSE otherwise.
COLORREF DataGridView_GetAltBkColor(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the data grid view's alternate back color.
BOOL DataGridView_SetAltBkColor(
HWND hwnd
COLORREF clrBk
BOOLfPaintByRow
);
Parameters
hwnd
Handle to the data grid view control.
clrBk
Alternate background color to set.
fPaintByRow
TRUE to paint alternate rows or FALSE to paint alternate columns.
Return Values
Returns TRUE if successful or FALSE otherwise.
COLORREF DataGridView_GetAltTextColor(
HWND hwnd
);
Parameters
hwnd
Handle to the data grid view control.
Return Values
Returns the data grid view's alternate text color.
BOOL DataGridView_SetAltTextColor(
HWND hwnd
COLORREF clrTxt
);
Parameters
hwnd
Handle to the data grid view control.
clrTxt
Data grid view's new alternate text color.
Return Values
Returns TRUE if successful or FALSE otherwise.
BOOL DataGridView_DisplayRowHeaders(
HWND hwnd
BOOL fShow
);
Parameters
hwnd
Handle to the data grid view control.
fShow
TRUE to draw row header buttons in first column.
Default FALSE to display normal first column.
Note: if this is set to TRUE column header and row header will
indicate selected cell by depressed button state.
Return Values
Returns TRUE if successful or FALSE otherwise.
BOOL DataGridView_SetRowHeight(
HWND hwnd
int iHeight
);
Parameters
hwnd
Handle to the data grid view control.
iHeight
Specifies the height, in pixels, of the rows.
Return Values
Returns TRUE if successful or FALSE otherwise.
BOOL DataGridView_ExtendLastColumn(
HWND hwnd
BOOL fExtend
);
Parameters
hwnd
Handle to the data grid view control.
fExtend
TRUE to extend last column to the edge of the control otherwise false.
Return Values
Returns TRUE if successful or FALSE otherwise.
In addition to the above macros, I have defined three that simplify setting up and loading data into the grid.
void DataGridView_AddColumn(
HWND hGrid
int nCol
int iWidth
LPSTR szColText
);
Parameters
hGrid
Handle to the data grid view control.
nCol
The number of this column.
iWidth
The width of this column.
szColText
The column header text.
Return Values
Returns nothing.
void DataGridView_AddColumns(
HWND hGrid
LPSTR* aryColTxt
int iColCount
);
Parameters
hGrid
Handle to the data grid view control.
aryColTxt
An array of column header text strings.
iColCount
The number of columns desired (length of aryColTxt).
Return Values
Returns nothing.
void DataGridView_AddRow(
HWND hGrid
LPSTR* aryItemTxt
int iColCount
);
Parameters
hGrid
Handle to the data grid view control.
aryItemTxt
An array of item and subitem strings.
iColCount
The number of columns desired (length of aryItemTxt).
Return Values
Returns nothing.
Writing a custom control begins with defining its windows class (this is not to be confused with a C++ class.) A look at the InitDataGridView()
function in DataGridView.c demonstrates how this is done.
BOOL InitDataGridView(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
ATOM aReturn;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_BYTEALIGNCLIENT;
wcex.lpfnWndProc = (WNDPROC)Grid_WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hCursor = NULL;
wcex.hbrBackground = (HBRUSH)(GetStockObject(GRAY_BRUSH)); wcex.lpszMenuName = NULL;
wcex.lpszClassName = g_szClassName;
wcex.hIcon = NULL;
wcex.hIconSm = NULL;
aReturn = RegisterClassEx(&wcex);
This information is used by Windows when CreateWindow()
or CreateWindowEx()
is called to create an instance of the control. In the demo, Windows creates the control from the resource script internally probably by calling CreateWindowEx()
.
The public
interface of a message based control is presented in the form of windows messages defined in the control's header file. Messages are passed to a control primarily by means of the SendMessage()
Windows API function. SendMessage()
returns the result codes returned by the control's callback procedure and in this way achieves the functionality of property getters and setters common to Object Oriented languages today.
When I set about to define messages for this control, I used the macro defs for the list view found in commctrl.h as a pattern and an example. My intent was to write the messages to mimic as closely as possible those of a standard Microsoft control. I noticed that certain values (BOOL
s and INT
s) are typically packed into the wParam
and others (struct
pointers and string
s) are typically packed into the lParam
. There are historical reasons for this [^] , however both parameters now are of the same type.
Here I define a message and its corresponding parameters in a macro:
#define DGVM_SETEDITORTEXTCLR WM_USER + 0x07
#define DataGridView_SetEditorTxtColor(hwnd,clrTxt) \
(BOOL)SNDMSG((hwnd),DGVM_SETEDITORTEXTCLR,0,(LPARAM)(COLORREF)(clrTxt))
The messages are handled within the control in its main callback procedure Grid_WndProc()
a portion of which you can see here.
static LRESULT CALLBACK Grid_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (Grid_GetInstanceData(hwnd, &g_lpInst))
{
if (LVM_SETITEMA == msg || LVM_SETITEMW == msg)
{
if (0 == ((LPLVITEM)lParam)->iItem) CallWindowProc((WNDPROC)ListView_Proc, g_lpInst->hwndList,
WM_KEYDOWN, VK_LEFT, 0L);
}
if (LVM_FIRST <= msg && msg <= LVM_FIRST + 181)
return SNDMSG(g_lpInst->hwndList, msg, wParam, lParam);
}
switch (msg)
{
HANDLE_MSG(hwnd, WM_CTLCOLOREDIT, Grid_OnCtlColorEdit);
HANDLE_MSG(hwnd, WM_CREATE, Grid_OnCreate);
HANDLE_MSG(hwnd, WM_DESTROY, Grid_OnDestroy);
HANDLE_MSG(hwnd, WM_GETDLGCODE, Grid_OnGetDlgCode);
HANDLE_MSG(hwnd, WM_SIZE, Grid_OnSize);
HANDLE_MSG(hwnd, WM_NOTIFY, Grid_OnNotify);
HANDLE_MSG(hwnd, WM_SETCURSOR, Grid_OnSetCursor);
HANDLE_MSG(hwnd, WM_SETFOCUS, Grid_OnSetFocus);
case WM_NOTIFYFORMAT:
#ifdef UNICODE
return NFR_UNICODE;
#else
return NFR_ANSI;
#endif
case DGVM_GETLISTVIEWCONTROL:
return (LRESULT)g_lpInst->hwndList;
case DGVM_GETEDITCONTROL:
return (LRESULT)g_lpInst->hwndEditor;
default: return DefWindowProc (hwnd, msg, wParam, lParam);
}
}
There are several things that I want to point out here.
First, all of the private
fields that I want to persist with this instance of the control, I maintain in a structure linked to this instance. Since all processes within the code module originate here, I update the global pointer using Grid_GetInstanceData(hwnd,&g_lpInst)
to refer to the struct
for this instance. This is analogous to the self referencing this
pointer of C++ or me
pointer in VB.
Second, it is very easy for this particular procedure to get out of hand. I have encountered callbacks that took up nearly 50% of a code module and became, in effect spaghetti with messages being sent or posted from one case branch to another. They can be quite difficult to debug when this happens so there are some rules of thumb that I use in my code to keep things neat and tidy.
- Use message crackers for all standard messages -- These are found primarily in windowsx.h and commctrl.h and can be implemented easily using a wizard [^]. Message crackers part out messages to various sub procedures where they can be handled discretely.
- Custom messages may be handled in the callback procedure if it can be done in only a few lines (all should be visible in the monitor) and shouldn't require the declaration of a variable. To put it another way, these messages should be treated the same way one would a property getter or setter in an OO language. When in doubt, part it out.
Third, due to some helpful feedback from CodeProject and Pelles C communities I have included some improvements, one of which is Unicode support. Notice that the grid now handles the WM_NOTIFYFORMAT
message. WM_NOTIFY
messages include a notification message code that can either target wide character or ANSI format. ANSI is the default even if the control was created using CreateWindowExW()
and therefore I must explicitly specify the desired format of the code here. (More on Unicode later.)
Here is the
struct
that contains any data that I want to persist with an instance of the grid control.
typedef struct _tagINSTANCEDATA{
HINSTANCE hInstance;
HWND hwndList;
HWND hwndEditor;
LVHITTESTINFO hti;
COLORREF Editor_TxtColr;
COLORREF Editor_BkColr;
COLORREF Cell_AltTxtColr;
COLORREF Cell_AltBkColr;
BOOL fPaintByRow;
BOOL fRowHeaders;
BOOL fResizableHeader;
BOOL fExtendLastCol;
BOOL fSuppressEraseBackground;
BOOL fsizeCol;
}INSTANCEDATA, *LPINSTANCEDATA;
I created some helper methods to allocate and attach, retrieve, and destroy this structure during the control's life cycle. In this way, there are practically no limitations on how many instances of the control there can be at any given time.
static BOOL Grid_CreateInstanceData(HWND hGrid, LPINSTANCEDATA pInstanceData)
{
LPINSTANCEDATA pInst = (LPINSTANCEDATA)malloc(sizeof(INSTANCEDATA));
memmove(pInst, pInstanceData, sizeof(INSTANCEDATA));
return SetProp(hGrid, TEXT("lpInsData"), pInst);
}
static BOOL Grid_GetInstanceData(HWND hGrid, LPINSTANCEDATA * ppInstanceData)
{
*ppInstanceData = (LPINSTANCEDATA)GetProp(hGrid, TEXT("lpInsData"));
if (NULL != *ppInstanceData)
return TRUE;
return FALSE;
}
static BOOL Grid_FreeInstanceData(HWND hGrid)
{
LPINSTANCEDATA pInst;
if (Grid_GetInstanceData(hGrid, &pInst))
{
free((LPINSTANCEDATA)pInst);
RemoveProp(hGrid, TEXT("lpInsData"));
return TRUE;
}
return FALSE;
}
Since Windows classes are message based, naturally there is a message fired to indicate that the control is being built. WM_CREATE
is one of the first messages that our control receives as it comes into existence. The message cracker handler for WM_CREATE
serves as a constructor for the control.
static BOOL Grid_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
INSTANCEDATA inst;
memset(&inst.hti, -1, sizeof(LVHITTESTINFO));
inst.hInstance = lpCreateStruct->hInstance;
inst.hwndList = CreateListView(lpCreateStruct->hInstance, hwnd);
if(NULL == inst.hwndList) return FALSE;
ListView_SetExtendedListViewStyle(inst.hwndList,LVS_EX_GRIDLINES);
inst.fPaintByRow = TRUE;
inst.Cell_AltTxtColr = ListView_GetTextColor(inst.hwndList);
inst.Cell_AltBkColr = ListView_GetBkColor(inst.hwndList);
inst.fRowHeaders = FALSE;
inst.fExtendLastCol = FALSE;
inst.hwndEditor = CreateCellEditor(lpCreateStruct->hInstance, hwnd);
if(NULL == inst.hwndEditor) return FALSE;
inst.Editor_BkColr = ListView_GetBkColor(inst.hwndList);
inst.Editor_TxtColr = ListView_GetTextColor(inst.hwndList);
Grid_SetRowHeight(inst.hwndList, 20);
return Grid_CreateInstanceData(hwnd,&inst);
}
In the above code, I create a list view and an edit control as children of the Data Grid View. The Data Grid View custom control class wraps these two controls and any notifications that they post are handled internally by the Data Grid View control. Now let's have a look at CreateCellEditor()
.
static HWND CreateCellEditor(HINSTANCE hInstance, HWND hwndParent)
{
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_WANTRETURN;
dwExStyle = WS_EX_TRANSPARENT | WS_EX_LEFT;
hwnd = CreateWindowEx(
dwExStyle, WC_EDIT, TEXT(""), dwStyle, 0, 0 0, 0, hwndParent, (HMENU)ID_EDIT, hInstance, NULL);
if (!hwnd)
return NULL;
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L);
SetProp(hwnd, TEXT("Wprc"), (HANDLE)GetWindowLongPtr(hwnd, GWL_WNDPROC));
SubclassWindow(hwnd, Editor_Proc);
return hwnd;
}
This is boiler plate instantiation with the exception of the last few lines where I subclass the newly created edit control. This will allow me to override default behavior in a windows message based control. Specifically I provide Editor_Proc()
as the new target for any messages to the edit component. Let's have a look at Editor_Proc()
.
static LRESULT CALLBACK Editor_Proc (HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam)
{
static TCHAR buf[2048];
if(WM_DESTROY==msg) {
SetWindowLong(hEdit,GWL_WNDPROC,(DWORD)GetProp(hEdit,TEXT("Wprc")));
RemoveProp(hEdit,"Wprc");
return 0;
}
else if(WM_CHAR==msg&&VK_RETURN==wParam)
return TRUE;
else if(WM_KEYDOWN==msg&&VK_ESCAPE==wParam) {
Grid_GetInstanceData(GetParent(hEdit),&g_lpInst);
ListView_GetItemText(g_lpInst->hwndList,
g_lpInst->hti.iItem,g_lpInst->hti.iSubItem,buf,sizeof buf);
Edit_SetText(hEdit, buf);
Edit_CenterTextVertically(hEdit);
SetFocus(g_lpInst->hwndList);
return 0;
}
return CallWindowProc((WNDPROC)GetProp(hEdit,
(LPCSTR)TEXT("Wprc")),hEdit,msg,wParam,lParam);
}
This proc differs from the top level Grid_WndProc()
in several particulars. First notice how WM_DESTROY
is handled. This is boiler plate code that ensures that before the Grid is destroyed, the allocated property storage for the edit control's base callback procedure's pointer is freed. Second, notice the last line where we route un-handled messages, not to DefWindowProc()
, but to the edit control's base window procedure. These two pieces of boilerplate code serve as a template for any subclassed component's callback procedure and anything we want to override goes between them.
Up to this point, I have described the backbone of the Data Grid View's design, much of which provides for basic message routing. This type of architecture could be applied to any custom control that includes, within itself, subcomponent controls. There were several technical challenges to overcome to get the whole thing to work together in a way that felt natural and was visually appealing. The remainder of this article will focus on those particulars.
The list view provides for resizable columns but resizing rows is not quite so straight forward. I employed the following trick to accomplish this.
VOID Grid_SetRowHeight(HWND hList, INT iRowHeight)
{
static HIMAGELIST hIconList;
ImageList_Destroy (hIconList);
hIconList = ImageList_Create(1,iRowHeight,ILC_COLOR,0,1);
ListView_SetImageList(hList,hIconList,LVSIL_SMALL);
}
The rows of a list view will resize to accommodate whatever type of image is to be displayed even if there are no images in the list.
The Edit box needed to be nearly invisible. By this I mean that when it was positioned over a cell, the text should not appear to move about. In order to achieve this effect, it was necessary to center the editâ„¢s text vertically. There were a few solutions out there but they tended to be rather elaborate. I distilled it all down to the following simple method.
VOID Edit_CenterTextVertically(HWND hwnd)
{
RECT rcTxt = {0,0,0,0};
RECT rcEdt = {0,0,0,0};
HDC hdc;
hdc = GetDC(hwnd);
DrawText(hdc, TEXT("Ky"), 2, &rcTxt, DT_CALCRECT | DT_LEFT);
ReleaseDC(hwnd, hdc);
GetClientRect(hwnd,&rcEdt);
rcEdt.left += 4;
rcEdt.top = ((rcEdt.bottom - (rcTxt.bottom - rcTxt.top)) / 2);
Edit_SetRect(hwnd, &rcEdt);
}
One of the problems with making a data grid view from an edit and a list view is ensuring that the edit box sizes appropriately if the column width is changed in a mouse drag operation. The data grid view projects, that I looked at, didn't handle this well. Typically the edit would become under or over sized until moved via mouse click or key board navigation. I spent some time observing the list view and the messages it issued during the column resize and finally hit upon a nifty trick. The list view's header will issue an HDN_ITEMCHANGING
notification to signal that one of its items is beginning to change size. I only needed a cue that the sizing operation had concluded and then I noticed the following behavior.
Notice that the cursor changes once the mouse button is released and the mouse position slips off of the header. This is the perfect indication that a sizing operation has completed. In fact, if the mouse button is down the cursor will not change no matter where in the list view it is positioned (sizing is still taking place). In addition to this, if sizing has begun and the mouse button is momentarily released over the header sizing mode does not change until the cursor slips off of the header.
After having made these observations, I put together the following code to handle resizing.
BOOL Grid_OnSetCursor(HWND hwnd, HWND hwndCursor, UINT codeHitTest, UINT msg)
{
if(g_lpInst->fsizeCol)
{
RECT rc;
ListView_GetSubItemRect(g_lpInst->hwndList,g_lpInst->
hti.iItem,g_lpInst->hti.iSubItem,LVIR_LABEL,&rc);
MapWindowPoints(g_lpInst->hwndList, hwnd, (LPPOINT)&rc.left,2);
MoveWindow(g_lpInst->hwndEditor,
rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,TRUE);
Edit_CenterTextVertically(g_lpInst->hwndEditor);
Edit_SetSel(g_lpInst->hwndEditor,0,-1);
ShowWindow(g_lpInst->hwndEditor,TRUE);
g_lpInst->fsizeCol = FALSE;
}
return FALSE; }
BOOL Grid_OnColumnResize(HWND hwnd, LPNMHDR pnm)
{
if(!g_lpInst->fResizableHeader) return TRUE;
ShowWindow(g_lpInst->hwndEditor,FALSE);
g_lpInst->fsizeCol = TRUE;
return FALSE;
}
In Grid_OnColumnResize()
I hide the edit control. I paint the list view sub item cell the same back color as the edit so the only indication that it is hidden is the lack of a black border. When the sizing operation concludes, I resize and position the editor and re-load and show it. The illusion is of a flicker free perfect real time resize of the edit control!
The list view component may look like it is an owner draw list view but that is not the case. I handle the NM_CUSTOMDRAW
notification sent by the list view and do all drawing (including skinning the header) there. For more information about this method, I recommend the following excellent CodeProject article: Neat Stuff to Do in List Controls Using Custom Draw [^].
Drawing a flicker free control is a real challenge. I have seen many articles on this topic and have come to the conclusion that there is no silver bullet that will ensure a flicker free control. Each solution must be tailored to the specific redrawing needs of the control at hand. With the data grid view here, there were two critical events that demanded a problematic redraw of the control. First, if I had row headers in the first column and then clicked the mouse in the grid so that the selected cell changed, I had to invalidate and redraw the first column row headers. This produced annoying flicker in that column. The second cause of flicker occurred whenever I moved the edit around via key board. I had to invalidate the adjacent rows so that there would be a redraw after each key stroke. This flicker was minimal but needed improvement. The solution in my case was to suppress the WM_ERASEBKGND
message in the ListView_Proc()
under these two conditions.
Updating the Data Grid View to work with Unicode was a fairly straight forward process the particulars of which I document here.
Step 1. Define the UNICODE symbol at the top of the code module.
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
Step 2. Wrap all string
literals in the TEXT()
macro.
static BOOL Grid_GetInstanceData(HWND hGrid, LPINSTANCEDATA * ppInstanceData)
{
*ppInstanceData = (LPINSTANCEDATA)GetProp(hGrid, TEXT("lpInsData"));
Step 3. Convert all fixed length string
declarations (EX: char buf[256]
) to TCHAR
.
BOOL Grid_OnMouseClick(HWND hwnd, LPNMHDR pnm)
{
static TCHAR buf[2048];
RECT rc;
Step 4. Handle the WM_NOTIFYFORMAT
message. I went into some detail on this previously here.
case WM_NOTIFYFORMAT:
#ifdef UNICODE
return NFR_UNICODE;
#else
return NFR_ANSI;
#endif
Note: I include the #idef
compiler directives here in case I might want to compile this as an ANSI based control.
In order to make the control compliant with 64 bit windows, I replaced the calls to Get[Set]WindowLong()
with Get[Set]WindowLongPtr()
.
SetProp(hwnd, TEXT("Wprc"), (HANDLE)GetWindowLongPtr(hwnd, GWL_WNDPROC));
SubclassWindow(hwnd, ListView_Proc);
return hwnd;
}
static LRESULT CALLBACK Editor_Proc(HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam)
{
static TCHAR buf[2048];
if (WM_DESTROY == msg) {
SetWindowLongPtr(hEdit, GWL_WNDPROC,
(DWORD)GetProp(hEdit, TEXT("Wprc")));
RemoveProp(hEdit, TEXT("Wprc"));
return 0;
}
Note: Get[Set]WindowLongPtr()
allows for both 32bit and 64bit operation.
- July 13, 2009 version 1.0.0.0
- July 17, 2009 version 1.0.0.1
- Updated to provide support for Unicode and 64bit, compilations
- Improved Editor placement behavior when control is scrolled
- May 24, 2010
- Updated download files - fixed bug in demo project