Создание прокручиваемого диалога

Мне было интересно, существует ли общепринятая стратегия создания прокручиваемого диалогового окна в Windows API, возможно, с использованием прокручиваемых классов WTL (CScrollImpl, CScrollWindowImpl, CScrollContainer и т. д.).

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

Но, может быть, я думаю обо всем этом неправильно. Кто-нибудь имел дело с этой проблемой раньше? Спасибо за любую помощь.


person Community    schedule 23.03.2021    source источник
comment
По сути, это то, что делает MFC CFormView. Хотя, какой у тебя вопрос?   -  person IInspectable    schedule 23.03.2021
comment
Спасибо, хотя я не работаю над проектом MFC. Я предполагаю, что мой вопрос заключается в том, каков типичный подход к созданию прокручиваемого диалогового окна. Переместить все элементы управления вместе? Используя дочерний диалоговый подход?   -  person    schedule 23.03.2021
comment
Система не предоставляет реализацию, поэтому вам придется использовать собственное решение. Является ли это типичным подходом или нет, следование MFC, безусловно, гарантирует, что вы испытали его на сотнях и тысячах приложений в течение нескольких десятилетий.   -  person IInspectable    schedule 23.03.2021
comment
Поверьте, у меня нет желания изобретать велосипед. Я просто надеялся избежать зависимости от MFC и вместо этого придерживаться ATL/WTL.   -  person    schedule 24.03.2021
comment
Я не предлагал вам использовать MFC. Вы спрашивали о конкретной стратегии реализации и о том, была ли она разумной. И ответ на этот вопрос заключается в том, что да, это возможный подход, и обычная библиотека успешно его использует. Как перевести это в конкретную библиотеку окон, которую вы выбрали, вам придется решить.   -  person IInspectable    schedule 24.03.2021
comment
@user15025873 user15025873 Вам нужна прокрутка в представлении (например, в представлении формы) или обычный диалог с прокручиваемым содержимым?   -  person PaulusHub    schedule 26.04.2021


Ответы (1)


Это модифицированная версия, которая работает в диалогах:

template <class T>
class CScroller
{
public:
    enum { uSCROLL_FLAGS = SW_INVALIDATE };

    POINT m_ptOffset;
    SIZE m_sizeAll;
    SIZE m_sizeLine;
    SIZE m_sizePage;
    SIZE m_sizeClient;
    int m_zDelta;              // current wheel value
    int m_nWheelLines;         // number of lines to scroll on wheel
    int m_zHDelta;              // current horizontal wheel value
    int m_nHWheelChars;         // number of chars to scroll on horizontal wheel
    UINT m_uScrollFlags;
    DWORD m_dwExtendedStyle;   // scroll specific extended styles

// Constructor
    CScroller() : m_zDelta(0), m_nWheelLines(3),
        m_zHDelta(0), m_nHWheelChars(3),
        m_uScrollFlags(0U), m_dwExtendedStyle(0)
    {
        m_ptOffset.x = 0;
        m_ptOffset.y = 0;
        m_sizeAll.cx = 0;
        m_sizeAll.cy = 0;
        m_sizePage.cx = 0;
        m_sizePage.cy = 0;
        m_sizeLine.cx = 0;
        m_sizeLine.cy = 0;
        m_sizeClient.cx = 0;
        m_sizeClient.cy = 0;

        SetScrollExtendedStyle(SCRL_SCROLLCHILDREN | SCRL_ERASEBACKGROUND);
    }

    // Attributes & Operations
    DWORD GetScrollExtendedStyle() const
    {
        return m_dwExtendedStyle;
    }

    DWORD SetScrollExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
    {
        DWORD dwPrevStyle = m_dwExtendedStyle;
        if (dwMask == 0)
            m_dwExtendedStyle = dwExtendedStyle;
        else
            m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
        // cache scroll flags
        T* pT = static_cast<T*>(this);
        (void)pT;   // avoid level 4 warning
        m_uScrollFlags = pT->uSCROLL_FLAGS | (IsScrollingChildren() ? SW_SCROLLCHILDREN : 0) | (IsErasingBackground() ? SW_ERASE : 0);
        m_uScrollFlags |= (IsSmoothScroll() ? SW_SMOOTHSCROLL : 0);
        return dwPrevStyle;
    }

    // offset operations
    void SetScrollOffset(int x, int y, BOOL bRedraw = TRUE)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        pT->AdjustScrollOffset(x, y);

        int dx = m_ptOffset.x - x;
        int dy = m_ptOffset.y - y;
        m_ptOffset.x = x;
        m_ptOffset.y = y;

        // block: set horizontal scroll bar
        {
            SCROLLINFO si = { sizeof(SCROLLINFO) };
            si.fMask = SIF_POS;
            if ((m_dwExtendedStyle & SCRL_DISABLENOSCROLLH) != 0)
                si.fMask |= SIF_DISABLENOSCROLL;
            si.nPos = m_ptOffset.x;
            pT->SetScrollInfo(SB_HORZ, &si, bRedraw);
        }

        // block: set vertical scroll bar
        {
            SCROLLINFO si = { sizeof(SCROLLINFO) };
            si.fMask = SIF_POS;
            if ((m_dwExtendedStyle & SCRL_DISABLENOSCROLLV) != 0)
                si.fMask |= SIF_DISABLENOSCROLL;
            si.nPos = m_ptOffset.y;
            pT->SetScrollInfo(SB_VERT, &si, bRedraw);
        }

        // Move all children if needed
        if (IsScrollingChildren() && ((dx != 0) || (dy != 0)))
        {
            for (HWND hWndChild = ::GetWindow(pT->m_hWnd, GW_CHILD); hWndChild != NULL; hWndChild = ::GetWindow(hWndChild, GW_HWNDNEXT))
            {
                RECT rect = {};
                ::GetWindowRect(hWndChild, &rect);
                ::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rect, 1);
                ::SetWindowPos(hWndChild, NULL, rect.left + dx, rect.top + dy, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
            }
        }

        if (bRedraw)
            pT->Invalidate();
    }

    void SetScrollOffset(POINT ptOffset, BOOL bRedraw = TRUE)
    {
        SetScrollOffset(ptOffset.x, ptOffset.y, bRedraw);
    }

    void GetScrollOffset(POINT& ptOffset) const
    {
        ptOffset = m_ptOffset;
    }

    // size operations
    void SetScrollSize(int cx, int cy, BOOL bRedraw = TRUE, bool bResetOffset = true)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        m_sizeAll.cx = cx;
        m_sizeAll.cy = cy;

        int x = 0;
        int y = 0;
        if (!bResetOffset)
        {
            x = m_ptOffset.x;
            y = m_ptOffset.y;
            pT->AdjustScrollOffset(x, y);
        }

        int dx = m_ptOffset.x - x;
        int dy = m_ptOffset.y - y;
        m_ptOffset.x = x;
        m_ptOffset.y = y;

        // block: set horizontal scroll bar
        {
            SCROLLINFO si = { sizeof(SCROLLINFO) };
            si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
            if ((m_dwExtendedStyle & SCRL_DISABLENOSCROLLH) != 0)
                si.fMask |= SIF_DISABLENOSCROLL;
            si.nMin = 0;
            si.nMax = m_sizeAll.cx - 1;
            si.nPage = m_sizeClient.cx;
            si.nPos = m_ptOffset.x;
            pT->SetScrollInfo(SB_HORZ, &si, bRedraw);
        }

        // block: set vertical scroll bar
        {
            SCROLLINFO si = { sizeof(SCROLLINFO) };
            si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
            if ((m_dwExtendedStyle & SCRL_DISABLENOSCROLLV) != 0)
                si.fMask |= SIF_DISABLENOSCROLL;
            si.nMin = 0;
            si.nMax = m_sizeAll.cy - 1;
            si.nPage = m_sizeClient.cy;
            si.nPos = m_ptOffset.y;
            pT->SetScrollInfo(SB_VERT, &si, bRedraw);
        }

        // Move all children if needed
        if (IsScrollingChildren() && ((dx != 0) || (dy != 0)))
        {
            for (HWND hWndChild = ::GetWindow(pT->m_hWnd, GW_CHILD); hWndChild != NULL; hWndChild = ::GetWindow(hWndChild, GW_HWNDNEXT))
            {
                RECT rect = {};
                ::GetWindowRect(hWndChild, &rect);
                ::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rect, 1);
                ::SetWindowPos(hWndChild, NULL, rect.left + dx, rect.top + dy, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
            }
        }

        SetScrollLine(0, 0);
        SetScrollPage(0, 0);

        if (bRedraw)
            pT->Invalidate();
    }

    void SetScrollSize(SIZE size, BOOL bRedraw = TRUE, bool bResetOffset = true)
    {
        SetScrollSize(size.cx, size.cy, bRedraw, bResetOffset);
    }

    void GetScrollSize(SIZE& sizeWnd) const
    {
        sizeWnd = m_sizeAll;
    }

    // line operations
    void SetScrollLine(int cxLine, int cyLine)
    {
        ATLASSERT((cxLine >= 0) && (cyLine >= 0));
        ATLASSERT((m_sizeAll.cx != 0) && (m_sizeAll.cy != 0));

        m_sizeLine.cx = T::CalcLineOrPage(cxLine, m_sizeAll.cx, 100);
        m_sizeLine.cy = T::CalcLineOrPage(cyLine, m_sizeAll.cy, 100);
    }

    void SetScrollLine(SIZE sizeLine)
    {
        SetScrollLine(sizeLine.cx, sizeLine.cy);
    }

    void GetScrollLine(SIZE& sizeLine) const
    {
        sizeLine = m_sizeLine;
    }

    // page operations
    void SetScrollPage(int cxPage, int cyPage)
    {
        ATLASSERT((cxPage >= 0) && (cyPage >= 0));
        ATLASSERT((m_sizeAll.cx != 0) && (m_sizeAll.cy != 0));

        m_sizePage.cx = T::CalcLineOrPage(cxPage, m_sizeAll.cx, 10);
        m_sizePage.cy = T::CalcLineOrPage(cyPage, m_sizeAll.cy, 10);
    }

    void SetScrollPage(SIZE sizePage)
    {
        SetScrollPage(sizePage.cx, sizePage.cy);
    }

    void GetScrollPage(SIZE& sizePage) const
    {
        sizePage = m_sizePage;
    }

    // commands
    void ScrollLineDown()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_VERT, SB_LINEDOWN, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
    }

    void ScrollLineUp()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_VERT, SB_LINEUP, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
    }

    void ScrollPageDown()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_VERT, SB_PAGEDOWN, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
    }

    void ScrollPageUp()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_VERT, SB_PAGEUP, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
    }

    void ScrollTop()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_VERT, SB_TOP, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
    }

    void ScrollBottom()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_VERT, SB_BOTTOM, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
    }

    void ScrollLineRight()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_HORZ, SB_LINEDOWN, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
    }

    void ScrollLineLeft()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_HORZ, SB_LINEUP, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
    }

    void ScrollPageRight()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_HORZ, SB_PAGEDOWN, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
    }

    void ScrollPageLeft()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_HORZ, SB_PAGEUP, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
    }

    void ScrollAllLeft()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_HORZ, SB_TOP, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
    }

    void ScrollAllRight()
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_HORZ, SB_BOTTOM, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
    }

    // scroll to make point/view/window visible
    void ScrollToView(POINT pt)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        RECT rect = { pt.x, pt.y, pt.x, pt.y };
        pT->ScrollToView(rect);
    }

    void ScrollToView(RECT& rect)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        RECT rcClient = {};
        pT->GetClientRect(&rcClient);

        int x = m_ptOffset.x;
        if (rect.left < m_ptOffset.x)
            x = rect.left;
        else if (rect.right > (m_ptOffset.x + rcClient.right))
            x = rect.right - rcClient.right;

        int y = m_ptOffset.y;
        if (rect.top < m_ptOffset.y)
            y = rect.top;
        else if (rect.bottom > (m_ptOffset.y + rcClient.bottom))
            y = rect.bottom - rcClient.bottom;

        SetScrollOffset(x, y);
    }

    void ScrollToView(HWND hWnd)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        RECT rect = {};
        ::GetWindowRect(hWnd, &rect);
        ::OffsetRect(&rect, m_ptOffset.x, m_ptOffset.y);
        ::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rect, 2);
        ScrollToView(rect);
    }

    BEGIN_MSG_MAP(CScroller)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
        MESSAGE_HANDLER(WM_VSCROLL, OnVScroll)
        MESSAGE_HANDLER(WM_HSCROLL, OnHScroll)
        MESSAGE_HANDLER(WM_MOUSEWHEEL, OnMouseWheel)
        MESSAGE_HANDLER(WM_MOUSEHWHEEL, OnMouseHWheel)
        MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
        MESSAGE_HANDLER(WM_SIZE, OnSize)
        // standard scroll commands
        ALT_MSG_MAP(1)
        COMMAND_ID_HANDLER(ID_SCROLL_UP, OnScrollUp)
        COMMAND_ID_HANDLER(ID_SCROLL_DOWN, OnScrollDown)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, OnScrollPageUp)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, OnScrollPageDown)
        COMMAND_ID_HANDLER(ID_SCROLL_TOP, OnScrollTop)
        COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, OnScrollBottom)
        COMMAND_ID_HANDLER(ID_SCROLL_LEFT, OnScrollLeft)
        COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, OnScrollRight)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, OnScrollPageLeft)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, OnScrollPageRight)
        COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, OnScrollAllLeft)
        COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, OnScrollAllRight)
    END_MSG_MAP()

    LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
    {
        T* pT = static_cast<T*>(this);
        pT->GetSystemSettings();

        bHandled = FALSE;
        return 1;
    }

    LRESULT OnVScroll(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_VERT, (int)(short)LOWORD(wParam), (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
        return 0;
    }

    LRESULT OnHScroll(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));
        pT->DoScroll(SB_HORZ, (int)(short)LOWORD(wParam), (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
        return 0;
    }

    LRESULT OnMouseWheel(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        int zDelta = (int)GET_WHEEL_DELTA_WPARAM(wParam);
        int nScrollCode = (m_nWheelLines == WHEEL_PAGESCROLL) ? ((zDelta > 0) ? SB_PAGEUP : SB_PAGEDOWN) : ((zDelta > 0) ? SB_LINEUP : SB_LINEDOWN);
        m_zDelta += zDelta;   // cumulative
        int zTotal = (m_nWheelLines == WHEEL_PAGESCROLL) ? abs(m_zDelta) : abs(m_zDelta) * m_nWheelLines;
        if (m_sizeAll.cy > m_sizeClient.cy)
        {
            for (int i = 0; i < zTotal; i += WHEEL_DELTA)
            {
                pT->DoScroll(SB_VERT, nScrollCode, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
                pT->UpdateWindow();
            }
        }
        else if (m_sizeAll.cx > m_sizeClient.cx)   // can't scroll vertically, scroll horizontally
        {
            for (int i = 0; i < zTotal; i += WHEEL_DELTA)
            {
                pT->DoScroll(SB_HORZ, nScrollCode, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
                pT->UpdateWindow();
            }
        }
        m_zDelta %= WHEEL_DELTA;

        return 0;
    }

    LRESULT OnMouseHWheel(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        int zDelta = (int)GET_WHEEL_DELTA_WPARAM(wParam);
        int nScrollCode = (m_nHWheelChars == WHEEL_PAGESCROLL) ? ((zDelta > 0) ? SB_PAGERIGHT : SB_PAGELEFT) : ((zDelta > 0) ? SB_LINERIGHT : SB_LINELEFT);
        m_zHDelta += zDelta;   // cumulative
        int zTotal = (m_nHWheelChars == WHEEL_PAGESCROLL) ? abs(m_zHDelta) : abs(m_zHDelta) * m_nHWheelChars;
        if (m_sizeAll.cx > m_sizeClient.cx)
        {
            for (int i = 0; i < zTotal; i += WHEEL_DELTA)
            {
                pT->DoScroll(SB_HORZ, nScrollCode, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
                pT->UpdateWindow();
            }
        }
        m_zHDelta %= WHEEL_DELTA;

        return 0;
    }

    LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        GetSystemSettings();
        return 0;
    }

    LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        pT->DoSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));

        bHandled = FALSE;
        return 1;
    }

    // scrolling handlers
    LRESULT OnScrollUp(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollLineUp();
        return 0;
    }

    LRESULT OnScrollDown(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollLineDown();
        return 0;
    }

    LRESULT OnScrollPageUp(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollPageUp();
        return 0;
    }

    LRESULT OnScrollPageDown(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollPageDown();
        return 0;
    }

    LRESULT OnScrollTop(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollTop();
        return 0;
    }

    LRESULT OnScrollBottom(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollBottom();
        return 0;
    }

    LRESULT OnScrollLeft(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollLineLeft();
        return 0;
    }

    LRESULT OnScrollRight(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollLineRight();
        return 0;
    }

    LRESULT OnScrollPageLeft(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollPageLeft();
        return 0;
    }

    LRESULT OnScrollPageRight(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollPageRight();
        return 0;
    }

    LRESULT OnScrollAllLeft(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollAllLeft();
        return 0;
    }

    LRESULT OnScrollAllRight(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ScrollAllRight();
        return 0;
    }

    // Overrideables
    void DoPaint(CDCHandle /*dc*/)
    {
        // must be implemented in a derived class
        ATLASSERT(FALSE);
    }

    // Implementation
    void DoSize(int cx, int cy)
    {
        m_sizeClient.cx = cx;
        m_sizeClient.cy = cy;

        T* pT = static_cast<T*>(this);

        // block: set horizontal scroll bar
        {
            SCROLLINFO si = { sizeof(SCROLLINFO) };
            si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
            si.nMin = 0;
            si.nMax = m_sizeAll.cx - 1;
            if ((m_dwExtendedStyle & SCRL_DISABLENOSCROLLH) != 0)
                si.fMask |= SIF_DISABLENOSCROLL;
            si.nPage = m_sizeClient.cx;
            si.nPos = m_ptOffset.x;
            pT->SetScrollInfo(SB_HORZ, &si, TRUE);
        }

        // block: set vertical scroll bar
        {
            SCROLLINFO si = { sizeof(SCROLLINFO) };
            si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
            si.nMin = 0;
            si.nMax = m_sizeAll.cy - 1;
            if ((m_dwExtendedStyle & SCRL_DISABLENOSCROLLV) != 0)
                si.fMask |= SIF_DISABLENOSCROLL;
            si.nPage = m_sizeClient.cy;
            si.nPos = m_ptOffset.y;
            pT->SetScrollInfo(SB_VERT, &si, TRUE);
        }

        int x = m_ptOffset.x;
        int y = m_ptOffset.y;
        if (pT->AdjustScrollOffset(x, y))
        {
            // Children will be moved in SetScrollOffset, if needed
            pT->ScrollWindowEx(m_ptOffset.x - x, m_ptOffset.y - y, (m_uScrollFlags & ~SCRL_SCROLLCHILDREN));
            SetScrollOffset(x, y, FALSE);
        }
    }

    void DoScroll(int nType, int nScrollCode, int& cxyOffset, int cxySizeAll, int cxySizePage, int cxySizeLine)
    {
        T* pT = static_cast<T*>(this);
        RECT rect = {};
        pT->GetClientRect(&rect);
        int cxyClient = (nType == SB_VERT) ? rect.bottom : rect.right;
        int cxyMax = cxySizeAll - cxyClient;

        if (cxyMax < 0)   // can't scroll, client area is bigger
            return;

        bool bUpdate = true;
        int cxyScroll = 0;

        switch (nScrollCode)
        {
        case SB_TOP:        // top or all left
            cxyScroll = cxyOffset;
            cxyOffset = 0;
            break;
        case SB_BOTTOM:     // bottom or all right
            cxyScroll = cxyOffset - cxyMax;
            cxyOffset = cxyMax;
            break;
        case SB_LINEUP:     // line up or line left
            if (cxyOffset >= cxySizeLine)
            {
                cxyScroll = cxySizeLine;
                cxyOffset -= cxySizeLine;
            }
            else
            {
                cxyScroll = cxyOffset;
                cxyOffset = 0;
            }
            break;
        case SB_LINEDOWN:   // line down or line right
            if (cxyOffset < cxyMax - cxySizeLine)
            {
                cxyScroll = -cxySizeLine;
                cxyOffset += cxySizeLine;
            }
            else
            {
                cxyScroll = cxyOffset - cxyMax;
                cxyOffset = cxyMax;
            }
            break;
        case SB_PAGEUP:     // page up or page left
            if (cxyOffset >= cxySizePage)
            {
                cxyScroll = cxySizePage;
                cxyOffset -= cxySizePage;
            }
            else
            {
                cxyScroll = cxyOffset;
                cxyOffset = 0;
            }
            break;
        case SB_PAGEDOWN:   // page down or page right
            if (cxyOffset < cxyMax - cxySizePage)
            {
                cxyScroll = -cxySizePage;
                cxyOffset += cxySizePage;
            }
            else
            {
                cxyScroll = cxyOffset - cxyMax;
                cxyOffset = cxyMax;
            }
            break;
        case SB_THUMBTRACK:
            if (IsNoThumbTracking())
                break;
            // else fall through
        case SB_THUMBPOSITION:
        {
            SCROLLINFO si = { sizeof(SCROLLINFO), SIF_TRACKPOS };
            if (pT->GetScrollInfo(nType, &si))
            {
                cxyScroll = cxyOffset - si.nTrackPos;
                cxyOffset = si.nTrackPos;
            }
        }
        break;
        case SB_ENDSCROLL:
        default:
            bUpdate = false;
            break;
        }

        if (bUpdate && (cxyScroll != 0))
        {
            pT->SetScrollPos(nType, cxyOffset, TRUE);
            if (nType == SB_VERT)
                pT->ScrollWindowEx(0, cxyScroll, m_uScrollFlags);
            else
                pT->ScrollWindowEx(cxyScroll, 0, m_uScrollFlags);
        }
    }

    static int CalcLineOrPage(int nVal, int nMax, int nDiv)
    {
        if (nVal == 0)
        {
            nVal = nMax / nDiv;
            if (nVal < 1)
                nVal = 1;
        }
        else if (nVal > nMax)
        {
            nVal = nMax;
        }

        return nVal;
    }

    bool AdjustScrollOffset(int& x, int& y)
    {
        int xOld = x;
        int yOld = y;

        int cxMax = m_sizeAll.cx - m_sizeClient.cx;
        if (x > cxMax)
            x = (cxMax >= 0) ? cxMax : 0;
        else if (x < 0)
            x = 0;

        int cyMax = m_sizeAll.cy - m_sizeClient.cy;
        if (y > cyMax)
            y = (cyMax >= 0) ? cyMax : 0;
        else if (y < 0)
            y = 0;

        return ((x != xOld) || (y != yOld));
    }

    void GetSystemSettings()
    {
        ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &m_nWheelLines, 0);

#ifndef SPI_GETWHEELSCROLLCHARS
        const UINT SPI_GETWHEELSCROLLCHARS = 0x006C;
#endif
        ::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &m_nHWheelChars, 0);
    }

    bool IsScrollingChildren() const
    {
        return (m_dwExtendedStyle & SCRL_SCROLLCHILDREN) != 0;
    }

    bool IsErasingBackground() const
    {
        return (m_dwExtendedStyle & SCRL_ERASEBACKGROUND) != 0;
    }

    bool IsNoThumbTracking() const
    {
        return (m_dwExtendedStyle & SCRL_NOTHUMBTRACKING) != 0;
    }

    bool IsSmoothScroll() const
    {
        return (m_dwExtendedStyle & SCRL_SMOOTHSCROLL) != 0;
    }
};

template <class T, class TBase = ATL::CWindow>
class ATL_NO_VTABLE CScrollDialogImpl : public ATL::CDialogImpl<T, TBase>, public CScroller< T >
{
public:
    void InitializeScroll(HWND hWnd)
    {
        T* pT = static_cast<T*>(this);
        pT->GetSystemSettings();

        RECT rect = {};
        this->GetClientRect(&rect);
        pT->DoSize(rect.right, rect.bottom);
    }

    BEGIN_MSG_MAP(CScrollDialogImpl)
        MESSAGE_HANDLER(WM_VSCROLL, CScroller< T >::OnVScroll)
        MESSAGE_HANDLER(WM_HSCROLL, CScroller< T >::OnHScroll)
        MESSAGE_HANDLER(WM_MOUSEWHEEL, CScroller< T >::OnMouseWheel)
        MESSAGE_HANDLER(WM_MOUSEHWHEEL, CScroller< T >::OnMouseHWheel)
        MESSAGE_HANDLER(WM_SETTINGCHANGE, CScroller< T >::OnSettingChange)
        MESSAGE_HANDLER(WM_SIZE, CScroller< T >::OnSize)
        ALT_MSG_MAP(1)
        COMMAND_ID_HANDLER(ID_SCROLL_UP, CScroller< T >::OnScrollUp)
        COMMAND_ID_HANDLER(ID_SCROLL_DOWN, CScroller< T >::OnScrollDown)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, CScroller< T >::OnScrollPageUp)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, CScroller< T >::OnScrollPageDown)
        COMMAND_ID_HANDLER(ID_SCROLL_TOP, CScroller< T >::OnScrollTop)
        COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, CScroller< T >::OnScrollBottom)
        COMMAND_ID_HANDLER(ID_SCROLL_LEFT, CScroller< T >::OnScrollLeft)
        COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, CScroller< T >::OnScrollRight)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, CScroller< T >::OnScrollPageLeft)
        COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, CScroller< T >::OnScrollPageRight)
        COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, CScroller< T >::OnScrollAllLeft)
        COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, CScroller< T >::OnScrollAllRight)
    END_MSG_MAP()
};

Затем вы можете использовать его, как показано ниже (немодальное диалоговое окно с включенными вертикальными и горизонтальными полосами прокрутки)

class CMainDlg : public CScrollDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,
        public CMessageFilter, public CIdleHandler
{
public:
    enum { IDD = IDD_MAINDLG };

    virtual BOOL PreTranslateMessage(MSG* pMsg)
    {
        return CWindow::IsDialogMessage(pMsg);
    }

    virtual BOOL OnIdle()
    {
        UIUpdateChildWindows();
        return FALSE;
    }

    BEGIN_UPDATE_UI_MAP(CMainDlg)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CMainDlg)
        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
        COMMAND_ID_HANDLER(IDOK, OnOK)
        COMMAND_ID_HANDLER(IDCANCEL, OnCancel)

        CHAIN_MSG_MAP(CScrollDialogImpl<CMainDlg>);
    END_MSG_MAP()

    LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        this->InitializeScroll(m_hWnd);

        RECT rect = {};
        this->GetClientRect(&rect);

        SetScrollSize(rect.right + 200, rect.bottom + 200);

        // center the dialog on the screen
        CenterWindow();

        // set icons
        HICON hIcon = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR, 
            ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON));
        SetIcon(hIcon, TRUE);
        HICON hIconSmall = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR, 
            ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON));
        SetIcon(hIconSmall, FALSE);

        // register object for message filtering and idle updates
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        ATLASSERT(pLoop != NULL);
        pLoop->AddMessageFilter(this);
        pLoop->AddIdleHandler(this);

        UIAddChildWindowContainer(m_hWnd);

        return TRUE;
    }

    LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        // unregister message filtering and idle updates
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        ATLASSERT(pLoop != NULL);
        pLoop->RemoveMessageFilter(this);
        pLoop->RemoveIdleHandler(this);

        return 0;
    }

    LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        CAboutDlg dlg;
        dlg.DoModal();
        return 0;
    }

    LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        // TODO: Add validation code 
        CloseDialog(wID);
        return 0;
    }

    LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        CloseDialog(wID);
        return 0;
    }

    void CloseDialog(int nVal)
    {
        DestroyWindow();
        ::PostQuitMessage(nVal);
    }
};
person PaulusHub    schedule 26.04.2021