While looking for ways to drag around a UIElement in WPF I came across some code and have been experimenting with it. When the Element is clicked, it nicely follows the mouse, but on a subsequent drag-event, the Element reset itself to its original position.
The xaml setup: Very simple, just a named Canvas with the most original name ever and the Element, in this case a grid, called Tile1.
<Grid>
<Canvas x:Name="Canvas" Width="200" Height="300" Background="LightGray">
<Grid x:Name="Tile1">
<Border BorderBrush="Black" BorderThickness="1" Background="White">
<Control Width="25" Height="25"/>
</Border>
</Grid>
</Canvas>
</Grid>
some code-behind:
public TranslateTransform transPoint;
public Point originPoint;
public MainWindow()
{
InitializeComponent();
Tile1.MouseLeftButtonDown += Tile1_MouseLeftButtonDown;
Tile1.MouseLeftButtonUp += Tile1_MouseLeftButtonUp;
Tile1.MouseMove += Tile1_MouseMove;
}
private void Tile1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var myLocation = e.GetPosition(Canvas);
originPoint = new Point(myLocation.X, myLocation.Y);
transPoint = new TranslateTransform(originPoint.X, originPoint.Y);
}
private void Tile1_MouseMove(object sender, MouseEventArgs e)
{
var mouseLocation = e.GetPosition(Canvas);
if (e.LeftButton == MouseButtonState.Pressed)
{
transPoint.X = (mouseLocation.X - originPoint.X);
transPoint.Y = (mouseLocation.Y - originPoint.Y);
Tile1.RenderTransform = transPoint;
}
}
private void Tile1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var mouseLocationOnCanvas = e.GetPosition(Canvas);
var mouseLocationOnTile = e.GetPosition(Tile1);
//attempting to account for offset of mouse on the Tile:
var newX = mouseLocationOnCanvas.X - mouseLocationOnTile.X;
var newY = mouseLocationOnCanvas.Y - mouseLocationOnTile.Y;
Tile1.Margin = new Thickness(newX, newY, 0, 0);
}
In the original example (reference added here) A MouseUpEvent wasn't even used. Without it, my element just resets to its original position at 0,0 at every MouseDragEvent. And with it, it'll jump all over the place.
My train of thought was to somehow set the Element's current position to where the MouseUpEvent occurred.
I've been fiddling around with different things, as this particular stuff is rather new to me. Examples are: Tile1.TransformToAncestor(Canvas).Transform(mouseLocation);
I also found that VisualOffset has the information I need, so somehow it's already stored on the object, before being reset, but I haven't found a way to access it in any form.
Tile1.SetValue(VisualOffset.X = ...); or Tile1Grid.GetValue(VisualOffset);
So basically, is there a way to not have the element reset its position after RenderTransform?
I didn't find MouseLeftButtonDown event useful so I removed it:
I noticed some lags when moving the mouse that ould be annoying maybe the event needs to run asynchronously MouseMove performance slow using GetPosition.
public TranslateTransform transPoint = new TranslateTransform(0, 0);
public Point originPoint = new Point(0, 0);
public MainWindow()
{
InitializeComponent();
Tile1.MouseLeftButtonUp += Tile1_MouseLeftButtonUp;
Tile1.MouseMove += Tile1_MouseMove;
}
private void Tile1_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var mouseLocation = e.GetPosition(Canvas);
transPoint.X = (mouseLocation.X - originPoint.X);
transPoint.Y = (mouseLocation.Y - originPoint.Y);
Tile1.RenderTransform = transPoint;
}
}
private void Tile1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var mouseLocation = e.GetPosition(Tile1);
originPoint.X = mouseLocation.X;
originPoint.Y = mouseLocation.Y;
}
The RenderTransform seems to erratic, but I got the UIElement to stay put after moving it using the following:
private void MovableTile_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
tile.CaptureMouse();
}
with
private void MovableTile_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var tile = sender as UIElement;
var mousePosition = e.GetPosition(canvas);
Canvas.SetLeft(tile, mousePosition.X);
Canvas.SetTop(tile, mousePosition.Y);
}
}
and then
private void MovableTile_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
tile.ReleaseMouseCapture();
}
The MouseCapture was the key here. :)
Related
I'm trying to drag a rectangle element called "Rec" using the mouse. I can drag it to the location I want and the rectangle stays there, but when I try to drag it again it returns to the first location and the dragging starts from there. I want to drag it from where I left it the last time. I just don't get where the problem is.
I have one Canvas only and all my elements are inside it, the Canvas is called the "maincanvas". I use the following very simple events to for dragging.
Point originalPosition = new Point(0, 0);
private void Rec_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
Point CurrPosition = Mouse.GetPosition(MainCanvas);
Canvas.SetLeft(e.Source as UIElement, -( originalPosition.X - CurrPosition.X));
Canvas.SetTop(e.Source as UIElement, -(originalPosition.Y - CurrPosition.Y));
}
private void Rec_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UIElement a = e.Source as UIElement;
a.CaptureMouse();
Rec.MouseMove += Rec_MouseMove;
originalPosition = Mouse.GetPosition(MainCanvas);
}
private void Rec_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Rec.MouseMove -= Rec_MouseMove;
UIElement a = e.Source as UIElement;
a.ReleaseMouseCapture();
originalPosition = new Point(0, 0);
}
I hope you guys could help me.
You should handle the mouse events on the Canvas like this:
<Canvas MouseLeftButtonDown="CanvasMouseLeftButtonDown"
MouseLeftButtonUp="CanvasMouseLeftButtonUp"
MouseMove="CanvasMouseMove">
...
</Canvas>
In the mouse down handler you would get the element that should be dragged by the MouseButtonEventArgs's OriginalSource property:
private UIElement draggedElement;
private Point lastMousePos;
private void CanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource != sender)
{
IInputElement canvas = (IInputElement)sender;
canvas.CaptureMouse();
draggedElement = e.OriginalSource as UIElement;
lastMousePos = e.GetPosition(canvas);
}
}
private void CanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((IInputElement)sender).ReleaseMouseCapture();
draggedElement = null;
}
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
if (draggedElement != null)
{
var p = e.GetPosition((IInputElement)sender);
var dx = p.X - lastMousePos.X;
var dy = p.Y - lastMousePos.Y;
lastMousePos = p;
Canvas.SetLeft(draggedElement, Canvas.GetLeft(draggedElement) + dx);
Canvas.SetTop(draggedElement, Canvas.GetTop(draggedElement) + dy);
}
}
I want to move the label by using the Mouse_Move/Mouse_Down events.
I tried to do it like this:
private void control_MouseDown(object sender, MouseButtonEventArgs e)
{
Label l = e.Source as Label;
if (l != null)
{
l.CaptureMouse();
moving = true;
PositionInLabel = e.GetPosition(l);
}
}
private void control_MouseMove(object sender, MouseEventArgs e)
{
if (moving)
{
Point p = e.GetPosition(null);
DeltaX = p.X - BasePoint.X - PositionInLabel.X;
DeltaY = p.Y - BasePoint.Y - PositionInLabel.Y;
RaisePropertyChanged("XPosition");
RaisePropertyChanged("YPosition");
}
}
Suppose your label is on a Canvas:
public MainWindow()
{
InitializeComponent();
this.label.MouseLeftButtonDown += control_MouseLeftButtonDown;
this.label.MouseMove += control_MouseMove;
this.label.MouseLeftButtonUp += control_MouseLeftButtonUp;
}
// Keep track of the Canvas where this element is placed.
private Canvas canvas;
// Keep track of when the element is being dragged.
private bool isDragging = false;
// When the element is clicked, record the exact position
// where the click is made.
private Point mouseOffset;
private void control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Label l = e.Source as Label;
if (l == null)
return;
// Find the Canvas.
if (canvas == null)
canvas = (Canvas)VisualTreeHelper.GetParent(l);
// Dragging mode begins.
isDragging = true;
// Get the position of the click relative to the element
// (so the top-left corner of the element is (0,0).
mouseOffset = e.GetPosition(l);
// Capture the mouse. This way you'll keep receiving
// the MouseMove event even if the user jerks the mouse
// off the element.
l.CaptureMouse();
}
private void control_MouseMove(object sender, MouseEventArgs e)
{
Label l = e.Source as Label;
if (l == null)
return;
if (isDragging)
{
// Get the position of the element relative to the Canvas.
Point point = e.GetPosition(canvas);
// Move the element.
l.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
l.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
}
}
private void control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Label l = e.Source as Label;
if (l == null)
return;
if (isDragging)
{
l.ReleaseMouseCapture();
isDragging = false;
}
}
Let containerCanvas be a Canvas which contains the Label; then you can utilize the _MouseMove(object sender, MouseEventArgs e) to move the label along with your mouse pointer:
Xaml code:
<Canvas Name="containerCanvas" MouseMove="containerCanvas_MouseMove" Background="Aqua" Width="525" Height="350" >
<Label Name="floatingLabel" Height="28" Width="161" Background="AliceBlue"></Label>
</Canvas>
C# code:
private void containerCanvas_MouseMove(object sender, MouseEventArgs e)
{
Point p = Mouse.GetPosition(Application.Current.MainWindow);
floatingLabel.Margin = new Thickness(p.X, p.Y, 0, 0);
}
I have done a sample code. Check this. It should work.
XAML :
<UserControl HorizontalAlignment="Left" x:Class="WPFDiagramDesignerControl.Components.TestApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="100" Width="100" IsEnabled="True">
<Grid >
<Canvas x:Name="MyDesigner">
<TextBox x:Name="txtBox" IsEnabled="True" Background="AntiqueWhite" Margin="10,10,10,10" TextWrapping="Wrap"> </TextBox>
</Canvas>
</Grid>
Code Behind :
public TestApp()
{
InitializeComponent();
txtBox.MouseDoubleClick+=new MouseButtonEventHandler(control_MouseDoubleClick);
txtBox.MouseMove+=new MouseEventHandler(control_MouseMove);
txtBox.PreviewMouseDown+=new MouseButtonEventHandler(control_PreviewMouseDown);
txtBox.PreviewMouseUp+=new MouseButtonEventHandler(control_PreviewMouseUp);
txtBox.Cursor = Cursors.SizeAll;
}
private void control_MouseMove(object sender, RoutedEventArgs e)
{
if (isClicked)
{
Point mousePos = Mouse.GetPosition(parentCanvas);
parentItem = this.Parent as DesignerItem;
parentCanvas = parentItem.Parent as DesignerCanvas;
Point relativePosition = Mouse.GetPosition(parentCanvas);
DesignerCanvas.SetLeft(this, DesignerCanvas.GetLeft(this) - (startPoint.X - mousePos.X));
DesignerCanvas.SetTop(this, DesignerCanvas.GetTop(this) - (startPoint.Y - mousePos.Y));
}
}
private void control_PreviewMouseDown(object sender, RoutedEventArgs e)
{
if (!isClicked)
{
isClicked = true;
parentItem = this.Parent as DesignerItem;
parentCanvas = parentItem.Parent as DesignerCanvas;
startPoint = Mouse.GetPosition(parentCanvas);
}
}
private void control_PreviewMouseUp(object sender, RoutedEventArgs e)
{
isClicked = false;
}
I have a canvas in XAML in which I want to draw random irregular (Hand drawn) line using C#.
This is what I want: http://jsfiddle.net/GfGVE/9/ HTML Canvas but in XAML C#
I want the line to be drawn Horizontally.
Xaml:
<Canvas Grid.Column="1" x:Name="ItemCanvas"></Canvas>
C#:
public ItemControl()
{
this.InitializeComponent();
canvas = this.ItemCanvas;
}
You need to add methods for the events MouseMove, MouseDown, MouseEnter, MouseUp and MouseLeave. Additionally you need two variables for the current position and the last position. If you also want to undo you need to add a Stack<int> for this. These are the needed methods:
private void Canvas_MouseDown(Object sender, MouseButtonEventArgs e)
{
// push start of line onto undo stack
this.undo.Push(this.paintCanvas.Children.Count);
this.last = e.GetPosition(this.paintCanvas);
}
private void Canvas_MouseMove(Object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (this.last.X != -1)
{
Point current = e.GetPosition(this.paintCanvas);
Line l = new Line();
l.X1 = this.last.X;
l.Y1 = this.last.Y;
l.X2 = current.X;
l.Y2 = current.Y;
l.Stroke = this.brush;
l.StrokeThickness = this.thickness;
this.paintCanvas.Children.Add(l);
this.last = current;
}
}
}
private void Canvas_MouseUp(Object sender, MouseButtonEventArgs e)
{
// push end of line onto undo stack
this.undo.Push(this.paintCanvas.Children.Count);
}
private void Canvas_MouseLeave(Object sender, MouseEventArgs e)
{
this.last = new Point(-1, -1);
}
private void Canvas_MouseEnter(Object sender, MouseEventArgs e)
{
this.last = e.GetPosition(this.paintCanvas);
}
To remove the last line from your canvas you can call this Undo-method:
private void Undo()
{
if (this.undo.Count == 0)
return;
// pop indexes of last line (start index is one below top of stack)
int to = this.undo.Pop();
int from = this.undo.Pop();
// remove last line from UIElement collection
this.paintCanvas.Children.RemoveRange(from, to);
}
You could use InkCanvas for this purpose. It defines an area that receives and displays ink strokes:
<Grid>
<InkCanvas Name="InkCanvas1"></InkCanvas>
</Grid>
Result:
I want to draw something on my canvas whenever drawing_mode and is_drawing booleans are on. Right now I am using a polyline list and BackgroundWorker for threading. My main problem is that my code only creates one polyline and the dots are ALWAYS connected. In other words I can stop drawing for a while but then wherever I click a new line connection is made with the previous one. The end result is that my canvas.Children only has one polyline element with all the points. Could anyone help me solve this?
P.S. I am not very good with threading yet...
private BackgroundWorker drawing_worker;
private void drawing_worker_ProgressChanged(object sender, ProgressChangedEventArgs eventargs)
{
Polyline polyline = new Polyline();
polyline.Points = last_polyline.Points;
canvas.Children.Remove(last_polyline);
var pos = canvas_relative_pos;
polyline.Points.Add(pos);
polyline.Stroke = new SolidColorBrush(Colors.Black);
polyline.StrokeThickness = 1;
canvas.Children.Add(polyline);
last_polyline = polyline;
}
private void drawing_worker_DoWork(object sender, DoWorkEventArgs eventargs)
{
while (drawing_mode_enabled && is_drawing)
{
drawing_worker.ReportProgress(0);
Thread.Sleep(5);
if (drawing_worker.CancellationPending) break;
}
}
private void ContentControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (drawing_mode_enabled)
{
is_drawing = true;
drawing_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
drawing_worker.ProgressChanged += drawing_worker_ProgressChanged;
drawing_worker.DoWork += drawing_worker_DoWork;
drawing_worker.RunWorkerAsync();
}
}
You don't need a BackgroundWorker or any other asynchronous stuff to draw polylines on Canvas.
Just create a Canvas with a Background (so that it gets input events) and handlers for the MouseLeftButtonDown, MouseLeftButtonUp and MouseMove events:
<Canvas Background="Transparent"
MouseLeftButtonDown="CanvasMouseLeftButtonDown"
MouseLeftButtonUp="CanvasMouseLeftButtonUp"
MouseMove="CanvasMouseMove"/>
In the mouse down handler create a new Polyline and add it to the Canvas. Also capture the mouse so that you get mouse move events even when the mouse cursor leaves the Canvas. In the mouse up handler release the mouse capture. Finally, in the mouse move handler, add points to the last polyline child of the Canvas.
private void CanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var panel = (Panel)sender;
var polyline = new Polyline
{
Stroke = Brushes.Black,
StrokeThickness = 3
};
panel.Children.Add(polyline);
panel.CaptureMouse();
}
private void CanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
}
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
var panel = (Panel)sender;
if (panel.IsMouseCaptured)
{
var polyline = panel.Children.OfType<Polyline>().Last();
polyline.Points.Add(e.GetPosition(panel));
}
}
I have added the following parameters to my Window:
WindowStyle="None"
WindowStartupLocation="CenterScreen"
AllowsTransparency="True"
ResizeMode="NoResize" Background="Transparent"
And now I can't move the Window, so I have added the following part of code to my Window:
#region Window: Moving
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
#endregion
Also I must specify that my XAML code in my Window is the following (the Window look like the Polygon):
<Window Title="New Science"
Height="588" Width="760" MinHeight="360" MinWidth="360"
WindowStyle="None" WindowStartupLocation="CenterScreen"
AllowsTransparency="True"
ResizeMode="NoResize" Background="Transparent"
xmlns:my="clr-namespace:Bourlesque.Lib.Windows.Media;assembly=Bourlesque.Lib.Windows.Media">
<Grid>
<my:UniPolygon DefaultRadiusIn="10" DefaultRadiusOut="10" Fill="#FF92C2F2" Name="m_tPlgOuter" Offset="0" Points=" 0;26;; 10;19;10;; 10;0;; 265;0;20;; 290;20;20;; -60,1;20;3;; -60,1;5;10;; -40,1;5;10;; -40,1;20;2.5;; -35,1;20;2.5;; -35,1;5;10;; -15,1;5;10;; -15,1;20;3;; 0,1;20;; 0,1;0,1;; 0;0,1;; " Stretch="None" Stroke="#FF535353" StrokeThickness="0.1" />
</Grid>
</Window>
I would like to know what should I do to make the Window change it's position on mouse drag and what to add to resize the window with the condition that the controls and other things I will add will resize too(I have found this code to resize and I would like to know if is good here).
I used the event MouseDown:
<Window .....
MouseDown="Window_MouseDown" >
with this code:
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if(e.ChangedButton == MouseButton.Left)
this.DragMove();
}
Found a example:
http://cloudstore.blogspot.com.br/2008/06/moving-wpf-window-with-windowstyle-of.html
Anyway, to move a Window in WinForms I used in a project the following code, can be useful if you are having problems:
private bool clicado = false;
private Point lm = new Point();
void PnMouseDown(object sender, MouseEventArgs e)
{
clicado = true;
this.lm = MousePosition;
}
void PnMouseUp(object sender, MouseEventArgs e)
{
clicado = false;
}
void PnMouseMove(object sender, MouseEventArgs e)
{
if(clicado)
{
this.Left += (MousePosition.X - this.lm.X);
this.Top += (MousePosition.Y - this.lm.Y);
this.lm = MousePosition;
}
}
I've tried another solution and worked (not sure if it is the most correct though)
private void GridOfWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var move = sender as System.Windows.Controls.Grid;
var win = Window.GetWindow(move);
win.DragMove();
}
where GridOfWindow is the name of the Grid
<Grid x:Name="GridOfWindow" MouseLeftButtonDown="GridOfWindow_MouseLeftButtonDown">
good code to the answer, but buggy. it will get your moving out of control.
try my modify:
private bool clicado = false;
private Point lm = new Point();
void PnMouseDown(object sender, System.Windows.Input.MouseEventArgs e)
{
clicado = true;
this.lm = System.Windows.Forms.Control.MousePosition;
this.lm.X = Convert.ToInt16(this.Left) - this.lm.X;
this.lm.Y = Convert.ToInt16(this.Top) - this.lm.Y;
}
void PnMouseUp(object sender, System.Windows.Input.MouseEventArgs e)
{
clicado = false;
}
void PnMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (clicado)
{
this.Left = (System.Windows.Forms.Control.MousePosition.X + this.lm.X);
this.Top = (System.Windows.Forms.Control.MousePosition.Y + this.lm.Y);
}
}
it will get your moving stick to your cursor.(///▽///)
#Marcio there is no Windows.Forms in WPF.
I got this version to work (steady) with WPF,
private bool clicked = false;
private Point lmAbs = new Point();
void PnMouseDown(object sender, System.Windows.Input.MouseEventArgs e)
{
clicked = true;
this.lmAbs = e.GetPosition(this);
this.lmAbs.Y = Convert.ToInt16(this.Top) + this.lmAbs.Y;
this.lmAbs.X = Convert.ToInt16(this.Left) + this.lmAbs.X;
}
void PnMouseUp(object sender, System.Windows.Input.MouseEventArgs e)
{
clicked = false;
}
void PnMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (clicked)
{
Point MousePosition = e.GetPosition(this);
Point MousePositionAbs = new Point();
MousePositionAbs.X = Convert.ToInt16(this.Left) + MousePosition.X;
MousePositionAbs.Y = Convert.ToInt16(this.Top) + MousePosition.Y;
this.Left = this.Left + (MousePositionAbs.X - this.lmAbs.X);
this.Top = this.Top + (MousePositionAbs.Y - this.lmAbs.Y);
this.lmAbs = MousePositionAbs;
}
}
Kind regards,
Lex
I had to fix above's a bit to work properly... working as charm now! Use this with: MouseLeftButtonDown at your main Window.
private void EnableDrag(object sender, MouseButtonEventArgs e)
{
var move = sender as Window;
if (move != null)
{
Window win = Window.GetWindow(move);
win.DragMove();
}
}