The .NET UserControl
(which descends from ScrollableControl
) has to ability to display horizontal and vertical scrollbars.
The caller can set the visibility, and range, of these horizontal and vertical scrollbars:
UserControl.AutoScroll = true;
UserControl.AutoScrollMinSize = new Size(1000, 4000); //1000x4000 scroll area
Note: The
UserControl
(i.e.ScrollableControl
) uses the Windows standard mechanism of specifyingWS_HSCROLL
andWS_VSCROLL
window styles to make scrollbars appear. That is: they do not create separate Windows or .NET scroll controls, positioning them at the right/bottom of the window. Windows has a standard mechanism for displaying one, or both, scrollbars.
If the user scrolls the control, the UserControl
is sent a WM_HSCROLL
or WM_VSCROLL
message. In response to these messages i want the ScrollableControl to invalidate the client area, which is what would happen in native Win32:
switch (uMsg)
{
case WM_VSCROLL:
...
GetScrollInfo(...);
...
SetScrollInfo(...);
...
InvalidateRect(g_hWnd,
null, //erase entire client area
true, //background needs erasing too (trigger WM_ERASEBKGND));
break;
}
i need the entire client area invalidated. The problem is that UserControl (i.e. ScrollableControl
) calls the ScrollWindow
API function:
protected void SetDisplayRectLocation(int x, int y)
{
...
if ((nXAmount != 0) || ((nYAmount != 0) && base.IsHandleCreated))
{
...
SafeNativeMethods.ScrollWindowEx(new HandleRef(this, base.Handle), nXAmount, nYAmount, null, ref rectClip, NativeMethods.NullHandleRef, ref prcUpdate, 7);
}
...
}
Rather than triggering an InvalidateRect on the entire client rectangle, ScrollableControl tries to "salvage" the existing content in the client area. For example, the user scrolls up, the current client content is pushed down by ScrollWindowEx
, and then only the newly uncovered area is invalidated, triggering a WM_PAINT
:
In the above diagram, the checkerboard area is the content that is invalid and will have to be painted during the next WM_PAINT.
In my case this is no good; the top of my control contains a "header" (e.g. listview column headers). Scrolling this content further down is incorrect:
and it causes visual corruption.
i want the ScrollableControl to not use ScrollWindowEx
, but instead just invalidate the entire client area.
i tried overriding OnScroll
protected method:
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
But it causes an double-draw.
Note: i could use double-buffering to mask the problem, but that's not a real solution
- double buffering should not be used under remote desktop/terminal session
- it's wasteful of CPU resources
- it's not the question i'm asking
i considered using a Control
instead of UserControl
(i.e. before ScrollableControl
in the inheritance chain) and manually add a HScroll or VScroll .NET control - but that's not desirable either:
- Windows already provides a standard look for the position of scrollbars (it's not trivial to duplicate)
- that is a lot of functionality to have to reproduce from scratch, when i only want it to InvalidateRect rather than ScrollWindowEx
Since i can see, and posted, the code internal to ScrollableControl
i know there is no property to disable use of ScrollWindow
, but is there a property to disable the use of ScrollWindow
?
Update:
i tried overriding the offending method, and using reflector to steal all the code:
protected override void SetDisplayRectLocation(int x, int y)
{
...
Rectangle displayRect = this.displayRect;
...
this.displayRect.X = x;
this.displayRect.Y = y;
if ((nXAmount != 0) || ((nYAmount != 0) && base.IsHandleCreated))
{
...
SafeNativeMethods.ScrollWindowEx(new HandleRef(this, base.Handle), nXAmount, nYAmount, null, ref rectClip, NativeMethods.NullHandleRef, ref prcUpdate, 7);
}
...
}
The problem is that SetDisplayRectLocation reads and writes to a private member variable (displayRect
). Unless Microsoft changes C# to allow descendants access to private members: i cannot do that.
Update Two
i realized that copy-pasting the implementation of ScrollableControl
, fixing the one issue means i will also have to copy-n-paste the entire inheritance chain down to UserControl
...
ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
ContainerControl2 : ScrollableControl2, IContainerControl
UserControl2 : ContainerControl2
i'd really prefer to work with object-oriented design, rather than against it.