Выполните поведение прокрутки по умолчанию, когда пользователь перетаскивает элемент listview через его scrolbar

введение:

Я не носитель английского языка, и я не очень опытный программист либо.

я столкнулся с проблемой, которую мне трудно описать, поэтому, пожалуйста, имейте это в виду, читая этот вопрос.

СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ:

Я работаю над реализацией функций перетаскивания в listview. Я просто хочу иметь возможность переставлять строки внутри listview, не будет перетаскивания элементов в другие окна.

Я не хочу использовать OLE для этого, и я не удовлетворен реализацией "по умолчанию", которую я нашел по многочисленным ссылкам.

у меня есть свое представление о том, как я хотел бы это сделать, но моя неопытность мешает мне реализовать мои мысли.

я разрабатываю в Visual Studio, на C++ и raw WinAPI. Я не использую никаких библиотек, и я не хотел бы начинать использовать их сейчас.

:

Я хочу реализовать следующие поведение:

пользователь нажимает левую кнопку мыши и начинает перетаскивать элемент -> пользователь перемещает мышь по вертикальной полосы прокрутки -> прокрутка по умолчанию происходит.

после прокрутки считается неклиентской области, это означает, что я должен как-то проанализировать поведение по умолчанию для WM_NCMOUSEMOVE и WM_NCLBUTTONDOWN но я не знаю как это сделать.

позвольте мне попытаться объяснить лучше, что я имею в виду:

когда вы перетаскиваете элемент, логично, что приложение указывает, где он будет отброшен когда мышь находится над элементом (в клиентской области listview ).

когда вы перетаскиваете элемент через полосу прокрутки, очевидно, что пользователь не может удалить элемент там. Вместо указания недопустимой точки падения (например, путем изменения курсора, как это делает OLE), я хочу выполнить следующее:

Я хочу выполнить поведение полосы прокрутки по умолчанию (как будто пользователь вообще не перетаскивает элемент ). Это будет так, как если бы пользователь зависает над полосой прокрутки, нажимает и удерживает левую кнопку мыши, и необязательно, перемещает мышь вверх или вниз.

когда пользователь перемещает мышь из полосы прокрутки обратно в клиентскую область listview, перетаскивание продолжается.

SSCCE

мой английский был недостаточно хорош, чтобы провести надлежащее исследование ( как я обычно делаю перед публикацией здесь), и я не знаю ни одного приложения, которое имеет такой тип поведения, поэтому мне было очень трудно попытаться решить это самостоятельно.

все-таки, продираясь сквозь Блог Рэймонда Чена я пришел к идее.

приведенный ниже пример кода прекрасно демонстрирует поведение, о котором я говорил выше. Это не идеально, но это ближе всего к реализации поведения, которое я хочу.

создать пустой проект C++ и просто скопируйте/вставьте код ниже.

затем попробуйте перетащить элемент через полосу прокрутки.

важно: я не реализовал перестановку элементов и не изменил форму курсора, чтобы сохранить код минимальным. Этот цель этого 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;
}

теперь начните перетаскивать элемент, а затем переместите мышь над/под / над большим пальцем полосы прокрутки - > поведение, которое вы наблюдаете, я ищу.

эта программа имеет недостаток:

когда я пытаюсь перетащить элемент обратно в клиентскую область listview, вместо моего перетаскивания кода полоса прокрутки по-прежнему контролируется. Это поведение по умолчанию, но мне нужно изменить его таким образом, чтобы мой перетащив код вместо этого исполняет.

Это лучшее, что я смог сделать сам. Теперь вы видите, что я пытаюсь сделать.

если требуется дополнительная информация, я обновлю свой пост. Между тем я буду продолжать пытаться самостоятельно и обновлять этот пост, если я добьюсь прогресса.

Спасибо за ваше время и помощь. С уважением.

2 ответов


единственный способ для этого подхода к работе-найти способ получить mouse move сообщения во время прокрутки. Мышь "lost" но захват все еще остается в listview(полоса прокрутки). Итак, когда мышь покидает область прокрутки, нам нужно освободить захват (из полосы прокрутки) и снова установить его в listview. Для этого мы применим WH_MOUSE_LL крюк, когда мы получим LVN_BEGINDRAG уведомления сообщения и отцепить когда мы закончим 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);
}

в подклассе 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 (прокрутка по умолчанию)

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

я добавляю еще один ответ, потому что код немного longgggg, но не сложно. Следующий пример для простоты для 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;

главное окно:

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:

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