隐藏滚动条,同时允许在 FlowLayoutPanel 中使用鼠标滚轮滚动

问题描述

我正在尝试创建一个动态面板,我可以在其中添加控件并在控件超出面板高度时滚动,同时还隐藏滚动条。

我正在使用 FlowLayoutPanel,我不断地向其中添加自定义面板,并将它们的 Width 设置为父容器的 Width
我还将其 AutoScroll 属性设置为 true

然而,仍然存在一个问题。如何隐藏该死的滚动条?两个。

我尝试了以下方法:

this.lobbiesPanel.AutoScroll = false;
this.lobbiesPanel.HorizontalScroll.Visible = false;
this.lobbiesPanel.VerticalScroll.Visible = false;
this.lobbiesPanel.AutoScroll = true;

令我失望的是,它没有按预期工作。滚动条仍然可见。 如何隐藏滚动条,同时仍保持使用鼠标滚轮滚动的能力?

Demonstration of my issue

解决方法

由于您只需要隐藏 FlowLayoutPanel 的 ScrollBars,而不是用您自己的 Controls 替换 ScrollBars,您可以构建一个从 FlowLayoutPanel 派生的自定义控件。

自定义控件需要一些祖先没有的功能:

  • 必须是可选的
  • 必须接收鼠标输入
  • 如果鼠标指针悬停在子控件上时旋转鼠标滚轮,它应该能够滚动,否则在填充时不会滚动。

要使其可选择并接收鼠标输入,您可以添加到其构造函数:

SetStyle(ControlStyles.UserMouse | ControlStyles.Selectable,true);

要使其无论鼠标指针位于何处都能滚动,它需要预先过滤 WM_MOUSEWHEEL 条消息和可能的 WM_LBUTTONDOWN 条消息。
您可以使用 IMessageFilter 接口在发送消息之前对其进行预过滤并对其采取行动(这可能很棘手,您不能贪婪并学习何时需要放手或为自己保留消息) .

当收到 WM_MOUSEWHEEL 消息并显示它已定向到您的控件时,您可以将其发送到 FlowLayoutPanel。

现在,有一个hack-ish部分:一个 ScrollableControl 非常努力地尝试显示它的滚动条,你(有点)需要它们,因为这个 Control 有一种非常奇怪的方法来计算它的 PreferredSize(整个区域子控件占用的控件)并且它根据 FlowDirection 进行更改,此外没有真正的方法来管理标准滚动条:您可以摆脱它们或隐藏它们。
或者您用自己设计的控件替换它们,但这是另一回事。

要隐藏滚动条,常用的方法是调用ShowScrollBar函数。
int wBar 参数指定要隐藏/显示哪个滚动条。
bool bShow 参数指定是显示 (true) 还是隐藏 (false) 这些滚动条。

  • FlowLayoutPanel 会尝试在特定条件下显示其 ScrollBar,因此您需要捕获一些特定的消息并每次都调用 ShowScrollBar(您不能只调用一次该函数而忘记它)。

这是一个实现所有这些东西的测试自定义控件:
(它是工作代码,但不完全是生产级:我想你必须对它进行一些工作,以使其在特定条件/用例中表现得像你喜欢的那样)

using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

[DesignerCategory("code")]
public class FlowLayoutPanelNoScrollbars : FlowLayoutPanel,IMessageFilter
{
    public FlowLayoutPanelNoScrollbars() {
        SetStyle(ControlStyles.UserMouse | ControlStyles.Selectable,true);
    }

    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        Application.AddMessageFilter(this);

        this.VerticalScroll.LargeChange = 60;
        this.VerticalScroll.SmallChange = 20;

        this.HorizontalScroll.LargeChange = 60;
        this.HorizontalScroll.SmallChange = 20;
    }

    protected override void OnHandleDestroyed(EventArgs e) 
    {
        base.OnHandleDestroyed(e);
        Application.RemoveMessageFilter(this);
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        switch (m.Msg) {
            case WM_PAINT:
            case WM_ERASEBKGND:
            case WM_NCCALCSIZE:
                if (DesignMode || !AutoScroll) break;
                ShowScrollBar(this.Handle,SB_SHOW_BOTH,false);
                break;
            case WM_MOUSEWHEEL:
                // Handle Mouse Wheel for other specific cases
                int delta = (int)(m.WParam.ToInt64() >> 16);
                int direction = Math.Sign(delta);
                ShowScrollBar(this.Handle,false); 
                break;
        }
    }

    public bool PreFilterMessage(ref Message m)
    {
        switch (m.Msg) {
            case WM_MOUSEWHEEL:
            case WM_MOUSEHWHEEL:
                if (DesignMode || !AutoScroll) return false;
                if (VerticalScroll.Maximum <= ClientSize.Height) return false;
                // Should also check whether the ForegroundWindow matches the parent Form.
                if (RectangleToScreen(ClientRectangle).Contains(MousePosition)) {
                    SendMessage(this.Handle,WM_MOUSEWHEEL,m.WParam,m.LParam);
                    return true;
                }
                break;
            case WM_LBUTTONDOWN:
                // Pre-handle Left Mouse clicks for all child Controls
                if (RectangleToScreen(ClientRectangle).Contains(MousePosition)) {
                    // Do something here,if needed
                    return true; // <= eventually,with caution
                }
                return false;
        }
        return false;
    }

    private const int WM_PAINT = 0x000F;
    private const int WM_ERASEBKGND = 0x0014;
    private const int WM_NCCALCSIZE = 0x0083;
    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_MOUSEWHEEL = 0x020A;
    private const int WM_MOUSEHWHEEL = 0x020E;
    private const int SB_SHOW_VERT = 0x1;
    private const int SB_SHOW_BOTH = 0x3; 

    [DllImport("user32.dll",SetLastError = true)]
    private static extern bool ShowScrollBar(IntPtr hWnd,int wBar,bool bShow);

    [DllImport("user32.dll",CharSet = CharSet.Unicode,SetLastError = true)]
    private static extern int SendMessage(IntPtr hWnd,uint uMsg,IntPtr wParam,IntPtr lParam);
}

这是它的工作原理:

FlowLayoutPanel No ScrollBars

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...