使用 ScrollWindowEx 在 Cwnd 中滚动后控件消失

2022-01-12 00:00:00 c++ mfc

我在 Cwnd 中实现了一个 CScrollBar,但滚动后窗口上的控件消失了.我听说我可以以某种方式使用 DeferWindowPos,但我不知道该怎么做.有什么想法吗?

I've implemented a CScrollBar in a Cwnd, but after scroll the controls on the window disappear. I've heard I could use DeferWindowPos someway, but I don't know how to do this. Any ideas?

CPanel::CPanel()
{
    CreateEx(WS_EX_CONTROLPARENT, _T("Static"), NULL, WS_CHILD | WS_TABSTOP | WS_BORDER, m_clRect, pwndParent, IDC_PANEL_FORM);
    ScrollBarInit();    
}

创建滚动条

void CPanel::ScrollBarInit()
{

    //Before this i calculate size of scrollbar and size of scrollarea
    m_pclScrollBar = new CScrollBar();
    m_pclScrollBar->Create(WS_CHILD | WS_VISIBLE | SBS_VERT | SBS_RIGHTALIGN, clRectScrollbar, this, IDC_SCROLLBAR_FORM);
    m_pclScrollBar->SetScrollRange(VSCROLL_RANGE_MIN, VSCROLL_RANGE_MAX);
    //After this I add scrollbar info

}

处理消息

void CPanel::OnVScroll(UINT iSBCode, UINT iPos, CScrollBar* pclScrollBar)
    {
        switch(pclScrollBar->GetDlgCtrlID())
        {
            case IDC_SCROLLBAR_FORM:
                ScrollBarScroll(iSBCode, iPos, pclScrollBar);
                break;
        }
    }

滚动

void CPanel::ScrollBarScroll(UINT iSBCode, UINT iPos, CScrollBar *pclScrollBar)
    {
        int     iScrollPositionPrevious;
        int     iScrollPosition;
        int     iScrollPositionOriginal;

        iScrollPositionOriginal = m_pclScrollBar->GetScrollPos();
        iScrollPosition = iScrollPositionOriginal;

        if(m_pclScrollBar != NULL)
        {
            SCROLLINFO info = {sizeof( SCROLLINFO ), SIF_ALL};
            pclScrollBar->GetScrollInfo(&info, SB_CTL);

            pclScrollBar->GetScrollRange(&info.nMin, &info.nMax);
            info.nPos = pclScrollBar->GetScrollPos();

            iScrollPositionPrevious = info.nPos;

            switch(iSBCode)
            {
                case SB_TOP:            // Scroll to top
                    iScrollPosition = VSCROLL_RANGE_MIN;
                    break;

                case SB_BOTTOM:         // Scroll to bottom
                    iScrollPosition = VSCROLL_RANGE_MAX;
                    break;

                case SB_ENDSCROLL:      // End scroll
                    break;

                case SB_LINEUP:         // Scroll one line up
                    if(iScrollPosition - VSCROLL_LINE >= VSCROLL_RANGE_MIN)
                        iScrollPosition -= VSCROLL_LINE;
                    else
                        iScrollPosition = VSCROLL_RANGE_MIN;
                    break;

                case SB_LINEDOWN:       // Scroll one line down
                    if(iScrollPosition + VSCROLL_LINE <= VSCROLL_RANGE_MAX)
                        iScrollPosition += VSCROLL_LINE;
                    else
                        iScrollPosition = VSCROLL_RANGE_MAX;
                    break;

                case SB_PAGEUP:         // Scroll one page up
                {
                    // Get the page size
                    SCROLLINFO   scrollInfo;
                    m_pclScrollBar->GetScrollInfo(&scrollInfo, SIF_ALL);

                    if(iScrollPosition > VSCROLL_RANGE_MIN)
                        iScrollPosition = max(VSCROLL_RANGE_MIN, iScrollPosition - VSCROLL_PAGE);
                    break;
                }

                case SB_PAGEDOWN:       // Scroll one page down
                {
                    // Get the page size
                    SCROLLINFO   scrollInfo;
                    m_pclScrollBar->GetScrollInfo(&scrollInfo, SIF_ALL);

                    if(iScrollPosition < VSCROLL_RANGE_MAX)
                        iScrollPosition = min(VSCROLL_RANGE_MAX, iScrollPosition + VSCROLL_PAGE);
                    break;
                }

                case SB_THUMBPOSITION:  // Scroll to the absolute position. The current position is provided in nPos
                case SB_THUMBTRACK:     // Drag scroll box to specified position. The current position is provided in nPos
                    iScrollPosition = iPos;
                    break;

                default:
                    break;
            }

            if(iScrollPositionOriginal != iScrollPosition)
            {
               m_pclScrollBar->SetScrollPos(iScrollPosition);

               CRect clientArea;
               GetClientRect(clientArea);

               CRect scrollbarArea;
               m_pclScrollBar->GetWindowRect(scrollbarArea);

               CRect scrollArea(clientArea);
               scrollArea.DeflateRect(0, 0, scrollbarArea.Width(), 0);
               ScrollWindowEx(0, iScrollPositionOriginal - iScrollPosition, scrollArea, NULL,
                       NULL, NULL, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
            }
        }
    }

推荐答案

使用 CWnd::ScrollWindowEx 使用 SW_SCROLLCHILDREN 标志是有问题的:

Moving child windows using CWnd::ScrollWindowEx using the SW_SCROLLCHILDREN flag is problematic:

如果指定了 SW_SCROLLCHILDREN 标志,Windows 将无法正确更新屏幕如果子窗口的一部分被滚动.位于源矩形之外的滚动子窗口部分不会被擦除,也不会在其新目标中正确重绘.使用 DeferWindowPos Windows 函数移动不完全位于 lpRectScroll 内的子窗口矩形.

If the SW_SCROLLCHILDREN flag is specified, Windows will not properly update the screen if part of a child window is scrolled. The part of the scrolled child window that lies outside the source rectangle will not be erased and will not be redrawn properly in its new destination. Use the DeferWindowPos Windows function to move child windows that do not lie completely within the lpRectScroll rectangle.

解决方法是手动移动子窗口.DeferWindowPos 与调用 SetWindowPos 适用于多个窗口,但经过优化以在单个调用中执行布局.这有助于减少视觉伪影,其中控件似乎相对于彼此移动,直到一切都解决了.

The solution is to move child windows manually. DeferWindowPos has the same effect as calling SetWindowPos for multiple windows, but is optimized to perform the layout in a single call. This helps reduce visual artifacts, where controls appear to move relative to each other, until everything is settled.

DeferWindowPos 需要一个保存新窗口属性的结构.它是调用 BeginDeferWindowPos 创建的,然后为每个窗口更新一个调用 DeferWindowPos,最后发送到系统执行重新定位 EndDeferWindowPos.以下代码假定一个数组包含数组中所有子控件的 CWnd*,其中 cx 和 cy 保存水平和垂直偏移量.它旨在替换对 ScrollWindowEx 的调用:

DeferWindowPos requires a structure holding the new window properties. It is created calling BeginDeferWindowPos, then updated for each window with a call to DeferWindowPos, and finally sent off to the system to perform the repositioning with EndDeferWindowPos. The following code assumes an array containing CWnd*s of all child controls in an array, with cx and cy holding the horizontal and vertical offset. It is meant to replace the call to ScrollWindowEx:

CWnd* controls[] = { m_pEdit, m_pButton, ... };

HDWP hDwp = ::BeginDeferWindowPos( ARRAYSIZE( controls ) );

for ( size_t index = 0; index < ARRAYSIZE( controls ); ++index ) {
    // Find the current window position
    CRect wndRect;
    controls[index]->GetWindowRect( wndRect );
    // DeferWindowPos requires client coordinates, so we need to convert from screen coords
    ScreenToClient( wndRect );
    // Set the control's new position
    hDwp = ::DeferWindowPos( hDwp, *controls[index], NULL,
                             wndRect.left + cx, wndRect.top + cy, 0, 0,
                             SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE |
                             SWP_NOZORDER );
}

// All new control positions have been recorded. Now perform the operation
::EndDeferWindowPos( hDwp );

相关文章