Search code examples
c#wpfbuttondrag-and-dropgrid

Drag and Drop content from one grid-child to another


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.

While I'm not allowed to post images with 0 Rep, here is a Link to an img which shows my Grid over my buildingplan http://alexander.valerius.wilhelm-gym.net/Grid.PNG


Solution

  • 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.