Drag and drop Elements on a Canvas - c#

This may be a very basic question. I have a canvas, which I put different Elements on in runtime (TextBoxes, Shapes, Buttons). Now I want to be able to drag and drop those elements to another location on the same canvas.
Can anyone provide me with some code, how to implement something like onDrag in runtime?

You can use for example the MouseDown, MouseMove and MouseUp events and then the MouseEventArgs's GetPosition(element) that returns you the coordinates relative to element (there are more events that expose this method).
Also, take advantage of the RoutedEvent's OriginalSource to check which element inside the canvas was clicked (in this case it's only the rectangle).
Here's an example:
<Grid>
<Canvas Name="MyCanvas" PreviewMouseLeftButtonDown="OnMouseDown" MouseMove="OnMouseMove">
<Rectangle Width="20" Height="20" Canvas.Left="10" Canvas.Top="10" Fill="Blue"/>
<Rectangle Width="20" Height="20" Canvas.Left="50" Canvas.Top="10" Fill="Red"/>
</Canvas>
</Grid>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AddHandler(Mouse.MouseUpEvent, new MouseButtonEventHandler(OnMouseUp), true);
}
private bool _isBeingDragged;
public void OnMouseDown(object sender, MouseEventArgs args)
{
if (!(args.OriginalSource is Canvas))
{
_isBeingDragged = true;
}
}
public void OnMouseMove(object sender, MouseEventArgs args)
{
if (_isBeingDragged)
{
var elementBeingDragged = (FrameworkElement) args.OriginalSource;
var position = args.GetPosition(MyCanvas);
Canvas.SetLeft(elementBeingDragged, position.X - elementBeingDragged.ActualWidth / 2);
Canvas.SetTop(elementBeingDragged, position.Y - elementBeingDragged.ActualHeight / 2);
}
}
public void OnMouseUp(object sender, MouseEventArgs args)
{
_isBeingDragged = false;
}
}
I had to use the UIElement's AddHandler method to register MouseUp because somehow it has being marked as Handled (the AddHandler method allows you to register an event handler for events that have been handled previously, i.e. e.Handled = true)

Related

Prevent WPF controls from overlapping on MouseMove event

I'm working on a dynamic C# WPF application (on Windows 10) that uses a fullscreen Grid. Controls are added to the grid dynamically at runtime (which are managed in a Dictionary<>) and I recently added code to move the controls along the grid with the mouse (also at runtime) using a TranslateTransform (which I am now doubting the viability of).
Is there a way I can prevent the controls from overlapping or "sharing space" on the grid when moving them? In other words, adding some sort of collision detection. Would I use an if statement to check the control margin ranges or something? My move events are shown below:
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
// Orientation variables:
public bool _isInDrag = false;
public Dictionary<object, TranslateTransform> PointDict = new Dictionary<object, TranslateTransform();
public Point _anchorPoint;
public Point _currentPoint;
public MainWindow()
{
InitializeComponent();
}
public static void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_isInDrag)
{
var element = sender as FrameworkElement;
element.ReleaseMouseCapture();
_isInDrag = false;
e.Handled = true;
}
}
public static void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = sender as FrameworkElement;
_anchorPoint = e.GetPosition(null);
element.CaptureMouse();
_isInDrag = true;
e.Handled = true;
}
public static void Control_MouseMove(object sender, MouseEventArgs e)
{
if (_isInDrag)
{
_currentPoint = e.GetPosition(null);
TranslateTransform tt = new TranslateTransform();
bool isMoved = false;
if (PointDict.ContainsKey(sender))
{
tt = PointDict[sender];
isMoved = true;
}
tt.X += _currentPoint.X - _anchorPoint.X;
tt.Y += (_currentPoint.Y - _anchorPoint.Y);
(sender as UIElement).RenderTransform = tt;
_anchorPoint = _currentPoint;
if (isMoved)
{
PointDict.Remove(sender);
}
PointDict.Add(sender, tt);
}
}
}
MainWindow.xaml (example):
<Window x:Name="MW" x:Class="MyProgram.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyProgram"
mc:Ignorable="d"
Title="MyProgram" d:DesignHeight="1080" d:DesignWidth="1920" ResizeMode="NoResize" WindowState="Maximized" WindowStyle="None">
<Grid x:Name="MyGrid" />
<Image x:Name="Image1" Source="pic.png" Margin="880,862,0,0" Height="164" Width="162" HorizontalAlignment="Left" VerticalAlignment="Top" MouseLeftButtonDown="Control_MouseLeftButtonDown" MouseLeftButtonUp="Control_MouseLeftButtonUp" MouseMove="Control_MouseMove" />
<TextBox x:Name="Textbox1" Margin="440,560,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" MouseLeftButtonDown="Control_MouseLeftButtonDown" MouseLeftButtonUp="Control_MouseLeftButtonUp" MouseMove="Control_MouseMove" />
</Window>
Edit: It seems that moving a control with a TranslateTransform does not change the margin for that control. Not sure why.
Edit 2: Not getting much traction. If anyone needs clarification on anything, please ask.
Edit 3: Pretty sure I can't use TranslateTransform because it does not change the margin of a given control. Is there an alternative?
Edit 4: Added some 'boilerplate' code for those who want to copy & paste. Let me know if you have any questions about it.
TL;DR: Demo from the bottom of this answer
When you want to modify your UI without adding event handlers to every single control, the way to go is with Adorners. Adorners are (as the name implies) controls that adorn another control to add additional visuals or as in your case functionality. Adorners reside in an AdornerLayer which you can either add yourself or use the one that every WPF Window already has. The AdornerLayer is on top of all your other controls.
You never mentioned what should happen when the user lets go of the mouse button when controls overlap so I just reset the control to its original position if that happens.
At this point I'd usually explain what to keep in mind when moving controls but since your original example even contains the CaptureMouse people usually forget, I think you'll understand the code without further explanation :)
A couple of things you might want to add / improve:
A snap to grid feature (pixel precise movement can be a bit overwhelming for the average user)
Take RenderTransform, LayoutTransform and non-rectangular shapes (if needed) into account when calculating the overlap
Move the editing functionality (enable, disable, etc.) into a separate control and add a dedicated AdornerLayer
Disable interactive controls (Buttons, TextBoxes, ComboBoxes, etc.) in edit-mode
Cancel movement when the user presses Esc
Restrict movement to the bounds of the parent container done
Move the active Adorner to the top of the AdornerLayer
Let the user move multiple controls at once (typically by selecting them with Ctrl)
Previously unanswered question:
Are you saying controls are no longer assigned a margin when using TranslateTransform?
Not at all - You could use a combination of Grid.Row, Grid.Column, Margin, RenderTransform and LayoutTransform but then it would be a nightmare to determine where the control is actually displayed. If you stick with one (In this case for example Margin or LayoutTransform) it is much easier to work with and keep track of. If you ever find yourself in a situation where you need more than one at the same time, you would have to find the actual position by determining the corners of the control by transforming (0, 0) and (ActualWidth, ActualHeight) with TransformToAncestor. Trust me, you don't want to go there - keep it simple, stick with one of them.
The below code is not the "holy grail of how to move things" but it should give you an idea of how to do it and what else you could do with it (resize, rotate, remove controls, etc.). The layouting is based purely on the Left and Top margin of the controls. It shouldn't be to hard to swap out all Margins for LayoutTransforms if you prefer that, as long as you keep it consistent.
Move Adorner
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
public class MoveAdorner : Adorner
{
// The parent of the adorned Control, in your case a Grid
private readonly Panel _parent;
// Same as "AdornedControl" but as a FrameworkElement
private readonly FrameworkElement _child;
// The visual overlay rectangle we can click and drag
private readonly Rectangle _rect;
// Our own collection of child elements, in this example only _rect
private readonly UIElementCollection _visualChildren;
private bool _down;
private Point _downPos;
private Thickness _downMargin;
private List<Rect> _otherRects;
protected override int VisualChildrenCount => _visualChildren.Count;
protected override Visual GetVisualChild(int index)
{
return _visualChildren[index];
}
public MoveAdorner(FrameworkElement adornedElement) : base(adornedElement)
{
_child = adornedElement;
_parent = adornedElement.Parent as Panel;
_visualChildren = new UIElementCollection(this,this);
_rect = new Rectangle
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
StrokeThickness = 1,
};
SetColor(Colors.LightGray);
_rect.MouseLeftButtonDown += RectOnMouseLeftButtonDown;
_rect.MouseLeftButtonUp += RectOnMouseLeftButtonUp;
_rect.MouseMove += RectOnMouseMove;
_visualChildren.Add(_rect);
}
private void SetColor(Color color)
{
_rect.Fill = new SolidColorBrush(color) {Opacity = 0.3};
_rect.Stroke = new SolidColorBrush(color) {Opacity = 0.5};
}
private void RectOnMouseMove(object sender, MouseEventArgs args)
{
if (!_down) return;
Point pos = args.GetPosition(_parent);
UpdateMargin(pos);
}
private void UpdateMargin(Point pos)
{
double deltaX = pos.X - _downPos.X;
double deltaY = pos.Y - _downPos.Y;
Thickness newThickness = new Thickness(_downMargin.Left + deltaX, _downMargin.Top + deltaY, 0, 0);
//Restrict to parent's bounds
double leftMax = _parent.ActualWidth - _child.ActualWidth;
double topMax = _parent.ActualHeight - _child.ActualHeight;
newThickness.Left = Math.Max(0, Math.Min(newThickness.Left, leftMax));
newThickness.Top = Math.Max(0, Math.Min(newThickness.Top, topMax));
_child.Margin = newThickness;
bool overlaps = CheckForOverlap();
SetColor(overlaps ? Colors.Red : Colors.Green);
}
// Check the current position for overlaps with all other controls
private bool CheckForOverlap()
{
if (_otherRects == null || _otherRects.Count == 0)
return false;
Rect thisRect = GetRect(_child);
foreach(Rect otherRect in _otherRects)
if (thisRect.IntersectsWith(otherRect))
return true;
return false;
}
private Rect GetRect(FrameworkElement element)
{
return new Rect(new Point(element.Margin.Left, element.Margin.Top), new Size(element.ActualWidth, element.ActualHeight));
}
private void RectOnMouseLeftButtonUp(object sender, MouseButtonEventArgs args)
{
if (!_down) return;
Point pos = args.GetPosition(_parent);
UpdateMargin(pos);
if (CheckForOverlap())
ResetMargin();
_down = false;
_rect.ReleaseMouseCapture();
SetColor(Colors.LightGray);
}
private void ResetMargin()
{
_child.Margin = _downMargin;
}
private void RectOnMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
_down = true;
_rect.CaptureMouse();
_downPos = args.GetPosition(_parent);
_downMargin = _child.Margin;
// The current position of all other elements doesn't have to be updated
// while we move this one so we only determine it once
_otherRects = new List<Rect>();
foreach (FrameworkElement child in _parent.Children)
{
if (ReferenceEquals(child, _child))
continue;
_otherRects.Add(GetRect(child));
}
}
// Whenever the adorned control is resized or moved
// Update the size of the overlay rectangle
// (Not 100% necessary as long as you only move it)
protected override Size MeasureOverride(Size constraint)
{
_rect.Measure(constraint);
return base.MeasureOverride(constraint);
}
protected override Size ArrangeOverride(Size finalSize)
{
_rect.Arrange(new Rect(new Point(0,0), finalSize));
return base.ArrangeOverride(finalSize);
}
}
Usage
private void DisableEditing(Grid theGrid)
{
// Remove all Adorners of all Controls
foreach (FrameworkElement child in theGrid.Children)
{
var layer = AdornerLayer.GetAdornerLayer(child);
var adorners = layer.GetAdorners(child);
if (adorners == null)
continue;
foreach(var adorner in adorners)
layer.Remove(adorner);
}
}
private void EnableEditing(Grid theGrid)
{
foreach (FrameworkElement child in theGrid.Children)
{
// Add a MoveAdorner for every single child
Adorner adorner = new MoveAdorner(child);
// Add the Adorner to the closest (hierarchically speaking) AdornerLayer
AdornerLayer.GetAdornerLayer(child).Add(adorner);
}
}
Demo XAML
<Grid>
<Button Content="Enable Editing" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="100" Click="BtnEnable_Click"/>
<Button Content="Disable Editing" HorizontalAlignment="Left" Margin="115,10,0,0" VerticalAlignment="Top" Width="100" Click="BtnDisable_Click"/>
<Grid Name="grid" Background="AliceBlue" Margin="10,37,10,10">
<Button Content="Button" HorizontalAlignment="Left" Margin="83,44,0,0" VerticalAlignment="Top" Width="75"/>
<Ellipse Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="100" Margin="207,100,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/>
<Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="100" Margin="33,134,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/>
</Grid>
</Grid>
Expected Result
When editing is disabled controls cannot be moved, interactive controls can be clicked / interacted with without obstruction. When editing mode is enabled, each control is overlayed with an adorner that can be moved. If the target position overlaps with another control, the adorner will turn red and the margin will be reset to the initial position if the user lets go of the mouse button.
There is no other way then to check if there control exists on place where you are moving.
Since you are moving UI elements a lot it is better to use canvas instead of grid where you can layout elements with Top and Left parameters.
Here is modified code of yours that do that
public partial class MainWindow : Window
{
public bool _isInDrag = false;
public Dictionary<object, TranslateTransform> PointDict = new Dictionary<object, TranslateTransform>();
public Point _anchorPoint;
public Point _currentPoint;
public MainWindow()
{
InitializeComponent();
}
public void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_isInDrag)
{
var element = sender as FrameworkElement;
element.ReleaseMouseCapture();
Panel.SetZIndex(element, 0);
_isInDrag = false;
e.Handled = true;
}
}
public void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = sender as FrameworkElement;
_anchorPoint = e.GetPosition(null);
element.CaptureMouse();
Panel.SetZIndex(element, 10);
_isInDrag = true;
e.Handled = true;
}
public void Control_MouseMove(object sender, MouseEventArgs e)
{
if (_isInDrag)
{
_currentPoint = e.GetPosition(null);
FrameworkElement fw = sender as FrameworkElement;
if (fw != null)
{
FrameworkElement fwParent = fw.Parent as FrameworkElement;
if (fwParent != null)
{
Point p = new Point(_currentPoint.X - _anchorPoint.X + Canvas.GetLeft((sender as UIElement)), _currentPoint.Y - _anchorPoint.Y + Canvas.GetTop((sender as UIElement)));
List<HitTestResult> lst = new List<HitTestResult>()
{
VisualTreeHelper.HitTest(fwParent , p),
VisualTreeHelper.HitTest(fwParent, new Point(p.X + fw.Width, p.Y)),
VisualTreeHelper.HitTest(fwParent, new Point(p.X, p.Y + fw.Height)),
VisualTreeHelper.HitTest(fwParent, new Point(p.X + fw.Width, p.Y +fw.Height)),
};
bool success = true;
foreach (var item in lst)
{
if (item != null)
{
if (item.VisualHit != sender && item.VisualHit != fwParent && fw.IsAncestorOf(item.VisualHit) == false)
{
success = false;
break;
}
}
}
if (success)
{
Canvas.SetTop((sender as UIElement), p.Y);
Canvas.SetLeft((sender as UIElement), p.X);
_anchorPoint = _currentPoint;
}
}
}
}
}
}
Xaml
<Window x:Class="ControlsOverlapWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ControlsOverlapWpf"
mc:Ignorable="d"
Title="MyProgram" d:DesignHeight="500" d:DesignWidth="500" ResizeMode="NoResize" WindowState="Normal" WindowStyle="None">
<Canvas Background="Pink">
<Button Canvas.Top=" 200" Canvas.Left="200" Height="150" Width="150" Background="Aqua" HorizontalAlignment="Left" VerticalAlignment="Top" PreviewMouseLeftButtonDown="Control_MouseLeftButtonDown" PreviewMouseLeftButtonUp="Control_MouseLeftButtonUp" PreviewMouseMove="Control_MouseMove" />
<Button Canvas.Top=" 200" Canvas.Left="200" Height="150" Width="150" Background="Aqua" HorizontalAlignment="Left" VerticalAlignment="Top" PreviewMouseLeftButtonDown="Control_MouseLeftButtonDown" PreviewMouseLeftButtonUp="Control_MouseLeftButtonUp" PreviewMouseMove="Control_MouseMove" />
<Button Canvas.Top=" 200" Canvas.Left="200" Height="150" Width="150" Background="Aqua" HorizontalAlignment="Left" VerticalAlignment="Top" PreviewMouseLeftButtonDown="Control_MouseLeftButtonDown" PreviewMouseLeftButtonUp="Control_MouseLeftButtonUp" PreviewMouseMove="Control_MouseMove" />
<Button Canvas.Top=" 200" Canvas.Left="200" Height="150" Width="150" Background="Aqua" HorizontalAlignment="Left" VerticalAlignment="Top" PreviewMouseLeftButtonDown="Control_MouseLeftButtonDown" PreviewMouseLeftButtonUp="Control_MouseLeftButtonUp" PreviewMouseMove="Control_MouseMove" />
<Button Canvas.Top=" 200" Canvas.Left="200" Height="150" Width="150" Background="Aqua" HorizontalAlignment="Left" VerticalAlignment="Top" PreviewMouseLeftButtonDown="Control_MouseLeftButtonDown" PreviewMouseLeftButtonUp="Control_MouseLeftButtonUp" PreviewMouseMove="Control_MouseMove" />
<Button Canvas.Top=" 200" Canvas.Left="200" Height="150" Width="150" Background="Aqua" HorizontalAlignment="Left" VerticalAlignment="Top" PreviewMouseLeftButtonDown="Control_MouseLeftButtonDown" PreviewMouseLeftButtonUp="Control_MouseLeftButtonUp" PreviewMouseMove="Control_MouseMove" />
</Canvas>
</Window>

How to Get Tap Event from User Control in Page

Hopefully the question makes sense. What I would like to do is place an Ellipse (or styled button to look like an Ellipse with an icon inside in a user control that is dynamically populated in my code behind, and then be able to determine when the Ellipse was tapped on the user control and perform a separate action than when the user control is tapped. The sample I'm using actually comes from a Nokia Imaging SDK sample with a few tweaks.
PhotoThumbnail.xaml //The UserControl
<Ellipse Grid.Row="0" Grid.Column="1" Stroke="LightGray" StrokeThickness="3"
VerticalAlignment="Top" HorizontalAlignment="Right" Width="50" Height="50" Margin="7"/>
PhotoThumbnail.xaml.cs
public event PropertyChangedEventHandler PropertyChanged;
public PhotoThumbnail()
{
InitializeComponent();
DataContext = this;
}
Page.xaml.cs
//Creae PhotoThumbnail
PhotoThumbnail photoThumbnail = new PhotoThumbnail()
{
..
};
photoThumbnail.Tap += (object sender, System.Windows.Input.GestureEventArgs e) =>
{
// do something
};
panel.Children.Add(photoThumbnail);
}
}
}
}
The photoThumbnail.Tap event above performs an action, but how can I determine if the user tapped the Ellipse on the PhotoThumbnail UserControl as opposed to the control itself?
I just added the following in Page.xaml.cs and named the Ellipse in the User Control x:Name="EditableEllipse"
photoThumbnail.EditableEllipse.Tap += (object sender, System.Windows.Input.GestureEventArgs e) =>
{
if (sender != null)
{
.. do something ..
}
};

Viewport3D Mouse Event doesn't fire when hitting background

I set up a Viewport3D with a MouseEventHandler
[...]
Main3DWindow.MouseUp += new MouseButtonEventHandler(mainViewport_MouseUp);
[...]
void mainViewport_MouseUp (object sender, MouseButtonEventArgs e) {
Point location = e.GetPosition(Main3DWindow);
ModelVisual3D result = GetHitTestResult(location);
if (result == null) {
_CurrentData.Unselect();
return;
}
_CurrentData.SelectItemFromObjectList(result);
}
And it works pretty fine when an object is clicked.
My expectation was: If no object is clicked (because the user clicked at the background) the result is null. But in fact the mainViewport_MouseUp-method is not even called.
My question: how can i detect clicks on the background of the Viewport3D?
It is as you wrote, it wont be fired.
I solved that by defining events on border and put viewport into border. Sample is from XAML:
<Border
MouseWheel="mainViewport_MouseWheel"
MouseMove="mainViewport_MouseMove"
MouseLeftButtonDown="mainViewport_MouseLeftButtonDown"
Background="Black">
<Viewport3D
Name="mainViewport"
ClipToBounds="True"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="0,0,0,0">
.....
</Viewport3D>
</Border>
And in the code:
private void mainViewport_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point location = e.GetPosition(mainViewport);
try
{
ModelVisual3D result = (ModelVisual3D)GetHitTestResult(location);
//some code.......
}
catch
{
//some code .......
}
}

Why does the MouseMove event not work if mouse loses focus on object?

I basically have a simple problem in my program that I just want to make sure goes right. It should on the click of the mouse button add the MouseEventHandler and then move the circle along with the mouse until the event handler gets removed. I simplified the code to the very basics:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="grid1" Background="White" MouseLeftButtonUp="grid_MouseUp">
<Ellipse Height="50" HorizontalAlignment="Left" Margin="12,12,0,0" Name="ellipse1" Stroke="{x:Null}" VerticalAlignment="Top" Width="50" Fill="Black" MouseLeftButtonDown="ellipse1_MouseDown" />
</Grid>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private static Point _oldPoint = new Point(), _newPoint = new Point();
private void ellipse1_MouseDown(object sender, MouseButtonEventArgs e)
{
_oldPoint = e.GetPosition(grid1);
grid1.MouseMove += new MouseEventHandler(grid_MouseMove);
}
private void grid_MouseUp(object sender, MouseButtonEventArgs e)
{
grid1.MouseMove -= new MouseEventHandler(grid_MouseMove);
}
private void grid_MouseMove(object sender, MouseEventArgs e)
{
_newPoint = e.GetPosition(grid1);
ellipse1.Margin = new Thickness(ellipse1.Margin.Left - _oldPoint.X + _newPoint.X, ellipse1.Margin.Top - _oldPoint.Y + _newPoint.Y, 0, 0);
_oldPoint = _newPoint;
}
}
Now in general this code works fine and I think is quite neat as it doesn't check the movement of the mouse until one actually presses the button. However, my question is as follows:
I had to add the MouseMove event to the grid rather than to the circle, because once the mouse pointer loses focus of the circle (by moving the mouse too fast) it doesn't trigger the MouseMove event anymore. But why exactly does that happen? At the beginning of the event the mouse was definitely above the circle and then it moved. Yes, it moved away from the circle but shouldn't that still trigger the event?
You can capture the mouse and handle all events in your ellipse.
<Grid Name="grid1" Background="White">
<Ellipse Height="50" HorizontalAlignment="Left" Margin="12,12,0,0" Name="ellipse1" Stroke="{x:Null}" VerticalAlignment="Top" Width="50" Fill="Black"
MouseLeftButtonDown="ellipse1_MouseDown" MouseLeftButtonUp="ellipse1_MouseUp" />
</Grid>
with this code behind
private void ellipse1_MouseDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(ellipse1);
_oldPoint = e.GetPosition(grid1);
ellipse1.MouseMove += new MouseEventHandler(ellipse1_MouseMove);
}
private void ellipse1_MouseUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
ellipse1.MouseMove -= new MouseEventHandler(ellipse1_MouseMove);
}
I've moved and renamed grid_MouseMove to ellipse1_MouseMove.
Adding to what Peter said, if you use the Grid.MouseDown event and checked if the oldPoint is within Ellipse and have then handled the MouseMove event, this odd behavior wont be seen.
I also suggest exploring drag events.
A control only gets the mouse-events as long as the mouse is hovering over that particularly control.
If moving to a new control, the mouse is getting unhooked from the old control and hooked to the new control.
There are ways where you can create a global hook attached to the entire process, but I guess this is not what we are talking about.

Drag and drop an image in WPF

I'm trying to drag and drop an image from one spot on the canvas to another (should be relatively simple), but can't figure it out. The image which I want to move has the following XAML:
<Image Height="28" HorizontalAlignment="Left" Margin="842,332,0,0" Name="cityImage" Stretch="Fill" VerticalAlignment="Top" Width="42" Source="/Settlers;component/Images/city.png" MouseLeftButtonDown="cityImage_MouseLeftButtonDown" MouseMove="cityImage_MouseMove" MouseLeftButtonUp="cityImage_MouseLeftButtonUp"/>
The code is as follows:
bool isDragging = false; Point initMousePos; private void cityImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
isDragging = true;
initMousePos = e.GetPosition(theGrid); } private void cityImage_MouseMove(object sender, MouseEventArgs e) {
if (isDragging)
{
Image image = sender as Image;
Canvas.SetTop(image, initMousePos.X);
Canvas.SetLeft(image, initMousePos.Y);
image.Visibility = System.Windows.Visibility.Visible;
} }
private void cityImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
isDragging = false; }
What I do to accomplish what you want is to use
System.Windows.Controls.Primitives.Thumb
as the Root of a UserControl and set the ControlTemplate to display an image (within a border but it should work without as well), something like:
<Thumb Name="myRoot" DragDelta="MyRootDragDelta">
<Thumb.Template>
<ControlTemplate>
<Image ... >
... see below ...
</Image>
</ControlTemplate>
</Thumb.Template>
</Thumb>
Also, I bind the Source of the Image to a property of the class:
<Image.Source>
<Binding Path="ImageSource" RelativeSource=
{RelativeSource FindAncestor,
AncestorType=my:MyImageControl, AncestorLevel=1}" />
</Image.Source>
The UserControl has a named TranslateTransform (let's say translateTransform) whose properties X and Y are to be set in the DragDelta event handler:
private void MyRootDragDelta(object sender, DragDeltaEventArgs e)
{
translateTransform.X += e.HorizontalChange;
translateTransform.Y += e.VerticalChange;
}
Don't forget to add:
public ImageSource ImageSource { get; set; }
Hope this helps. If anything's unclear feel free to ask further.
You want to set the Left and Top properties of the Canvas to something other than the initial position. In the MouseMove handler you have to get the position relative to the parent. Also; make sure the parent element is a canvas and not a grid. You have a pretty big left and top margin on the image, aswell as a control with the variable name "theGrid".

Categories

Resources