Perform default scrolling behavior, when user drags listview item over its scrolbar
Asked Answered
L

2

7

INTRODUCTION:

I am not native English speaker, and I am not very experienced programmer either.

I have met a problem that I have a hard time describing, so please bear this in mind while reading this question.

RELEVANT INFORMATION:

I am working on implementing drag and drop functionality in listview. I just want to be able to rearrange rows inside listview, there will be no dragging items to other windows.

I do not want to use OLE to do this, and I am not satisfied with "default" implementation I found on numerous links.

I have my own idea on how I would like to do this, but my inexperience prevents me from implementing my thoughts.

I am developing in Visual Studio, in C++ and raw WinAPI. I am not using any libraries, nor would I like to start using them now.

PROBLEM:

I wish to implement the following behavior:

User presses left mouse button and starts dragging an item -> user moves mouse over vertical scrollbar -> default scrolling occurs.

Since scrollbar counts as nonclient area, this means that I must somehow perform default behavior for WM_NCMOUSEMOVE and WM_NCLBUTTONDOWN but I do not know how to do it.

Let me try and explain better what I mean:

When you drag item, it is logical that application indicates where it will be dropped when the mouse is over an item ( in the client area of the listview ).

When you drag item over scrollbar, it is obvious that user can not drop item there. Instead of indicating invalid dropping point ( by changing the cursor, for example, like OLE does), I wish to perform the following:

I wish to perform default scrollbar behavior (as if user does not drag item at all ). It would be as if user hovers over scrollbar, presses and holds down left mouse button, and optionally, moves the mouse up or down.

When user moves mouse from scrollbar back into the client area of the listview, drag and drop continues.

SSCCE

My English was not good enough to conduct a proper research ( as I usually do before posting here ), and I do not know of any app that has this type of behavior, so it was really hard for me to try to solve this on my own.

Still, trudging through the Raymond Chen's blog I came to an idea.

The example code below perfectly demonstrates the behavior I talked about above. It is not perfect, but it is the closest to implementing the behavior I want.

Create empty C++ project and simply copy/paste the code below.

Then try to drag item over scrollbar.

IMPORTANT: I haven't implemented rearranging of items, nor have changed cursor shape in order to keep the code minimal. The purpose of this SSCCE is to demonstrate the the behavior 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 an item and then move the mouse over/below/above scrollbar thumb -> the behavior you observe is the one I seek.

This program has a flaw:

When I try to drag item back into the client area of the listview, instead of my dragging code, scrollbar still gets controlled. This is the default behavior, but I need to change it in such a way so my dragging code executes instead.

This is the best I was able to do on my own. You can now see what I am trying to do.

If further info is required I will update my post. Meanwhile I will keep trying on my own and update this post if I make progress.

Thank you for your time and help. Best regards.

Lowrie answered 16/4, 2015 at 16:59 Comment(6)
The answer is the same as before - capture the mouse input. But why go to these lengths to add nonstandard behavior that no one will expect? No other windows app scrolls like that.Hayne
@JonathanPotter: But why go to these lengths to add nonstandard behavior that no one will expect? No other windows app scrolls like that. I like my way better :) The answer is the same as before - capture the mouse input. I have done so in LVN_BEGINDRAG handler, please see the SSCCE above. At the moment I can not conclude more from your comment. Please be patient, as I am preparing important edit as I type this comment. Thank you for trying to help. Best regards.Lowrie
Your method of controling the scrolling can not work for the simple reason that when you send the WM_NCLBUTTONDOWN message you lose the mouse. The cursor is captured by the scrollbar. You don't get any messages to check if the cursor has left the scrollbar area any more. Another problem with this method is when you move the mouse not on the thumb but on the up-down arrows of the scrollbar.Evocative
@γηράσκωδ'αείπολλάδιδασκόμε: After extensively experimenting with the tools mentioned in my post, I came to the same conclusion -> I lose the mouse. I apologize for asking, but do you think that my task can be done in any way? Sadly, I fear like I ask impossible... Thank you for trying to help. Best regards.Lowrie
@Lowrie Have you seen this article?Fabliau
@Axalo: Of course, it was the first thing I found. It does not handle scrolling though and has bug when moving multiple items ( see the comments ). Thanks for trying to help though, I appreciate it. Best regards.Lowrie
T
2

The only way for this approach to work is to find a way to get the mouse move messages while we are scrolling. The mouse is "lost" but the capture still remains to listview(scrollbar). So, when the mouse leaves the scrolling area we need to release the capture(from the scrollbar) and set it again to listview. To accomplish this, we will apply a WH_MOUSE_LL hook when we get the LVN_BEGINDRAG notify message and unhook when we finish the dragging (this is for vertical scroll bar. The idea is exactly the same for horizontal):

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);
}

In the subclassed listview:

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);
}

EDIT (default scrolling)

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);
}
Tirrell answered 22/4, 2015 at 20:21 Comment(8)
This is very close to what I am trying to achieve. I wish I could explain better but I can not. It is all in my post. Technically, I do need to use arrows as well, as well as page up and page down. Your example uses just SB_THUMBTRACK-ing. Still, worth the bounty as I believe no one else will try to help. Just wait for expiration period and then I will award the points. Thank you. Best regards.Lowrie
@Lowrie To help me understand what exactly you want(because it is not that difficult). Lets say g_bDrag = true and you move the mouse on top of the thumb. You start as you say SB_THUMBTRACK-ing. The question is when to stop SB_THUMBTRACK-ing: when you enter only the client area? When you leave the listview and scrollbar area to the right(for vertical scrollbar) do you want to still SB_THUMBTRACK-ing? When you enter the up or down arrow area do you want to start automatically line scrolling or continue SB_THUMBTRACK-ing? etcEvocative
When you enter the up or down arrow area SB_LINEDOWN / SB_LINEUP-ing. SB_THUMBTRACK-ing is fine from what I have seen. And finally, when over SB_PAGEUP / SB_PAGEDOWN default behavior while cursor is on vertical scrollbar. When cursor leaves vertical scrollbar abort scrolling and resume dragging. Thanks.Lowrie
@Lowrie There is a problem with WM_NCLBUTTONDOWN when there is a page and line scroll. I am trying to solve it.Evocative
OK, no problem. I haven't given up on this. I just dislike the usage of hooks but if that is the only way... I am trying to handle this myself as well. Still, in my production code I will go as user Hans Passant suggested, implementing default paradigm. Too bad he deleted his answer... Best regards.Lowrie
@Lowrie If you want the default way to scroll, it is very easy. See edit. I thought you didn't want that.Evocative
I thought you didn't want that. No I did not, but will have to. I prefer your solution, but the problem is that arrows and page up/down is not affected with your current code. I guess you need time to figure it out. What I am trying to say is that your solution is very close to what I need ( only arrows + pages remain to be implemented ). That is the answer I seek to this question, do not be confused with my previous comment. Since I am reaching deadline, I will have to solve the problem somehow and that is why I said I will use the other approach. I am still working on this, I haven't quit.Lowrie
@Lowrie I have already finished the code but didn't post it because i thought you went with the second approach. Ok, I will post the code later.Evocative
T
0

I am adding another answer because the code is a bit longgggg, but not difficult. The following example for simplicity is for vertical scrollbar only. Check it and if it works correctly I will add for the horizontal scrollbar also:

Variables you will need:

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;

Main 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);
}

ListView 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);
}

Hook function:

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);
}

Various functions:

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;
}
Tirrell answered 16/5, 2015 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.