How to preserve selection when dragging multiple items in WPF

Amelia Moseley 20 Reputation points
2023-07-17T13:23:07.18+00:00

Edit Updated the post to include more of the source code, please let me know if you have any issues, I could not copy and paste the code in so had to type it out on here so it's possible there is the odd mistake here or there. (e.g. the MouseState you mentioned before should have been MouseButtonState which is part of C# not user defined)

I have a ListView (with a GridView), with selection mode set to multiple. This is on one side of a grid, and on the other side is a TreeView. I have got it so that I can drag multiple items over from the ListView to the Tree view; using a MouseMove event on the ListView and a Drop Event on the TreeView. The Issue I am having is that if you select multiple items, then click and drag across, the item getting clicked to initiate the drag gets deselected.

I've been browsing through other related questions and documentation and I am struggling to find an answer. I have tried using separate mouseup and mousedown events, and keeping track of the selected items in a list.

Below is the code behind:

namespace TaskPlanner
{
 
	public partial class MainWindow : Window
	{
		 public class Task
		 {
			public string Headline {get; set;}
			public string TaskType {get; set;}
			public int Priority {get; set;}
		 }
			
		 public ObservableCollection<TreeViewItem> TreeViewSourceItems { get; set; }
		 public ObservableCollection<Task> ListViewSourceItems { get; set; }
	
		 public MainWindow()
		 {
			InitializeComponent();
			DataContext = this;
			
			TreeViewSourceItems = new ObservableCollection<TreeViewItem>();
			ListViewSourceItems = new ObervableCollection<Task>();
			
			/* Normally the task data comes from a database, but the data source is not a factor in this issue*/
			ListViewSourceItems.Add(new Task { Headline = "Task 1", TaskType = "Review", Priority = 1});
			ListViewSourceItems.Add(new Task { Headline = "Task 2", TaskType = "Dev", Priority = 2});
			ListViewSourceItems.Add(new Task { Headline = "Task 3", TaskType = "Write", Priority = 3});
			
			taskListView.ItemsSource = ListViewSourceItems;
			taskTreeView.ItemsSource = TreeViewSourceItems.
			
	     }
	
	
		 
		
		 private void OnMouseMove(object sender, MouseEventArgs e)
		 {
		 	if (e.LeftButton == MouseButtonState.Pressed)
		    {
		        List<Task> taskList = new List<Task>();
		        
		        foreach (Task task in taskListView.SelectedItems)
		        {
		            taskList.Add(task);
		        }
		
		        DataObject data = new DataObject(taskList);
		        DragDrop.DoDragDrop(taskListView, data, DragDropEffects.Move);
		    }
		 }
		
		 private void OnDropEvent(object sender, DragEventArgs e)
		 {
		 	Type type = typeof(List<Task>);
		    
		    List<Task> selectedItems = e.Data.GetData(type) as List<Task>;
		    
		    foreach (Task task in selectedItems)
		    {
		    	TreeViewItem treeViewItem = new TreeViewItem();
		        
		        treeViewItem.Header = task.Headline;
		        TreeViewSourceItems.Add(treeViewItem);
		        ListViewSourceItems.Remove(task);
		        
		        ICollection dataView =
		    CollectionViewSource.GetDefaultView(taskListView.ItemsSource);
		        dataView.Refresh();
		        
		        dataView = CollectionViewSource.GetDefaultView(taskTreeView.ItemsSource);
		            dataView.Refresh();
		    }
		        
		}
	
	}
}
<Window x:Class="TaskPlanner.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:d="http://schemas.microsoft.com/expressions/blend/2008"
		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
		xmlns:local="clr-namespace:TaskPlanner"
		mc:Ignorable="d"
		Title="MainWindow" Height="450" Width="800" WindowState="Maximized">
	<Grid>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="*"/>
			<ColumnDefinition Width="5"/>
			<ColumnDefinition Width="*"/>
		</Grid.ColumnDefinitions>
		<Grid.RowDefinitions>
			<RowDefinition Height="30"/>
			<RowDefinition Height="30"/>
			<RowDefinition Height="*"/>
			<RowDefinition Height="100"/>
		</Grid.RowDefinitions>

		<StackPanel Grid.Row="0" Grid.ColumnSpan="3" Orientation="Horizontal" 					Background="LightGray">
		</StackPanel>

		<ScrollViewer Grid.Column="0" Grid.Row="1" Grid.RowSpan="2">
			<ListView x:Name="taskListView" AllowDrop="True" MouseMove="OnMouseMove" SelectionMode="Multiple">
				<ListView.View>
					<GridView>
						<GridViewColumn DisplayMemberBinding="{Binding Headline}" Header="Headline" Width="400"/>
						<GridViewColumn DisplayMemberBinding="{Binding Task}" Header="Headline" Width="80"/>
						<GridViewColumn DisplayMemberBinding="{Binding Headline}" Header="Headline" Width="80"/>
					</GridView>
				</ListView.View>
			</ListView>
		</ScrollViewer>

		<GridSplitter Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" Width="5" HorizontalAlignment="Stretch"/>
		<TreeView x:Name="taskTreeView" Grid.Column="2" Grid.Row="2" AllowDrop="True" Drop="OnDropEvent"/>

		<StackPanel Grid.Row="3" Grid.ColumnSpan="3" Orientation="Horizontal" Background="LightGray">
		</StackPanel>
	</Grid>

</Window>

The drag and drop functionality mostly works it's just the issue as mentioned above is with selection/deselection when dragging items from the ListView, if you select some items and then click one of said items to initiate the drag/drop that clicked item is deselected and not included in the dragged items. The desired result is to keep it so that items can be deselected if clicked, but not deselected if that click is followed by a drag.

The target framework is .Net Framework 4.7.2, as this is the most recent version available to me on the system this work is being carried out on.

Developer technologies Windows Presentation Foundation
Developer technologies XAML
Developer technologies C#
{count} votes

Accepted answer
  1. Hui Liu-MSFT 48,676 Reputation points Microsoft External Staff
    2023-07-20T08:16:28.9033333+00:00

    Hi,@Amelia Moseley. To ensure that the selection is preserved during the drag-and-drop operation, you could use the following code to preserve the selection.

    The following code handles the mouse events and drag-and-drop operations for transferring items from the ListView to the TreeView.

    xaml:

    <ListView x:Name="taskListView" AllowDrop="True" PreviewMouseLeftButtonDown="OnPreviewMouseLeftButtonDown" MouseMove="OnMouseMove" SelectionMode="Multiple">
    

    codebedhind:

    
      private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                
                DependencyObject originalSource = e.OriginalSource as DependencyObject;
                ListViewItem clickedItem = FindVisualParent<ListViewItem>(originalSource);
    
                if (clickedItem != null && clickedItem.IsSelected)
                {
                   
                    clickedItem.IsSelected = false;
                }
            }
    
       private void OnMouseMove(object sender, MouseEventArgs e)
            {
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    List<Task> taskList = new List<Task>();
    
                    foreach (Task task in taskListView.SelectedItems)
                    {
                        taskList.Add(task);
                    }
    
                    DataObject data = new DataObject(taskList);
                    DragDrop.DoDragDrop(taskListView, data, DragDropEffects.Move);
                }
            }
    
            private void OnDropEvent(object sender, DragEventArgs e)
            {
                Type type = typeof(List<Task>);
    
                List<Task> selectedItems = e.Data.GetData(type) as List<Task>;
    
                foreach (Task task in selectedItems)
                {
                    TreeViewItem treeViewItem = new TreeViewItem();
    
                    treeViewItem.Header = task.Headline;
                    TreeViewSourceItems.Add(treeViewItem);
                    ListViewSourceItems.Remove(task);
    
                    ICollectionView dataView = CollectionViewSource.GetDefaultView(taskListView.ItemsSource);
                    dataView.Refresh();
    
                    dataView = CollectionViewSource.GetDefaultView(taskTreeView.ItemsSource);
                    dataView.Refresh();
                }
            }
    
            private static T FindVisualParent<T>(DependencyObject obj) where T : DependencyObject
            {
                while (obj != null)
                {
                    if (obj is T parent)
                    {
                        return parent;
                    }
                    obj = VisualTreeHelper.GetParent(obj);
                }
                return null;
            }
    
    
    

    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.