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();
}
Creating scrollbar
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
}
Handle message
void CPanel::OnVScroll(UINT iSBCode, UINT iPos, CScrollBar* pclScrollBar)
{
switch(pclScrollBar->GetDlgCtrlID())
{
case IDC_SCROLLBAR_FORM:
ScrollBarScroll(iSBCode, iPos, pclScrollBar);
break;
}
}
Scroll
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);
}
}
}
Moving child windows using CWnd::ScrollWindowEx
using the SW_SCROLLCHILDREN
flag is problematic:
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 theDeferWindowPos
Windows function to move child windows that do not lie completely within thelpRectScroll
rectangle.
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
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 );