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

时间:2011-04-25 18:31:11

标签: .net scroll scrollable

.NET UserControl(来自ScrollableControl)必须能够显示水平和垂直滚动条。

调用者可以设置这些水平和垂直滚动条的可见性和范围:

UserControl.AutoScroll = true;
UserControl.AutoScrollMinSize = new Size(1000, 4000); //1000x4000 scroll area
  

注意: UserControl(即ScrollableControl)使用指定WS_HSCROLLWS_VSCROLL窗口样式的Windows标准机制来显示滚动条。也就是说:它们不会创建单独的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;
 }

我需要整个客户区无效。问题是 UserControl (即ScrollableControlcalls 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}}:

enter image description here

在上图中,棋盘区域是无效的内容,必须在下一个WM_PAINT期间绘制。

就我而言,这是不好的;我的控件顶部包含一个“标题”(例如listview列标题)。进一步向下滚动此内容是不正确的:

enter image description here

它会导致视觉损坏。

我希望ScrollableControl 使用WM_PAINT,而只是使整个客户区无效。

我尝试覆盖ScrollWindowEx受保护的方法:

OnScroll

但它会导致双抽奖。

  

注意:我可以使用双缓冲来掩盖问题,但这不是一个真正的解决方案

     
      
  • 不应在远程桌面/终端会话下使用双缓冲
  •   
  • 浪费CPU资源
  •   
  • 这不是我要问的问题
  •   

我考虑使用protected override void OnScroll(ScrollEventArgs se) { base.OnScroll(se); this.Invalidate(); } 而不是Control(即在继承链中UserControl之前)并手动添加HScroll或VScroll .NET控件 - 但这也不可取:< / p>

  • Windows已经提供了滚动条位置的标准外观(复制并不容易)
  • 这是很多必须从头开始重现的功能,当我只想要 InvalidateRect 而不是 ScrollWindowEx

因为我可以查看并发布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

我更愿意使用面向对象的设计,而不是反对它。

3 个答案:

答案 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)

汤姆的解决方案非常棒,但我认为有一个小优化的机会。 没有Tom的两种方法,当我进行滚动时,例如,通过单击滚动条端点,我的onPaint会看到一次调用。 当我添加Tom的两个方法时,我的onPaint开始获得两个方法的调用 相同的滚动条位置。 我的解决方案似乎是忽略了最后发生的SB_ENDSCROLL是滚动操作。有了这个,我停止在同一个滚动位置看到重复的油漆。

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