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);
}
}
}
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.
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 😂
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.
User contributions licensed under CC BY-SA 3.0