Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to get smartphone like scrolling for a winforms touchscreen app ( scrolling panel )

After scouring the articles online I have come up with this design for a winforms based touchscreen app that needs smartphone like scrolling. The app itself will run on a tablet laptop or touchscreen desktop.

  • I put everything I want to scroll on a panel.
  • Set autoscroll to true (which will show scrollbars)
  • Now put this whole panel inside a groupbox
  • Shrink the groupbox until the scrollbars are hidden (visually hidden, not visible = false)

Now the fun part I am stuck at. I think I have to handle the mousedown, mouseup & mousemove on the panel to set the autoscrollposition so that when someone touches the panel and drags, it does it's scroll magic. Please help fill in the few lines of code in below method stubs. The msdn doc on autoscrollposition is very confusing since it returns negative numbers but needs to be set to positive with abs and what not.

Point mouseDownPoint;
Point mouseUpPoint;
Point mouseDragPoint;
 private void myPanel_MouseDown(object sender, MouseEventArgs e)
 {
    this.mouseDownPoint = e.Location;
    Console.WriteLine("Mouse down at {0}", e.location);
 }

 private void myPanel_MouseUp(object sender, MouseEventArgs e)
 {
    this.mouseUpPoint = e.Location;
    this.mouseDownPoint = new Point(); //will set for IsEmpty check
    Console.WriteLine("Mouse Up at {0}", e.location);
 }

 private void myPanel_MouseMove(object sender, MouseEventArgs e)
 {
    Console.WriteLine("Mouse at {0}", e.location);
    if (mouseDownPoint.IsEmpty()) //finger is off the touchscreen
       return;
    myPanel.Autocrollposition = ??
 }

thank you

//UPDATE - Below I have with trial and error working & tested code. (not refactored). If someone has a more elegant solution please post.

    Point mouseDownPoint;
    private void innerpanel_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
            this.mouseDownPoint = e.Location;
    }

    private void innerpanel_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button != MouseButtons.Left)
            return;
        if ((mouseDownPoint.X == e.Location.X) && (mouseDownPoint.Y == e.Location.Y))
            return;

        Point currAutoS = innerpanel.AutoScrollPosition;
        if (mouseDownPoint.Y > e.Location.Y)
        {
            //finger slide UP
            if (currAutoS.Y != 0)
                currAutoS.Y = Math.Abs(currAutoS.Y) - 5;
        }
        else if (mouseDownPoint.Y < e.Location.Y)
        {
            //finger slide down
            currAutoS.Y = Math.Abs(currAutoS.Y) + 5;
        }
        else
        {
            currAutoS.Y = Math.Abs(currAutoS.Y);
        }

        if (mouseDownPoint.X > e.Location.X)
        {
            //finger slide left
            if (currAutoS.X != 0)
                currAutoS.X = Math.Abs(currAutoS.X) - 5;
        }
        else if (mouseDownPoint.X < e.Location.X)
        {
            //finger slide right
            currAutoS.X = Math.Abs(currAutoS.X) + 5;
        }
        else
        {
            currAutoS.X = Math.Abs(currAutoS.X);
        }
        innerpanel.AutoScrollPosition = currAutoS;
        mouseDownPoint = e.Location; //IMPORTANT

    }
like image 425
Gullu Avatar asked Aug 05 '11 23:08

Gullu


2 Answers

7 Years later but for anyone that is looking for a neat and tidy winforms solution:

using System;
using System.Drawing;
using System.Windows.Forms;

    /// <summary>
    /// Pass the panel into constructor and the control will be turned into a touch scrollable control.
    /// </summary>
    public class TouchScroll
    {
        private Point mouseDownPoint;
        private Panel parentPanel;

        /// <summary>
        /// pass in the panel you would like to be touch scrollable and it will be handled here.
        /// </summary>
        /// <param name="panel">The root panel you need to scroll with</param>
        public TouchScroll(Panel panel)
        {
            parentPanel = panel;
            AssignEvents(panel);
        }

        private void AssignEvents(Control control)
        {
            control.MouseDown += MouseDown;
            control.MouseMove += MouseMove;
            foreach (Control child in control.Controls)
                AssignEvents(child);
        }

        private void MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                this.mouseDownPoint = Cursor.Position;
        }

        private void MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
                return;

            Point pointDifference = new Point(Cursor.Position.X - mouseDownPoint.X, Cursor.Position.Y - mouseDownPoint.Y);

            if ((mouseDownPoint.X == Cursor.Position.X) && (mouseDownPoint.Y == Cursor.Position.Y))
                return;

            Point currAutoS = parentPanel.AutoScrollPosition;
            parentPanel.AutoScrollPosition = new Point(Math.Abs(currAutoS.X) - pointDifference.X, Math.Abs(currAutoS.Y) - pointDifference.Y);
            mouseDownPoint = Cursor.Position; //IMPORTANT
        }
    }
}
like image 39
Tom Hobson Avatar answered Sep 19 '22 20:09

Tom Hobson


And as a Component:

public partial class TouchableFlowLayoutPanel : FlowLayoutPanel
{
    private bool _doTouchScroll;
    private Point _mouseStartPoint = Point.Empty;
    private Point _panelStartPoint = Point.Empty;

    /// <summary>
    ///     Initializes a new instance of the <see cref="TouchableFlowLayoutPanel" /> class.
    /// </summary>
    public TouchableFlowLayoutPanel()
    {
        InitializeComponent();

        Program.mouseFilter.MouseFilterDown += mouseFilter_MouseFilterDown;
        Program.mouseFilter.MouseFilterMove += mouseFilter_MouseFilterMove;
        Program.mouseFilter.MouseFilterUp += mouseFilter_MouseFilterUp;
    }

    /// <summary>
    ///     Initializes a new instance of the <see cref="TouchableFlowLayoutPanel" /> class.
    /// </summary>
    /// <param name="container">The container.</param>
    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 mouseFilter 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(-AutoScrollPosition.X,
                                                 -AutoScrollPosition.Y);
        }
    }

    /// <summary>
    ///     Handles the MouseFilterMove event of the mouseFilter 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)
                {
                    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 mouseFilter 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 && !AutoScrollPosition.Equals(_panelStartPoint) &&
                !_panelStartPoint.Equals(Point.Empty))
            {
                // don't fire Click-Event
                e.Handled = true;
            }
            _doTouchScroll = false;
            _mouseStartPoint = Point.Empty;
            _panelStartPoint = Point.Empty;
        }
    }
}

internal class MouseFilter : IMessageFilter
{
    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_LBUTTONUP = 0x0202;
    private const int WM_MOUSEMOVE = 0x0200;

    /// <summary>
    ///     Filters a message before sending it
    /// </summary>
    /// <param name="m">The message to be sent.This message can not be changed.</param>
    /// <returns>
    ///     true to filter the message and prevent it from being sent. false to allow the message to be sent to the next filter or control.
    /// </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; }
}

static class Program
{
    public static MouseFilter mouseFilter = new MouseFilter();

    /// <summary>
    /// Der Haupteinstiegspunkt für die Anwendung.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.AddMessageFilter(mouseFilter);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }
}
like image 132
André Fiedler Avatar answered Sep 18 '22 20:09

André Fiedler