Last active
March 30, 2022 12:23
-
-
Save PBfordev/27e6186e101c89c49e744cd3a3699e64 to your computer and use it in GitHub Desktop.
Demonstrates an issue with ListView_HitTest() when the header is displayed.
This file contains 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
/********************************************************************************* | |
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