.NET UserControl
(从ScrollableControl
衍生而来)必须能够显示水平和垂直滚动条。
调用者可以设置这些水平和垂直滚动条的可见性和范围:
UserControl.AutoScroll = true;
UserControl.AutoScrollMinSize = new Size(1000, 4000); //1000x4000 scroll area
注意:
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;
}
我需要整个客户区都无效。问题是UserControl(即
ScrollableControl
)calls ScrollWindow
API函数: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);
}
...
}
ScrollableControl不会在整个客户矩形上触发InvalidateRect,而是尝试“抢救”客户区域中的现有内容。例如,用户向上滚动,当前客户端内容被
ScrollWindowEx
按下,然后只有新发现的区域无效,从而触发WM_PAINT
:在上图中,棋盘区域是无效的内容,必须在下一个WM_PAINT期间进行绘制。
就我而言,这不好。我控件的顶部包含一个“标题”(例如listview列标题)。向下滚动此内容是不正确的:
并导致视觉损坏。
我希望ScrollableControl不使用
ScrollWindowEx
,而只是使整个工作区无效。我尝试覆盖
OnScroll
受保护的方法:protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
但这会导致两次抽奖。
注意:我可以使用双缓冲来掩盖问题,但这不是真正的解决方案
远程桌面/终端会话下不应使用双缓冲
浪费CPU资源
这不是我要问的问题
我考虑使用
Control
而不是UserControl
(即在继承链中ScrollableControl
之前)并手动添加HScroll或VScroll .NET控件-但这也不可取:Windows已经为滚动条的位置提供了标准外观(复制并不容易)
当我只希望它为InvalidateRect而不是ScrollWindowEx时,必须从头开始复制很多功能
因为我可以看到并发布
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);
}
...
}
问题是SetDisplayRectLocation读取和写入私有成员变量(
displayRect
)。除非Microsoft更改C#以允许后代访问私有成员:否则我将无法做到。更新二
我意识到复制粘贴
ScrollableControl
的实现,解决了一个问题,这意味着我还必须将整个继承链复制n粘贴到UserControl
...
ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
ContainerControl2 : ScrollableControl2, IContainerControl
UserControl2 : ContainerControl2
我真的更喜欢使用面向对象的设计,而不是反对它。
最佳答案
我也遇到了同样的问题,谢谢发布。我可能已经找到解决您问题的方法。我的解决方案是重载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 );
}
我之所以考虑尝试此操作,是因为建议重载WndProc,并结合您无法重载SetDisplayRectLocation的观察。我认为在UserControl处理滚动事件期间禁用WM_PAINT可能有效。
希望这可以帮助。
汤姆