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

7rtdyuoh  于 2022-11-30  发布在  其他
关注(0)|答案(2)|浏览(690)

我试图创建一个动态面板,在其中我可以添加控件,并在控件超出面板高度时滚动,同时还隐藏滚动条。
我正在使用FlowLayoutPanel,并不断向其中添加自定义面板,将其Width设置为父容器的Width
我还将其AutoScroll属性设置为true
然而,还有一个问题。我如何隐藏该死的滚动条?两个都有。
我尝试了以下方法:

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

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

xxls0lw8

xxls0lw81#

由于您只需要隐藏FlowLayoutPanel的ScrollBars,而不是用自己的控件替换ScrollBars,因此可以生成从FlowLayoutPanel派生的自定义控件。
自定义控件需要一些祖先控件不具备的功能:

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

要使其可选并接收鼠标输入,可以将以下内容添加到其构造函数:

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

要使其无论鼠标指针位于何处都能滚动,它需要预过滤WM_MOUSEWHEEL消息,可能还需要预过滤WM_LBUTTONDOWN消息。
您可以使用IMessageFilter接口在消息被调度和处理之前对消息进行预过滤(这可能很棘手,您不能太贪心,必须了解何时需要放弃或保留消息)。
当收到WM_MOUSEWHEEL消息并且它看起来定向到您的控件时,您可以将其发送到FlowLayoutPanel。
现在,有黑客式的部分:ScrollableControl非常努力地显示它的Scrollbar,而您(有点)需要它们,因为该控件有一种非常奇怪的方法来计算它的PreferredSize(子控件占用的控件的总面积),它基于FlowDirection而变化,而且没有真正的方法来管理标准的Scrollbar:要么扔掉要么藏起来。
或者你用你自己设计的控件替换它们,但这完全是另一回事。
要隐藏滚动条,常用的方法是调用ShowScrollBar函数。
int wBar参数指定要隐藏/显示的滚动条。
bool bShow参数指定是显示(true)还是隐藏(false)这些滚动条。

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

下面是一个测试自定义控件,它实现了所有这些功能:
(it的工作代码,但不完全是 * 生产级 *:我想,您必须对它进行一些修改,使其在特定条件/用例中按照您的喜好运行)

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

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

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

    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, SB_SHOW_BOTH, 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
                //Console.WriteLine($"WM_LBUTTONDOWN");
                if (RectangleToScreen(ClientRectangle).Contains(MousePosition)) {
                    var mousePos = MousePosition;
                    if (GetForegroundWindow() != TopLevelControl.Handle) return false;
                    // The hosted Control that contains the mouse pointer 
                    var ctrl = FromHandle(ChildWindowFromPoint(this.Handle, PointToClient(mousePos)));
                    // A child Control of the hosted Control that will be clicked 
                    // If no child Controls at that position the Parent's handle
                    var child = FromHandle(WindowFromPoint(mousePos));
                }
                return false;
                // Eventually, if you don't want the message to reach the child Control
                // return true; 
        }
        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);

    [DllImport("user32.dll")]
    internal static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    internal static extern IntPtr WindowFromPoint(Point point);

    [DllImport("user32.dll")]
    internal static extern IntPtr ChildWindowFromPoint(IntPtr hWndParent, Point point);
}

它是这样工作的:

1szpjjfi

1szpjjfi2#

还有一种简单的方法。您可以添加一个Panel,并将FlowLayoutPanel放入Panel中。将FlowLayoutPanel的宽度设置为宽20px左右。现在FlowLayoutPanel的滚动条将不再可见。您可以使用下面的代码以编程方式处理它。

private void Form1_Shown(object sender, EventArgs e)
    {
        Panel pnl = new Panel
        {
            Size = flowLayoutPanel1.Size,
            Location = flowLayoutPanel1.Location,
            BorderStyle = flowLayoutPanel1.BorderStyle
        };
        Control c = flowLayoutPanel1.Parent;
        c.Controls.Add(pnl);
        pnl.Controls.Add(flowLayoutPanel1);
        flowLayoutPanel1.BorderStyle = BorderStyle.None;
        flowLayoutPanel1.Location = new Point(0, 0);
        flowLayoutPanel1.Size = new Size(pnl.Width + 20, pnl.Height);
    }

相关问题