How To Drag and Drop Images inside Canvas in WPF using C# - c#

I am able to dynamically add images into the WPF Canvas control as child elements but failing to drag & drop those images inside the canvas. Please help me as how could i move or drag/drop images inside the canvas window.
Thanks in advance.
Below is what I have done so far:
<Canvas x:Name="canvasImages" Height="325" Margin="0,0,0,0" Width="430"
HorizontalAlignment="Left" VerticalAlignment="Top" AllowDrop="True"
PreviewMouseLeftButtonDown="MouseLeftButtonDown"
PreviewMouseLeftButtonUp="MouseLeftButtonUp"
PreviewMouseMove="MouseMove"
MaxWidth="430" MaxHeight="325"
ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
</Grid>
**Code**
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select Multiple Pictures";
op.Multiselect = true;
op.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.jfif, *.png) |
*.jpg; *.jpeg; *.jpe; *.jfif; *.png";
foreach (string imageFile in op.FileNames)
{
Image img = new Image();
img.Source = new BitmapImage(new Uri(imageFile));
img.Height = 100;
img.Width = 100;
img.AllowDrop = true;
Canvas.SetTop(img, y); //Setting up images to the Top position
Canvas.SetLeft(img, x); //Setting up images to the left position
canvasImages.Children.Add(img);
}
private new void MouseLeftButtonDown(object sender,
MouseButtonEventArgs
e)
{
IsDragging = true;
draggedItem = (UIElement)sender;
itemRelativePosition = e.GetPosition(draggedItem);
}
private new void MouseMove(object sender,
System.Windows.Input.MouseEventArgs e)
{
if (!IsDragging)
return;
Point canvasRelativePosition = e.GetPosition(canvasImages);
Canvas.SetTop(draggedItem, canvasRelativePosition.Y -
itemRelativePosition.Y);
Canvas.SetLeft(draggedItem, canvasRelativePosition.X -
itemRelativePosition.X);
}
private new void MouseLeftButtonUp(object sender, MouseButtonEventArgs
e)
{
if (!IsDragging)
return;
IsDragging = false;
}

Ok, a few things wrong here...
1) Your mouse down handler has to be on the image, not the canvas, otherwise your code has no way of knowing which item is being dragged.
2) Once you've clicked on an image the handler should capture the mouse for the canvas so that you get all mouse move messages.
3) The Canvas MouseMove and MouseUp handlers then need to be handled accordingly.
4) The Canvas needs to have a background. If you don't give it a background then it's effectively transparent to the hit-testing and you won't get mouse messages for it. If you don't want it to have a visible background then set it to Transparent.
So your Canvas tag needs to look like this:
<Canvas x:Name="canvasImages" Height="325" Margin="0,0,0,0" Width="430"
HorizontalAlignment="Left" VerticalAlignment="Top" AllowDrop="True"
PreviewMouseLeftButtonUp="CanvasImages_PreviewMouseLeftButtonUp"
PreviewMouseMove="CanvasImages_PreviewMouseMove"
MaxWidth="430" MaxHeight="325"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
Background="Transparent" />
And every image you create needs to have a handler set for it's MouseDown event:
img.MouseLeftButtonDown += Img_MouseLeftButtonDown;
Then it's just a matter is implementing your handlers like this:
private void Img_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.draggedItem = (UIElement)sender;
itemRelativePosition = e.GetPosition(this.draggedItem);
e.Handled = true;
}
private void CanvasImages_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (this.draggedItem == null)
return;
var newPos = e.GetPosition(canvasImages) - itemRelativePosition;
Canvas.SetTop(this.draggedItem, newPos.Y);
Canvas.SetLeft(this.draggedItem, newPos.X);
canvasImages.CaptureMouse();
e.Handled = true;
}
private void CanvasImages_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (this.draggedItem != null)
{
this.draggedItem = null;
canvasImages.ReleaseMouseCapture();
e.Handled = true;
}
}

#Mark, Here are the XAML code and the respective Class for your perusal. Basically I am allowing multiple selection of images by "OpenFileDialog()" and adding those images dynamically to Canvas control as mentioned in my previous code, which then i am failing to drag around those images internally within the Canvas control.
Below is the XAML Code
<Window x:Class="PicturesMovement.CanvasControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Connectlite Clients"
Height="394" Width="445"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<Grid Margin="0,0,2,0" Background="{DynamicResource {x:Static
SystemColors.MenuBarBrushKey}}">
<Button x:Name="Select" Content="Select" HorizontalAlignment="Left"
Height="22" Margin="329,328,0,0" VerticalAlignment="Top" Width="42"
Click="SelectImages"/>
<Button x:Name="Cancel" Content="Cancel" HorizontalAlignment="Left"
Margin="374,328,0,0" VerticalAlignment="Top" Width="49"
Click="closeBox"/>
<Canvas x:Name="canvasImages" Height="325" Margin="0,0,0,0"
Width="430" HorizontalAlignment="Left" VerticalAlignment="Top"
AllowDrop="True" PreviewMouseDown="PreviewMouseDown"
PreviewMouseUp="PreviewMouseUp"
PreviewMouseMove="PreviewMouseMove" MaxWidth="430"
MaxHeight="325"
ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
</Grid>
</Window>
Below are the respective Class that triggers those Mouse Events
public partial class CanvasControl : System.Windows.Window,
System.Windows.Markup.IComponentConnector {
this.canvasImages.PreviewMouseDown += new
System.Windows.Input.MouseButtonEventHandler
(this.PreviewMouseDown);
this.canvasImages.PreviewMouseUp += new
System.Windows.Input.MouseButtonEventHandler
(this.PreviewMouseUp);
this.canvasImages.PreviewMouseMove += new
System.Windows.Input.MouseEventHandler
(this.PreviewMouseMove);
}
Any suggestions will be highly appreciated...Thanks

Related

How To Let A Grid Capture The Mouse, But Still Allow It's Children To Handle Click Events

Sorry in advance if the title is confusing. Here's the situation. I have a grid called grdFilters. This grid has a series of CheckBoxes within it (one per row). Normally this grid is hidden. But I wanted it to show up when prompted (on button click) and leave when the user clicks somewhere other than the grid. To handle outside control mouse clicks I tried first capturing the mouse as such:
AddHandler(Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler(HandleClickOutsideOfControl));
private void HandleClickOutsideOfControl(object sender, MouseButtonEventArgs e)
{
if (this.filters) //Check if the Filters grid is visible
{
ShowHideMenu("sbHideFilters", grdFilters); //Method that hides the grid
Mouse.Capture(null); //Release the mouse
}
}
private void btnFilters_Click(object sender, RoutedEventArgs e)
{
if (!this.filters) //Check if the filters grid is shown
{
ShowHideMenu("sbShowFilters", grdFilters); //Method that reveals the grid
Mouse.Capture(grdFilters); //Capture the mouse
}
}
The problem is that while the Filters grid has captured the mouse, none of the grids children (the Check Boxes) can be clicked on. I would really like to find a way to detect when the mouse is clicked outside of the grid while still allowing the children of the grid to accept mouse down events. Any help would be greatly appreciated, thanks in advance.
As per request here is some of my Xaml:
<Grid>
<Label x:Name="label" Content="Events" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<ScrollViewer HorizontalAlignment="Left" Height="619" Margin="0,26,0,0" VerticalAlignment="Top" Width="450" VerticalScrollBarVisibility="Hidden">
<Grid x:Name="Schedule" HorizontalAlignment="Left" Height="Auto" VerticalAlignment="Top" Width="450" Margin="10,0,0,0"/>
</ScrollViewer>
<Grid x:Name="grdFilters" HorizontalAlignment="Left" Height="619" Margin="490,26,-176,0" VerticalAlignment="Top" Width="135" Background="{StaticResource TransparentBackground}" Panel.ZIndex="95">
<CheckBox x:Name="chckAll" Content="All" HorizontalAlignment="Left" Margin="0,10,0,0" VerticalAlignment="Top" Checked="chckAll_Checked" Unchecked="chckAll_Unchecked"/>
<Grid x:Name="grdFilters" HorizontalAlignment="Left" Height="588" Margin="0,31,0,0" VerticalAlignment="Top" Width="136"/>
</Grid>
<Button x:Name="btnFilters" Content="" Margin="436,223,-18,0" VerticalAlignment="Top" Background="Cyan" Opacity="0.15" Style="{StaticResource MyTabStyle}" Height="80" Click="btnFilters_Click"/>
</Grid>
The only thing I left out were the Resource Dictionaries and the page definition itself.
I think the Mouse.Capture and PreviewMouseDownOutsideCapturedElementEvent and are too specific for what you want.
I would rather use a hitResultsList, which can be used in a lot of different scenarios:
I slightly modified eh AddHandler
AddHandler(Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler(HandleMouseDown));
And I added the VisualTreeHelper.HitTest logic
//List to store all the elements under the cursor
private List<DependencyObject> hitResultsList = new List<DependencyObject>();
private void HandleMouseDown(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((UIElement)sender);
hitResultsList.Clear();
//Retrieving all the elements under the cursor
VisualTreeHelper.HitTest(this, null,
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(pt));
//Testing if the grdFilters is under the cursor
if (!hitResultsList.Contains(this.grdFilters) && grdFilters.Visibility == System.Windows.Visibility.Visible)
{
grdFilters.Visibility = System.Windows.Visibility.Hidden;
}
}
//Necessary callback function
private HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
hitResultsList.Add(result.VisualHit);
return HitTestResultBehavior.Continue;
}
that way you can also remove the Mouse.Capture call from the btnFilters_Click:
private void btnFilters_Click(object sender, RoutedEventArgs e)
{
if (grdFilters.Visibility != System.Windows.Visibility.Visible)
grdFilters.Visibility = System.Windows.Visibility.Visible; }
}

Bind margin inside datatemplate is not updating

Im trying to bind my usercontrols (that have a move on mouse left btn logic inside its code behind) margin inside a datatemplate of an itemscontrol. However when being bound inside the datatemplate it doesnt update itself when a move on the grid behind it (which itself is a usercontrol aswell and has a move on mouse middle button logic inside its code behind).
The code behind of those is: (where in the grid it reacts to middle mouse)
private void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isDragging = true;
var draggableControl = sender as UserControl;
lasttransform = new Point(0, 0);
clickPosition = e.GetPosition(this.Parent as UIElement);
draggableControl.CaptureMouse();
}
private void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging == true)
{
isDragging = false;
var draggable = sender as UserControl;
draggable.ReleaseMouseCapture();
completetransform = Point.Add(completetransform,new Vector( lasttransform.X, lasttransform.Y));
lasttransform = new Point(0, 0);
}
}
private void Control_MouseMove(object sender, MouseEventArgs e)
{
var draggableControl = sender as UserControl;
if (isDragging && draggableControl != null && draggableControl.GetType() == typeof(BaseNode))
{
Point currentPosition = e.GetPosition(this.Parent as UIElement);
currentPosition = new Point(currentPosition.X, currentPosition.Y);
var transform = draggableControl.RenderTransform as TranslateTransform;
if (transform == null)
{
transform = new TranslateTransform();
draggableControl.RenderTransform = transform;
}
transform.X = completetransform.X + (currentPosition.X - clickPosition.X);
transform.Y = completetransform.Y + (currentPosition.Y - clickPosition.Y);
lasttransform = new Point((currentPosition.X - clickPosition.X), (currentPosition.Y - clickPosition.Y));
}
}
}
When i now want to show my controls in some window like:
<Grid>
<my:EventGrid x:Name="XDisplayedEventGrid" Margin="-20" DataContext="{Binding DisplayedEventGrid}"/>
<Grid Background="Red" Width="100" Height="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="{Binding DisplayedEventGrid.ActualTransform, Converter={StaticResource ResourceKey=PointToMarginConverter}}"/>
<my:BaseNode HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Height="100" Margin="{Binding DisplayedEventGrid.ActualTransform, Converter={StaticResource ResourceKey=PointToMarginConverter}}"/>
<ItemsControl ItemsSource="{Binding DisplayedNodes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:BaseNode HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Height="100" Margin="{Binding ElementName=XDisplayedEventGrid, Path=ActualTransform, Converter={StaticResource ResourceKey=PointToMarginConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Where 'my:EventGrid 'is the movable grid, 'my:BaseNode' is a control to test everything and DataTemplate> my:BaseNode .../> /DataTemplate> is the actual displaying control for these usercontrols.
What should happen:
1) When you move the grid behind the visual brush makes it look like you can scroll unlimitedly.
2) The 'Grid Background="Red"' stays at its place on the eventgrid.
3) The Margin is bound to the eventgrid's moved coordinates which is translated to a margin by a converter, so for the user it looks like the controls stay on place on the grid and get moved out of view if the user looks at another portion of the grid.
4) When a node is moved its RenderTransform is set so it can be placed by and bound to a margin while being able to reposition it locally with the RenderTransform.
My Problem now is, that when the test-node has no DataContext bound it has the behavior of the grid and moves accordingly, but if it has one set it acts like the node inside the datatemplate and stays at 0,0 of the window.
The Datacontext of the node is an empty class deriving of a standard implementation of INotifyPropertyChanged.
When not having a datacontext bound inside the datatemplate it still has the same wrong behavior.
Where is my error and
are there any better controls to store externally marged usercontrols?
Thanks
ShinuSha

How to remove line on tapped point inside canvas in Windows Phone 8?

Im trying to draw a line with finger on windows phone canvas, i Done this but when i try to remove/clear that line it's Removing all Elements in that Canvas ,
ContentPanelCanvas.Children.Clear();
Hear is My Complete Code:
XAML
<Canvas x:Name="ContentPanelCanvas" Grid.Row="1" Background="Transparent" Margin="12,0,12,0">
<Grid Width="450" Height="600">
<Button x:Name="clickMeBtn" Height="72"
Width="200"
Content="Click Me"
HorizontalAlignment="Center"
VerticalAlignment="Center" Click="clickMeBtn_Click" />
</Grid>
</Canvas>
C# Code is Like this :
public MainPage()
{
InitializeComponent();
this.ContentPanelCanvas.MouseMove += new MouseEventHandler(ContentPanelCanvas_MouseMove);
this.ContentPanelCanvas.MouseLeftButtonDown += new MouseButtonEventHandler(ContentPanelCanvas_MouseLeftButtonDown);
}
void ContentPanelCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
currentPoint = e.GetPosition(ContentPanelCanvas);
oldPoint = currentPoint;
}
void ContentPanelCanvas_MouseMove(object sender, MouseEventArgs e)
{
currentPoint = e.GetPosition(this.ContentPanelCanvas);
line = new Line() { X1 = currentPoint.X, Y1 = currentPoint.Y, X2 = oldPoint.X, Y2 = oldPoint.Y };
line.Stroke = new SolidColorBrush(Colors.White);
line.StrokeThickness = 10;
this.ContentPanelCanvas.Children.Add(line);
oldPoint = currentPoint;
}
private void clickMeBtn_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello Every One ");
ContentPanelCanvas.Children.Clear();
}
Code is Executing Without Error , But it's Removing button to , How can i Remove Only Lines I Draw
Well, the code is doing exactly what you wrote, it is clearing all children of the ContentPanelCanvas. If you only want to remove the lines, then only remove the lines:
foreach (var line in ContentPanelCanvas.Children.OfType<Line>().ToList())
ContentPanelCanvas.Children.Remove(line);
You can use
ContentPanelCanvas.Children.Remove(Line);
or remove children by index
ContentPanelCanvas.Children.RemoveAt(1);
If you clear the canvas it will "Removes all elements from a UIElementCollection."
You should create an event handler with line for example tap event handler , once tap you should hold the line control in another control to remove, then in button click handler remove it like this
ContentPanelCanvas.Children.Remove(controlToDelete);
controlToDelete is the control you hold to delete from the line tap handler i.e line control

Create WPF ellipse from C# code and move it by mouse

I have a canvas, and I need to put an (for example) ellipse on it, not from XAML but from code. So I do
Ellipse e1;
public MainWindow()
{
...
...
e1 = new Ellipse();
e1.Height = 100;
e1.Width = 100;
e1.Stroke = Brushes.Red;
e1.StrokeThickness = 5;
Canvas.SetLeft(e1,40);
Canvas.SetTop(e1,50);
e1.MouseDown += ellipse_MouseDown;
Canvas1.Children.Add(e1);
}
private void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
Ellipse el = (Ellipse)sender;
el.Stroke = Brushes.Green;
buttonAdd.Content = "New_TEXT";
}
But it doesn't react on clicking. Anyway, I tried to add this ellipse_MouseDownmethod to ellipse that was created from XAML - and it works.
<Canvas x:Name="Canvas1" HorizontalAlignment="Left" Height="421" Margin="10,10,0,0" VerticalAlignment="Top" Width="346">
<Ellipse x:Name="ellipse" Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="111" Margin="117,152,0,0" Stroke="Black" VerticalAlignment="Top" Width="131" MouseDown="ellipse_MouseDown" MouseMove="ellipse_MouseMove" MouseUp="ellipse_MouseUp"/>
</Canvas>
Where can be a problem?
UPD.
According to Rohit Vats's answer just add
e1.Fill = Brushes.Transparent;
or
e1.Fill = new SolidColorBrush((Color)ColorConverter
.ConvertFromString("#FFF4F4F5"));
'cause by default Fill is null which doesn't respond to mouse events
You need to set Fill to Transparent so that it can react to mouse events. By default Fill is null which doesn't respond to mouse events -
e1.Stroke = Brushes.Red;
e1.Fill = Brushes.Transparent; <-- HERE
UPDATE
As evident from XAML code, you are setting Fill to #FFF4F4F5 but not setting it from code behind.
e1.Fill = new SolidColorBrush((Color)ColorConverter
.ConvertFromString("#FFF4F4F5"));

Finding Child Objects in ViewBox/Canvas Object

I've implemented a drag and drop that is mostly working in my Silverlight 4 app. Once the users have dropped the shapes onto the canvas, I wanted to use Size & Child Decorators. I've tried to implement the sample code. Code below is problem section of much larger app.
xmal -
<ScrollViewer Grid.RowSpan="1" Grid.Row="2" Grid.ColumnSpan="2" Grid.Column="2" Name="scrollViewer">
<Viewbox Margin="0" MinWidth="400" MinHeight="500"
HorizontalAlignment="Left" VerticalAlignment="Top" Name="ViewBoxTestBuild">
<Canvas x:Name="Camera1Canvas" telerikDragDrop:RadDragAndDropManager.AllowDrop="True"
Width="1200" Height="768" MouseLeftButtonDown="Camera1Canvas_MouseLeftButtonDown">
<Image x:Name="Camera1Image" Source="timemagadj.jpg" Canvas.ZIndex="-1"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<local:Three_Line_Graphic x:Name="threeLineEditTool"
HorizontalAlignment="Left" Canvas.Left="594" Canvas.Top="621" />
<l:Adorner x:Name="adorn" Canvas.ZIndex="100" />
</Canvas>
</Viewbox>
</ScrollViewer>
c# code -
private void Camera1Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sendingObj = sender as Canvas;
if (sendingObj == null) return;
foreach (UserControl l in VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), sendingObj))
{
if (l.Parent == Camera1Canvas )
{
adorn.AdornedElement = l as FrameworkElement;
adorn.adorned_MouseLeftButtonDown(l, e);
break;
}
}
base.OnMouseLeftButtonDown(e);
}
My problem is that when VisualTreeHelper.FindElementsInHostCoordinates is called on left mouse click event, it returns no elements when I click on any object. I sure that it is a coordinate mapping issue, but as this is new ground for me, I'm unsure how to fix it.
Maybe in your case there is no need to use the VisualTreeHelper method.
Since you control the elements inside the canvas and you know their type, you can try something like this:
private void Camera1Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sendingObj = sender as Canvas;
if (sendingObj == null) return;
foreach (UserControl l in sendingObj.Children)
{
var position = e.GetPosition(l);
var lArea = new Rect(0,0,l.ActualWidth,l.ActualHeight);
if (lArea.Contains(position))
{
adorn.AdornedElement = l as FrameworkElement;
adorn.adorned_MouseLeftButtonDown(l, e);
break;
}
}
base.OnMouseLeftButtonDown(e);
}
Which is more efficient than hit-testing. However, this only works if rectangular areas are acceptable.
If you need to detect shapes other than rectangles, you can use the following, as long as you are filling a canvas with instances of UserControl:
private void Camera1Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sendingObj = sender as Canvas;
if (sendingObj == null) return;
var elements = VisualTreeHelper
.FindElementsInHostCoordinates(
e.GetPosition(sendingObj), sendingObj);
foreach (var l in elements)
{
if (l is UserControl)
{
adorn.AdornedElement = l as FrameworkElement;
adorn.adorned_MouseLeftButtonDown(l, e);
break;
}
}
base.OnMouseLeftButtonDown(e);
}
You need to change your VisualTreeHelper line to
GeneralTransform transform = sendingObj.TransformToVisual(Application.Current.RootVisual);
Point pnt = transform.Transform(e.GetPosition(sendingObj));
var elements = VisualTreeHelper.FindElementsInHostCoordinates(pnt,Application.Current.RootVisual);

Categories