.net 如何停止UserControl(nee ScrollableControl)调用ScrollWindow?

g52tjvyc  于 2023-08-08  发布在  .NET
关注(0)|答案(4)|浏览(87)

.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(即ScrollableControlcallsScrollWindow 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


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

mrfwxfqh

mrfwxfqh1#

我也遇到了同样的问题,谢谢你把这个贴出来。我可能找到了解决你问题的办法。我的解决方案是重载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的建议,以及您观察到无法重载SetDisplayLocation。我认为在UserControl处理滚动事件期间禁用WM_PAINT可能会起作用。
希望这对你有帮助。
汤姆

zkure5ic

zkure5ic2#

你试过和微软的程序员联系吗?我相信如果你联系微软,你可以把你的问题发给他们,甚至可以得到电话支持。
下面是对.NET框架支持的链接:click here。它提到您可以通过电子邮件、电话或在线与.NET支持专业人员取得联系。

pcww981p

pcww981p3#

Tom的解决方案很棒,但我认为还有一个小优化的机会。如果没有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);
}

字符串

v7pvogib

v7pvogib4#

我还尝试禁用调用ScrollWindowEx()。在当前版本的ScrollableControl中,在WmVScroll()上,ScrollWindowEx()的调用如下:

protected void SetDisplayRectLocation(int x, int y)
{
    ...
    if (num != 0 || (num2 != 0 && base.IsHandleCreated))
    {
        Rectangle clientRectangle2 = base.ClientRectangle;
        NativeMethods.RECT rectClip = NativeMethods.RECT.FromXYWH(clientRectangle2.X, clientRectangle2.Y, clientRectangle2.Width, clientRectangle2.Height);
        NativeMethods.RECT prcUpdate = NativeMethods.RECT.FromXYWH(clientRectangle2.X, clientRectangle2.Y, clientRectangle2.Width, clientRectangle2.Height);
        SafeNativeMethods.ScrollWindowEx(new HandleRef(this, base.Handle), num, num2, null, ref rectClip, NativeMethods.NullHandleRef, ref prcUpdate, 7);
    }
    ...
}

字符串
而SetDisplayLocation()是从Wm调用的?Scroll()如下所示:

private void WmVScroll(ref Message m)
{
    ...
    if (GetScrollState(16) || flag)
    {
        SetScrollState(8, value: true);
        SetDisplayRectLocation(displayRect.X, -num);
        SyncScrollbars(AutoScroll);
    }
    WmOnScroll(ref m, oldValue, num, ScrollOrientation.VerticalScroll);
}


所以我试着打电话

SetScrollState( 16, false )

以防止调用SetDisplayLocation()。而且很有效。
但也发现了一个新的问题。数字16是在ScrollableControl中定义的ScrollStateFullDrag。当禁用此状态时,当用户拖动拇指时,滚动条位置不会更新。
所以,我需要得到滚动事件,并在事件中,保存当前滚动位置到局部变量和无效控制。在OnPaint()上,它基于局部变量而不是基于滚动位置进行绘制。
我尝试了一些测试,但到目前为止,它是确定的,工作正常。

相关问题