Introduction:
I am not a native speaker of English, and I am also not a very experienced programmer.
I ran into a problem that is difficult for me to describe, so please keep this in mind when reading this question.
RELATED INFORMATION:
I am working on implementing drag and drop functions in listview. I just want to be able to reorder the lines inside the listview, there will be no drag and drop of items into other windows.
I do not want to use OLE for this, and I am not satisfied with the default implementation that I found on numerous links.
I have my own idea of how I would like to do this, but my inexperience prevents me from realizing my thoughts.
I am developing in Visual Studio, in C ++ and raw WinAPI. I do not use any libraries, and I would not start using them now.
Problem:
I want to implement the following behavior:
The user presses the left mouse button and starts dragging an element → the user moves the mouse along the vertical scroll bar → the default scroll occurs.
Since scrollbar counts as a non-client area, this means that I have to somehow execute the default behavior for WM_NCMOUSEMOVE and WM_NCLBUTTONDOWN , but I don't know how to do it.
Let me better explain what I mean:
When you drag an item, it’s logical that the application indicates where it will be deleted when the mouse is over the item (in the client area of the list).
When you drag an item along the scroll bar, it is obvious that the user cannot delete the item. Instead of specifying an invalid drop point (for example, by changing the cursor, for example, OLE), I want to do the following:
I want to execute the default scroll behavior (as if the user is not dragging the item at all). It would be as if the user was hovering over the scrollbar, pressing and holding the left mouse button and, optionally, moving the mouse up or down.
When the user moves the mouse from the scrollbar back to the client area of the list, drag and drop continues.
Sscce
My English was not good enough to do the right research (as I usually do before posting here), and I don’t know any application that has this type of behavior, so it was very difficult for me to try to solve this on my own.
However, while strolling through the Raymond Chen blog, I came up with the idea.
The sample code below perfectly demonstrates the behavior that I talked about above. This is not ideal, but it is closest to the implementation of the behavior that I want.
Create an empty C ++ project and just copy / paste the code below.
Then try dragging the item along the scroll bar.
IMPORTANT: I did not implement reordering of elements and did not change the shape of the cursor to keep the code minimal. The purpose of this SSCCE is to demonstrate the behavior that I want.
#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; }
Now start dragging the item and then move the mouse pointer above / below / above the finger of the scroll bar -> the behavior you are observing is the one I'm looking for.
This program has the disadvantage of:
When I try to drag an item back to the client area of the list, instead of dragging the code, the scrollbar is still controlled. This is the default behavior, but I need to change it so that the drag code is executed instead.
This is the best I could do myself. Now you can see what I'm trying to do.
If you need more information, I will update the message. In the meantime, I will continue on my own and update this post if I make progress.
Thanks for your time and help. Best wishes.