Skip to content

Instantly share code, notes, and snippets.

@yonran
Last active August 29, 2015 14:21
Show Gist options
  • Save yonran/8c7f187a2ade8aba7f5f to your computer and use it in GitHub Desktop.
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)
//! 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