Drag/Drop Button in same application throwing an exception - wpf

I have a Canvas that contains a button which I want to be able to drag and drop into another canvas. I want to copy the button to the other Canvas. Here is the code I am using:
The XAML:
<Window>
<Grid>
<Canvas
Height="300"
Width="500"
Background="Gray">
<Canvas
Name="cnvToolBox"
Canvas.Left="10"
Canvas.Top="10"
Background="AliceBlue"
Width="100"
Height="200">
<Button
Content="Drag Me!"
PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
PreviewMouseMove="Button_PreviewMouseMove"></Button>
</Canvas>
<Rectangle
Canvas.Left="119"
Canvas.Top="9"
Width="102"
Height="202"
StrokeDashArray="0.5 1.0 0.3"
Stroke="Black"
StrokeThickness="2"/>
<Canvas
Name="cnvButtonDropZone"
Canvas.Left="120"
Canvas.Top="10"
Width="100"
Height="200"
Background="LightGreen"
AllowDrop="True"
DragEnter="Canvas_DragEnter"
Drop="Canvas_Drop">
</Canvas>
</Canvas>
</Grid>
</Window>
Here's the Code Behind:
public partial class MainWindow : Window
{
private Point startPoint;
public MainWindow()
{
InitializeComponent();
}
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(null);
}
private void Button_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point currentPosition = e.GetPosition(null);
Vector diff = startPoint - currentPosition;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
Button button = sender as Button;
DataObject dragData = new DataObject("myFormat", button);
DragDrop.DoDragDrop(button, dragData, DragDropEffects.Copy);
}
}
private void Canvas_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("myFormat") || sender == e.Source)
{
e.Effects = DragDropEffects.None;
}
}
private void Canvas_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("myFormat"))
{
Button button = e.Data.GetData("myFormat") as Button;
Canvas canvas = sender as Canvas;
canvas.Children.Add(button);
}
}
}
When I drop the button I get the following exception when I'm adding the button to the canvas:
Specified element is already the logical child of another element. Disconnect it first.
I'm just trying to learn how to drag and drop controls and not really sure what that error means and how to resolve it. I don't know where I'm going wrong. Any suggestions would be welcome.
Thanks!

The button is owned by its parent cnvToolBox. You need to remove it from cnvToolBox before adding it to the canvas.
cnvToolBox.Children.Remove(button);
var canvas = sender as Canvas;
canvas.Children.Add(button);
This moves the button from your toolbox to the canvas. If you actually want to clone the item you want something like:
if (e.Data.GetDataPresent("myFormat"))
{
var contentControl = (ContentControl)e.Data.GetData("myFormat");
var constructorInfo = contentControl.GetType().GetConstructor(new Type[] {});
if (constructorInfo != null)
{
var newElement = (UIElement)constructorInfo.Invoke(new object[]{});
var newContentControl = newElement as ContentControl;
if(newContentControl != null)
{
newContentControl.Content = contentControl.Content;
}
((Panel)sender).Children.Add(newElement);
}
}

It's because the Button already has a parent associated with it; the previous Canvas.
You can set the parent of the Button to null; which will essentially remove it from the logical relationship.
button.Parent = null;
You will then be able to add that Button to another Canvas as you have done in your code behind.
You can also remove the Button from the Children property directly if you prefer and then add it accordingly within the new Canvas.
Canvas.Children.Remove(button);

Related

Draggable and clickable button

I'm trying to make button.
If user clicked/taped on it then click event fires.
If user pressed and hold and moved then drag window.
<DockPanel>
<Grid DockPanel.Dock="Top" x:Name="grid" PreviewMouseMove="Grid_PreviewMouseMove">
<Button Content="Button" Width="100" Height="50"
PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"/>
</Grid>
<ListBox x:Name="logListBox" Width="200" Height="200"/>
</DockPanel>
public partial class MainWindow : Window
{
private Point _startMousePosition;
public MainWindow()
{
InitializeComponent();
}
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
logListBox.Items.Add("Left button event.");
_startMousePosition = e.GetPosition(grid);
}
private void Grid_PreviewMouseMove(object sender, MouseEventArgs e)
{
var currentMousePosition = e.GetPosition(grid);
var isMouseMoving = _startMousePosition.X != currentMousePosition.X ||
_startMousePosition.Y != currentMousePosition.Y;
if (e.LeftButton == MouseButtonState.Pressed && isMouseMoving)
{
DragMove();
logListBox.Items.Add("Drag window.");
}
}
}
But left button event fires in case of dragging (press and hold) and in case of single click (press and release).
Screenshot
How can i separate it?
How is it implemented usually?

WPF Drop event is firing twice

2nd Edit
so removing the Panel.ZIndex properties from the control template resolved this issue for me, giving me 1 drop event.
including them triggers two drop events.
can any one answer me why though?
id love to know why z index ?
Original Question :
I am trying to add a custom object (state) to a canvas called MainCanvas on the MainWindow.
I am trying to drag a state object from a wrap panel and drop it onto the canvas.
the code works but there are two items being added.
I know there are two because I can move the two item around the canvas.
I have searched existing answers and added e.Handled=true, but still adds two items
I tried using Drop event and PreviewDrop Event on MainCanvas, no difference.
Cam someone help as to how I can make it so that only 1 item gets added?
the maincanvas exists at design time
a new state is created at runtime at the drop event.
Here is the OnMouseMove handler for the state
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
var parent = VisualTreeHelper.GetParent(this);
if (parent as WrapPanel != null)
{
DataObject dragData = new DataObject();
dragData.SetData(DataFormats.StringFormat, this.ItemType);
DragDrop.DoDragDrop(this, dragData, DragDropEffects.Copy);
}
}
e.Handled = true;
}
Within the Code Behind I have set the following events for the canvas:
private void MainCanvas_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
e.Effects = DragDropEffects.Copy;
else
e.Effects = DragDropEffects.None;
e.Handled = true;
}
private void MainCanvas_Drop(object sender, DragEventArgs e)
{
var itemType = e.Data.GetData(typeof(string));
switch (itemType)
{
case "state":
var pos = e.GetPosition(this.MainCanvas);
State item = new State();
item.Template = (ControlTemplate)FindResource("StateViewModelControlTemplate");
this.MainCanvas.Children.Add(item);
Canvas.SetLeft(item, pos.X);
Canvas.SetTop(item, pos.Y);
e.Handled = true;
break;
default:
break;
}
e.Handled = true;
}
Finally here is the xaml for the Main Canvas
<Canvas x:Name="MainCanvas" x:Name="MainCanvas"
DockPanel.Dock="Top"
Background="#666"
Height="600"
Margin="4"
AllowDrop="True"
DragEnter="MainCanvas_DragEnter"
Drop="MainCanvas_Drop"/>
Edit:
ok so after lupus' response i went back and reconstructed everything from scratch in a separate temp project
<ControlTemplate x:Key="StateViewModelControlTemplate" TargetType="{x:Type vm:State}">
<Grid Width="100" Height="60">
<!--
If I comment out the following Thumb
the drop event will only trigger once
If i leave it in then it triggers twice
Move Thumb is derived from thumb
-->
<local:MoveThumb Panel.ZIndex="99"
x:Name="StateViewModelMoveThumb"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
Opacity="0"/>
<Border Panel.ZIndex="98"
Margin="4"
Padding="4"
BorderBrush="white"
BorderThickness="2"
CornerRadius="5">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF59C7D4" Offset="0.5"/>
<GradientStop Color="#FF075A64" Offset="0"/>
<GradientStop Color="#FF00626E" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding StateName}"/>
</Border>
</Grid>
</ControlTemplate>
By the way what I trying to do is very loosely based on the following article: https://www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part-1
I tried your code, and I cannot reproduce your issue. Changes:
private void MainCanvas_Drop(object sender, DragEventArgs e)
{
var itemType = e.Data.GetData(typeof(string));
switch (itemType)
{
var pos = e.GetPosition(this.MainCanvas);
Border item = new Border()
{
Width = 10,
Height = 10,
Background = Brushes.Red
};
//item.Template = (ControlTemplate)FindResource("StateViewModelControlTemplate");
this.MainCanvas.Children.Add(item);
...
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
DataObject dragData = new DataObject();
dragData.SetData(DataFormats.StringFormat, "state");
DragDrop.DoDragDrop(this, dragData, DragDropEffects.Copy);
}
e.Handled = true;
}
and in xaml,
<Window ...
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Canvas x:Name="MainCanvas" Grid.Column="0"
DockPanel.Dock="Top"
Background="#666"
Height="600"
Margin="4"
AllowDrop="True"
DragEnter="MainCanvas_DragEnter"
Drop="MainCanvas_Drop"/>
<Grid Grid.Column="1" Background="Red"/>
</Grid>
Have not touch the other piece of code. Everytime I drag from the Grid to the Canvas, I get one Border item at the drop position.
So maybe the problem is not in the code you posted, but in the code left out or commented.
So I eventually got this working and here is how
Here is the front end xaml on the mainwindow
<cc:DiagramCanvas x:Name="MainCanvas"
DockPanel.Dock="Top"
Margin="0"
MinHeight="450"
AllowDrop="True" Background="White">
</cc:DiagramCanvas>
Here is the custom canvas object and the drag drop handler
public class DiagramCanvas : Canvas
{
public DiagramCanvas()
{
this.Drop += DoDrop;
this.DragEnter += MainCanvas_DragEnter;
}
readonly MainWindow mainWin = (MainWindow)Application.Current.MainWindow;
#region works dont touch
public void DoDrop(object sender, DragEventArgs e)
{
var mainWin = (MainWindow)App.Current.MainWindow;
DragDropHandler<StateVM>.Instance.Drop(sender, e);
}
private void MainCanvas_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
e.Effects = DragDropEffects.Copy;
else
e.Effects = DragDropEffects.None;
e.Handled = true;
}
#endregion
//other code here
}
Here is the drag drop handler singleton
public sealed class DragDropHandler<T> where T : Control
{
#region singleton
private static DragDropHandler<T> instance = null;
private static readonly object padlock = new object();
DragDropHandler()
{
}
public static DragDropHandler<T> Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new DragDropHandler<T>();
}
return instance;
}
}
private set
{
instance = value;
}
}
#endregion
public static bool IsDragging { get; set; }
public static WrapPanel AllowedDragSource { get; set; }
readonly MainWindow mainWin = (MainWindow)Application.Current.MainWindow;
public static void CreateInstance(WrapPanel allowedSource)
{
if (DragDropHandler<T>.IsDragging == false)
{
instance = new DragDropHandler<T>();
DragDropHandler<T>.AllowedDragSource = allowedSource;
}
}
public void Drag(object sender, MouseEventArgs e)
{
if (sender as T == null
|| mainWin.Radio_EditStates.IsChecked == false
|| e.LeftButton != MouseButtonState.Pressed
|| IsDragging == true)
{
e.Handled = true;
return;
}
var item = (T)sender;
if (Control.ReferenceEquals(item.Parent, AllowedDragSource) == false)
{
e.Handled = true;
return;
}
IsDragging = true;
DragDrop.DoDragDrop(((StateVM)sender), new DataObject(((StateVM)sender)), DragDropEffects.Copy);
IsDragging = false;
e.Handled = true;
}
public void Drop(object sender, DragEventArgs e)
{
var mainWin = (MainWindow)App.Current.MainWindow;
if (IsDragging)
{
//TODO: Switch here to handle different shapes
var pos = e.GetPosition(mainWin.MainCanvas);
//
Canvas.SetLeft(item, pos.X.RoundDownTo10());
Canvas.SetTop(item, pos.Y.RoundDownTo10());
//update main win observalbe collections to inclue the item dropped
IsDragging = false;
e.Handled = true;
DestroyInstance();
}
}
private static void DestroyInstance()
{
DragDropHandler<T>.Instance = null;
}
}
Here is the on mouse move code for the item you are dragging
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed && Control.ReferenceEquals(this.Parent, mainWin.libraryContainer))
{
DragDropHandler<StateVM>.CreateInstance(mainWin.libraryContainer);
if (DragDropHandler<StateVM>.Instance != null)
{
DragDropHandler<StateVM>.Instance.Drag(this, e);
}
}
}

WPF Popup wrapping when dragging vertically

I have a project that uses a System.Windows.Controls.Primatives.Popup to drag a 'tooltip' like control along with a mouse.
Whenever the drag crosses a horizontal line the popup 'wraps' to the bottom of the screen - despite having sane values for the VerticalOffset. The point at which this wrapping occurs appears to be tied to the HEIGHT of the window, but not it's position.
Here's the code from the sandbox project I have created that also exhibits the same behavior:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MainGrid.MouseDown += Grid_MouseDown;
this.MainGrid.MouseUp += Grid_MouseUp;
this.MainGrid.MouseMove += (s, e) => { if (this.Popup.IsOpen) { Popup_Drag(s, e); } };
this.Popup.MouseMove += Popup_Drag;
}
private void Popup_Drag(object sender, MouseEventArgs e)
{
Popup.HorizontalOffset = e.GetPosition(this.Popup).X;
Popup.VerticalOffset = e.GetPosition(this.Popup).Y;
this.Status_Top.Text = String.Format("Height/Top: {0}/{1} Width/Left: {2}/{3}", this.Height, this.Top, this.Width, this.Left);
this.Status.Text = String.Format("Vertical Offset: {0} Horizontal Offset: {1}", Popup.VerticalOffset, Popup.HorizontalOffset);
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
this.Popup.IsOpen = false;
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
this.Popup.IsOpen = true;
Popup_Drag(sender, e);
}
}
And the Window XAML:
<Window x:Class="WpfSandbox.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 x:Name="MainGrid" Background="Purple">
<TextBlock x:Name="Status_Top"></TextBlock>
<Popup x:Name="Popup" Cursor="Hand" HorizontalAlignment="Left"
VerticalAlignment="Bottom" IsOpen="True">
<TextBlock Background="Blue" Foreground="White">
<TextBlock x:Name="Status">TEXT</TextBlock></TextBlock>
</Popup>
</Grid>
</Window>
I was able to fix this by adding Placement="RelativePoint" to the Popup attributes. Apparently this is the default in Silverlight, but not WPF.

Get owner of context menu on Button

Title just about says it all.I have a lot of buttons, all of them have the same context menu, I want to determine from the click event which button was rgiht clicked to get there.
This code does not work, placementTarget is null:
private void mi_Click(object sender, RoutedEventArgs e)
{
Button contextMenuEzen = null;
MenuItem mnu = sender as MenuItem;
if (mnu != null)
{
ContextMenu ize =(ContextMenu)mnu.Parent;
contextMenuEzen = ize.PlacementTarget as Button;
}
}
Please help me!
For me, this example works:
XAML
<Window.Resources>
<!-- For all MenuItems set the handler -->
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MenuItem_Click" />
</Style>
<!-- One ContextMenu for all buttons (resource) -->
<ContextMenu x:Key="MyContextMenu">
<MenuItem Header="Click this" />
<MenuItem Header="Two" />
<MenuItem Header="Three" />
</ContextMenu>
</Window.Resources>
<Grid>
<Button x:Name="MyButton1" Width="100" Height="30" Content="MyButton1" ContextMenu="{StaticResource MyContextMenu}" />
<Button x:Name="MyButton2" Margin="0,110,0,0" Width="100" Height="30" Content="MyButton2" ContextMenu="{StaticResource MyContextMenu}" />
</Grid>
Code behind
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mnu = sender as MenuItem;
Button MyButton = null;
if (mnu != null)
{
ContextMenu MyContextMenu = (ContextMenu)mnu.Parent;
MyButton = MyContextMenu.PlacementTarget as Button;
}
MessageBox.Show(MyButton.Content.ToString());
}
RoutedEvents don't work exactly like regular events - the signature of the handler is EventHandler(object sender, RoutedEventArgs e). The RoutedEventArgs has a property called OriginalSource that represents the element in your UI that was actually clicked. In contrast, the sender parameter will always be the object on which the event handler is registered.
use the ContextMenuService to get the placement target as in the following example:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += (sender, args) => {
RoutedEventHandler clickHandler = delegate(object o, RoutedEventArgs eventArgs) {
var mi = (MenuItem) o;
var contextMenu = (ContextMenu) mi.Parent;
var pTarget = ContextMenuService.GetPlacementTarget(contextMenu) as Button;
// just to make sure
if (pTarget == null) {
return;
}
string message = "You clicked on the button " + pTarget.Content;
MessageBox.Show(message);
};
// create a single instance of the ContextMenu
var cm = new ContextMenu();
for (int i = 0; i < 10; i++) {
var mi = new MenuItem {Header = "Item " + i};
mi.Click += clickHandler;
cm.Items.Add(mi);
}
// create a set of buttons and assign them to the RootVisual(StackPanel)
for (int i = 0; i < 5; i++) {
var button = new Button {Content = "Button " + i, ContextMenu = cm};
this.RootVisual.Children.Add(button);
}
};
}
}

WPF Not sending MouseMove events after CaptureMouse();

I'm trying to have a WPF canvas with rounded rectangles on that I can drag round using the mouse. However once I try and capture the mouse on the canvas I don't get the move events any more.
This is a "mycanvas" user control and the rectangles are "foo" user controls. The XAML for these (minus the preamble) are:
mycanvas.xaml:
<Canvas MouseDown="CanvasMouseDown" MouseMove="CanvasMouseMove" MouseUp="CanvasMouseUp" Background="White">
<my:Foo HorizontalAlignment="Left" Canvas.Left="97" Canvas.Top="30" x:Name="m_foo" VerticalAlignment="Top" Height="87" Width="128" />
</Canvas>
foo.xaml:
<Border BorderThickness="2" BorderBrush="Black" CornerRadius="15" Background="Plum">
<Grid>
<Label Content="Foo" Height="28" HorizontalAlignment="Left" Margin="6,6,0,0" Name="label1" VerticalAlignment="Top" />
</Grid>
</Border>
And then the handlers are:
mycanvas.xaml.cs:
private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is Foo)
{
m_moving = e.Source as Foo;
CaptureMouse();
e.Handled = true;
}
}
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
if (m_moving != null)
{
Canvas.SetLeft(m_moving, e.GetPosition(this).X);
Canvas.SetTop(m_moving, e.GetPosition(this).Y);
}
}
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
{
ReleaseMouseCapture();
m_moving = null;
}
The MouseDown fires and so the CaptureMouse gets called (and works because I can no longer close the app or click anything else in it!) but the MouseMove never gets called anymore - so where do the MouseMove events get sent now???
If I alt-tab to another application and then go back now suddendly the MouseMove is called and the Foo moves with the mouse.
Try either:
Mouse.Capture(this, CaptureMode.SubTree);
or
m_moving.CaptureMouse();
...
if (m_moving != null)
{
m_moving.ReleaseMouseCapture();
m_moving = null;
}
The mouse events were being raised by the Foo, not by the Canvas, so when you capture the mouse with the Canvas you prevent them from being raised.
You can directly use the MouseMove event on the Window:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MouseMove += MouseEventHandler;
}
private void MouseEventHandler(Object sender, MouseEventArgs e)
{
System.Windows.Point position = e.GetPosition(this);
Canvas.SetLeft(ElipseElement, position.X-5);
Canvas.SetTop(ElipseElement, position.Y-5);
}
}

Resources