Ian*_*oyd 13 .net scroll scrollable
.NET UserControl(来自ScrollableControl)必须能够显示水平和垂直滚动条.
调用者可以设置这些水平和垂直滚动条的可见性和范围:
UserControl.AutoScroll = true;
UserControl.AutoScrollMinSize = new Size(1000, 4000); //1000x4000 scroll area
Run Code Online (Sandbox Code Playgroud)
注:在
UserControl(即ScrollableControl)使用的指定Windows标准的机制WS_HSCROLL和WS_VSCROLL窗口风格使滚动条显示.也就是说:它们不会创建单独的Windows或.NET滚动控件,将它们定位在窗口的右侧/底部.Windows有一个标准机制,用于显示一个或两个滚动条.
如果用户滚动控件,UserControl则发送一个WM_HSCROLL或WM_VSCROLL消息.为了响应这些消息,我希望ScrollableControl使客户区无效,这在本机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;
}
Run Code Online (Sandbox Code Playgroud)
我需要整个客户区无效.问题是,用户控件(即ScrollableControl)调用的ScrollWindowAPI函数:
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);
}
...
}
Run Code Online (Sandbox Code Playgroud)
ScrollableControl不是在整个客户端矩形上触发InvalidateRect,而是尝试" 抢救 "客户区域中的现有内容.例如,当用户滚动起来,当前的客户端内容被推向下通过ScrollWindowEx,然后只将新发现的区域是无效的,引发了WM_PAINT:

在上图中,棋盘区域是无效的内容,必须在下一个WM_PAINT期间绘制.
在我的情况下,这是不好的; 我的控件顶部包含一个"标题"(例如listview列标题).向下滚动此内容是不正确的:

它会导致视觉腐败.
我希望ScrollableControl 不使用ScrollWindowEx,而只是使整个客户区无效.
我尝试重写OnScroll受保护的方法:
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
Run Code Online (Sandbox Code Playgroud)
但它会导致双重吸引.
注意:我可以使用双缓冲来掩盖问题,但这不是一个真正的解决方案
- 在远程桌面/终端会话下不应使用双缓冲
- 它浪费了CPU资源
- 这不是我要问的问题
我考虑使用a Control代替UserControl(即ScrollableControl在继承链之前)并手动添加HScroll或VScroll .NET控件 - 但这也是不可取的:
既然我可以看到并发布内部代码,ScrollableControl我知道没有属性可以禁用ScrollWindow,但是有没有属性可以禁用ScrollWindow?
我尝试重写违规方法,并使用反射器窃取所有代码:
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);
}
...
}
Run Code Online (Sandbox Code Playgroud)
问题是SetDisplayRectLocation读取和写入私有成员变量(displayRect).除非Microsoft更改C#以允许后代访问私有成员:我不能这样做.
我意识到复制粘贴实现ScrollableControl,修复一个问题意味着我还必须将整个继承链复制粘贴到UserControl
...
ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
ContainerControl2 : ScrollableControl2, IContainerControl
UserControl2 : ContainerControl2
Run Code Online (Sandbox Code Playgroud)
我真的更喜欢使用面向对象的设计,而不是反对它.
我有同样的问题,谢谢发布这个.我可能找到了解决问题的方法.我的解决方案是重载WndProc以处理滚动消息,在调用基类处理程序时关闭重绘,然后在处理消息后强制重绘整个窗口.此解决方案似乎正常工作:
private void sendRedrawMessage( bool redrawFlag )
{
const int WM_SETREDRAW = 0x000B;
IntPtr wparam = new IntPtr( redrawFlag ? 1 : 0 );
Message msg = Message.Create( Handle, WM_SETREDRAW, wparam, IntPtr.Zero );
NativeWindow.FromHandle( Handle ).DefWndProc( ref msg );
}
protected override void WndProc( ref Message m )
{
switch ( m.Msg )
{
case 276: // WM_HSCROLL
case 277: // WM_VSCROLL
sendRedrawMessage( false );
base.WndProc( ref m );
sendRedrawMessage( true );
Refresh(); // Invalidate all
return;
}
base.WndProc( ref m );
}
Run Code Online (Sandbox Code Playgroud)
我想尝试这个,因为建议重载WndProc结合你的观察,你不能重载SetDisplayRectLocation.我认为在UserControl处理scroll事件期间禁用WM_PAINT可能会起作用.
希望这可以帮助.
汤姆