【发布时间】:2011-04-25 18:31:10
【问题描述】:
.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,而是尝试“挽救”客户区域中的现有内容。比如用户滚动向上,当前客户端内容被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 的实现,修复一个问题意味着我还必须复制粘贴整个继承链到 UserControl
...
ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
ContainerControl2 : ScrollableControl2, IContainerControl
UserControl2 : ContainerControl2
我真的更喜欢使用面向对象的设计,而不是反对它。
【问题讨论】:
-
能否提供不带脏话的截图?
-
你去吧;处女的眼睛得救了。
-
好问题。你可能已经尝试了很多我会做的事情。我假设您已尝试覆盖 WndPrc 并拦截滚动消息和绘制消息?
-
@Pedery 我想过这个问题,但是我仍然需要重新实现很多处理。我最好的选择是简单地覆盖有问题的受保护的
SetDisplayLocation方法,而不是调用base,并复制粘贴除一行之外的整个内容。该解决方案的缺点是 a) 我更愿意使用 .NETUserControl(如果可能的话),而不是反对它,b) 复制当前的实现我将失去任何未来的 .NET 增强功能到ScrollableControl和 c) 虽然我不在乎:这是不合法的 -
是的,我明白了。我必须自己解决这个问题才能找到解决方案。您似乎与 .Net 的可能性和局限性非常一致,所以我认为您的尝试和我的一样好。祝你好运!
标签: .net scroll scrollable