触摸滚动 C# 中的 Winforms

本文关键字:Winforms 中的 滚动 触摸 | 更新日期: 2023-09-27 18:34:29

我试图在 C# 中实现一个包含大量按钮和面板的面板,并带有触摸 UI。我特别对滚动功能感兴趣。该应用程序应该在部分提供此功能的 Windows 10 平板电脑上运行(即,如果您在滚动面板的背景上滑动手指,则会执行滚动。但是,如果手势从面板的子元素开始,则不起作用。无论在哪里,使用鼠标执行相同的手势都不起作用。不幸的是,我无法从 winform 切换到 WPF 应用程序。现在,我已经使用透明面板(覆盖其OnPaintBackground((方法(实现了该功能,并将其放在实际滚动面板的顶部。它获取鼠标向下/移动/向上事件并将其转换为滚动操作,并将单击转发到滚动面板的子控件。此解决方案工作正常,但非常慢(滞后(。我想知道是否有修复方法或其他更好的解决方案。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ETNA
{
    public class TransparentPanel : Panel
    {
        bool mouseDown = false;
        bool isScrolling = false;
        FlowLayoutPanel flap;
        Point mouseDownPos = new Point();
        int curserYPosBeforeScroll;
        Point initScrollPos = new Point();
        public TransparentPanel(FlowLayoutPanel flap)
        {
            this.flap = flap;
            Location = flap.Location;
            Size = flap.Size;
        }
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
                return cp;
            }
        }
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            mouseDown = true;
            mouseDownPos.X = e.X;
            mouseDownPos.Y = e.Y;
            curserYPosBeforeScroll = Cursor.Position.Y;
            initScrollPos = new Point(0, -flap.AutoScrollPosition.Y); // XXX unclear why negtion is necessary
        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            isScrolling = false;
            mouseDown = false;
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (mouseDown)
            {
                isScrolling = true;
                int initMousePos = curserYPosBeforeScroll;
                int currMousePos = Cursor.Position.Y;
                int autoScrollPos = initScrollPos.Y + initMousePos - currMousePos;
                autoScrollPos = clamp(autoScrollPos, 0, flap.VerticalScroll.Maximum);
                flap.AutoScrollPosition = new Point(initScrollPos.X, autoScrollPos);
                flap.Refresh();
                Refresh();
            }
        }
        private static int clamp(int value, int min, int max)
        {
            return value < min ? min : value > max ? max : value;
        }
        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);
            Point clickPos = new Point(e.X, e.Y);
            if (!isScrolling && mouseDownPos.Equals(clickPos))
            {
                foreach (Control c in flap.Controls)
                {
                    if (c.Bounds.Contains(mouseDownPos))
                    {
                        if (c.GetType().Equals(typeof(UserButton)))
                        {
                            ((Button)c).PerformClick();
                        }
                        else if (c.GetType().Equals(typeof(ProductPanel)))
                        {
                            ((ProductPanel)c).virtualClick(this, e);
                        }
                    }
                }
            }
        }
        // skipping the paint Background method makes the panel transparent
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //base.OnPaintBackground(e);
        }
    }
}

触摸滚动 C# 中的 Winforms

这不是一个真正的答案,但评论太长了。
我遇到了类似的问题,并在多次尝试在 winforms 应用程序中管理平移后设法说服我的老板去 wpf。
但是 - 在我的应用程序中,滚动容器也是透明的,所以整个事情看起来很糟糕。

但是,我

处理平移的解决方案与您的解决方案不同,因此您可以从我的经验中受益。我在网上找到了这个 gem,它使我能够在 c# 代码中处理平移手势,它也可能对您有所帮助。

我实际上有同样的问题我知道我已经很晚了,但你可以通过冒泡活动来解决你的第一个问题

foreach(var ctrl in panel.controls){ ctrl.MouseDown += Panel_mouseDown;...etc}

然后它们都将具有相同的滚动手势功能这个 tho 的问题在于它会导致一些卡顿和故障,我不知道它来自 哪里

由于这个问题排在首位,我将发布我的解决方案。我希望它能对某人有所帮助。

首先是过滤来自硬件的输入消息的 MouseFilter(适用于鼠标和触摸手势(:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
namespace MediTab
{
    class MouseFilter : IMessageFilter
    {
        private const int WM_LBUTTONDOWN = 0x0201;
        private const int WM_LBUTTONUP = 0x0202;
        private const int WM_MOUSEMOVE = 0x0200;
        /// <summary>
        ///     Filtert eine Meldung, bevor sie gesendet wird.
        /// </summary>
        /// <param name="m">Die zu sendende Meldung. Diese Meldung kann nicht geändert werden.</param>
        /// <returns>
        ///     true, um die Meldung zu filtern und das Senden zu verhindern. false, um das Senden der Meldung bis zum nächsten Filter oder Steuerelement zu ermöglichen.
        /// </returns>
        public bool PreFilterMessage(ref Message m)
        {
            Point mousePosition = Control.MousePosition;
            var args = new MouseFilterEventArgs(MouseButtons.Left, 0, mousePosition.X, mousePosition.Y, 0);
            switch (m.Msg)
            {
                case WM_MOUSEMOVE:
                    if (MouseFilterMove != null)
                    {
                        MouseFilterMove(Control.FromHandle(m.HWnd), args);
                    }
                    break;
                case WM_LBUTTONDOWN:
                    if (MouseFilterDown != null)
                    {
                        MouseFilterDown(Control.FromHandle(m.HWnd), args);
                    }
                    break;
                case WM_LBUTTONUP:
                    if (MouseFilterUp != null)
                    {
                        MouseFilterUp(Control.FromHandle(m.HWnd), args);
                    }
                    break;
            }
            // Always allow message to continue to the next filter control
            return args.Handled;
        }
        /// <summary>
        ///     Occurs when [mouse filter up].
        /// </summary>
        public event MouseFilterEventHandler MouseFilterUp;
        /// <summary>
        ///     Occurs when [mouse filter down].
        /// </summary>
        public event MouseFilterEventHandler MouseFilterDown;
        /// <summary>
        ///     Occurs when [mouse filter move].
        /// </summary>
        public event MouseFilterMoveEventHandler MouseFilterMove;
    }
    internal delegate void MouseFilterEventHandler(object sender, MouseFilterEventArgs args);
    internal delegate void MouseFilterMoveEventHandler(object sender, MouseFilterEventArgs args);
    internal class MouseFilterEventArgs
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="MouseFilterEventArgs" /> class.
        /// </summary>
        /// <param name="mouseButton">The mouse button.</param>
        /// <param name="clicks">The clicks.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="delta">The delta.</param>
        public MouseFilterEventArgs(MouseButtons mouseButton, int clicks, int x, int y, int delta)
        {
            Button = mouseButton;
            Clicks = clicks;
            X = x;
            Y = y;
            Delta = delta;
            Handled = false;
        }
        /// <summary>
        ///     Gets or sets the button.
        /// </summary>
        /// <value>
        ///     The button.
        /// </value>
        public MouseButtons Button { get; set; }
        /// <summary>
        ///     Gets or sets a value indicating whether this <see cref="MouseFilterEventArgs" /> is handled.
        /// </summary>
        /// <value>
        ///     <c>true</c> if handled; otherwise, <c>false</c>.
        /// </value>
        public bool Handled { get; set; }
        /// <summary>
        ///     Gets or sets the X.
        /// </summary>
        /// <value>
        ///     The X.
        /// </value>
        public int X { get; set; }
        /// <summary>
        ///     Gets or sets the Y.
        /// </summary>
        /// <value>
        ///     The Y.
        /// </value>
        public int Y { get; set; }
        /// <summary>
        ///     Gets or sets the clicks.
        /// </summary>
        /// <value>
        ///     The clicks.
        /// </value>
        public int Clicks { get; set; }
        /// <summary>
        ///     Gets or sets the delta.
        /// </summary>
        /// <value>
        ///     The delta.
        /// </value>
        public int Delta { get; set; }
    }
}

在您的控件中,将滚动位置的更改添加到鼠标筛选器事件。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
namespace MediTab
{
    public partial class TouchableFlowLayoutPanel : FlowLayoutPanel
    {
        private bool _doTouchScroll;
        private Point _mouseStartPoint = Point.Empty;
        private Point _PanelStartPoint = Point.Empty;
        public TouchableFlowLayoutPanel()
        {
            InitializeComponent();
            Program.mouseFilter.MouseFilterDown += mouseFilter_MouseFilterDown;
            Program.mouseFilter.MouseFilterMove += mouseFilter_MouseFilterMove;
            Program.mouseFilter.MouseFilterUp += mouseFilter_MouseFilterUp;
        }
        public TouchableFlowLayoutPanel(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
            Program.mouseFilter.MouseFilterDown += mouseFilter_MouseFilterDown;
            Program.mouseFilter.MouseFilterMove += mouseFilter_MouseFilterMove;
            Program.mouseFilter.MouseFilterUp += mouseFilter_MouseFilterUp;
        }
        /// <summary>
        ///     Handles the MouseFilterDown event of the mudmFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterDown(object sender, MouseFilterEventArgs e)
        {
            if (!_doTouchScroll && e.Button == MouseButtons.Left)
            {
                _mouseStartPoint = new Point(e.X, e.Y);
                _PanelStartPoint = new Point(-this.AutoScrollPosition.X,
                                                     -this.AutoScrollPosition.Y);
            }
        }
        /// <summary>
        ///     Handles the MouseFilterMove event of the mudmFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterMove(object sender, MouseFilterEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (!_mouseStartPoint.Equals(Point.Empty))
                {
                    int dx = (e.X - _mouseStartPoint.X);
                    int dy = (e.Y - _mouseStartPoint.Y);
                    if (_doTouchScroll)
                    {
                        this.AutoScrollPosition = new Point(_PanelStartPoint.X - dx,
                                                                     _PanelStartPoint.Y - dy);
                    }
                    else if (Math.Abs(dx) > 10 || Math.Abs(dy) > 10)
                    {
                        _doTouchScroll = true;
                    }
                }
            }
        }
        /// <summary>
        ///     Handles the MouseFilterUp event of the mudmFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterUp(object sender, MouseFilterEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (_doTouchScroll && !this.AutoScrollPosition.Equals(_PanelStartPoint) &&
                    !_PanelStartPoint.Equals(Point.Empty))
                {
                    // dont fire Click-Event
                    e.Handled = true;
                }
                _doTouchScroll = false;
                _mouseStartPoint = Point.Empty;
                _PanelStartPoint = Point.Empty;
            }
        }
    }
}

但是,如果您有选择,请选择 WPF。触摸设备的所有控件都已经存在,您将节省很多问题。