如何阻止UserControl(nee ScrollableControl)调用ScrollWindow?

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_HSCROLLWS_VSCROLL窗口风格使滚动条显示.也就是说:它们不会创建单独的Windows或.NET滚动控件,将它们定位在窗口的右侧/底部.Windows有一个标准机制,用于显示一个或两个滚动条.

如果用户滚动控件,UserControl则发送一个WM_HSCROLLWM_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控件 - 但这也是不可取的:

  • 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);
    }
    ...
}
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)

我真的更喜欢使用面向对象的设计,而不是反对它.

Tom*_*Tom 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 );
    }
Run Code Online (Sandbox Code Playgroud)

我想尝试这个,因为建议重载WndProc结合你的观察,你不能重载SetDisplayRectLocation.我认为在UserControl处理scroll事件期间禁用WM_PAINT可能会起作用.

希望这可以帮助.

汤姆