Silverlight, WPF – ListBox Drag and Drop

During my development I need to implement the drag and drop feature in Silverlight. It’s pretty easy to make this feature work in Windows Form but it’s not simple to enable this feature on control in Silverlight. Therefore the developer team of Silverlight has developed a Silverlight toolkit to help us to alleviate our work. The Silverlight Toolkit is a collection of Silverlight controls, components and utilities made available outside the normal Silverlight release cycle. For more details about this toolkit you can read here http://silverlight.codeplex.com/.
In this example I would like to illustrate how we can use this toolkit to adapt the drag and drop to list box component of Silverlight. First you need to browse to URL above and install Silverlight Toolkit, then use Microsoft Expression Blend to add two list boxes to our application

<UserControl x:Class="SVL_Drag_and_Drop.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="452" d:DesignHeight="372">

  <Grid x:Name="LayoutRoot">
  	<StackPanel Margin="8" Orientation="Horizontal">
		<ListBox x:Name="lstbFrom" Width="219" Height="500" DisplayMemberPath="FullName">
		</ListBox>
		<ListBox x:Name="lstbTo" Width="217" Height="500" DisplayMemberPath="FullName"/>
  	</StackPanel>
  </Grid>
</UserControl>

The DisplayMemberPath is data bound with property FullName of data source. Our data source is simply a list of students with their first and last name.

public static ObservableCollection<StudentDetails> GetListOfStudent()
{
	ObservableCollection<StudentDetails> lstSD = new ObservableCollection<StudentDetails>();
	for (int nIndex = 0; nIndex < 10; nIndex++)
	{
		StudentDetails sdDetails = new StudentDetails() { FirstName = "First: " + RandomString(8, true), LastName = " Last: " + RandomString(8, true) };
		lstSD.Add(sdDetails);
	}
	return lstSD;
}

public class StudentDetails
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public string FullName
	{
		get { return (FirstName + LastName); }
	}
}

We load data source into list box From when application is loaded

public partial class MainPage : UserControl
{
	public MainPage()
	{
		InitializeComponent();
		Loaded += new RoutedEventHandler(MainPage_Loaded);
	}

	void MainPage_Loaded(object sender, RoutedEventArgs e)
	{
		lstbFrom.ItemsSource = Students.GetListOfStudent();
	}
}

When we run our application, we’ll see list of student in list box From but we still cannot drag and drop items between list box. To adapt this feature, first let’s add reference to System.Windows.Controls.Toolkit, add toolkit:ListBoxDragDropTarget attribute to activate drag feature and mswindows:DragDrop.AllowDrop=”True” to allow drop

<Grid x:Name="LayoutRoot">
  	<StackPanel Margin="8" Orientation="Horizontal">
        <toolkit:ListBoxDragDropTarget mswindows:DragDrop.AllowDrop="True">
            <ListBox x:Name="lstbFrom" Width="219" Height="500" DisplayMemberPath="FullName">
            </ListBox>
        </toolkit:ListBoxDragDropTarget>
        <toolkit:ListBoxDragDropTarget mswindows:DragDrop.AllowDrop="True">
            <ListBox x:Name="lstbTo" Width="217" Height="500" DisplayMemberPath="FullName"/>
        </toolkit:ListBoxDragDropTarget>
  	</StackPanel>
</Grid>

After adding these attributes to list box, compile, we can drag and drop items between list boxes. If you want to move items within a list box, you can use StackPanel as ItemTemplate

<Grid x:Name="LayoutRoot">
  	<StackPanel Margin="8" Orientation="Horizontal">
        <toolkit:ListBoxDragDropTarget mswindows:DragDrop.AllowDrop="True">
            <ListBox x:Name="lstbFrom" Width="219" Height="500" DisplayMemberPath="FullName">
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel/>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>
        </toolkit:ListBoxDragDropTarget>
        <toolkit:ListBoxDragDropTarget mswindows:DragDrop.AllowDrop="True">
            <ListBox x:Name="lstbTo" Width="217" Height="500" DisplayMemberPath="FullName"/>
        </toolkit:ListBoxDragDropTarget>
  	</StackPanel>
</Grid>

The complete source code you can download here “SVL Drag and Drop”.

UPDATE 05.11.2011

For WPF there is unfortunately no toolkit helping us to solve Drag and Drop reasonably. We must implement this feature ourselves. Create new project of WPF and insert a StackPanel with 2 list boxes as below

<StackPanel Margin="8" Orientation="Horizontal">
	<ListBox x:Name="lstbFrom" Width="219" Height="500" DisplayMemberPath="FullName" PreviewMouseLeftButtonDown="lstbFrom_PreviewMouseLeftButtonDown" PreviewMouseMove="lstbFrom_PreviewMouseMove" SelectionMode="Multiple"  >
	</ListBox>
	<ListBox x:Name="lstbTo" Width="217" Height="500" DisplayMemberPath="FullName" Drop="lstbTo_Drop" DragEnter="lstbTo_DragEnter" AllowDrop="True"/>
</StackPanel>

Because we have no clues to know if a Drag Action is taking place, we must calculate the difference of mouse position to see if it is greater than a threshold. When it is above the threshold, we make a call to DoDragDrop with appropriate data

private void lstbFrom_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
	startPoint = e.GetPosition(null);
}

private void lstbFrom_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
	// Get the current mouse position
	Point mousePos = e.GetPosition(null);
	Vector diff = startPoint - mousePos;

	if (e.LeftButton == MouseButtonState.Pressed &&
		(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
		Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
	{
		DataObject dragData = new DataObject(DataObjectFormat, (sender as ListBox).SelectedItems.Cast<StudentDetails>().ToList());

		DragDrop.DoDragDrop(sender as ListBox, dragData, DragDropEffects.Move);
	}
}

As you can see, I wrap our data into DataObject and sent it to receiver. At the receiver, we extract the data back from DataObject, insert it into destination and remove it from source.

private void lstbTo_Drop(object sender, DragEventArgs e)
{
	if (e.Data.GetDataPresent(DataObjectFormat))
	{
		foreach (StudentDetails contact in (e.Data.GetData(DataObjectFormat) as List<StudentDetails>))
		{
			if ((e.Effects & DragDropEffects.Move) == DragDropEffects.Move)
				((IList)lstbFrom.ItemsSource).Remove(contact);
			(sender as ListBox).Items.Add(contact);
		}
	}
}

private void lstbTo_DragEnter(object sender, DragEventArgs e)
{
	if (!e.Data.GetDataPresent(DataObjectFormat) ||
sender == e.Source)
	{
		e.Effects = DragDropEffects.None;
	}
}

The demo source you can download at “WPF ListBox MultiSelect Drag And Drop

Leave a Reply

Your email address will not be published. Required fields are marked *