Last active
August 29, 2015 14:21
-
-
Save yonran/8c7f187a2ade8aba7f5f to your computer and use it in GitHub Desktop.
How to fix paint artifacts when replacing items in Tree View? (sample code for http://stackoverflow.com/questions/30202671/how-to-fix-paint-artifacts-when-replacing-items-in-tree-view)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! cl.exe /EHsc /Tp treeview-and-tabcontrol.cpp User32.lib ComCtl32.lib Ole32.lib | |
// Based on Raymond Chen's scratch program | |
// http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx | |
#define STRICT | |
#define UNICODE | |
#define _UNICODE | |
#include <windows.h> | |
#include <windowsx.h> | |
#include <ole2.h> | |
#include <commctrl.h> | |
#include <shlwapi.h> | |
#include <shlobj.h> | |
#include <shellapi.h> | |
#include <stdlib.h> // rand | |
#include <map> | |
HINSTANCE g_hinst; | |
const WORD IDM_REGENERATETREE = 1; | |
const WORD IDM_SELECTRANDOM = 2; | |
class Window | |
{ | |
public: | |
HWND GetHWND() { return m_hwnd; } | |
protected: | |
virtual LRESULT HandleMessage( | |
UINT uMsg, WPARAM wParam, LPARAM lParam); | |
virtual void PaintContent(PAINTSTRUCT *pps) { } | |
virtual LPCTSTR ClassName() = 0; | |
virtual BOOL WinRegisterClass(WNDCLASS *pwc) | |
{ return RegisterClass(pwc); } | |
virtual ~Window() { } | |
HWND WinCreateWindow(DWORD dwExStyle, LPCTSTR pszName, | |
DWORD dwStyle, int x, int y, int cx, int cy, | |
HWND hwndParent, HMENU hmenu) | |
{ | |
Register(); | |
return CreateWindowEx(dwExStyle, ClassName(), pszName, dwStyle, | |
x, y, cx, cy, hwndParent, hmenu, g_hinst, this); | |
} | |
private: | |
void Register(); | |
void OnPaint(); | |
void OnPrintClient(HDC hdc); | |
static LRESULT CALLBACK s_WndProc(HWND hwnd, | |
UINT uMsg, WPARAM wParam, LPARAM lParam); | |
protected: | |
HWND m_hwnd; | |
}; | |
void Window::Register() | |
{ | |
WNDCLASS wc; | |
wc.style = 0; | |
wc.lpfnWndProc = Window::s_WndProc; | |
wc.cbClsExtra = 0; | |
wc.cbWndExtra = 0; | |
wc.hInstance = g_hinst; | |
wc.hIcon = NULL; | |
wc.hCursor = LoadCursor(NULL, IDC_ARROW); | |
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); | |
wc.lpszMenuName = NULL; | |
wc.lpszClassName = ClassName(); | |
WinRegisterClass(&wc); | |
} | |
LRESULT CALLBACK Window::s_WndProc( | |
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) | |
{ | |
Window *self; | |
if (uMsg == WM_NCCREATE) { | |
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); | |
self = reinterpret_cast<Window *>(lpcs->lpCreateParams); | |
self->m_hwnd = hwnd; | |
SetWindowLongPtr(hwnd, GWLP_USERDATA, | |
reinterpret_cast<LPARAM>(self)); | |
} else { | |
self = reinterpret_cast<Window *> | |
(GetWindowLongPtr(hwnd, GWLP_USERDATA)); | |
} | |
if (self) { | |
return self->HandleMessage(uMsg, wParam, lParam); | |
} else { | |
return DefWindowProc(hwnd, uMsg, wParam, lParam); | |
} | |
} | |
LRESULT Window::HandleMessage( | |
UINT uMsg, WPARAM wParam, LPARAM lParam) | |
{ | |
LRESULT lres; | |
switch (uMsg) { | |
case WM_NCDESTROY: | |
lres = DefWindowProc(m_hwnd, uMsg, wParam, lParam); | |
SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0); | |
delete this; | |
return lres; | |
case WM_PAINT: | |
OnPaint(); | |
return 0; | |
case WM_PRINTCLIENT: | |
OnPrintClient(reinterpret_cast<HDC>(wParam)); | |
return 0; | |
} | |
return DefWindowProc(m_hwnd, uMsg, wParam, lParam); | |
} | |
void Window::OnPaint() | |
{ | |
PAINTSTRUCT ps; | |
BeginPaint(m_hwnd, &ps); | |
PaintContent(&ps); | |
EndPaint(m_hwnd, &ps); | |
} | |
void Window::OnPrintClient(HDC hdc) | |
{ | |
PAINTSTRUCT ps; | |
ps.hdc = hdc; | |
GetClientRect(m_hwnd, &ps.rcPaint); | |
PaintContent(&ps); | |
} | |
class RootWindow : public Window | |
{ | |
public: | |
virtual LPCTSTR ClassName() { return TEXT("Scratch"); } | |
static RootWindow *Create(); | |
protected: | |
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); | |
LRESULT OnCreate(); | |
private: | |
void RegenerateTree(); | |
void SelectRandom(); | |
HWND m_hwndChild; | |
HWND m_hwndTreeView; | |
}; | |
LRESULT RootWindow::OnCreate() | |
{ | |
this->m_hwndChild = CreateWindowEx(0, WC_TABCONTROL, TEXT("My TabControl"), | |
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_CLIPSIBLINGS, | |
0, 0, 0, 0, this->m_hwnd, NULL, g_hinst, NULL); | |
this->m_hwndTreeView = CreateWindowEx(0, WC_TREEVIEW, TEXT("My tree"), | |
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | TVS_HASLINES | TVS_SHOWSELALWAYS | TVS_HASBUTTONS | TVS_LINESATROOT, | |
0, 0, 0, 0, this->m_hwnd, NULL, g_hinst, NULL); | |
if (! this->m_hwndChild || !this->m_hwndTreeView) { | |
return -1; | |
} | |
SetWindowPos(this->m_hwndTreeView, HWND_TOP, 0, 0, 0, 0, SWP_DEFERERASE | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); | |
TCITEM tabItem; | |
tabItem.mask = TCIF_TEXT; | |
tabItem.pszText = TEXT("Tab 1"); | |
TabCtrl_InsertItem(this->m_hwndChild, 0, &tabItem); | |
this->RegenerateTree(); | |
return 0; | |
} | |
LRESULT RootWindow::HandleMessage( | |
UINT uMsg, WPARAM wParam, LPARAM lParam) | |
{ | |
switch (uMsg) { | |
case WM_CREATE: | |
return OnCreate(); | |
case WM_NCDESTROY: | |
// Death of the root window ends the thread | |
PostQuitMessage(0); | |
break; | |
case WM_SIZE: | |
if (m_hwndChild) { | |
SetWindowPos(m_hwndChild, NULL, 0, 0, | |
GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), | |
SWP_NOZORDER | SWP_NOACTIVATE); | |
if (m_hwndTreeView) { | |
RECT tabcontent; | |
tabcontent.top = tabcontent.left = 0; | |
tabcontent.right = GET_X_LPARAM(lParam); | |
tabcontent.bottom = GET_Y_LPARAM(lParam); | |
TabCtrl_AdjustRect(this->m_hwndChild, FALSE, &tabcontent); | |
SetWindowPos(this->m_hwndTreeView, NULL, tabcontent.left, tabcontent.top, | |
tabcontent.right - tabcontent.left, tabcontent.bottom - tabcontent.top, | |
SWP_NOZORDER | SWP_NOACTIVATE); | |
} | |
} | |
return 0; | |
case WM_SETFOCUS: | |
if (m_hwndChild) { | |
SetFocus(m_hwndChild); | |
} | |
return 0; | |
case WM_COMMAND: | |
switch (LOWORD(wParam)) { | |
case IDM_REGENERATETREE: | |
this->RegenerateTree(); | |
return 0; | |
case IDM_SELECTRANDOM: | |
this->SelectRandom(); | |
return 0; | |
} | |
break; | |
} | |
return __super::HandleMessage(uMsg, wParam, lParam); | |
} | |
RootWindow *RootWindow::Create() | |
{ | |
RootWindow *self = new RootWindow(); | |
if (self && self->WinCreateWindow(0, | |
TEXT("Scratch"), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, | |
CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, | |
NULL, NULL)) { | |
return self; | |
} | |
delete self; | |
return NULL; | |
} | |
int PASCAL | |
WinMain(HINSTANCE hinst, HINSTANCE, LPSTR, int nShowCmd) | |
{ | |
g_hinst = hinst; | |
if (SUCCEEDED(CoInitialize(NULL))) { | |
InitCommonControls(); | |
ACCEL accel[2]; | |
accel[0].fVirt = FCONTROL | FVIRTKEY; | |
accel[0].key = 'R'; | |
accel[0].cmd = IDM_REGENERATETREE; | |
accel[1].fVirt = FCONTROL | FVIRTKEY; | |
accel[1].key = 'S'; | |
accel[1].cmd = IDM_SELECTRANDOM; | |
HACCEL haccel = CreateAcceleratorTable(accel, 2); | |
RootWindow *prw = RootWindow::Create(); | |
if (prw) { | |
ShowWindow(prw->GetHWND(), nShowCmd); | |
MSG msg; | |
while (GetMessage(&msg, NULL, 0, 0)) { | |
if (TranslateAccelerator(prw->GetHWND(), haccel, &msg)) { | |
} else if (IsDialogMessage(prw->GetHWND(), &msg)) { | |
} else { | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
} | |
} | |
} | |
CoUninitialize(); | |
} | |
return 0; | |
} | |
static HTREEITEM addTreeItem(HWND hwndTreeView, HTREEITEM hParent, HTREEITEM hInsertAfter, LPCTSTR str, LPARAM lParam) { | |
TVINSERTSTRUCT tvins; | |
tvins.hParent = hParent; | |
tvins.hInsertAfter = hInsertAfter; | |
tvins.itemex.mask = TVIF_TEXT | TVIF_PARAM; | |
tvins.itemex.pszText = const_cast<LPTSTR>(str); | |
tvins.itemex.lParam = lParam; | |
HTREEITEM node = TreeView_InsertItem(hwndTreeView, &tvins); | |
return node; | |
} | |
static HTREEITEM nextItem(HWND hwndTreeView, HTREEITEM item) { | |
HTREEITEM child = TreeView_GetChild(hwndTreeView, item); | |
if (NULL != child) | |
return child; | |
HTREEITEM sibling = TreeView_GetNextSibling(hwndTreeView, item); | |
if (NULL != sibling) | |
return sibling; | |
HTREEITEM ancestor = item; | |
while (NULL != ancestor) { | |
ancestor = TreeView_GetParent(hwndTreeView, ancestor); | |
HTREEITEM uncle = TreeView_GetNextSibling(hwndTreeView, ancestor); | |
if (NULL != uncle) | |
return uncle; | |
} | |
return NULL; | |
} | |
void RootWindow::RegenerateTree() { | |
UINT count = TreeView_GetCount(this->m_hwndTreeView); | |
std::map<LPARAM, UINT> previousStates; | |
for (HTREEITEM item = TreeView_GetRoot(this->m_hwndTreeView); NULL != item; item = nextItem(this->m_hwndTreeView, item)) { | |
TVITEM tvitem; | |
tvitem.mask = TVIF_HANDLE | TVIF_STATE | TVIF_PARAM; | |
tvitem.hItem = item; | |
tvitem.stateMask = TVIS_SELECTED | TVIS_EXPANDED; | |
tvitem.lParam = 0; | |
tvitem.state = 0; | |
if (TreeView_GetItem(this->m_hwndTreeView, &tvitem)) { | |
previousStates[tvitem.lParam] = tvitem.state; | |
} | |
} | |
SetWindowRedraw(this->m_hwndTreeView, FALSE); | |
TreeView_DeleteAllItems(this->m_hwndTreeView); | |
// The first time the tree is generated, previousStates is empty here and | |
// previousStates[nIndex] will insert and return 0. | |
LPARAM nIndex = 0; | |
HTREEITEM node1 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 1"), nIndex); nIndex++; | |
HTREEITEM node2 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 2"), nIndex); nIndex++; | |
HTREEITEM node3 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 3"), nIndex); nIndex++; | |
HTREEITEM node4 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 4"), nIndex); nIndex++; | |
HTREEITEM node5 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 5"), nIndex); nIndex++; | |
HTREEITEM node6 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 6"), nIndex); nIndex++; | |
HTREEITEM node7 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 7"), nIndex); nIndex++; | |
HTREEITEM node8 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 8"), nIndex); nIndex++; | |
HTREEITEM node9 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 9"), nIndex); nIndex++; | |
HTREEITEM node10 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 10"), nIndex); nIndex++; | |
HTREEITEM node11 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 11"), nIndex); nIndex++; | |
HTREEITEM node12 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 12"), nIndex); nIndex++; | |
HTREEITEM node13 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 13"), nIndex); nIndex++; | |
HTREEITEM node14 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 14"), nIndex); nIndex++; | |
HTREEITEM node15 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 15"), nIndex); nIndex++; | |
HTREEITEM node16 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 16"), nIndex); nIndex++; | |
HTREEITEM node17 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 17"), nIndex); nIndex++; | |
HTREEITEM node18 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 18"), nIndex); nIndex++; | |
HTREEITEM node19 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 19"), nIndex); nIndex++; | |
HTREEITEM node20 = addTreeItem(this->m_hwndTreeView, TVI_ROOT, TVI_LAST, TEXT("Node 20"), nIndex); nIndex++; | |
HTREEITEM node1_1 = addTreeItem(this->m_hwndTreeView, node1, TVI_LAST, TEXT("Node 1.1"), nIndex); nIndex++; | |
HTREEITEM node1_1_1 = addTreeItem(this->m_hwndTreeView, node1_1, TVI_LAST, TEXT("Node 1.1.1"), nIndex); nIndex++; | |
addTreeItem(this->m_hwndTreeView, node1, TVI_LAST, TEXT("Node 1.2"), nIndex); nIndex++; | |
addTreeItem(this->m_hwndTreeView, node2, TVI_LAST, TEXT("Node 2.1"), nIndex); nIndex++; | |
addTreeItem(this->m_hwndTreeView, node2, TVI_LAST, TEXT("Node 2.2"), nIndex); nIndex++; | |
HTREEITEM itemToEnsureVisible = NULL; | |
for (HTREEITEM item = TreeView_GetRoot(this->m_hwndTreeView); NULL != item; item = nextItem(this->m_hwndTreeView, item)) { | |
TVITEM tvitem; | |
tvitem.mask = TVIF_HANDLE | TVIF_PARAM; | |
tvitem.hItem = item; | |
if (!TreeView_GetItem(this->m_hwndTreeView, &tvitem)) { | |
continue; | |
} | |
UINT previousState = previousStates[tvitem.lParam]; | |
if (previousState & TVIS_EXPANDED) { | |
if (!TreeView_Expand(this->m_hwndTreeView, item, TVE_EXPAND)) { | |
continue; | |
} | |
} | |
if (false && !TreeView_SetItem(this->m_hwndTreeView, &tvitem)) { | |
continue; | |
} | |
if (TVIS_SELECTED & previousState) { | |
TreeView_SelectItem(this->m_hwndTreeView, item); | |
itemToEnsureVisible = item; | |
} | |
} | |
SetWindowRedraw(this->m_hwndTreeView, TRUE); | |
RedrawWindow(this->m_hwndTreeView, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); | |
if (itemToEnsureVisible) { | |
// Scrolling into view doesn't work when redraw is disabled | |
TreeView_EnsureVisible(this->m_hwndTreeView, itemToEnsureVisible); | |
} | |
} | |
void RootWindow::SelectRandom() { | |
UINT count = TreeView_GetCount(this->m_hwndTreeView); | |
int index = rand() % count; | |
for (HTREEITEM item = TreeView_GetRoot(this->m_hwndTreeView); NULL != item; item = nextItem(this->m_hwndTreeView, item)) { | |
if (index == 0) | |
TreeView_SelectItem(this->m_hwndTreeView, item); | |
index--; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment