Skip to content

Instantly share code, notes, and snippets.

@PBfordev
Last active March 30, 2022 12:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PBfordev/27e6186e101c89c49e744cd3a3699e64 to your computer and use it in GitHub Desktop.
Save PBfordev/27e6186e101c89c49e744cd3a3699e64 to your computer and use it in GitHub Desktop.
Demonstrates an issue with ListView_HitTest() when the header is displayed.
/*********************************************************************************
Demonstrates that ListView_HitTest() doesn't account for the header.
If the mouse cursor is over the listview header and we translate the current
mouse cursor screen coordinates with ScreenToClient(hwndListView),
the listview reports:
* for the unscrolled view, hit on item 0,
* for the scrolled view, hit on an item above the top visible item.
**********************************************************************************/
#include <windows.h>
#include <commctrl.h>
#include <tchar.h>
#include <windowsx.h>
#include <assert.h>
#pragma comment(lib, "comctl32")
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#pragma warning(disable : 4996)
HINSTANCE g_hInstance = nullptr;
HWND g_hMainFrame = nullptr;
HWND g_hListView = nullptr;
enum
{
ID_ListView = 1000,
ID_TimerEvent = 10000,
};
void FatalError(LPCTSTR errorMessage)
{
MessageBox(nullptr, errorMessage, _T("Fatal error"), MB_OK | MB_ICONERROR);
exit(-1);
}
HWND CreateListView()
{
HWND hListView = ::CreateWindow(WC_LISTVIEW, _T(""), WS_CHILD | WS_VISIBLE | LVS_REPORT,
0, 0, 0, 0, g_hMainFrame, (HMENU)ID_ListView, g_hInstance, nullptr);
if ( !hListView )
FatalError(_T("Could not create the list view."));
ListView_SetExtendedListViewStyle(hListView, LVS_EX_FULLROWSELECT | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT);
const size_t textSize = 1024;
TCHAR text[textSize];
LVCOLUMN lvc = {};
lvc.mask = LVCF_WIDTH | LVCF_TEXT;
lvc.cx = 200;
_tcscpy(text, _T("Item Column"));
lvc.pszText = text;
if ( ListView_InsertColumn(hListView, 0, &lvc) == -1 )
FatalError(_T("Could not create list view item column."));
_tcscpy(text, _T("SubItem Column"));
lvc.pszText = text;
if ( ListView_InsertColumn(hListView, 1, &lvc) == -1 )
FatalError(_T("Could not create list view subitem column."));
LVITEM lvi = {};
lvi.mask = LVIF_TEXT;
for ( int i = 0; i < 10; ++i )
{
lvi.iItem = i;
_stprintf(text, _T("Item %d"), i);
lvi.pszText = text;
lvi.iSubItem = 0;
if ( ListView_InsertItem(hListView, &lvi) == -1 )
FatalError(_T("Could not add list view item."));
_stprintf(text, _T("SubItem %d"), i);
lvi.pszText = text;
lvi.iSubItem = 1;
if ( ListView_SetItem(hListView, &lvi) == -1 )
FatalError(_T("Could not add list view subitem."));
}
return hListView;
}
LRESULT CALLBACK MainFrameWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
HWND CreateMainFrame()
{
LPCTSTR className = _T("TestFrame");
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = MainFrameWndProc;
wcex.hInstance = g_hInstance;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszClassName = className;
if ( !RegisterClassEx(&wcex) )
FatalError(_T("Could not register the class for the application window."));
HWND hFrame = CreateWindow(className, _T("Test ListView_HitTest()"), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 400, nullptr, nullptr, g_hInstance, nullptr);
if ( !hFrame )
FatalError(_T("Could not create the application window."));
return hFrame;
}
void UpdateListItemInfo()
{
const size_t textSize = 1024;
static DWORD msgPosPrev = (DWORD)-1;
DWORD msgPos = GetMessagePos();
POINT mousePosScreen, mousePosClient;
LVHITTESTINFO lvhti;
TCHAR infoText[textSize];
int itemHit;
if ( msgPos == msgPosPrev )
return;
msgPosPrev = msgPos;
mousePosScreen.x = GET_X_LPARAM(msgPos);
mousePosScreen.y = GET_Y_LPARAM(msgPos);
mousePosClient = mousePosScreen;
ScreenToClient(g_hListView, &mousePosClient);
lvhti.pt = mousePosClient;
itemHit = ListView_HitTest(g_hListView, &lvhti);
assert(itemHit == lvhti.iItem);
if ( lvhti.iItem < 0 )
{
_stprintf(infoText, _T("Cursor (screen: %d, %d; client: %d, %d) is not over an item"),
mousePosScreen.x, mousePosScreen.y, mousePosClient.x, mousePosClient.y);
}
else
{
TCHAR itemText[textSize];
RECT itemRect;
ListView_GetItemText(g_hListView, lvhti.iItem, 0, itemText, textSize)
ListView_GetItemRect(g_hListView, lvhti.iItem, &itemRect, LVIR_BOUNDS);
_stprintf(infoText, _T("Cursor (%d, %d) over item '%s' (index %d, rect %d, %d, %d, %d)"),
mousePosClient.x, mousePosClient.y, itemText, lvhti.iItem,
itemRect.left, itemRect.top, itemRect.right, itemRect.bottom);
}
SetWindowText(g_hMainFrame, infoText);
_stprintf(infoText, _T("Cursor screen: %d, %d\n"), mousePosScreen.x, mousePosScreen.y);
OutputDebugString(infoText);
}
LRESULT CALLBACK MainFrameWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
const size_t textSize = 1024;
TCHAR text[textSize];
RECT r;
POINT p;
auto LogRect = [&text, &r](LPCTSTR label)
{
_stprintf(text, _T("%s: %d, %d, %d, %d\n"), label, r.left, r.top, r.right, r.bottom);
OutputDebugString(text);
};
switch ( message )
{
case WM_SIZE:
MoveWindow(g_hListView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
GetWindowRect(g_hListView, &r);
LogRect(_T("ListView WindowRect"));
ListView_GetViewRect(g_hListView, &r);
LogRect(_T("ListView ViewRect "));
ListView_GetOrigin(g_hListView, &p);
_stprintf(text, _T("ListView Origin: %d, %d\n"), p.x, p.y);
OutputDebugString(text);
break;
case WM_TIMER:
UpdateListItemInfo();
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
g_hInstance = hInstance;
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
INITCOMMONCONTROLSEX icex = {};
icex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex);
g_hMainFrame = CreateMainFrame();
g_hListView = CreateListView();
ShowWindow(g_hMainFrame, nCmdShow);
UpdateWindow(g_hMainFrame);
UINT_PTR timerID = SetTimer(g_hMainFrame, ID_TimerEvent, 25, nullptr);
if ( !timerID )
FatalError(_T("Could not create the timer."));
// run the message pump
MSG msg;
while ( GetMessage(&msg, nullptr, 0, 0) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
KillTimer(g_hMainFrame, timerID);
return (int)msg.wParam;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment