Introduction
In the development of business websites, we have encountered some hurdles in the use of Silverlight, in particular using the drag and drop functionality that was not easily solved by a quick search of the internet. There are many examples of using Silverlight drag and drop with file system utilities, but how does it work if you want to have drag and drop in your business application where the objects come from your database instead of a file system? All the examples we found ignore the need for a business application to know exactly what moved and from where and the need to update the database with the information that changed.
This article presents the solution we came up with to solve the problem of knowing which object was moved and details about that object. Due to the extensive functionality of Silverlight, there are probably other ways to solve this problem.
Background
This article assumes at least some familiarity with Silverlight, particularly version 4, and C#.
What's necessary to set up dynamic drag and drop
The technique we used is fairly simple as far as representing the objects is concerned. A ListBox
forms the main container, and ListBoxItem
s are used to display the business objects. Since we need a little flexibility to display additional information, such as the headings as well as enabling the drag and drop functionality, there are a few things that must also be included. To handle this, we used a StackPanel
as our column container, with its first child being the heading for the column, followed by the ListBoxDragDropTarget
which in turn contains the ListBox
. So with this hierarchy of containers, the visual drag and drop functionality already works, meaning that you can drag from one column and drop it into another column with no additional code. The limitation here is that the database does not get updated with this change.
Creating this hierarchy takes surprisingly little code, and results in a model that is completely dynamic, meaning that you can have as many columns and items in each column as you want. The XAML is fairly short, and is as follows:
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Margin="20,0,0,0" HorizontalAlignment="Left"
VerticalAlignment="Top" Text="Silverlight Business Drag and Drop"
FontSize="16"/>
<Grid x:Name="DragDropRoot" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="40,40,0,0"/>
</Grid>
Since all the remaining work was done from code, the columns and items in code are a little more involved than would be in XAML, since you need to make sure all the properties are set so that the visuals look correct.
Let's start by building the columns themselves. Using the above mentioned hierarchy, we start with StackPanel
s, and add a column header to indicate to the user the purpose of the column. Creating the ListBoxDragDropTarget
is only three lines of code, then you add a ListBox
to actually house the items.
StackPanel column1 = new StackPanel();
column1.Margin = new Thickness(0, 0, 0, 0);
column1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
column1.VerticalAlignment = System.Windows.VerticalAlignment.Top;
column1.Width = 150;
column1.Height = 400;
TextBlock hdr1 = new TextBlock();
hdr1.Text = "Column 1";
hdr1.Height = 30;
hdr1.Margin = new Thickness(0, 0, 0, 0);
hdr1.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
hdr1.VerticalAlignment = System.Windows.VerticalAlignment.Top;
ListBoxDragDropTarget source = new ListBoxDragDropTarget();
source.Drop += new Microsoft.Windows.DragEventHandler(dest_Drop);
source.AllowDrop = true;
ListBox box1 = new ListBox();
box1.Name = "Column1";
box1.Margin = new Thickness(1, 1, 0, 0);
box1.Background = new SolidColorBrush(Colors.Transparent);
box1.FlowDirection = System.Windows.FlowDirection.LeftToRight;
box1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
box1.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
box1.Width = 140;
box1.Height = 360;
s1.Children.Add(txt1);
i1.Content = s1;
box1.Items.Add(i1);
column1.Children.Add(hdr1);
source.Content = box1;
column1.Children.Add(source);
DragDropRoot.Children.Add(column1);
Note: at the bottom of the above code segment, the hierarchy is built so that the objects all work correctly.
After the background is set up for a column, the column is still empty as far as items to be able to demonstrate it. Though I only show two columns in this demo, it is certainly feasible to have any number of columns that your application may need.
Since business objects (including both the columns and the items within each column) come from a database and usually would have a unique identifier for each, we can use that unique identifier as part of the name for each item. In the following example, we see how to build the individual items, such that it can be done for any number of items. Each ListBoxItem
represents a business object, so the Name
associated with the ListBoxItem
could contain your unique identifier, or a name plus an identifier if you are representing multiple objects, to ensure that you can recognize it when it is dropped on another column.
Within each ListBoxItem
, you will generally have multiple items to show that each help describes this item. For that reason, I generally use a StackPanel
(or Grid
) which then contains each of the items that will be shown in the user interface. Within the StackPanel
, you might have TextBlock
s like I demonstrate here, or any number of other objects (images, buttons, etc.) that make the user view complete.
The final three lines of code are used to collect all the items together into a package that can be added to the column defined above.
ListBoxItem i2 = new ListBoxItem();
i2.BorderBrush = new SolidColorBrush(Colors.Red);
i2.BorderThickness = new Thickness(1);
i2.Name = "2";
StackPanel s2 = new StackPanel();
s2.Background = new SolidColorBrush(Colors.Magenta);
s2.Width = 130;
s2.Height = 100;
s2.FlowDirection = System.Windows.FlowDirection.LeftToRight;
TextBlock txt2 = new TextBlock();
txt2.Width = 129;
txt2.Height = 90;
txt2.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
txt2.VerticalAlignment = System.Windows.VerticalAlignment.Top;
txt2.TextWrapping = TextWrapping.Wrap;
txt2.Text = "This is another source item that can be dragged and dropped";
s1.Children.Add(txt1);
i1.Content = s1;
box1.Items.Add(i1);
That defines the columns and the items for those columns. What comes next is the code required to recognize the actions taken in the drag and drop. While this is only a few lines of code, because of the extraction of data from the complex parameter passed in, it is not obvious how you get the name of the item from the DragEventArgs
parameter. By extracting both column names as well as the name of the item that was moved, you should have complete details to be able to update your database or apply any rules that may be necessary.
void dest_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
var format = e.Data.GetFormats()[0];
ItemDragEventArgs dragItem = e.Data.GetData(format) as ItemDragEventArgs;
bs = (ListBox)dragItem.DragSource;
ListBoxDragDropTarget b = (ListBoxDragDropTarget)sender;
bd = (ListBox)b.Content;
string WhoWasIt = ((ListBoxItem)bs.SelectedItem).Name;
MessageBox.Show("Item " + WhoWasIt + " was moved from " +
bs.Name + " to " + bd.Name);
}
The first two lines above extract the object being dragged, including where it came from. From that, the middle three lines give you the DragSource
as the source object and the sender
's Content
as the destination object. Once you have that, you can pick up the SelectedItem
as the ListBoxItem
being dragged and extract the name to get your business object's unique code (the WhoWasIt
variable above shows the original name of the object). With that information, you can then update your database with the information that the object was moved from one column to another column. If you also need the resulting order, you will need to add a StoryBoard
event to give this event time to complete so that you can look through the destination ListBox
to find out where in the ListBox
the item ended up. Given that you already have the object name, it is easy to find in the destination ListBox
.
If you require multiple item drag and drop, you would then step through the SelectedItems
collection instead of using the SelectedItem
(which will only give you the first item if you drag multiple items).
Using the code
To use this code, you will need to consider a few things, such as what information from your database will be used to generate each ListBoxItem
, then what information will be needed to update your database with the results of the drag and drop operation. Since this example does not include database access code, or the loops you will need to build multiple objects, you will need to wrap at least the item creation in a loop, and perhaps the column creation as well if there are a number of columns.
Points of interest
It might be hard to find the information you need from Silverlight, but once you have found it, it is easy to accomplish many of the tough problems in only a few lines of code.