当用户将列表视图项目拖到其滚动条上时,执行默认滚动行为
Perform default scrolling behavior, when user drags listview item over its scrolbar
简介:
我的母语不是英语,也不是很有经验的程序员。
我遇到了一个很难描述的问题,所以在阅读这个问题时请记住这一点。
相关信息:
我正在致力于在列表视图中实现拖放功能。我只是希望能够在 listview 中重新排列行,不会将项目拖到其他 windows。
我不想使用 OLE 来执行此操作,而且我对在许多链接上找到的 "default" 实现不满意。
我有自己的想法,我想怎么做,但我的经验不足使我无法实现我的想法。
我正在使用 Visual Studio、C++ 和原始 WinAPI 进行开发。我没有使用任何库,也不想现在开始使用它们。
问题:
我希望实现以下行为:
用户按下鼠标左键并开始拖动项目 -> 用户将鼠标移到垂直滚动条上 -> 发生默认滚动。
由于滚动条算作非客户区,这意味着我必须以某种方式为 WM_NCMOUSEMOVE
和 WM_NCLBUTTONDOWN
执行默认行为,但我不知道该怎么做。
让我试着更好地解释一下我的意思:
当您拖动项目时,应用程序指示当鼠标悬停在某个项目上(在列表视图的客户区中)时项目将被放置的位置是合乎逻辑的。
当您将项目拖到滚动条上时,很明显用户不能将项目放在那里。我希望执行以下操作,而不是指示无效的放置点(通过更改光标,例如,像 OLE 那样):
我希望执行默认的滚动条行为(就像用户根本不拖动项目一样)。就好像用户将鼠标悬停在滚动条上,按下并按住鼠标左键,并可选择向上或向下移动鼠标。
当用户将鼠标从滚动条移回列表视图的客户区时,拖放继续。
SSCCE
我的英语不够好,无法进行适当的研究(就像我以前 post 在这里做的那样),而且我不知道有任何应用程序有这种行为,所以真的我很难自己解决这个问题。
不过,翻阅 Raymond Chen 的博客,我想到了一个主意。
下面的示例代码完美地演示了我上面谈到的行为。它并不完美,但它最接近实现我想要的行为。
创建空的 C++ 项目并简单地 copy/paste 下面的代码。
然后尝试将项目拖动到滚动条上。
重要提示:我没有实现项目的重新排列,也没有改变光标形状以保持代码最少。此 SSCCE 的目的是演示我想要的行为。
#include <windows.h>
#include <windowsx.h> // various listview macros etc
#include <CommCtrl.h>
#include <stdio.h> // swprintf_s()
// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
// link with Common Controls library
#pragma comment( lib, "comctl32.lib")
//global variables
HINSTANCE hInst;
BOOL g_bDrag;
// subclass procedure for listview -> implements drag and drop
LRESULT CALLBACK DragAndDrop(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (message)
{
case WM_CAPTURECHANGED: // in case user ALT+TAB to another window, for example
{
g_bDrag = FALSE;
}
return DefSubclassProc(hwnd, message, wParam, lParam);
case WM_LBUTTONUP: // do the drop ->omitted for brewity
{
if (g_bDrag)
{
POINT pt = { 0 };
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
g_bDrag = FALSE;
ReleaseCapture();
}
}
return DefSubclassProc(hwnd, message, wParam, lParam);
case WM_MOUSEMOVE:
{
if (g_bDrag)
{
POINT pt = { 0 };
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
LVHITTESTINFO lvhti = { 0 };
lvhti.pt = pt;
ListView_HitTest(hwnd, &lvhti);
ClientToScreen(hwnd, &pt); // WM_NCHITTEST takes screen coordinates
UINT hittest = SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y));
if (hittest == HTVSCROLL) // my try to do the default behavior
{
SendMessage(hwnd, WM_NCLBUTTONDOWN, (WPARAM)hittest, (LPARAM)POINTTOPOINTS(pt));
//SendMessage(hwnd, WM_NCMOUSEMOVE, (WPARAM)hittest, (LPARAM)POINTTOPOINTS(pt));
}
}
}
return DefSubclassProc(hwnd, message, wParam, lParam);
case WM_NCDESTROY:
::RemoveWindowSubclass(hwnd, DragAndDrop, 0);
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return ::DefSubclassProc(hwnd, message, wParam, lParam);
}
// main window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
g_bDrag = FALSE; // user is not dragging listview item
//================ create an example listview
RECT rec = { 0 };
GetClientRect(hwnd, &rec);
HWND hwndLV = CreateWindowEx(0, WC_LISTVIEW,
L"", WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT,
50, 50, 250, 200, hwnd, (HMENU)2000, hInst, 0);
// set extended listview styles
ListView_SetExtendedListViewStyle(hwndLV, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
// add some columns
LVCOLUMN lvc = { 0 };
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvc.fmt = LVCFMT_LEFT;
for (long nIndex = 0; nIndex < 5; nIndex++)
{
wchar_t txt[50];
swprintf_s(txt, 50, L"Column %d", nIndex);
lvc.iSubItem = nIndex;
lvc.cx = 60;
lvc.pszText = txt;
ListView_InsertColumn(hwndLV, nIndex, &lvc);
}
// add some items
LVITEM lvi;
lvi.mask = LVIF_TEXT;
for (lvi.iItem = 0; lvi.iItem < 10000; lvi.iItem++)
{
for (long nIndex = 0; nIndex < 5; nIndex++)
{
wchar_t txt[50];
swprintf_s(txt, 50, L"Item %d%d", lvi.iItem, nIndex);
lvi.iSubItem = nIndex;
lvi.pszText = txt;
if (!nIndex) // item
SendDlgItemMessage(hwnd, 2000, LVM_INSERTITEM, 0, reinterpret_cast<LPARAM>(&lvi));
else // sub-item
SendDlgItemMessage(hwnd, 2000, LVM_SETITEM, 0, reinterpret_cast<LPARAM>(&lvi));
}
}
//============================ subclass it
SetWindowSubclass(hwndLV, DragAndDrop, 0, 0);
}
return 0L;
case WM_NOTIFY:
{
switch (((LPNMHDR)lParam)->code)
{
case LVN_BEGINDRAG: // user started dragging listview item
{
g_bDrag = TRUE;
SetCapture(((LPNMHDR)lParam)->hwndFrom); // listview must capture the mouse
}
break;
default:
break;
}
}
break;
case WM_CLOSE:
::DestroyWindow(hwnd);
return 0L;
case WM_DESTROY:
{
::PostQuitMessage(0);
}
return 0L;
default:
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
// store hInstance in global variable for later use
hInst = hInstance;
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
// register main window class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"Main_Window";
wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!", MB_ICONEXCLAMATION |
MB_OK);
return 0;
}
// initialize common controls
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&iccex);
// create main window
hwnd = CreateWindowEx(0, L"Main_Window", L"Listview Drag and Drop",
WS_OVERLAPPEDWINDOW,
50, 50, 400, 400, NULL, NULL, hInstance, 0);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
现在开始拖动一个项目然后移动鼠标over/below/above滚动条拇指->你观察到的行为就是我要寻找的行为。
这个程序有一个缺陷:
当我尝试将项目拖回列表视图的客户区时,而不是我的拖动代码,滚动条仍然受到控制。这是默认行为,但我需要以这种方式更改它,以便执行我的拖动代码。
这是我自己能做的最好的。您现在可以看到我正在尝试做什么。
如果需要更多信息,我会更新我的 post。同时,我会继续自己尝试,如果取得进展,我会更新这个post。
感谢您的宝贵时间和帮助。最好的问候。
此方法起作用的唯一方法是找到一种在我们滚动时获取 mouse move
消息的方法。鼠标是 "lost"
但捕获仍然保留到列表视图(滚动条)。因此,当鼠标离开滚动区域时,我们需要释放捕获(从滚动条)并将其再次设置为列表视图。为此,我们将在收到 LVN_BEGINDRAG
通知消息时应用 WH_MOUSE_LL
挂钩,并在完成 dragging
时取消挂钩(这是用于垂直滚动条。想法完全相同水平):
HHOOK mouseHook = NULL;
unsigned char g_bDrag = false, g_bScroll = false; //if we are scrolling
unsigned char g_bVsrollExist = false;
RECT scrollRect; //the scrollbar rectangle, in screen coordinates
int thumbTop, thumbBottom; //the y in screen coordinates
int arrowHeight; //the height of the scrollbar up-down arrow buttons
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
NMHDR *nmr;
switch(message){ //handle the messages
case WM_NOTIFY:
nmr = (NMHDR *)lParam;
if( nmr->code == LVN_BEGINDRAG ){
//printf("BeginDrag \n");
g_bDrag = true;
SetCapture(hwndListView); // listview must capture the mouse
if( g_bVsrollExist == true ){
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)LowLevelMouseProc, NULL, NULL);
}
}
default: //for messages that we don't deal with
return DefWindowProc(hwnd, message, wParam, lParam);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam){
HWND hwnd;
MSLLHOOKSTRUCT *mslhs;
if(nCode == HC_ACTION){
switch( (int)wParam ){ //handle the messages
case WM_LBUTTONUP:
//check if we are dragging and release the mouse and unhook
if( g_bDrag == true ){
g_bDrag = false;
g_bScroll = false;
hwnd = GetCapture();
if( hwnd == hwndListView ){
ReleaseCapture();
}
if( mouseHook != NULL ){UnhookWindowsHookEx(mouseHook); mouseHook = NULL;}
}
break;
case WM_MOUSEMOVE:
if( g_bDrag == true ){
mslhs = (MSLLHOOKSTRUCT *)lParam;
// check if we are outside the area which is: scrollbar area minus the arrow buttons
if( mslhs->pt.x < scrollRect.left || mslhs->pt.x >= scrollRect.right ||
mslhs->pt.y <= scrollRect.top + arrowHeight + 1 || mslhs->pt.y > scrollRect.bottom - arrowHeight - 1 ){
if( g_bScroll == true ){
//we need to release the capture from scrollbar
ReleaseCapture();
//set it again to listview
SetTimer(hwndListView, 1, 10, NULL);
g_bScroll = false;
}
}
}
break;
default: //for messages that we don't deal with
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
在子类列表视图中:
LRESULT CALLBACK ListViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwrefData){
POINT pnt;
SCROLLBARINFO sf;
//UNREFERENCED_PARAMETER(uIdSubclass)
//UNREFERENCED_PARAMETER(dwrefData)
switch(message){ //handle the messages
case WM_MOUSEMOVE:
if( g_bDrag == true && g_bScroll == false && g_bVsrollExist == true ){
sf.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sf);
//in client coordinates
thumbTop = sf.xyThumbTop;
thumbBottom = sf.xyThumbBottom;
//in screen coordinates
thumbTop += scrollRect.top + 1;
thumbBottom += scrollRect.top - 2;
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
ClientToScreen(hwnd, &pnt);
//we check if we enter the thumb area
if( pnt.x >= scrollRect.left && pnt.x <= scrollRect.right &&
pnt.y > thumbTop + 1 && pnt.y <= thumbBottom - 1 ){
g_bScroll = true;
SendMessage(hwnd, WM_NCLBUTTONDOWN, (WPARAM)HTVSCROLL, (LPARAM)POINTTOPOINTS(pnt));
}
}
break;
case WM_TIMER:
//set the capture to listview to continue getting mouse move messages
if( (int)wParam == 1 ){
UpdateWindow(hwndListView);
SetCapture(hwndListView);
KillTimer(hwndListView, 1);
}
break;
case WM_LBUTTONDOWN:
sf.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sf);
//check if vertical scrolbar exist
if( sf.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
g_bVsrollExist = false;
break;
}
else{g_bVsrollExist = true;}
arrowHeight = sf.dxyLineButton;
scrollRect = sf.rcScrollBar;
//in client coordinates
thumbTop = sf.xyThumbTop;
thumbBottom = sf.xyThumbBottom;
//in screen coordinates
thumbTop += scrollRect.top + 1;
thumbBottom += scrollRect.top - 2;
break;
case WM_LBUTTONUP:
if(g_bDrag == true){
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
g_bDrag = false;
ReleaseCapture();
}
break;
default: //for messages that we don't deal with
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return DefSubclassProc(hwnd, message, wParam, lParam);
}
编辑(默认滚动)
unsigned char scrollUp = false, scrollDown = false, scrollLeft = false,
scrollRight = false, scrolling = false, vertScrollIsVisible = false,
horzScrollIsVisible = false;
int top, down, left, right; //client window in screen coordinates
LRESULT CALLBACK ListViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwrefData){
POINT pnt;
SCROLLBARINFO sbiVert, sbiHorz;
//UNREFERENCED_PARAMETER(uIdSubclass)
//UNREFERENCED_PARAMETER(dwrefData)
switch(message){ //handle the messages
case WM_MOUSEMOVE:
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
ClientToScreen(hwnd, &pnt);
if( g_bDrag == true && (horzScrollIsVisible == true || vertScrollIsVisible == true) ){
CheckMouse(pnt);
}
break;
case WM_LBUTTONDOWN:
sbiVert.cbSize = sizeof(SCROLLBARINFO);
sbiHorz.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
GetScrollBarInfo(hwndListView, OBJID_HSCROLL, &sbiHorz);
if( sbiVert.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
vertScrollIsVisible = false;
}
else{
vertScrollIsVisible = true;
}
if( sbiHorz.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
horzScrollIsVisible = false;
}
else{
horzScrollIsVisible = true;
}
if( vertScrollIsVisible == true ){
//you can get the header handle with hwndHeader = ListView_GetHeader(hwndListView);
GetWindowRect(hwndHeader, &rt);
top = rt.bottom;
GetWindowRect(hwndListView, &rt);
if( horzScrollIsVisible == true ){
bottom = rt.bottom - sbiHorz.dxyLineButton;
}
else{
bottom = rt.bottom;
}
}
if( horzScrollIsVisible == true ){
GetWindowRect(hwndListView, &rt);
left = rt.left;
if( vertScrollIsVisible == true ){
right = rt.right - sbiVert.dxyLineButton;
}
else{
right = rt.right;
}
}
break;
case WM_LBUTTONUP:
if(g_bDrag == true){
KillTimer(hwndWin, 1); //hwndWin is your main window
g_bDrag = false;
ReleaseCapture();
}
break;
default: //for messages that we don't deal with
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return DefSubclassProc(hwnd, message, wParam, lParam);
}
void CheckMouse(POINT pnt){
if( pnt.y < top ){
scrollUp = true;
scrollDown = false;
}
else if( pnt.y >= bottom ){
scrollDown = true;
scrollUp = false;
}
else{
scrollUp = false;
scrollDown = false;
}
if( pnt.x >= right ){
scrollRight = true;
scrollLeft = false;
}
else if( pnt.x < left ){
scrollLeft = true;
scrollRight = false;
}
else{
scrollRight = false;
scrollLeft = false;
}
if( scrollUp == true || scrollDown == true || scrollLeft == true || scrollRight == true ){
if( scrolling == false ){
scrolling = true;
SetTimer(hwndWin, 1, 20, NULL);
}
}
else{
if( scrolling == true ){
scrolling = false;
KillTimer(hwndWin, 1);
}
}
return;
}
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
NMHDR *nmr;
switch(message){ //handle the messages
case WM_NOTIFY:
nmr = (NMHDR *)lParam;
if( nmr->code == LVN_BEGINDRAG ){
//printf("BeginDrag \n");
g_bDrag = true;
SetCapture(hwndListView); // listview must capture the mouse
}
break;
case WM_TIMER:
if( (int)wParam == 1 ){
if( scrollUp == true && vertScrollIsVisible == true ){
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000000, (LPARAM)0x0); //up
}
if( scrollDown == true && vertScrollIsVisible ){
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000001, (LPARAM)0x0); //down
}
if( scrollRight == true && horzScrollIsVisible ){
SendMessage(hwndListView, WM_HSCROLL, (WPARAM)0x00000001, (LPARAM)0x0); //right
}
if( scrollLeft == true && horzScrollIsVisible ){
SendMessage(hwndListView, WM_HSCROLL, (WPARAM)0x00000000, (LPARAM)0x0); //left
}
}
break;
default: //for messages that we don't deal with
return DefWindowProc(hwnd, message, wParam, lParam);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
我正在添加另一个答案,因为代码有点长,但并不难。为简单起见,以下示例仅适用于 vertical scrollbar
。检查它,如果它工作正常,我还会为 horizontal scrollbar
添加:
您需要的变量:
enum{NO_SCROLLING, VERT_TRACK, VERT_UP_LINE, VERT_DOWN_LINE, VERT_PAGE_UP, VERT_PAGE_DOWN,
HORZ_TRACK, HORZ_LEFT_LINE, HORZ_RIGHT_LINE, HORZ_PAGE_RIGHT, HORZ_PAGE_LEFT};
//function pointer
void (*scrollStatePointer[10])(POINT) = {
VerticalTrack, VerticalUpLine, VerticalDownLine, VerticalPageUp, VerticalPageDown,
HorizontalTrack, HorizontalLeftLine, HorizontalRightLine, HorizontalPageRight,
HorizontalPageLeft
};
unsigned char g_bDrag = false, vertScrollIsVisible = false, horzScrollIsVisible = false;
HWND hwndWin = NULL, hwndListView = NULL;
HHOOK mouseHook = NULL;
//vertScrollRect is the whole vertical scrollbar rectangle
//vertFreeScrollRect is the whole vertical scrollbar rectangle without the up and down
//arrows. All in screen coordinates
RECT vertScrollRect, vertFreeScrollRect, vertUpArrowRect, vertDownArrowRect, vertThumbRect,
horzScrollRect, horzFreeScrollRect, horzLeftArrowRect, horzRightArrowRect,
horzThumbRect;
主要window:
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
NMHDR *nmr;
POINT pnt;
switch(message){ //handle the messages
case WM_NOTIFY:
nmr = (NMHDR *)lParam;
if( nmr->code == LVN_BEGINDRAG ){
//printf("BeginDrag \n");
g_bDrag = true;
SetCapture(hwndListView); // listview must capture the mouse
if( g_bVsrollExist == true ){
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)LowLevelMouseProc, NULL, NULL);
}
}
break;
case WM_TIMER:
if( (int)wParam == 1 ){
SetCapture(hwndListView);
KillTimer(hwndWin, 1);
}
if( (int)wParam == 2 ){ //vert line up
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000000, (LPARAM)0x0);
}
if( (int)wParam == 3 ){ //vert line down
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000001, (LPARAM)0x0);
}
if( (int)wParam == 4 ){ //vert page Down
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000003, (LPARAM)0x0);
GetCursorPos(&pnt);
VerticalPageDown(pnt);
}
if( (int)wParam == 5 ){ //vert page Up
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000002, (LPARAM)0x0);
GetCursorPos(&pnt);
VerticalPageUp(pnt);
}
if( (int)wParam == 6 ){ //horz line right
}
if( (int)wParam == 7 ){ //horz line left
}
if( (int)wParam == 8 ){ //horz page right
}
if( (int)wParam == 9 ){ //horz page left
}
break;
default: //for messages that we don't deal with
return DefWindowProc(hwnd, message, wParam, lParam);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
列表视图window:
LRESULT CALLBACK ListViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwrefData){
POINT pnt;
SCROLLBARINFO sbiVert, sbiHorz;
//UNREFERENCED_PARAMETER(uIdSubclass)
//UNREFERENCED_PARAMETER(dwrefData)
switch(message){ //handle the messages
case WM_MOUSEMOVE:
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
ClientToScreen(hwnd, &pnt);
if( g_bDrag == true ){
if( vertScrollIsVisible == true && horzScrollIsVisible == false ){
CheckMouseInVert(pnt);
}
else if( vertScrollIsVisible == false && horzScrollIsVisible == true ){
CheckMouseInHorz(pnt);
}
else if( vertScrollIsVisible == true && horzScrollIsVisible == true ){
CheckMouseInBoth(pnt);
}
else{ //Both scrollbars are NOT visible
break;
}
}
break;
case WM_LBUTTONDOWN:
sbiVert.cbSize = sizeof(SCROLLBARINFO);
sbiHorz.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
GetScrollBarInfo(hwndListView, OBJID_HSCROLL, &sbiHorz);
if( sbiVert.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
vertScrollIsVisible = false;
}
else{
vertScrollIsVisible = true;
}
if( sbiHorz.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
horzScrollIsVisible = false;
}
else{
horzScrollIsVisible = true;
}
if( vertScrollIsVisible == true ){
SetVertRects(&sbiVert);
}
if( horzScrollIsVisible == true ){
}
break;
default: //for messages that we don't deal with
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return DefSubclassProc(hwnd, message, wParam, lParam);
}
钩子函数:
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam){
MSLLHOOKSTRUCT *mslhs;
HWND hwnd;
if(nCode == HC_ACTION){
switch((int)wParam){ //handle the messages
case WM_LBUTTONUP:
if( g_bDrag == true ){
g_bDrag = false;
scrolling = NO_SCROLLING;
KillTimer(hwndWin, 1);
KillTimer(hwndWin, 2);
KillTimer(hwndWin, 3);
KillTimer(hwndWin, 4);
KillTimer(hwndWin, 5);
//KillTimer(hwndWin, 6);
//KillTimer(hwndWin, 7);
//KillTimer(hwndWin, 8);
//KillTimer(hwndWin, 9);
hwnd = GetCapture();
if( hwnd == hwndListView ){
ReleaseCapture();
}
if( mouseHook != NULL ){UnhookWindowsHookEx(mouseHook); mouseHook = NULL;}
}
break;
case WM_MOUSEMOVE:
if( g_bDrag == true && scrolling != NO_SCROLLING ){
mslhs = (MSLLHOOKSTRUCT *)lParam;
if( scrolling == VERT_TRACK ){
VerticalTrack( mslhs->pt );
}
else if( scrolling == HORZ_TRACK ){
HorizontalTrack( mslhs->pt );
}
else{
//Nothing
}
}
break;
default: //for messages that we don't deal with
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
各种功能:
void SetVertRects(SCROLLBARINFO *sbiVert){
vertScrollRect = sbiVert->rcScrollBar;
vertThumbRect.left = sbiVert->rcScrollBar.left;
vertThumbRect.top = sbiVert->rcScrollBar.top + sbiVert->xyThumbTop;
vertThumbRect.right = sbiVert->rcScrollBar.right;
vertThumbRect.bottom = sbiVert->rcScrollBar.top + sbiVert->xyThumbBottom;
vertUpArrowRect.left = sbiVert->rcScrollBar.left;
vertUpArrowRect.top = sbiVert->rcScrollBar.top;
vertUpArrowRect.right = sbiVert->rcScrollBar.right;
vertUpArrowRect.bottom = sbiVert->rcScrollBar.top + sbiVert->dxyLineButton;
vertDownArrowRect.left = sbiVert->rcScrollBar.left;
vertDownArrowRect.top = sbiVert->rcScrollBar.bottom - sbiVert->dxyLineButton;
vertDownArrowRect.right = sbiVert->rcScrollBar.right;
vertDownArrowRect.bottom = sbiVert->rcScrollBar.bottom;
vertFreeScrollRect.left = sbiVert->rcScrollBar.left;
vertFreeScrollRect.top = sbiVert->rcScrollBar.top + sbiVert->dxyLineButton;
vertFreeScrollRect.right = sbiVert->rcScrollBar.right;
vertFreeScrollRect.bottom = sbiVert->rcScrollBar.bottom - sbiVert->dxyLineButton;
return;
}
void VerticalTrack(POINT pnt){
SCROLLBARINFO sbiVert;
if( PtInRect(&vertFreeScrollRect, pnt) == false ){
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
ReleaseCapture();
SetTimer(hwndWin, 1, 10, NULL);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalUpLine(POINT pnt){
SCROLLBARINFO sbiVert;
if( PtInRect(&vertUpArrowRect, pnt) == false ){
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
KillTimer(hwndWin, 2);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalDownLine(POINT pnt){
SCROLLBARINFO sbiVert;
if( PtInRect(&vertDownArrowRect, pnt) == false ){
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
KillTimer(hwndWin, 3);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalPageUp(POINT pnt){
SCROLLBARINFO sbiVert;
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
if( PtInRect(&vertFreeScrollRect, pnt) == false || PtInRect(&vertThumbRect, pnt) != false ){
KillTimer(hwndWin, 5);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalPageDown(POINT pnt){
SCROLLBARINFO sbiVert;
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
if( PtInRect(&vertFreeScrollRect, pnt) == false || PtInRect(&vertThumbRect, pnt) != false ){
KillTimer(hwndWin, 4);
scrolling = NO_SCROLLING;
}
return;
}
void HorizontalTrack(POINT pnt){
return;
}
void HorizontalLeftLine(POINT pnt){
return;
}
void HorizontalRightLine(POINT pnt){
return;
}
void HorizontalPageRight(POINT pnt){
return;
}
void HorizontalPageLeft(POINT pnt){
return;
}
void CheckMouseInVert(POINT pnt){
SCROLLBARINFO sbiVert;
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
if( scrolling == NO_SCROLLING ){
if( PtInRect(&vertScrollRect, pnt) == true ){
if( PtInRect(&vertUpArrowRect, pnt) == true ){
SetTimer(hwndWin, 2, 50, NULL);
scrolling = VERT_UP_LINE;
return;
}
if( PtInRect(&vertDownArrowRect, pnt) == true ){
SetTimer(hwndWin, 3, 50, NULL);
scrolling = VERT_DOWN_LINE;
return;
}
if( PtInRect(&vertThumbRect, pnt) == true ){
scrolling = VERT_TRACK;
SendMessage(hwndListView, WM_NCLBUTTONDOWN, (WPARAM)HTVSCROLL, (LPARAM)POINTTOPOINTS(pnt));
return;
}
if( pnt.y < vertThumbRect.top ){
SetTimer(hwndWin, 5, 50, NULL);
scrolling = VERT_PAGE_UP;
}
else{
SetTimer(hwndWin, 4, 50, NULL);
scrolling = VERT_PAGE_DOWN;
}
}
}
else{
(*scrollStatePointer[ scrolling - 1 ])( pnt );
}
return;
}
char CheckMouseInHorz(POINT pnt){
return;
}
void CheckMouseInBoth(POINT pnt){
return;
}
简介:
我的母语不是英语,也不是很有经验的程序员。
我遇到了一个很难描述的问题,所以在阅读这个问题时请记住这一点。
相关信息:
我正在致力于在列表视图中实现拖放功能。我只是希望能够在 listview 中重新排列行,不会将项目拖到其他 windows。
我不想使用 OLE 来执行此操作,而且我对在许多链接上找到的 "default" 实现不满意。
我有自己的想法,我想怎么做,但我的经验不足使我无法实现我的想法。
我正在使用 Visual Studio、C++ 和原始 WinAPI 进行开发。我没有使用任何库,也不想现在开始使用它们。
问题:
我希望实现以下行为:
用户按下鼠标左键并开始拖动项目 -> 用户将鼠标移到垂直滚动条上 -> 发生默认滚动。
由于滚动条算作非客户区,这意味着我必须以某种方式为 WM_NCMOUSEMOVE
和 WM_NCLBUTTONDOWN
执行默认行为,但我不知道该怎么做。
让我试着更好地解释一下我的意思:
当您拖动项目时,应用程序指示当鼠标悬停在某个项目上(在列表视图的客户区中)时项目将被放置的位置是合乎逻辑的。
当您将项目拖到滚动条上时,很明显用户不能将项目放在那里。我希望执行以下操作,而不是指示无效的放置点(通过更改光标,例如,像 OLE 那样):
我希望执行默认的滚动条行为(就像用户根本不拖动项目一样)。就好像用户将鼠标悬停在滚动条上,按下并按住鼠标左键,并可选择向上或向下移动鼠标。
当用户将鼠标从滚动条移回列表视图的客户区时,拖放继续。
SSCCE
我的英语不够好,无法进行适当的研究(就像我以前 post 在这里做的那样),而且我不知道有任何应用程序有这种行为,所以真的我很难自己解决这个问题。
不过,翻阅 Raymond Chen 的博客,我想到了一个主意。
下面的示例代码完美地演示了我上面谈到的行为。它并不完美,但它最接近实现我想要的行为。
创建空的 C++ 项目并简单地 copy/paste 下面的代码。
然后尝试将项目拖动到滚动条上。
重要提示:我没有实现项目的重新排列,也没有改变光标形状以保持代码最少。此 SSCCE 的目的是演示我想要的行为。
#include <windows.h>
#include <windowsx.h> // various listview macros etc
#include <CommCtrl.h>
#include <stdio.h> // swprintf_s()
// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
// link with Common Controls library
#pragma comment( lib, "comctl32.lib")
//global variables
HINSTANCE hInst;
BOOL g_bDrag;
// subclass procedure for listview -> implements drag and drop
LRESULT CALLBACK DragAndDrop(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (message)
{
case WM_CAPTURECHANGED: // in case user ALT+TAB to another window, for example
{
g_bDrag = FALSE;
}
return DefSubclassProc(hwnd, message, wParam, lParam);
case WM_LBUTTONUP: // do the drop ->omitted for brewity
{
if (g_bDrag)
{
POINT pt = { 0 };
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
g_bDrag = FALSE;
ReleaseCapture();
}
}
return DefSubclassProc(hwnd, message, wParam, lParam);
case WM_MOUSEMOVE:
{
if (g_bDrag)
{
POINT pt = { 0 };
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
LVHITTESTINFO lvhti = { 0 };
lvhti.pt = pt;
ListView_HitTest(hwnd, &lvhti);
ClientToScreen(hwnd, &pt); // WM_NCHITTEST takes screen coordinates
UINT hittest = SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y));
if (hittest == HTVSCROLL) // my try to do the default behavior
{
SendMessage(hwnd, WM_NCLBUTTONDOWN, (WPARAM)hittest, (LPARAM)POINTTOPOINTS(pt));
//SendMessage(hwnd, WM_NCMOUSEMOVE, (WPARAM)hittest, (LPARAM)POINTTOPOINTS(pt));
}
}
}
return DefSubclassProc(hwnd, message, wParam, lParam);
case WM_NCDESTROY:
::RemoveWindowSubclass(hwnd, DragAndDrop, 0);
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return ::DefSubclassProc(hwnd, message, wParam, lParam);
}
// main window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
g_bDrag = FALSE; // user is not dragging listview item
//================ create an example listview
RECT rec = { 0 };
GetClientRect(hwnd, &rec);
HWND hwndLV = CreateWindowEx(0, WC_LISTVIEW,
L"", WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT,
50, 50, 250, 200, hwnd, (HMENU)2000, hInst, 0);
// set extended listview styles
ListView_SetExtendedListViewStyle(hwndLV, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
// add some columns
LVCOLUMN lvc = { 0 };
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvc.fmt = LVCFMT_LEFT;
for (long nIndex = 0; nIndex < 5; nIndex++)
{
wchar_t txt[50];
swprintf_s(txt, 50, L"Column %d", nIndex);
lvc.iSubItem = nIndex;
lvc.cx = 60;
lvc.pszText = txt;
ListView_InsertColumn(hwndLV, nIndex, &lvc);
}
// add some items
LVITEM lvi;
lvi.mask = LVIF_TEXT;
for (lvi.iItem = 0; lvi.iItem < 10000; lvi.iItem++)
{
for (long nIndex = 0; nIndex < 5; nIndex++)
{
wchar_t txt[50];
swprintf_s(txt, 50, L"Item %d%d", lvi.iItem, nIndex);
lvi.iSubItem = nIndex;
lvi.pszText = txt;
if (!nIndex) // item
SendDlgItemMessage(hwnd, 2000, LVM_INSERTITEM, 0, reinterpret_cast<LPARAM>(&lvi));
else // sub-item
SendDlgItemMessage(hwnd, 2000, LVM_SETITEM, 0, reinterpret_cast<LPARAM>(&lvi));
}
}
//============================ subclass it
SetWindowSubclass(hwndLV, DragAndDrop, 0, 0);
}
return 0L;
case WM_NOTIFY:
{
switch (((LPNMHDR)lParam)->code)
{
case LVN_BEGINDRAG: // user started dragging listview item
{
g_bDrag = TRUE;
SetCapture(((LPNMHDR)lParam)->hwndFrom); // listview must capture the mouse
}
break;
default:
break;
}
}
break;
case WM_CLOSE:
::DestroyWindow(hwnd);
return 0L;
case WM_DESTROY:
{
::PostQuitMessage(0);
}
return 0L;
default:
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
// store hInstance in global variable for later use
hInst = hInstance;
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
// register main window class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"Main_Window";
wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!", MB_ICONEXCLAMATION |
MB_OK);
return 0;
}
// initialize common controls
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&iccex);
// create main window
hwnd = CreateWindowEx(0, L"Main_Window", L"Listview Drag and Drop",
WS_OVERLAPPEDWINDOW,
50, 50, 400, 400, NULL, NULL, hInstance, 0);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
现在开始拖动一个项目然后移动鼠标over/below/above滚动条拇指->你观察到的行为就是我要寻找的行为。
这个程序有一个缺陷:
当我尝试将项目拖回列表视图的客户区时,而不是我的拖动代码,滚动条仍然受到控制。这是默认行为,但我需要以这种方式更改它,以便执行我的拖动代码。
这是我自己能做的最好的。您现在可以看到我正在尝试做什么。
如果需要更多信息,我会更新我的 post。同时,我会继续自己尝试,如果取得进展,我会更新这个post。
感谢您的宝贵时间和帮助。最好的问候。
此方法起作用的唯一方法是找到一种在我们滚动时获取 mouse move
消息的方法。鼠标是 "lost"
但捕获仍然保留到列表视图(滚动条)。因此,当鼠标离开滚动区域时,我们需要释放捕获(从滚动条)并将其再次设置为列表视图。为此,我们将在收到 LVN_BEGINDRAG
通知消息时应用 WH_MOUSE_LL
挂钩,并在完成 dragging
时取消挂钩(这是用于垂直滚动条。想法完全相同水平):
HHOOK mouseHook = NULL;
unsigned char g_bDrag = false, g_bScroll = false; //if we are scrolling
unsigned char g_bVsrollExist = false;
RECT scrollRect; //the scrollbar rectangle, in screen coordinates
int thumbTop, thumbBottom; //the y in screen coordinates
int arrowHeight; //the height of the scrollbar up-down arrow buttons
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
NMHDR *nmr;
switch(message){ //handle the messages
case WM_NOTIFY:
nmr = (NMHDR *)lParam;
if( nmr->code == LVN_BEGINDRAG ){
//printf("BeginDrag \n");
g_bDrag = true;
SetCapture(hwndListView); // listview must capture the mouse
if( g_bVsrollExist == true ){
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)LowLevelMouseProc, NULL, NULL);
}
}
default: //for messages that we don't deal with
return DefWindowProc(hwnd, message, wParam, lParam);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam){
HWND hwnd;
MSLLHOOKSTRUCT *mslhs;
if(nCode == HC_ACTION){
switch( (int)wParam ){ //handle the messages
case WM_LBUTTONUP:
//check if we are dragging and release the mouse and unhook
if( g_bDrag == true ){
g_bDrag = false;
g_bScroll = false;
hwnd = GetCapture();
if( hwnd == hwndListView ){
ReleaseCapture();
}
if( mouseHook != NULL ){UnhookWindowsHookEx(mouseHook); mouseHook = NULL;}
}
break;
case WM_MOUSEMOVE:
if( g_bDrag == true ){
mslhs = (MSLLHOOKSTRUCT *)lParam;
// check if we are outside the area which is: scrollbar area minus the arrow buttons
if( mslhs->pt.x < scrollRect.left || mslhs->pt.x >= scrollRect.right ||
mslhs->pt.y <= scrollRect.top + arrowHeight + 1 || mslhs->pt.y > scrollRect.bottom - arrowHeight - 1 ){
if( g_bScroll == true ){
//we need to release the capture from scrollbar
ReleaseCapture();
//set it again to listview
SetTimer(hwndListView, 1, 10, NULL);
g_bScroll = false;
}
}
}
break;
default: //for messages that we don't deal with
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
在子类列表视图中:
LRESULT CALLBACK ListViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwrefData){
POINT pnt;
SCROLLBARINFO sf;
//UNREFERENCED_PARAMETER(uIdSubclass)
//UNREFERENCED_PARAMETER(dwrefData)
switch(message){ //handle the messages
case WM_MOUSEMOVE:
if( g_bDrag == true && g_bScroll == false && g_bVsrollExist == true ){
sf.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sf);
//in client coordinates
thumbTop = sf.xyThumbTop;
thumbBottom = sf.xyThumbBottom;
//in screen coordinates
thumbTop += scrollRect.top + 1;
thumbBottom += scrollRect.top - 2;
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
ClientToScreen(hwnd, &pnt);
//we check if we enter the thumb area
if( pnt.x >= scrollRect.left && pnt.x <= scrollRect.right &&
pnt.y > thumbTop + 1 && pnt.y <= thumbBottom - 1 ){
g_bScroll = true;
SendMessage(hwnd, WM_NCLBUTTONDOWN, (WPARAM)HTVSCROLL, (LPARAM)POINTTOPOINTS(pnt));
}
}
break;
case WM_TIMER:
//set the capture to listview to continue getting mouse move messages
if( (int)wParam == 1 ){
UpdateWindow(hwndListView);
SetCapture(hwndListView);
KillTimer(hwndListView, 1);
}
break;
case WM_LBUTTONDOWN:
sf.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sf);
//check if vertical scrolbar exist
if( sf.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
g_bVsrollExist = false;
break;
}
else{g_bVsrollExist = true;}
arrowHeight = sf.dxyLineButton;
scrollRect = sf.rcScrollBar;
//in client coordinates
thumbTop = sf.xyThumbTop;
thumbBottom = sf.xyThumbBottom;
//in screen coordinates
thumbTop += scrollRect.top + 1;
thumbBottom += scrollRect.top - 2;
break;
case WM_LBUTTONUP:
if(g_bDrag == true){
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
g_bDrag = false;
ReleaseCapture();
}
break;
default: //for messages that we don't deal with
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return DefSubclassProc(hwnd, message, wParam, lParam);
}
编辑(默认滚动)
unsigned char scrollUp = false, scrollDown = false, scrollLeft = false,
scrollRight = false, scrolling = false, vertScrollIsVisible = false,
horzScrollIsVisible = false;
int top, down, left, right; //client window in screen coordinates
LRESULT CALLBACK ListViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwrefData){
POINT pnt;
SCROLLBARINFO sbiVert, sbiHorz;
//UNREFERENCED_PARAMETER(uIdSubclass)
//UNREFERENCED_PARAMETER(dwrefData)
switch(message){ //handle the messages
case WM_MOUSEMOVE:
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
ClientToScreen(hwnd, &pnt);
if( g_bDrag == true && (horzScrollIsVisible == true || vertScrollIsVisible == true) ){
CheckMouse(pnt);
}
break;
case WM_LBUTTONDOWN:
sbiVert.cbSize = sizeof(SCROLLBARINFO);
sbiHorz.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
GetScrollBarInfo(hwndListView, OBJID_HSCROLL, &sbiHorz);
if( sbiVert.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
vertScrollIsVisible = false;
}
else{
vertScrollIsVisible = true;
}
if( sbiHorz.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
horzScrollIsVisible = false;
}
else{
horzScrollIsVisible = true;
}
if( vertScrollIsVisible == true ){
//you can get the header handle with hwndHeader = ListView_GetHeader(hwndListView);
GetWindowRect(hwndHeader, &rt);
top = rt.bottom;
GetWindowRect(hwndListView, &rt);
if( horzScrollIsVisible == true ){
bottom = rt.bottom - sbiHorz.dxyLineButton;
}
else{
bottom = rt.bottom;
}
}
if( horzScrollIsVisible == true ){
GetWindowRect(hwndListView, &rt);
left = rt.left;
if( vertScrollIsVisible == true ){
right = rt.right - sbiVert.dxyLineButton;
}
else{
right = rt.right;
}
}
break;
case WM_LBUTTONUP:
if(g_bDrag == true){
KillTimer(hwndWin, 1); //hwndWin is your main window
g_bDrag = false;
ReleaseCapture();
}
break;
default: //for messages that we don't deal with
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return DefSubclassProc(hwnd, message, wParam, lParam);
}
void CheckMouse(POINT pnt){
if( pnt.y < top ){
scrollUp = true;
scrollDown = false;
}
else if( pnt.y >= bottom ){
scrollDown = true;
scrollUp = false;
}
else{
scrollUp = false;
scrollDown = false;
}
if( pnt.x >= right ){
scrollRight = true;
scrollLeft = false;
}
else if( pnt.x < left ){
scrollLeft = true;
scrollRight = false;
}
else{
scrollRight = false;
scrollLeft = false;
}
if( scrollUp == true || scrollDown == true || scrollLeft == true || scrollRight == true ){
if( scrolling == false ){
scrolling = true;
SetTimer(hwndWin, 1, 20, NULL);
}
}
else{
if( scrolling == true ){
scrolling = false;
KillTimer(hwndWin, 1);
}
}
return;
}
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
NMHDR *nmr;
switch(message){ //handle the messages
case WM_NOTIFY:
nmr = (NMHDR *)lParam;
if( nmr->code == LVN_BEGINDRAG ){
//printf("BeginDrag \n");
g_bDrag = true;
SetCapture(hwndListView); // listview must capture the mouse
}
break;
case WM_TIMER:
if( (int)wParam == 1 ){
if( scrollUp == true && vertScrollIsVisible == true ){
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000000, (LPARAM)0x0); //up
}
if( scrollDown == true && vertScrollIsVisible ){
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000001, (LPARAM)0x0); //down
}
if( scrollRight == true && horzScrollIsVisible ){
SendMessage(hwndListView, WM_HSCROLL, (WPARAM)0x00000001, (LPARAM)0x0); //right
}
if( scrollLeft == true && horzScrollIsVisible ){
SendMessage(hwndListView, WM_HSCROLL, (WPARAM)0x00000000, (LPARAM)0x0); //left
}
}
break;
default: //for messages that we don't deal with
return DefWindowProc(hwnd, message, wParam, lParam);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
我正在添加另一个答案,因为代码有点长,但并不难。为简单起见,以下示例仅适用于 vertical scrollbar
。检查它,如果它工作正常,我还会为 horizontal scrollbar
添加:
您需要的变量:
enum{NO_SCROLLING, VERT_TRACK, VERT_UP_LINE, VERT_DOWN_LINE, VERT_PAGE_UP, VERT_PAGE_DOWN,
HORZ_TRACK, HORZ_LEFT_LINE, HORZ_RIGHT_LINE, HORZ_PAGE_RIGHT, HORZ_PAGE_LEFT};
//function pointer
void (*scrollStatePointer[10])(POINT) = {
VerticalTrack, VerticalUpLine, VerticalDownLine, VerticalPageUp, VerticalPageDown,
HorizontalTrack, HorizontalLeftLine, HorizontalRightLine, HorizontalPageRight,
HorizontalPageLeft
};
unsigned char g_bDrag = false, vertScrollIsVisible = false, horzScrollIsVisible = false;
HWND hwndWin = NULL, hwndListView = NULL;
HHOOK mouseHook = NULL;
//vertScrollRect is the whole vertical scrollbar rectangle
//vertFreeScrollRect is the whole vertical scrollbar rectangle without the up and down
//arrows. All in screen coordinates
RECT vertScrollRect, vertFreeScrollRect, vertUpArrowRect, vertDownArrowRect, vertThumbRect,
horzScrollRect, horzFreeScrollRect, horzLeftArrowRect, horzRightArrowRect,
horzThumbRect;
主要window:
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
NMHDR *nmr;
POINT pnt;
switch(message){ //handle the messages
case WM_NOTIFY:
nmr = (NMHDR *)lParam;
if( nmr->code == LVN_BEGINDRAG ){
//printf("BeginDrag \n");
g_bDrag = true;
SetCapture(hwndListView); // listview must capture the mouse
if( g_bVsrollExist == true ){
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)LowLevelMouseProc, NULL, NULL);
}
}
break;
case WM_TIMER:
if( (int)wParam == 1 ){
SetCapture(hwndListView);
KillTimer(hwndWin, 1);
}
if( (int)wParam == 2 ){ //vert line up
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000000, (LPARAM)0x0);
}
if( (int)wParam == 3 ){ //vert line down
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000001, (LPARAM)0x0);
}
if( (int)wParam == 4 ){ //vert page Down
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000003, (LPARAM)0x0);
GetCursorPos(&pnt);
VerticalPageDown(pnt);
}
if( (int)wParam == 5 ){ //vert page Up
SendMessage(hwndListView, WM_VSCROLL, (WPARAM)0x00000002, (LPARAM)0x0);
GetCursorPos(&pnt);
VerticalPageUp(pnt);
}
if( (int)wParam == 6 ){ //horz line right
}
if( (int)wParam == 7 ){ //horz line left
}
if( (int)wParam == 8 ){ //horz page right
}
if( (int)wParam == 9 ){ //horz page left
}
break;
default: //for messages that we don't deal with
return DefWindowProc(hwnd, message, wParam, lParam);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
列表视图window:
LRESULT CALLBACK ListViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwrefData){
POINT pnt;
SCROLLBARINFO sbiVert, sbiHorz;
//UNREFERENCED_PARAMETER(uIdSubclass)
//UNREFERENCED_PARAMETER(dwrefData)
switch(message){ //handle the messages
case WM_MOUSEMOVE:
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
ClientToScreen(hwnd, &pnt);
if( g_bDrag == true ){
if( vertScrollIsVisible == true && horzScrollIsVisible == false ){
CheckMouseInVert(pnt);
}
else if( vertScrollIsVisible == false && horzScrollIsVisible == true ){
CheckMouseInHorz(pnt);
}
else if( vertScrollIsVisible == true && horzScrollIsVisible == true ){
CheckMouseInBoth(pnt);
}
else{ //Both scrollbars are NOT visible
break;
}
}
break;
case WM_LBUTTONDOWN:
sbiVert.cbSize = sizeof(SCROLLBARINFO);
sbiHorz.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
GetScrollBarInfo(hwndListView, OBJID_HSCROLL, &sbiHorz);
if( sbiVert.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
vertScrollIsVisible = false;
}
else{
vertScrollIsVisible = true;
}
if( sbiHorz.rgstate[0] == STATE_SYSTEM_INVISIBLE ){
horzScrollIsVisible = false;
}
else{
horzScrollIsVisible = true;
}
if( vertScrollIsVisible == true ){
SetVertRects(&sbiVert);
}
if( horzScrollIsVisible == true ){
}
break;
default: //for messages that we don't deal with
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return DefSubclassProc(hwnd, message, wParam, lParam);
}
钩子函数:
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam){
MSLLHOOKSTRUCT *mslhs;
HWND hwnd;
if(nCode == HC_ACTION){
switch((int)wParam){ //handle the messages
case WM_LBUTTONUP:
if( g_bDrag == true ){
g_bDrag = false;
scrolling = NO_SCROLLING;
KillTimer(hwndWin, 1);
KillTimer(hwndWin, 2);
KillTimer(hwndWin, 3);
KillTimer(hwndWin, 4);
KillTimer(hwndWin, 5);
//KillTimer(hwndWin, 6);
//KillTimer(hwndWin, 7);
//KillTimer(hwndWin, 8);
//KillTimer(hwndWin, 9);
hwnd = GetCapture();
if( hwnd == hwndListView ){
ReleaseCapture();
}
if( mouseHook != NULL ){UnhookWindowsHookEx(mouseHook); mouseHook = NULL;}
}
break;
case WM_MOUSEMOVE:
if( g_bDrag == true && scrolling != NO_SCROLLING ){
mslhs = (MSLLHOOKSTRUCT *)lParam;
if( scrolling == VERT_TRACK ){
VerticalTrack( mslhs->pt );
}
else if( scrolling == HORZ_TRACK ){
HorizontalTrack( mslhs->pt );
}
else{
//Nothing
}
}
break;
default: //for messages that we don't deal with
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
各种功能:
void SetVertRects(SCROLLBARINFO *sbiVert){
vertScrollRect = sbiVert->rcScrollBar;
vertThumbRect.left = sbiVert->rcScrollBar.left;
vertThumbRect.top = sbiVert->rcScrollBar.top + sbiVert->xyThumbTop;
vertThumbRect.right = sbiVert->rcScrollBar.right;
vertThumbRect.bottom = sbiVert->rcScrollBar.top + sbiVert->xyThumbBottom;
vertUpArrowRect.left = sbiVert->rcScrollBar.left;
vertUpArrowRect.top = sbiVert->rcScrollBar.top;
vertUpArrowRect.right = sbiVert->rcScrollBar.right;
vertUpArrowRect.bottom = sbiVert->rcScrollBar.top + sbiVert->dxyLineButton;
vertDownArrowRect.left = sbiVert->rcScrollBar.left;
vertDownArrowRect.top = sbiVert->rcScrollBar.bottom - sbiVert->dxyLineButton;
vertDownArrowRect.right = sbiVert->rcScrollBar.right;
vertDownArrowRect.bottom = sbiVert->rcScrollBar.bottom;
vertFreeScrollRect.left = sbiVert->rcScrollBar.left;
vertFreeScrollRect.top = sbiVert->rcScrollBar.top + sbiVert->dxyLineButton;
vertFreeScrollRect.right = sbiVert->rcScrollBar.right;
vertFreeScrollRect.bottom = sbiVert->rcScrollBar.bottom - sbiVert->dxyLineButton;
return;
}
void VerticalTrack(POINT pnt){
SCROLLBARINFO sbiVert;
if( PtInRect(&vertFreeScrollRect, pnt) == false ){
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
ReleaseCapture();
SetTimer(hwndWin, 1, 10, NULL);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalUpLine(POINT pnt){
SCROLLBARINFO sbiVert;
if( PtInRect(&vertUpArrowRect, pnt) == false ){
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
KillTimer(hwndWin, 2);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalDownLine(POINT pnt){
SCROLLBARINFO sbiVert;
if( PtInRect(&vertDownArrowRect, pnt) == false ){
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
KillTimer(hwndWin, 3);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalPageUp(POINT pnt){
SCROLLBARINFO sbiVert;
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
if( PtInRect(&vertFreeScrollRect, pnt) == false || PtInRect(&vertThumbRect, pnt) != false ){
KillTimer(hwndWin, 5);
scrolling = NO_SCROLLING;
}
return;
}
void VerticalPageDown(POINT pnt){
SCROLLBARINFO sbiVert;
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
if( PtInRect(&vertFreeScrollRect, pnt) == false || PtInRect(&vertThumbRect, pnt) != false ){
KillTimer(hwndWin, 4);
scrolling = NO_SCROLLING;
}
return;
}
void HorizontalTrack(POINT pnt){
return;
}
void HorizontalLeftLine(POINT pnt){
return;
}
void HorizontalRightLine(POINT pnt){
return;
}
void HorizontalPageRight(POINT pnt){
return;
}
void HorizontalPageLeft(POINT pnt){
return;
}
void CheckMouseInVert(POINT pnt){
SCROLLBARINFO sbiVert;
sbiVert.cbSize = sizeof(SCROLLBARINFO);
GetScrollBarInfo(hwndListView, OBJID_VSCROLL, &sbiVert);
vertThumbRect.left = sbiVert.rcScrollBar.left;
vertThumbRect.top = sbiVert.rcScrollBar.top + sbiVert.xyThumbTop;
vertThumbRect.right = sbiVert.rcScrollBar.right;
vertThumbRect.bottom = sbiVert.rcScrollBar.top + sbiVert.xyThumbBottom;
if( scrolling == NO_SCROLLING ){
if( PtInRect(&vertScrollRect, pnt) == true ){
if( PtInRect(&vertUpArrowRect, pnt) == true ){
SetTimer(hwndWin, 2, 50, NULL);
scrolling = VERT_UP_LINE;
return;
}
if( PtInRect(&vertDownArrowRect, pnt) == true ){
SetTimer(hwndWin, 3, 50, NULL);
scrolling = VERT_DOWN_LINE;
return;
}
if( PtInRect(&vertThumbRect, pnt) == true ){
scrolling = VERT_TRACK;
SendMessage(hwndListView, WM_NCLBUTTONDOWN, (WPARAM)HTVSCROLL, (LPARAM)POINTTOPOINTS(pnt));
return;
}
if( pnt.y < vertThumbRect.top ){
SetTimer(hwndWin, 5, 50, NULL);
scrolling = VERT_PAGE_UP;
}
else{
SetTimer(hwndWin, 4, 50, NULL);
scrolling = VERT_PAGE_DOWN;
}
}
}
else{
(*scrollStatePointer[ scrolling - 1 ])( pnt );
}
return;
}
char CheckMouseInHorz(POINT pnt){
return;
}
void CheckMouseInBoth(POINT pnt){
return;
}