.NET UserControl
(从ScrollableControl
继承而来)必须能够显示水平和垂直滚动条。
调用者可以设置这些水平和垂直滚动条的可见性和范围:
UserControl.AutoScroll = true;
UserControl.AutoScrollMinSize = new Size(1000, 4000); //1000x4000 scroll area
字符串
注:UserControl
(即ScrollableControl
)使用指定WS_HSCROLL
和WS_VSCROLL
窗口样式的Windows标准机制来显示滚动条。也就是说:它们不会创建单独的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
)callsScrollWindow
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,而是尝试“salvage”客户端区域中的现有内容。例如,用户向上滚动,当前客户端内容被向下推ScrollWindowEx
,然后只有新覆盖的区域无效,触发WM_PAINT
:
x1c 0d1x的数据
在上图中,棋盘格区域是无效的内容,必须在下一个WM_PAINT期间绘制。
在我的情况下,这是不好的;我的控件的顶部包含一个“标题”(例如,列表视图列标题)。向下滚动此内容是不正确的:
的
它会造成视觉上的破坏。
我希望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
的属性?
更新:
我试着覆盖这个有问题的方法,并使用reflector来窃取所有代码:
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
的实现,修复一个问题意味着我还必须复制粘贴整个继承链到UserControl
...
ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
ContainerControl2 : ScrollableControl2, IContainerControl
UserControl2 : ContainerControl2
型
我真的更喜欢使用面向对象设计,而不是反对它。
4条答案
按热度按时间mrfwxfqh1#
我也遇到了同样的问题,谢谢你把这个贴出来。我可能找到了解决你问题的办法。我的解决方案是重载WndProc以处理滚动消息,在调用基类处理程序时关闭重绘,然后在处理完消息后强制重绘整个窗口。此解决方案似乎工作正常:
字符串
我之所以想到尝试这个方法,是因为重载WndProc的建议,以及您观察到无法重载SetDisplayLocation。我认为在UserControl处理滚动事件期间禁用WM_PAINT可能会起作用。
希望这对你有帮助。
汤姆
zkure5ic2#
你试过和微软的程序员联系吗?我相信如果你联系微软,你可以把你的问题发给他们,甚至可以得到电话支持。
下面是对.NET框架支持的链接:click here。它提到您可以通过电子邮件、电话或在线与.NET支持专业人员取得联系。
pcww981p3#
Tom的解决方案很棒,但我认为还有一个小优化的机会。如果没有Tom的两个方法,当我引起滚动时,例如,通过单击滚动条端点,我的onPaint会看到一个调用。当我添加Tom的两个方法时,我的onPaint开始为相同的滚动条位置获得两个调用。对我来说,解决方案似乎是忽略最后发生的SB_ENDSCROLL是滚动操作。有了这个我不再看到重复的油漆在同一滚动位置。
字符串
v7pvogib4#
我还尝试禁用调用ScrollWindowEx()。在当前版本的ScrollableControl中,在WmVScroll()上,ScrollWindowEx()的调用如下:
字符串
而SetDisplayLocation()是从Wm调用的?Scroll()如下所示:
型
所以我试着打电话
以防止调用SetDisplayLocation()。而且很有效。
但也发现了一个新的问题。数字16是在ScrollableControl中定义的ScrollStateFullDrag。当禁用此状态时,当用户拖动拇指时,滚动条位置不会更新。
所以,我需要得到滚动事件,并在事件中,保存当前滚动位置到局部变量和无效控制。在OnPaint()上,它基于局部变量而不是基于滚动位置进行绘制。
我尝试了一些测试,但到目前为止,它是确定的,工作正常。