I'm looking for a way to let the user of my program move a button (which as an image as background, and is the content of a Grid-Children) to another Grid-Children by drag-and-drop.
My Buttons are created by the program itself based on a database in a for-iteration:
foreach (PC_Infos item in allPcsOnThisFloor)
{
if (item != null)
{
roomToCoordinates(allPcsOnThisFloor[i].Room,out column, out row);
Button btn = new Button();
btn.Name = Name+ "_Button";
btn.Background = new ImageBrush(my icon here);
btn.Click += btn_Click;
btn.SetValue(Grid.ColumnProperty, column);
btn.SetValue(Grid.RowProperty, row);
My_Grid.Children.Add (btn);
i++;
}
else
break;
}
PC_Infos
is just a class holding the Properties: PC_Name, Room and MAC_adress nothing else.
My Grid:
<Grid x:Name="Grid_10" AllowDrop="True">
<Grid.Background>
<ImageBrush ImageSource="C:\Users\XXX\XXX\buildingPlan.png"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition />
[...]
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
[...]
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
Now is there a way to use a btn.*
which fetches the current position (Row and Column) when dragged, and another btn.*
which creates a new copy of this button in the Grid-Child the mouse is currently over when the mouse drops the button?
Or is there even a way to 'move' the button from one Child to another?
BTW: My Program should show a building plan with PCs as icons(buttons) on it at the place they actually are. A rough guess is takes by the source code by reading the column "Room" in my db. But a room isn't just one place. Someone works at the window another on at the door, this should be marked by user (with drag&drop) I'm using a WPF-Application in VS'13 with C# and .Net 4.5.1.
PS: If there is anybody who says: Why Grid and why the hell buttons? :D : When the User clicks on the icon a new window with system value such as OS, RAM, CPU... will show up. But I'm open for any implementation. I haven't done programming for so long now, so I may not know the best way.
I may misunderstand your problem but as far as I can see, there no drag'n'drop here : you want to move UIElements
all around your Panel
.
First, I would recommand using a Canvas
to display your items rather than a Grid
as it will offer much more flexibility.
Let's say you have this class as your main object :
public class PC_Infos
{
public string PC_Name;
public string Room;
public string MAC_Adress;
}
Rather than building controls in code-behind, I prefer a simple wrapper like :
public class DragablePC : INotifyPropertyChanged
{
public DragablePC(PC_Infos pc, double x = 0d, double y = 0d)
{
_pcItem = pc;
PcRow = y;
PcColumn = x;
}
private double _x;
public double PcColumn
{
get { return _x; }
set
{
if (value == _x) return;
_x = value;
RaisePropertyChanged("PcColumn");
}
}
private double _y;
public double PcRow
{
get { return _y; }
set
{
if (value == _y) return;
_y = value;
RaisePropertyChanged("PcRow");
}
}
PC_Infos _pcItem;
public DragablePC(PC_Infos pc, double x = 0d, double y = 0d)
{
_pcItem = pc;
PcRow = y;
PcColumn = x;
}
public override string ToString() { return _pcItem.PC_Name; }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
This wrapper simply stores X/Y coordinates and your item. It allows to define a binding source as follows :
AllPCS = allPcsOnThisFloor.Select(p => new DragablePC(p));
Now, you can use an ItemsControl
to display your items. As stated above, I will use a Canvas
for the ItemsPanel
and define a simple ItemContainerStyle
to place them onto it.
<ItemsControl Name="DDPanel" ItemsSource="{Binding Path=AllPCS}"
MouseMove="DDPanel_MouseMove">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" Background="LightGray"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=PcRow}"/>
<Setter Property="Canvas.Left" Value="{Binding Path=PcColumn}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type dnd:DragablePC}">
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="6" Padding="6, 12"
Background="White" MinWidth="70"
MouseLeftButtonDown="Border_MouseLeftButtonDown"
MouseLeftButtonUp="Border_MouseLeftButtonUp">
<TextBlock Text="{Binding}" HorizontalAlignment="Center"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ItemTemplate
has to be updated to suit your needs, most important part being the handling of mouse events.
Here are the handlers I've added :
DragablePC _selected = null;
Point cursorPos = new Point();
private void DDPanel_MouseMove(object sender, MouseEventArgs e)
{
if (null == _selected) return;
Point newPosition = e.GetPosition(DDPanel);
_selected.PcColumn += newPosition.X - cursorPos.X;
_selected.PcRow += newPosition.Y - cursorPos.Y;
cursorPos = newPosition;
}
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var uiItem = sender as FrameworkElement;
var dpc = uiItem.DataContext as DragablePC;
if (dpc == null) return;
cursorPos = e.GetPosition(DDPanel);
_selected = dpc;
}
private void Border_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var uiItem = sender as FrameworkElement;
var dpc = uiItem.DataContext as DragablePC;
if (dpc == null) return;
_selected = null;
}
This now needs a lot of tweaking such as handling MouseLeave
event, adding some logic to the positionning... but you can now move your PC all around.
Hope this helps.