7
votes

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.

2
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.Jonathan Potter
@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.AlwaysLearningNewStuff
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.γηράσκω δ' αεί πολλά διδασκόμε
@γηράσκωδ'αείπολλάδιδασκόμε: 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.AlwaysLearningNewStuff
@AlwaysLearningNewStuff Have you seen this article?Axalo

2 Answers

2
votes

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

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