Win32 TreeView drag item flickering

Robinson 141 Reputation points
2024-08-02T17:24:18.4833333+00:00

I have a basic Win32 tree control I've implemented drag/drop with. The TreeView has been set to double buffer to avoid any flicker. The drag item is drawn no problem as I drag around. However in order to get correct drawing of the underlying view as the drag moves around and I highlight the current item, I have to call ImageList_DragShowNoLock. This causes the drag image to flicker. How can I prevent this flicker when dragging an item?

I understand this is kind-of an obscure topic (not a huge amount to search for on Google and ChatGPT is just as clueless as I am). Hopefully someone can give me a few tips.

My mouse move function is vaguely this. I can give a full repro if that's needed as this is itself a single file.

LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    if (My_Is_Dragging)
    {
        POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

        // Move the drag image to the new location (relative to the client coordinates)

        ImageList_DragMove(0, pt.y);

        // Turn off the dragged image so the background can be refreshed

        ImageList_DragShowNolock(FALSE);

        // Perform hit testing in the TreeView control... select drop item, etc...

		TVHITTESTINFO hitTestInfo = {};

		hitTestInfo.pt = pt;

		auto hitItem = TreeView_HitTest(m_hWnd, &hitTestInfo);

		if (hitTestInfo.flags & TVHT_ONITEM)
		{
	 	   My_Drop_Item = hitItem;

	 	   TreeView_SelectDropTarget(m_hWnd, My_Drop_Item);
		}
		else
		{
		    My_Drop_Item = nullptr;

		    TreeView_SelectDropTarget(m_hWnd, nullptr);
		}

        // Turn on the dragged image

        ImageList_DragShowNolock(TRUE);

        bHandled = TRUE;

        return 0;
    }

    bHandled = FALSE;

    return 0;
}

Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,603 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,709 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Castorix31 84,871 Reputation points
    2024-08-15T12:45:56.0766667+00:00

    If I adapt an old MS sample (TreeViewDragAndDropV6.exe), by fixing a few bugs, I don't see any flickering (Windows 10 22H2) :

    #include <windows.h>
    #include <tchar.h>
    
    #include <commctrl.h>
    #pragma comment (lib, "comctl32")
    
    #include <gdiplus.h>
    using namespace Gdiplus;
    #pragma comment(lib, "gdiplus")
    
    #include <Urlmon.h> // URLDownloadToCacheFile  
    #pragma comment (lib, "Urlmon")
    
    #include <shlwapi.h> // SHCreateStreamOnFile  
    #pragma comment (lib, "shlwapi")
    
    #pragma comment(linker,"\"/manifestdependency:type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    
    HINSTANCE hInst;
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int nWidth = 600, nHeight = 600;
    #define IDC_STATIC 10
    #define IDC_BUTTON 11
    #define IDC_TV 12
    
    HWND hWnd = NULL;
    
    void FillTreeView(HWND hWndTreeView);
    void TreeView_OnBeginDrag(HWND hWndTV, LPNMTREEVIEW pnmtv);
    HTREEITEM _hItemDragged = NULL;
    BOOL _bLeftDragging = FALSE;
    HTREEITEM _hItemOld = NULL;
    BOOL IsChild(HWND hWndTV, HTREEITEM hChild, HTREEITEM hParent);
    HTREEITEM CopyItemBranch(HWND hWndTV, HTREEITEM hTreeItemFrom, HTREEITEM hTreeItemTo);
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
    {
    	 GdiplusStartupInput gdiplusStartupInput;
    	 ULONG_PTR gdiplusToken;
    	 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
    	hInst = hInstance;
    	WNDCLASSEX wcex =
    	{
    		sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),
    		LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("WindowClass"), NULL,
    	};
    	if (!RegisterClassEx(&wcex))
    		return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);
    	int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;
    	hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Test TreeView Drag&Drop"), WS_OVERLAPPEDWINDOW, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);
    	if (!hWnd)
    		return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);
    	ShowWindow(hWnd, SW_SHOWNORMAL);
    	UpdateWindow(hWnd);
    	MSG msg;
    	while (GetMessage(&msg, NULL, 0, 0))
    	{
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	}
    	 GdiplusShutdown(gdiplusToken);
    	return (int)msg.wParam;
    }
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	static HWND hWndButton = NULL, hWndStatic = NULL;
    	static HWND hWndTV;
    	int wmId, wmEvent;
    	switch (message)
    	{
    	case WM_CREATE:
    	{	
    		hWndTV = CreateWindowEx(0, WC_TREEVIEW, TEXT(""),
    			WS_VISIBLE | WS_CHILD | WS_BORDER | TVS_HASLINES |
    			//TVS_HASBUTTONS | TVS_LINESATROOT | TVS_CHECKBOXES | WS_EX_CLIENTEDGE,
    			TVS_HASBUTTONS | TVS_LINESATROOT | WS_EX_CLIENTEDGE,
    			10, 10, 400, 542, hWnd, (HMENU)IDC_TV, hInst, NULL);
    
    		WCHAR wsURL[MAX_PATH] = TEXT("https://i.ibb.co/4NdT6hX/flowers.png");
    		WCHAR wsFilename[MAX_PATH];
    		HRESULT hr = URLDownloadToCacheFile(NULL, wsURL, wsFilename, ARRAYSIZE(wsFilename), 0x0, NULL);
    		if (SUCCEEDED(hr))
    		{
    			IStream* pStream = NULL;
    			hr = SHCreateStreamOnFile(wsFilename, STGM_READ | STGM_SHARE_DENY_WRITE, &pStream);
    			if (SUCCEEDED(hr))
    			{
    				HBITMAP hBitmap = NULL;
    				Gdiplus::Bitmap* pBitmap = new Gdiplus::Bitmap(pStream);
    				if (pBitmap)
    				{
    					pBitmap->GetHBITMAP(Gdiplus::Color(255, 255, 255), &hBitmap);
    					HIMAGELIST hImageList = ImageList_Create(32, 32, ILC_COLORDDB | ILC_MASK, 43, 1);
    					ImageList_AddMasked(hImageList, hBitmap, RGB(255, 0, 255));
    					DeleteObject(hBitmap);
    					TreeView_SetImageList(hWndTV, hImageList, TVSIL_NORMAL);
    					FillTreeView(hWndTV);
    				}
    			}
    		}
    	
    		// hWndStatic = CreateWindowEx(0, TEXT("Static"), TEXT(""), WS_CHILD | WS_VISIBLE | SS_BITMAP, 10, 10, 200, 200, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
    		// hWndButton = CreateWindowEx(0, L"Button", L"Click", WS_CHILD | WS_VISIBLE | BS_PUSHLIKE, 400, 60, 60, 32, hWnd, (HMENU)IDC_BUTTON, hInst, NULL);
    
    		return 0;
    	}
    	break;
    	case WM_COMMAND:
    	{
    		wmId = LOWORD(wParam);
    		wmEvent = HIWORD(wParam);
    		switch (wmId)
    		{
    		case IDC_BUTTON:
    		{
    			if (wmEvent == BN_CLICKED)
    			{
    				Beep(1000, 10);
    			}
    		}
    		break;
    		default:
    			return DefWindowProc(hWnd, message, wParam, lParam);
    		}
    	}
    	break;
    	case WM_NOTIFY:
    	{
    		LPNMHDR pnmh = (LPNMHDR)lParam;
    		switch (pnmh->code)
    		{
    		case TVN_BEGINDRAG:
    		{
    			TreeView_OnBeginDrag(hWndTV, (LPNMTREEVIEW)pnmh);			
    		}
    		break;
    		default:
    			return false;
    		}
    		return true;
    	}
    	break;
    	case WM_MOUSEMOVE:
    	{
    		if (_bLeftDragging)
    		{
    			HTREEITEM hItem;
    			POINT pt;
    			GetCursorPos(&pt);
    			MapWindowPoints(HWND_DESKTOP, hWndTV, (LPPOINT)&pt, 1);
    	
    			TVHITTESTINFO tvhti = { 0 };
    			tvhti.pt = pt;
    			hItem = TreeView_HitTest(hWndTV, &tvhti);
    			if (hItem != NULL)
    			{
    				if (hItem != _hItemOld || _hItemOld == NULL)
    				{
    					ImageList_DragShowNolock(FALSE);
    					TreeView_SelectDropTarget(hWndTV, hItem);
    					ImageList_DragShowNolock(TRUE);
    					_hItemOld = hItem;
    				}
    			}
    			ImageList_DragMove(pt.x, pt.y);
    			if (IsChild(hWndTV, hItem, _hItemDragged) || hItem == _hItemDragged)
    				SetCursor(LoadCursor(NULL, IDC_NO));
    			else
    				SetCursor(LoadCursor(NULL, IDC_ARROW));
    
    			SCROLLINFO si = { 0 };
    			si.cbSize = sizeof(si);
    			si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
    			if (GetScrollInfo(hWndTV, SB_VERT, &si))
    			{
    				if ((tvhti.flags == TVHT_BELOW) && (si.nPos < (si.nMax - max((int)si.nPage - 1, 0))))
    				{
    					SendMessage(hWndTV, WM_VSCROLL, MAKELONG(SB_LINEDOWN, 0), 0L);
    					InvalidateRect(hWndTV, NULL, TRUE);
    					UpdateWindow(hWndTV);
    				}
    				if ((tvhti.flags == TVHT_ABOVE) && (si.nPos > si.nMin))
    				{
    					SendMessage(hWndTV, WM_VSCROLL, MAKELONG(SB_LINEUP, 0), 0L);
    					InvalidateRect(hWndTV, NULL, TRUE);
    					UpdateWindow(hWndTV);
    				}
    			}
    		}
    	}
    	break;
    	case WM_LBUTTONUP:
    	{
    		if (_bLeftDragging)
    		{
    			HTREEITEM hItemTarget;
    			ImageList_EndDrag();
    			ReleaseCapture();
    			SetCursor(LoadCursor(NULL, IDC_ARROW));
    			_bLeftDragging = false;
    			hItemTarget = TreeView_GetDropHilight(hWndTV);
    			ImageList_DragShowNolock(FALSE);
    			TreeView_SelectDropTarget(hWndTV, NULL);
    			ImageList_DragShowNolock(TRUE);
    			if (hItemTarget != NULL)
    			{
    				//TVITEM tvItem = { 0 };
    				//tvItem.mask = TVIF_PARAM | TVIF_HANDLE;
    				//tvItem.hItem = hItemTarget;
    				//TreeView_GetItem(hWndTV, &tvItem);
    				
    				//tvItem = { 0 };	
    				//tvItem.mask = TVIF_PARAM | TVIF_HANDLE;			
    				//tvItem.hItem = _hTreeItemDragged;
    				//TreeView_GetItem(hWndTV, &tvItem);
    
    				CopyItemBranch(hWndTV, _hItemDragged, hItemTarget);
    				_hItemOld = NULL;
    			}
    		}
    	}
    	break;
    	
    	case WM_PAINT:
    	{
    		PAINTSTRUCT ps;
    		HDC hDC = BeginPaint(hWnd, &ps);
    
    		EndPaint(hWnd, &ps);
    	}
    	break;
    	case WM_DESTROY:
    	{
    		PostQuitMessage(0);
    		return 0;
    	}
    	break;
    	default:
    		return DefWindowProc(hWnd, message, wParam, lParam);
    	}
    	return 0;
    }
    
    void TreeView_OnBeginDrag(HWND hWndTV, LPNMTREEVIEW pnmtv)
    {
    	HIMAGELIST himlDrag;	
    	HTREEITEM hItem = pnmtv->itemNew.hItem;
    
    	himlDrag = TreeView_CreateDragImage(hWndTV, hItem);
    	ImageList_BeginDrag(himlDrag, 0, 0, 0);
    	ImageList_DragEnter(hWndTV, pnmtv->ptDrag.x, pnmtv->ptDrag.y);
    	//ImageList_DragEnter(hWndTV, 0, 0);
    	SetCursor(LoadCursor(NULL, IDC_ARROW));
    	SetCapture(hWnd);
    	ImageList_Destroy(himlDrag);
    	_bLeftDragging = true;
    	_hItemDragged = hItem;
    	return;
    }
    
    void FillTreeView(HWND hWndTreeView)
    {
    	WCHAR wsText[MAX_PATH];
    	TV_ITEM tvi;
    	TVINSERTSTRUCT tvins;
    	HTREEITEM hPrev = NULL;
    
    	for (int i = 0; i < 10; i++)
    	{
    		tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
    		wsprintf(wsText, TEXT("Item Number %d"), i);
    		tvi.pszText = wsText;
    		tvi.cchTextMax = MAX_PATH;
    		tvi.iImage = i;
    		tvi.iSelectedImage = i;
    		tvins.hInsertAfter = TVI_LAST;
    		tvins.item = tvi;
    		tvins.hParent = TVI_ROOT;
    		hPrev = TreeView_InsertItem(hWndTreeView, &tvins);
    
    		tvins.hParent = hPrev;
    		for (int j = 0; j < 10; j++)
    		{
    			wsprintf(wsText, TEXT("SubItem Number %d-%d"), i, j);
    			tvi.pszText = wsText;
    			HTREEITEM hChild = TreeView_InsertItem(hWndTreeView, &tvins);
    		}
    	}
    }
    
    BOOL IsChild(HWND hWndTV, HTREEITEM hChild, HTREEITEM hParent)
    {
    	HTREEITEM hCurrentParent;
    	hCurrentParent = hChild;
    	hCurrentParent = (HTREEITEM)SendMessage(hWndTV, TVM_GETNEXTITEM,
    		TVGN_PARENT, (LPARAM)hCurrentParent);
    	while (hCurrentParent)
    	{
    		if (hCurrentParent == hParent)
    			return TRUE;
    		hCurrentParent = (HTREEITEM)SendMessage(hWndTV, TVM_GETNEXTITEM,
    			TVGN_PARENT, (LPARAM)hCurrentParent);
    	}
    	return FALSE;
    }
    
    // Adapted from MS sample TreeViewDragAndDropV6.exe
    // _CopyItem: Copy Item given to the destination as a child.
    HTREEITEM _CopyItem(HWND hWndTV, HTREEITEM hTreeItemFrom, HTREEITEM hTreeItemTo)
    {
    	const int ciBuffSize = MAX_PATH;
    
    	if (hTreeItemFrom == NULL || hTreeItemTo == NULL || hTreeItemFrom == hTreeItemTo)
    	{
    		return FALSE;
    	}
    
    	TCHAR szBuff[ciBuffSize];
    	TVINSERTSTRUCT tvs;
    	tvs.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT;
    	tvs.item.pszText = szBuff;
    	tvs.item.cchTextMax = ciBuffSize;
    	tvs.item.hItem = hTreeItemFrom;
    	if (TreeView_GetItem(hWndTV, &(tvs.item)) == FALSE)
    	{
    		return FALSE;
    	}
    	tvs.hParent = hTreeItemTo;
    	tvs.hInsertAfter = TVI_LAST;
    
    	HTREEITEM hNewItem = TreeView_InsertItem(hWndTV, &tvs);
    
    	TreeView_Expand(hWndTV, hTreeItemTo, TVE_EXPAND);
    	return hNewItem;
    }
    
    HTREEITEM CopyItemBranch(HWND hWndTV, HTREEITEM hTreeItemFrom, HTREEITEM hTreeItemTo)
    {
    	if (IsChild(hWndTV, hTreeItemTo, hTreeItemFrom))
    		return NULL;
    	HTREEITEM hItemInsert, hItemChild;
    	hItemInsert = _CopyItem(hWndTV, hTreeItemFrom, hTreeItemTo);
    	hItemChild = (HTREEITEM)SendMessage(hWndTV, TVM_GETNEXTITEM,
    		TVGN_CHILD, (LPARAM)hTreeItemFrom);
    	while (hItemChild != 0)
    	{
    		CopyItemBranch(hWndTV, hItemChild, hItemInsert);
    		hItemChild = (HTREEITEM)SendMessage(hWndTV, TVM_GETNEXTITEM,
    			TVGN_NEXT, (LPARAM)hItemChild);
    	}
    	return hItemInsert;
    }
    
    

  2. jgjorg 0 Reputation points
    2024-09-28T07:26:04.2933333+00:00

    I had the same issue, and reduced the flickering quite a bit by keeping a record of the last item selected, and only using ImageList_DragShowNolock and TVM_SELECTITEM (which corresponds with your TreeView_SelectDropTarget) if the selection needed to change (because the result of the hittest was different).

    Here is my code (sorry it's in GoAsm assembly language). And by way of explanation the GET_CURSORPOS and CHECKIFINTV functions (not reproduced) get the x and y pos coordinates in main window client and tree view child client positions respectively.

    MOUSEMOVE:				;called on WM_MOUSEMOVE
    CALL GET_CURSORPOS      ;get cursor x pos in edi, y pos in esi (client coords)
    TEST B[DRAGITEMFLAG],80h    ;see if dragging an item from the mail listview 
    JZ >L168                ;no 
    INVOKE ImageList_DragMove,EDI,ESI
    CALL CHECKIFINTV		;get treeview client coords and control in ebx 
    JZ >L168				;the mouse is not in a treeview control
    MOV EDX,ADDR TVHITTESTINFO
    MOV [EDX],EDI			;give x-pos (treeview client coords)
    MOV [EDX+4],ESI			;give y-pos (treeview client coords)
    ;TVHT_ONITEMICON=2h, TVHT_ONITEMLABEL=4h, TVHT_ONITEMSTATEICON=40h
    MOV D[EDX+8h],46h
    INVOKE SendMessageW,EBX,1111h,0,EDX		;TVM_HITTEST
    OR EAX,EAX				;see if a TV item is at the point 
    JZ >L168				;no
    MOV ESI,EAX				;give to esi
    CMP ESI,[hItemNowSelected]				;see if selected item has changed 
    MOV [hItemNowSelected],ESI 
    JZ >L168				;no its the same as before so do not select again
    INVOKE ImageList_DragShowNolock,0		;allow treeview to redraw
    INVOKE SendMessageW,EBX,110Bh,8h,ESI	;TVM_SELECTITEM TVGN_DROPHILITE=8h 
    INVOKE ImageList_DragShowNolock,1		;allow image to be drawn again
    L168:
    XOR EAX,EAX             ;return zero
    RET
    
    
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.