I've created my own dragable control. The dragging is very simple:
bool moving = false; Point click = new Point(0, 0);
private void _MouseDown(object sender, MouseButtonEventArgs e)
{
moving = true;
click = Mouse.GetPosition(this);
}
private void _MouseUp(object sender, MouseButtonEventArgs e) { moving = false; }
private void _MouseMove(object sender, MouseEventArgs e)
{
if (moving == true)
{
Point po = Mouse.GetPosition(this);
this.Margin = new Thickness(this.Margin.Left + (po.X - click.X), this.Margin.Top + (po.Y - click.Y), 0, 0);
}
}
My problem is that if I drag too fast the cursor "escapes" my control. It's obvious why, however it's not too obvious how to fix this since I can't easily subscribe to every other control's mousemove in the window, and my control is small (about 35,15 px) so this happends a lot. I think that if I can easily force the mouse cursor to stay in the controll that would be a solution(not ideal, though).
So what is the bast way to fix this? How professinoal controls handle this?
P.S. I'm learning WPF, so I'm probably doing some things wrong
Your cursor leaves the usercontrol on fast moves and the MouseMove event will not be triggered anymore.
As said in the comments from the author in Drag Drop UserControls using the MouseMove event of a surrounding Canvas should help.
I've figured it out, it's very simple, using a timer.
bool moving = false; Point click = new Point(0, 0);
System.Timers.Timer _MOVER = new System.Timers.Timer();
public PersonControl()
{
InitializeComponent();
_MOVER.Elapsed += new System.Timers.ElapsedEventHandler((o, v) => { Dispatcher.Invoke(Move); });
_MOVER.Enabled = true;
_MOVER.Interval = 10;
}
private void _MouseDown(object sender, MouseButtonEventArgs e)
{
moving = true;
click = Mouse.GetPosition(this);
Canvas.SetZIndex(this, 100);
_MOVER.Start();
}
private void _MouseUp(object sender, MouseButtonEventArgs e)
{
moving = false;
Canvas.SetZIndex(this, 0);
_MOVER.Stop();
}
private void Move()
{
if (moving == true)
{
Point po = Mouse.GetPosition(this);
this.Margin = new Thickness(this.Margin.Left + (po.X - click.X), this.Margin.Top + (po.Y - click.Y), 0, 0);
}
}
Related
I have a borderless, transparent Window with only one Button.
My fancy button looks like this
The expected behaviour is:
When I click and drag the Button, the Button must follow the cursor.
When I only click on the Button, the MouseDown, PreviewMouseDown events or Command binding should raise.
At first I tried to call the DragMove() on PreviewMouseDown event, but that blocks the click events. Now my idea is: I set a 100ms delay after mouse down. If the time passed, than the button wil be dragging, otherwise it was just a click.
Code
private bool _dragging;
private Point startpos;
CancellationTokenSource cancellation;
private void Button_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_dragging && e.LeftButton == MouseButtonState.Pressed)
{
var currentpos = e.GetPosition(this);
Left += currentpos.X - startpos.X;
Top += currentpos.Y - startpos.Y;
}
}
private async void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
_dragging = false;
startpos = e.GetPosition(this);
cancellation?.Cancel();
cancellation = new CancellationTokenSource();
await Task.Delay(100, cancellation.Token).ContinueWith(task =>
{
_dragging = !task.IsCanceled;
});
}
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (_dragging)
{
_dragging = false;
e.Handled = true;
}
cancellation?.Cancel();
}
Basically it works, but has some bugs:
When I hold down the mouse button for longer time and release, then the click won't work, because after 100ms the dragging will be active.
After I dragged the button, and click anywhere outside the Button and Window control, the PreviewMouseDown and PreviewMouseUp events are raised. I don't know why??
Does somebody have any better solution?
For your first problem:
You must handle the case when your button is pushed down but not moved. I think a better way to do this (instead of the 100ms delay) would be to specify a minimum threshold of movement above which the dragging will start.
You could do it like this:
private const double _dragThreshold = 1.0;
private bool _dragging;
private Point startpos;
CancellationTokenSource cancellation;
private void Button_PreviewMouseMove(object sender, MouseEventArgs e)
{
var currentpos = e.GetPosition(this);
var delta = currentpos - startpos;
if ((delta.Length > _dragThreshold || _dragging) && e.LeftButton == MouseButtonState.Pressed)
{
_dragging = true;
Left += currentpos.X - startpos.X;
Top += currentpos.Y - startpos.Y;
}
}
private async void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
_dragging = false;
startpos = e.GetPosition(this);
cancellation?.Cancel();
cancellation = new CancellationTokenSource();
}
For your second problem: the button will capture the mouse on the mouse down event.
You need to release the captured mouse with the ReleaseMouseCapture method when you are done with your drag.
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (_dragging)
{
_dragging = false;
e.Handled = true;
var button = sender as Button;
button.ReleaseMouseCapture();
}
cancellation?.Cancel();
}
I have a problem when I try to move the textblock using touch there is a flickering issue which does not occur when I do the same using mouse interaction. can someone help me here?
How to Stop this flickering issue?
public partial class MainWindow: Window
{
Canvas can = new Canvas() { Background=Brushes.White};
Grid grid = new Grid() { Height=150,Width=100,Background=Brushes.Yellow};
TextBlock textBlock = new TextBlock() { Text = "TextBlock Text" ,FontWeight=FontWeights.Bold };
Point point = new Point();
public MainWindow()
{
InitializeComponent();
grid.Children.Add(textBlock);
can.Children.Add(grid);
Canvas.SetLeft(grid, 0);
Canvas.SetTop(grid, 0);
textBlock.PreviewMouseDown += TextBlock_PreviewMouseDown;
textBlock.PreviewTouchDown += TextBlock_PreviewTouchDown;
can.PreviewMouseMove += Can_PreviewMouseMove;
can.PreviewTouchMove += Can_PreviewTouchMove;
m_grid.Children.Add(can);
}
private void TextBlock_PreviewTouchDown(object sender, TouchEventArgs e)
{
point = e.GetTouchPoint(this).Position;
}
private void TextBlock_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
point = e.GetPosition(this);
}
private void Can_PreviewTouchMove(object sender, TouchEventArgs e)
{
if (e.OriginalSource is TextBlock )
{
Canvas.SetLeft(grid, e.GetTouchPoint(this).Position.X - point.X);
Canvas.SetTop(grid, e.GetTouchPoint(this).Position.Y - point.Y);
}
}
private void Can_PreviewMouseMove(object sender, MouseEventArgs e)
{
if(e.OriginalSource is TextBlock && e.LeftButton==MouseButtonState.Pressed&&e.StylusDevice==null)
{
Canvas.SetLeft(grid, e.GetPosition(this).X - point.X);
Canvas.SetTop(grid, e.GetPosition(this).Y - point.Y);
}
}
}
I wasn't able to reproduce the flickering you're encountering, but I think it may be due to your implementation of drag/touch and drop.
The first problem I encountered with the code you posted was in the PreviewTouchDown and PreviewMouseDown event handlers. The code was setting the point to be relative to this - the Window - which when you first drag is okay, but after that any other drag attempt will immediately move the item up to the top/left corner.
To fix this, I edited the event handlers to set point to be relative to the grid so that the drag calculations would be correct on subsequent drags.
The second problem is that you are not capturing the mouse or touch device when dragging, which when dragging near the edge of the grid will cause the drag to stop. Capturing the mouse or touch will also make the drag much smoother.
To fix this, I added mouse and touch capture to the PreviewTouchDown and PreviewMouseDown event handlers, and added TouchUp and MouseUp event handlers that release the capture.
Here's the updated code in full:
public partial class MainWindow : Window
{
private Canvas can = new Canvas() { Background = Brushes.White };
private Grid grid = new Grid() { Height = 150, Width = 100, Background = Brushes.Yellow };
private TextBlock textBlock = new TextBlock() { Text = "TextBlock Text", FontWeight = FontWeights.Bold };
private Point point = new Point();
public MainWindow()
{
InitializeComponent();
grid.Children.Add(textBlock);
can.Children.Add(grid);
Canvas.SetLeft(grid, 0);
Canvas.SetTop(grid, 0);
textBlock.PreviewMouseDown += TextBlock_PreviewMouseDown;
textBlock.PreviewTouchDown += TextBlock_PreviewTouchDown;
textBlock.MouseUp += TextBlock_MouseUp;
textBlock.TouchUp += TextBlock_TouchUp;
can.PreviewMouseMove += Can_PreviewMouseMove;
can.PreviewTouchMove += Can_PreviewTouchMove;
RootGrid.Children.Add(can);
}
private void TextBlock_PreviewTouchDown(object sender, TouchEventArgs e)
{
point = e.GetTouchPoint(grid).Position;
e.TouchDevice.Capture(textBlock);
}
private void TextBlock_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
point = e.GetPosition(grid);
e.MouseDevice.Capture(textBlock);
}
private void TextBlock_TouchUp(object sender, TouchEventArgs e)
{
e.TouchDevice.Capture(null);
}
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
e.MouseDevice.Capture(null);
}
private void Can_PreviewTouchMove(object sender, TouchEventArgs e)
{
if (e.OriginalSource is TextBlock)
{
Canvas.SetLeft(grid, e.GetTouchPoint(this).Position.X - point.X);
Canvas.SetTop(grid, e.GetTouchPoint(this).Position.Y - point.Y);
}
}
private void Can_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.OriginalSource is TextBlock && e.LeftButton == MouseButtonState.Pressed && e.StylusDevice == null)
{
Canvas.SetLeft(grid, e.GetPosition(this).X - point.X);
Canvas.SetTop(grid, e.GetPosition(this).Y - point.Y);
}
}
}
I hope this is helpful.
i want the mouse to freez (cant move) when mouse down
thanks
I used a tableLayoutPanel for your reference (Just remember to implement the code to the Control that is in the front):
OPTION1: Reset the mouse position:
Define two global variables:
bool mousemove = true;
Point currentp = new Point(0, 0);
Handel MouseDown Event to update mousemove :
private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
{
int offsetX = (sender as Control).Location.X + this.Location.X;
int offsetY = (sender as Control).Location.Y + this.Location.Y;
mousemove = false;
currentp = new Point(e.X+offsetX, e.Y+offsetY); //or just use Cursor.Position
}
Handel MouseMove to disable/enable move:
private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
{
if (!mousemove)
{
this.Cursor = new Cursor(Cursor.Current.Handle);
Cursor.Position = currentp;
}
}
Reset mousemove while Mouseup
private void tableLayoutPanel1_MouseUp(object sender, MouseEventArgs e)
{
mousemove = true;
}
OPTION2: Limit mouse clipping rectangle:
Limit it while MouseDown:
private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
{
this.Cursor = new Cursor(Cursor.Current.Handle);
Cursor.Position = Cursor.Position;
Cursor.Clip = new Rectangle(Cursor.Position, new Size(0, 0));
}
Release it after MouseUp:
private void tableLayoutPanel1_MouseUp(object sender, MouseEventArgs e)
{
this.Cursor = new Cursor(Cursor.Current.Handle);
Cursor.Position = Cursor.Position;
Cursor.Clip = Screen.PrimaryScreen.Bounds;
}
You can't.
Mouse acts in the OS Layer, not your app... even if you freeze your app, mouse will be able to run.
You can try to disconnect the mouse driver/port but you do need to ask the user what port the mouse is using as for the OS it's a Input device, just like a pen in a design board and you will not know the one to disconnect.
It's possible, Windows has a dedicated API for it, BlockInput(). Be sure to save all your work when you experiment with it, it is rather effective. You may need to reboot your machine, the thing your user will do when you use it in a program. Here's a sample Windows Forms form that uses it, it needs a button and a timer:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
timer1.Interval = 3000;
timer1.Tick += new EventHandler(timer1_Tick);
button1.Click += new EventHandler(button1_Click);
}
private void button1_Click(object sender, EventArgs e) {
timer1.Enabled = true;
BlockInput(true);
}
private void timer1_Tick(object sender, EventArgs e) {
timer1.Enabled = false;
BlockInput(false);
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool BlockInput(bool block);
}
You can fake that behavior for your window in the following way:
Remember current cursor and its position.
Set
this.Cursor = Cursors.None;
Draw the remembered cursor at specified position and introduce canExecute flag for all your mouse handlers to disable them during "fake mouse freezing".
Can't you move the mouse pointer somewhere? You could reset its position when moving (which may look ugly).
Setup a low level mouse hook with SetWindowsHookEx and ignore all messages to the HOOKPROC delegate you specified (means not to call CallNextHookEx).
we able to move windows forms when we mouse down on title bar .
but how can I move windows when mouse down in form ?
You'll need to record when the mouse is down and up using the MouseDown and MouseUp events:
private bool mouseIsDown = false;
private Point firstPoint;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
firstPoint = e.Location;
mouseIsDown = true;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
mouseIsDown = false;
}
As you can see, the first point is being recorded, so you can then use the MouseMove event as follows:
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (mouseIsDown)
{
// Get the difference between the two points
int xDiff = firstPoint.X - e.Location.X;
int yDiff = firstPoint.Y - e.Location.Y;
// Set the new point
int x = this.Location.X - xDiff;
int y = this.Location.Y - yDiff;
this.Location = new Point(x, y);
}
}
You can do it manually by handling the MouseDown event, as explained in other answers. Another option is to use this small utility class I wrote some time ago. It allows you to make the window "movable" automatically, without a line of code.
Listen for the event when the mouse button goes down in the form and then listen for mouse moves until it goes up again.
Here's a codeproject article that shows how to do this: Move window/form without Titlebar in C#
You can't use location provided in MouseUp or Down, you should use system location like this
private Point diffPoint;
bool mouseDown = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
//saves position difference
diffPoint.X = System.Windows.Forms.Cursor.Position.X - this.Left;
diffPoint.Y = System.Windows.Forms.Cursor.Position.Y - this.Top;
mouseDown = true;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
mouseDown = false;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
this.Left = System.Windows.Forms.Cursor.Position.X - diffPoint.X;
this.Top = System.Windows.Forms.Cursor.Position.Y - diffPoint.Y;
}
}
This works, tested.
Is there any way to make the form semi-transparent while it is being moved and then become opaque when it's not being moved anymore? I have tried the Form_Move event with no luck.
I'm stuck, any help?
The reason the form loads as semi-transparent is because the form has to be moved into the starting position, which triggers the Move event. You can overcome that by basing whether the opacity is set, on whether the form has fully loaded.
The ResizeEnd event fires after a form has finished moving, so something like this should work:
bool canMove = false;
private void Form1_Load(object sender, EventArgs e)
{
canMove = true;
}
private void Form1_Move(object sender, EventArgs e)
{
if (canMove)
{
this.Opacity = 0.5;
}
}
private void Form1_ResizeEnd(object sender, EventArgs e)
{
this.Opacity = 1;
}
To do it properly I expect you'd need to override the message processing to respond to the title bar being held, etc. But you could cheat, and just use a timer so that you make it opaque for a little while when moved, so continuous movement works:
[STAThread]
static void Main()
{
using (Form form = new Form())
using (Timer tmr = new Timer())
{
tmr.Interval = 500;
bool first = true;
tmr.Tick += delegate
{
tmr.Stop();
form.Opacity = 1;
};
form.Move += delegate
{
if (first) { first = false; return; }
tmr.Stop();
tmr.Start();
form.Opacity = 0.3;
};
Application.Run(form);
}
}
Obviously you could tweak this to fade in/out, etc - this is just to show the overall concept.