This is a multipart series outlining several topics is WPF
Drag and Drop
MSDN has a section called Drag and Drop Overview but as far as I can tell, it only outlines what to do with the data you are moving around. You can also get more D&D info from:- Jaime Rodriguez - Drag and Drop in WPF Explained End to End Part 1 Part 2 Part 3
- Bea Stollnitz - How can I drag and drop items between data bound ItemsControls?
- Clarity Consulting - WPF Drag and Drop Between ItemsControls
- Code Project - WPF Drag-and-Drop Smorgasbord
- Marlon Grech - Drag Drop using Listboxes Part 1 Part 2
- Chrisitan Mosers - Drag and Drop in WPF
- Determine if you are initiating a drag/drop operation either as a combination of MouseLeftButtonDown and MouseMove or just MouseLeftButtonDown as I have done.
- Determine what data is to be dragged and store that in a DataObject by passing in a format and the data to the constructor. See MSDN's Drag and Drop Overview for more information regarding DataObject.
- Add adorner: If you want to provide rich visual feedback during the drag operation, adding an adorner can be done at this stage.
- Call the static DragDrop.DoDragDrop() passing in the source of the drag (a ListBoxItem in this case), the actual data to be dragged and an appropriate DragDropEffects enumeration [see MSDN].
Note: Steps 1-4 can all be done in the MouseLeftButtonDown handler as I have outlined in the demo. - Set the AllowDrop property on elements that you want to be able to drop on. In this case, I made both the source and target ListBoxs droppable. The property is set in the MainWindow.xaml file.
- Handle either the DragEnter or DragOver event. Test in this handler if you are passing over an element that has it's AllowDrop property set and that is accepts the type of data that is being dragged. Do this check by calling the GetDataPresent() method on the DragEventArgs parameter.
- Update Adorner: If you are using a custom adorner, this is a good place to update its position on the screen as it is dragged around.
- Finally, handle the Drop event when the user releases the mouse button. If the drop is done on a target that is expecting it and the correct data format is used, then retrieve the dragged DataObject and do whatever you need to with it at this point. You get the data by accessing the Data property on the DragEventArgs parameter and calling the GetData() method.
- Remove Adorner: If you are using a custom adorner, once the drop is complete, this is a good place to remove it.
Let's take a closer look at each of the main points in the drag and drop operation. Listing 1 shows the markup with the ListBox elements and the associated events that necessary for D&D. Notice the use of the EventToCommand behavior here.
36 <ListBox x:Name="dragSource"37 Grid.Row="1"38 MinWidth="100"39 MinHeight="40"40 Margin="0,0,0,0"41 AllowDrop="True"42 Background="AliceBlue"43 ItemContainerStyleSelector="{DynamicResource styleSelector}"44 ItemsPanel="{DynamicResource SourceListBoxItemsPanelTemplate}"45 ItemsSource="{Binding Contacts}"46 ItemTemplateSelector="{DynamicResource dataTemplateSelector}"47 Style="{DynamicResource ListBoxStyle1}">48 <i:Interaction.Triggers>49 <i:EventTrigger EventName="PreviewMouseLeftButtonDown">50 <cmd:EventToCommand Command="{Binding PreviewMouseLeftButtonDownCommand, Mode=OneWay}" PassEventArgsToCommand="True" />51 </i:EventTrigger>52 <i:EventTrigger EventName="DragOver">53 <cmd:EventToCommand Command="{Binding DragOverCommand, Mode=OneWay}" PassEventArgsToCommand="True" />54 </i:EventTrigger>55 <i:EventTrigger EventName="Drop">56 <cmd:EventToCommand Command="{Binding DropSourceCommand, Mode=OneWay}" PassEventArgsToCommand="True" />57 </i:EventTrigger>58 </i:Interaction.Triggers>59 </ListBox>6061 <Border Grid.Row="2" Height="20" />6263 <TextBlock Grid.Row="3"64 Background="White"65 FontSize="18"66 Text="Listbox to drop to" />6768 <ListBox x:Name="dropTarget"69 Grid.Row="4"70 MinWidth="100"71 MinHeight="117"72 Margin="0,0,0,0"73 AllowDrop="True"74 Background="#FFE4F3FD"75 ItemContainerStyleSelector="{DynamicResource styleSelector}"76 ItemsPanel="{DynamicResource SourceListBoxItemsPanelTemplate}"77 ItemsSource="{Binding TargetContacts}"78 ItemTemplateSelector="{DynamicResource dataTemplateSelector}"79 Style="{DynamicResource ListBoxStyle1}">80 <i:Interaction.Triggers>81 <i:EventTrigger EventName="Drop">82 <cmd:EventToCommand Command="{Binding DropTargetCommand, Mode=OneWay}" PassEventArgsToCommand="True" />83 </i:EventTrigger>84 <i:EventTrigger EventName="DragOver">85 <cmd:EventToCommand Command="{Binding DragOverCommand, Mode=OneWay}" PassEventArgsToCommand="True" />86 </i:EventTrigger>87 </i:Interaction.Triggers>88 </ListBox>
Listing 1: Markup showing the ListBoxs and the events used for Drag and Drop
Steps 1-7: Drag
The PreviewMouseLeftButtonDown is a good place to determine if you want to begin a drag operation. The logic of what happens in this event is shown in Listing 2. In this case, I want to drag the data in a ListBoxItem, so first I needed to get a reference to the ListBoxItem that fired the mouse event as shown in Lines 76-77.72 PreviewMouseLeftButtonDownCommand = new RelayCommand<MouseButtonEventArgs>(73 e =>74 {75 // get dragged listbox76 ListBox listBox = e.Source as ListBox;77 ListBoxItem listBoxItem = VisualHelper.FindAncestor<ListBoxItem>((DependencyObject)e.OriginalSource);7879 // set up shared states80 _listBoxItem = listBoxItem; // adorned element81 _topLevelGrid = GetTopLevelGrid(e); // element to get an adorner layer from higher up the logical tree than the listboxitem8283 // Find the data behind the listBoxItem84 if (listBox == null || listBoxItem == null) return;8586 Contact contact = (Contact)listBox.ItemContainerGenerator.ItemFromContainer(listBoxItem);8788 AddAdorner(listBoxItem, _topLevelGrid);8990 // Initialize the drag & drop operation91 DataObject dragData = new DataObject("myContactData", contact);92 DragDrop.DoDragDrop(listBoxItem, dragData, DragDropEffects.Move);93 }94 );
Listing 2: Command to deal with the dragging part of the operation.
1 using System.Windows;2 using System.Windows.Media;34 namespace DragDropUsingMvvmLight015 {6 /// <summary>7 /// Walks up the visual tree to find the ancestor of a given type.8 /// </summary>9 internal static class VisualHelper10 {11 /// <summary>12 /// Recursive method to walk up the visual tree to return an ancestor type of the supplied type.13 /// </summary>14 /// <typeparam name="T">Type of ancestor to search for.</typeparam>15 /// <param name="current">Type to start search from.</param>16 /// <returns></returns>17 internal static T FindAncestor<T>(DependencyObject current) where T : DependencyObject18 {19 do20 {21 if (current is T)22 {23 return (T)current;24 }25 current = VisualTreeHelper.GetParent(current);26 } while (current != null);27 return null;28 }29 }30 }
Listing 3: Helper method to find an element up the visual tree hierarchy.
96 DragOverCommand = new RelayCommand<DragEventArgs>(97 e =>98 {99 if (!e.Data.GetDataPresent("contact"))100 {101 e.Effects = DragDropEffects.None;102 }103104 var currentMousePosition = e.GetPosition(_topLevelGrid);105106 if (_topLevelGrid != null && _draggedAdorner != null)107 _draggedAdorner.UpdateAdornerPosition(_topLevelGrid, currentMousePosition);108 }109 );
Listing 4: Checking for a viable drop target and updating the adorner position.
Steps 8-9: Drop
Each ListBox raises its own version of the Drop event. Lines 111-122 deals with the expected case of dragging and dropping from source to target. First, check the DragEventArgs parameter to see if there is data there in the format we are expecting. This is shown in Line 114. If there is correct data, then simply update the collections of Contact items that is data bound to the respective ListBox. If there is a successful drop, then at this point, remove the adorner.In Line 125 which deals with the case of doing a drag/drop on the source ListBox, there isn't much to do other than remove the adorner. See all this in Listing 5.
111 DropTargetCommand = new RelayCommand<DragEventArgs>(112 e =>113 {114 if(!e.Data.GetDataPresent("myContactData")) return;115 Contact contact = e.Data.GetData("myContactData") as Contact;116117 _targetContacts.Add(contact); // add to new collection118 _contacts.Remove(contact); // remove from source collection119120 // pass in the root grid since its adorner layer was used to add ListBoxItems adorners to121 RemoveAdorner(_listBoxItem, _topLevelGrid);122 });123124 // if dropping on the source list, remove the adorner125 DropSourceCommand = new RelayCommand<DragEventArgs>(e => RemoveAdorner(_listBoxItem, _topLevelGrid));
Listing 5: The drop operation.
2 comments:
Thanks for this interesting post.
The source code link isn't available ... is it correct?
Massimo
Great article! I want to bump the above question and ask for you to repost the source please.
Post a Comment