Silverlight ListBox Part II (Drag and drop in the same listbox and scroll)
Introduction
In this article, I am going to discuss some of the issues that I faced with ListBox. I spent a lot of time Googling to find out a solution to the problems, but couldn’t find much. Here is the list of problems that I will talk about in this article:
- Drag and drop in the same listbox.
- Scrolling (bring the selected item in the visible region).
Background
This article is a continuation to my previous article, so if you haven’t gone through it, please go through it as I am using the same sample to discuss the issue. Here is the link for the article:
There are three types of items in the listbox (software developer, team leader, manager). The requirement is to provide a drag and drop feature in the listbox so that the user can drag a developer item and drop it a on team leader item, which would include the developer in the team leader’s reporters list, and the same would apply for team leaders and managers. I found many articles on drag and drop on listbox, but most of them were implementing drag and drop from one listbox to another listbox. I had one more issue: to get the controls within a listbox. There is no direct way to get this because of abstraction. We can only get the listboxitems.
After running the attached sample, if you drag a developer item to a team leader item, you would see that the developer gets added into the team leader's list.
Using the code
The first thing that we do for drag and drop functionality is to add a popup control in the form to show the visual effect. Here is the code for the mouse down event where we set the data template and the content of the popup control which would move as we drag the listbox item:
<Popup x:Name="DragPopupControl" IsOpen="True">
<ContentControl x:Name="DragPopupControlContent" Opacity="0.5"/>
</Popup>
The code-behind:
private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Employee employee = (sender as UserControl).DataContext as Employee;
if(employee is Developer || employee is TeamLeader)
{
this.DragPopupControlContent.Content = employee;
DataTemplate dataTemplate =
this.EmployeeList.ItemTemplateSelector.SelectTemplate(employee, null);
dataTemplate.LoadContent();
this.DragPopupControlContent.ContentTemplate = dataTemplate;
this.DragPopupControl.CaptureMouse();
this.IsDragging = true;
this.DragPopupControl.IsOpen = true;
this.DragPopupControl.HorizontalOffset = e.GetPosition(LayoutRoot).X;
this.DragPopupControl.VerticalOffset = e.GetPosition(LayoutRoot).Y;
}
}
Here is the code for the mouse move where we change the coordinates of the popup control to move:
private void UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (this.IsDragging)
{
double currentVerticalPosition = e.GetPosition(LayoutRoot).Y;
double currentHorizontalPosition = e.GetPosition(LayoutRoot).X;
this.DragPopupControl.VerticalOffset = currentVerticalPosition;
this.DragPopupControl.HorizontalOffset = currentHorizontalPosition;
}
}
Everything was going very smooth up till here, but here comes the main issue. How to get the control in the listbox where we are dropping any other item? There is no direct way to do it. Thanks to one of my colleagues who gave me the code for an extension method which solved my problem. Here is the code for the extension method which returns the list of all the controls in a given control. You can pass the control whose children you want to find out and the mouse position:
public static class VisualTreeHelperExtensions
{
public static IList<T> GetChildControls<T>(
this DependencyObject obj) where T : DependencyObject
{
var childControls = new List<T>();
DependencyObject child;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
child = VisualTreeHelper.GetChild(obj, i);
if (child != null && (child.GetType() == typeof(T) ||
child.GetType().IsSubclassOf(typeof(T))))
{
childControls.Add(child as T);
}
else if (child != null)
{
IList<T> subChilds = child.GetChildControls<T>();
if (subChilds.Count > 0)
{
childControls.AddRange(subChilds);
}
}
}
return childControls;
}
}
We can use the extension method to get all the controls in the listbox at the given coordinates, and then we can iterate thorough all the items and find out if there is any team leader view if the user is dragging a developer item, or if there is a manager view if the user is dragging a team leader item. Here is the code which actually accepts the mouse up position and adds the dragged item to the dropped item if the drag and drop is a valid one.
private void EmployeeDropOnFolder(object sender, Employee employee, MouseEventArgs e)
{
if (this.IsDragging && this.DragPopupControlContent.Content != null && employee != null)
{
Point currentMousePosition = e.GetPosition(this);
List<UIElement> controlList =
VisualTreeHelper.FindElementsInHostCoordinates(currentMousePosition,
EmployeeList) as List<UIElement>;
if (controlList != null && controlList.Count > 0)
{
// Remove the sender as it can't be target.
controlList.Remove(sender as UIElement);
// Find out if there is FolderView in the list.
foreach (UIElement uiElement in controlList)
{
if(uiElement is TeamLeaderView && employee is Developer)
{
TeamLeader teamLeader =
(uiElement as TeamLeaderView).DataContext as TeamLeader;
if(!teamLeader.DirectReports.Contains(employee))
{
teamLeader.DirectReports.Add(employee);
}
}
else if (uiElement is ManagerView && employee is TeamLeader)
{
Manager manager = (uiElement as ManagerView).DataContext as Manager;
if (!manager.DirectReports.Contains(employee))
{
manager.DirectReports.Add(employee);
}
}
}
}
}
}
private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
EmployeeDropOnFolder(sender, this.DragPopupControlContent.Content as Employee, e);
this.IsDragging = true;
this.DragPopupControl.IsOpen = false;
this.DragPopupControlContent.Content = null;
this.DragPopupControlContent.ContentTemplate = null;
this.DragPopupControlContent.ReleaseMouseCapture();
}
There exists a direct method ScrollIntoView
in the listbox to bring the selected control in the visible region, but it was not working for me probably because I was using a WrapPanel
. I added code to maintain the row index and the height of the items in the WrapPanel
. When the user clicks on the button in the UI, it gets the currently selected item in the listbox, passes it to the WrapPanel
public method, and gets the position from the top of the wrap panel for the item. Now, we can set the position of the scrollbar. You can look at the code of the WrapPanel
in the attached source code.
Comments
- Anonymous
April 21, 2009
PingBack from http://microsoft-sharepoint.simplynetdev.com/silverlight-listbox-part-ii-drag-and-drop-in-the-same-listbox-and-scroll/ - Anonymous
May 06, 2009
The comment has been removed - Anonymous
May 17, 2009
I thing in this scanario we can move the listbox scroolbar as the mouse move within the listbox. There is not ready solution for it.