.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
)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
推送向下,然后只有新发现的区域无效,触发{{ 1}}:
在上图中,棋盘区域是无效的内容,必须在下一个WM_PAINT期间绘制。
就我而言,这是不好的;我的控件顶部包含一个“标题”(例如listview列标题)。进一步向下滚动此内容是不正确的:
它会导致视觉损坏。
我希望ScrollableControl 不使用WM_PAINT
,而只是使整个客户区无效。
我尝试覆盖ScrollWindowEx
受保护的方法:
OnScroll
但它会导致双抽奖。
注意:我可以使用双缓冲来掩盖问题,但这不是一个真正的解决方案
- 不应在远程桌面/终端会话下使用双缓冲
- 浪费CPU资源
- 这不是我要问的问题
我考虑使用protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
而不是Control
(即在继承链中UserControl
之前)并手动添加HScroll或VScroll .NET控件 - 但这也不可取:< / p>
因为我可以查看并发布ScrollableControl
内部的代码,我知道没有属性可以禁用ScrollableControl
,但是有一个属性可以禁用ScrollWindow
}?
我尝试重写违规方法,并使用反射器窃取所有代码:
ScrollWindow
问题是SetDisplayRectLocation读取和写入私有成员变量(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);
}
...
}
)。除非Microsoft更改C#以允许后代访问私有成员:我不能这样做。
我意识到复制粘贴displayRect
的实现,修复一个问题意味着我还必须将整个继承链复制粘贴到{{1} }
ScrollableControl
我更愿意使用与面向对象的设计,而不是反对它。
答案 0 :(得分:7)
我有同样的问题,谢谢你发布这个。我可能找到了解决问题的方法。我的解决方案是重载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可能有效。
希望这有帮助。
汤姆
答案 1 :(得分:1)
您是否尝试过与Microsoft的程序员联系?我敢肯定,如果你联系微软,你可以向他们发帖,甚至可以获得电话支持。
以下是支持.NET框架的链接:click here。它提到您可以通过电子邮件,电话或在线方式与.NET支持专业人员联系。
答案 2 :(得分:0)
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
if ((ushort)m.WParam == 8) // SB_ENDSCROLL ignore scroll bar release
break;
sendRedrawMessage(false);
base.WndProc(ref m);
sendRedrawMessage(true);
Refresh(); // Invalidate all
return;
}
base.WndProc(ref m);
}