touch scrolling for winforms in C#

1

Im trying to implement a panel containing a multitude of buttons and panels with a touch UI in C#. In particular I'm interested in the scrolling functionality. The application is supposed to run on a windows 10 tablet which offers this functionality partially (I.e. if you slide your fingers over the scroll panel's background the scrolling is performed. However, if the gesture starts on a child element of the panel it has no effect. Performing the same gestures with the mouse, no matter where, has no effect.) Unfortunately I don't have the possibility to switch from winforms to WPF application. Right now, I have implemented the functionality using a transparent panel (overwriting its OnPaintBackground() method) which I put on top of the actual scroll panel. It takes the mouse down/move/up events and transforms them into scrolling actions and forwards the clicks to the child controls of the scroll panel. This solution works ok but is very slow (lagging). I wonder if there is a fix to it or some other, better solution.

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
touch
windows-10
asked on Stack Overflow Feb 12, 2016 by Umidjon Urunov • edited Feb 12, 2016 by Umidjon Urunov

3 Answers

1

This is not really an answer, but it's too long for a comment.
I was faced with a similar problem and managed to convince my boss to go to wpf after a lot of attempts to manage panning in a winforms application.
But - in my application the scrolling container is also transparent, so the whole thing looked quite terrible.

However, My solution to handle panning was a different solution then yours, so you might be able to benefit from my experience. I've found this gem online, that enabled me to process panning gestures in my c# code, it might also help you.

answered on Stack Overflow Feb 14, 2016 by Zohar Peled • edited Feb 14, 2016 by Zohar Peled
0

I actually have the same issue i know i'm pretty late but you could have solved your first problem by bubbling the event

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

then they will all have the same scroll gesture functionality the problem with this tho is that it causes some stuttering and glitching that i have no idea where it comes from 😂

answered on Stack Overflow Jun 3, 2020 by Massaynus
0

Since this question apeared on top, I'll post my solution. I hope it'll help to somebody.

First a MouseFilter that filters input messages from hardware (works same for mouse and touch gestures):

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

In your control add change of scroll position to the MouseFilter events.

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

However, if you have a choice, go for WPF. All controls for touch devices are already there and you'll save yourself a lot of problems.

answered on Stack Overflow Jun 3, 2020 by David Pivovar

User contributions licensed under CC BY-SA 3.0