Export (0) Print
Expand All

Dragging and Dropping Items Between SurfaceListBox Controls



This topic describes how to use the Microsoft Surface drag-and-drop framework to drag items from one SurfaceListBox control and drop them on another SurfaceListBox control.

In the following example, users can drag all items from the source SurfaceListBox control, but users are stopped from dropping certain items on the target control.

Dragging and dropping between SurfaceListBoxes

Dragging and dropping between SurfaceListBox controls using the following code

Item Data for SurfaceListBox

The DataItem class is used to create the items for the SurfaceListBox control. It has two properties:

  • The Name property is the name of the item.

  • The CanDrop property specifies whether the item can be dropped on the target control.

Add the following code to a stand-alone class file in your project, or as a class-level definition inside the code-behind file for the SurfaceWindow1.xaml.cs file.

    public class DataItem
    {
        private string name;
        private bool canDrop;

        public string Name
        {
            get { return name; }
        }

        public bool CanDrop
        {
            get { return canDrop; }
        }

        public DataItem(string name, bool canDrop)
        {
            this.name = name;
            this.canDrop = canDrop;
        }
    }

The DataTemplate object (which is defined in the Resources section of the main SurfaceWindow object) specifies the layout of each item and binds the Name and CanDrop properties to the contents of two corresponding Label controls.

<s:SurfaceWindow x:Class="DraggingAndDroppingBetweenSurfaceListBoxes.SurfaceWindow1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="http://schemas.microsoft.com/surface/2008"
    Title="DraggingAndDroppingBetweenSurfaceListBoxes"
    >

    <s:SurfaceWindow.Resources>
        
        <DataTemplate x:Key="SurfaceListBoxItemDataTemplate">
            <StackPanel Height="120" Width="190" Background="Gray">
                    <Label 
                        Content="{Binding Name}" 
                        HorizontalAlignment="Center" 
                        VerticalAlignment="Center"
                        FontSize="20"/>

                    <Label 
                        Content="{Binding CanDrop}"
                        HorizontalAlignment="Center" 
                        VerticalAlignment="Center"
                        FontSize="18"/>
                </StackPanel>
        </DataTemplate>
    </s:SurfaceWindow.Resources>

    <Grid>
    </Grid>
    
</s:SurfaceWindow>

Two ObservableCollection objects are defined to hold the data items. The collections are exposed through public properties, defined in the code-behind file for the main SurfaceWindow object.

        private ObservableCollection<DataItem> sourceItems;
        private ObservableCollection<DataItem> targetItems;

        /// <summary>
        /// Items that bind with the drag source list box.
        /// </summary>
        public ObservableCollection<DataItem> SourceItems
        {
            get
            {
                if (sourceItems == null)
                {
                    sourceItems = new ObservableCollection<DataItem>();
                }

                return sourceItems;
            }
        }

        /// <summary>
        /// Items that bind with the drop target list box.
        /// </summary>
        public ObservableCollection<DataItem> TargetItems
        {
            get
            {
                if (targetItems == null)
                {
                    targetItems = new ObservableCollection<DataItem>();
                }

                return targetItems;
            }
        }

    }
noteNote
The ObservableCollection type is in the System.Collections.ObjectModel namespace. You must add a using statement to the SurfaceWindow1.xaml.cs file for this namespace.

using System.Collections.ObjectModel;

When the SurfaceWindow object is initialized, the collection for the source list box is populated with data. Some of the data items are prohibited from being dropped on the target list box by passing false to the constructor of DataItem.

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            DataContext = this;
            SourceItems.Add(new DataItem("Hammer", false));
            SourceItems.Add(new DataItem("Saw", true));
            SourceItems.Add(new DataItem("Pliers", true));
            SourceItems.Add(new DataItem("Tape Measure", false));
            SourceItems.Add(new DataItem("Square", true));
            SourceItems.Add(new DataItem("Wrench", false));
            SourceItems.Add(new DataItem("Screwdriver", true));
            SourceItems.Add(new DataItem("Plumb Bob", true));
            SourceItems.Add(new DataItem("Miter Box", false));
            SourceItems.Add(new DataItem("Knife", true));
            SourceItems.Add(new DataItem("Chisel", true));
            SourceItems.Add(new DataItem("Punch", true));
        }

The following illustration shows how the list box items appear when they are rendered.

4b6231f4-d80d-448d-b2b0-9f9584db0347

Creating the SurfaceListBox Controls and Binding the Items

In the application's SurfaceWindow control, two SurfaceListBox controls are defined within a Grid control. Both SurfaceListBox controls set their ItemTemplate property to the same DataTemplate object (previously defined as a static resource) and bind their ItemsSource property to the corresponding ObservableCollection objects.

  <Grid>
      
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <s:SurfaceListBox 
            Name="DragSource" 
            Grid.Column="0"
            Width="250"
            BorderBrush="{DynamicResource {x:Static s:SurfaceColors.ListBoxItemBackgroundBrushKey}}"
            ItemTemplate="{StaticResource SurfaceListBoxItemDataTemplate}"
            ItemsSource="{Binding Path=SourceItems}"
            PreviewTouchDown="OnDragSourcePreviewTouchDown"
            s:SurfaceDragDrop.DragCompleted="OnDragCompleted"/>

        <s:SurfaceListBox 
            Name="DropTarget" 
            Grid.Column="1"
            AllowDrop="True"
            Width="250"
            BorderBrush="{DynamicResource {x:Static s:SurfaceColors.ListBoxItemBackgroundBrushKey}}"
            ItemTemplate="{StaticResource SurfaceListBoxItemDataTemplate}"
            ItemsSource="{Binding Path=TargetItems}"
            s:SurfaceDragDrop.DragEnter="OnDropTargetDragEnter"
            s:SurfaceDragDrop.DragLeave="OnDropTargetDragLeave"
            s:SurfaceDragDrop.Drop="OnDropTargetDrop"/>

    </Grid>

In addition, the SurfaceListBox controls assign attached events to enable drag-and-drop operations. These events are described in more detail later in this topic.

SurfaceDragCursor Object

The SurfaceDragCursor class represents the cursor for an item that is being dragged. You do not create a SurfaceDragCursor object directly but your application is responsible for creating the FrameworkElement object that is associated with the cursor.

The FrameworkElement object is what users see as they drag an item; it is assigned to the Visual property of the SurfaceDragCursor object when the drag-and-drop operation begins, and it is available during drag-and-drop attached events.

To enable you to create the visual aspect of the cursor when a drag operation begins, a Style object is defined in the Resources section of the main SurfaceWindow object. The ContentTemplate template (of the ContentControl that the Style targets) is set to the same static resource that is used for the ItemTemplate property of the SurfaceListBox control. Although this setting is not required, it gives the cursor visual the same look as the items in the list box.

In addition, two Trigger objects are defined. These objects provide a visual cue for the item that is being dragged as it reaches the drop target. If the item is not prohibited from being dropped, the border of the visual cursor is changed to green; otherwise, the border is changed to red.

The following code example shows the Style object that is used at the start of a drag-and-drop operation to create the visual aspect of the drag cursor. You can add this code inside the Resources section of the main SurfaceWindow object.

        <Style x:Key="CursorStyle" TargetType="{x:Type ContentControl}">
            <Setter Property="ContentTemplate" Value="{StaticResource SurfaceListBoxItemDataTemplate}" />
            <Setter Property="BorderThickness" Value="9"/>
            <Setter Property="BorderBrush" Value="{DynamicResource {x:Static s:SurfaceColors.Accent1BrushKey}}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ContentControl}">
                        <Border 
                            BorderThickness="{TemplateBinding BorderThickness}" 
                            BorderBrush="{TemplateBinding BorderBrush}">
                            <ContentPresenter Margin="0"
                                Content="{TemplateBinding Content}" 
                                ContentTemplate="{TemplateBinding ContentTemplate}">
                            </ContentPresenter>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Tag" Value="CanDrop">
                    <Setter Property="BorderBrush" Value="{DynamicResource {x:Static s:SurfaceColors.Accent4BrushKey}}" />
                </Trigger>
                <Trigger Property="Tag" Value="CannotDrop">
                    <Setter Property="BorderBrush" Value="{DynamicResource {x:Static s:SurfaceColors.Accent3BrushKey}}" />
                </Trigger>
            </Style.Triggers>
        </Style>

Starting the Drag-and-Drop Operation

To start a drag-and-drop operation, the source SurfaceListBox control listens to the PreviewTouchDown event. This tunneling event is raised when a touch is placed on the SurfaceListBox control.

The handler for this event locates the SurfaceListBoxItem object that the touch is touching by traversing the visual tree from the OriginalSource property of the event arguments. If the touch was not placed on a SurfaceListBoxItem object (for example if a user placed the touch on the border of the SurfaceListBox control), no item is located and the work of the handler is done.

When the touch is associated with a SurfaceListBoxItem object, a ContentControl object for the visual of the drag cursor is created, and the previously defined Style object is applied to the control's Style property. An event handler for the TargetChangedEvent event is also attached to ContentControl, so the control can provide the visual cues as it reaches (and leaves) potential drop targets.

noteNote
Alternately, you could provide visual cues by attaching the DragEnterEvent event and the DragLeaveEvent event to the drop target control. This approach is used in the Dragging and Dropping Items from ScatterView Controls to SurfaceListBox Controls topic.

The input devices (the current touch and all touches that are currently captured by the element that is being dragged) are assembled in a list and passed to the static BeginDragDrop method. This method causes the touches to become captured to the drag cursor instead of the original element.

When BeginDragDrop returns, the drag-and-drop operation has started. The following code example shows the event handler for the PreviewTouchDown event.

        private void OnDragSourcePreviewTouchDown(object sender, TouchEventArgs e)
        {
            FrameworkElement findSource = e.OriginalSource as FrameworkElement;
            SurfaceListBoxItem draggedElement = null;

            // Find the touched SurfaceListBoxItem object.
            while (draggedElement == null && findSource != null)
            {
                if ((draggedElement = findSource as SurfaceListBoxItem) == null)
                {
                    findSource = VisualTreeHelper.GetParent(findSource) as FrameworkElement;
                }
            }

            if (draggedElement == null)
            {
                return;
            }

            // Create the cursor visual.
            ContentControl cursorVisual = new ContentControl()
            {
                Content = draggedElement.DataContext,
                Style = FindResource("CursorStyle") as Style
            };

            // Add a handler. This will enable the application to change the visual cues.
            SurfaceDragDrop.AddTargetChangedHandler(cursorVisual, OnTargetChanged);

            // Create a list of input devices. Add the touches that
            // are currently captured within the dragged element and
            // the current touch (if it isn't already in the list).
            List<InputDevice> devices = new List<InputDevice>();
            devices.Add(e.TouchDevice);
            foreach (TouchDevice touch in draggedElement.TouchesCapturedWithin)
            {
                if (touch != e.TouchDevice)
                {
                    devices.Add(touch);
                }
            }

            // Get the drag source object
            ItemsControl dragSource = ItemsControl.ItemsControlFromItemContainer(draggedElement);

            SurfaceDragCursor startDragOkay =
                SurfaceDragDrop.BeginDragDrop(
                  dragSource,                 // The SurfaceListBox object that the cursor is dragged out from.
                  draggedElement,             // The SurfaceListBoxItem object that is dragged from the drag source.
                  cursorVisual,               // The visual element of the cursor.
                  draggedElement.DataContext, // The data associated with the cursor.
                  devices,                    // The input devices that start dragging the cursor.
                  DragDropEffects.Move);      // The allowed drag-and-drop effects of the operation.

            // If the drag began successfully, set e.Handled to true. 
            // Otherwise SurfaceListBoxItem captures the touch 
            // and causes the drag operation to fail.
            e.Handled = (startDragOkay != null);
        }

The following illustration shows a SurfaceListBoxItem object as it begins a drag-and-drop operation.

Drag-and-drop - Beginning of SurfaceListBoxItem

An item when it begins a drag-and-drop operation

Other Ways to Start a Drag-and-Drop Operation

Typically, when a user touches an item in a SurfaceListBox control and moves a finger up or down, the list box's contents scroll. In the approach that has been described so far in this topic, this behavior is disabled because the PreviewTouchDown event handler begins a drag-and-drop operation and marks the event as handled.

You can also modify this design if you want. For example, you might require two touches to begin a drag-and-drop operation. This design enables a user to scroll the contents by using one finger or begin a drag-and-drop operation by using two fingers. To make this change, modify the event handler to ignore cases where there are no previously captured touches on the SurfaceListBoxItem object.

if (draggedElement == null || draggedElement.TouchesCapturedWithin.Count == 0)
{
    return;
}

Responding to Events as the Drag Cursor Moves

When a dragged item moves over a potential drop target (an element that the AllowDrop property is true on), the attached DragEnterEvent event is raised. The SurfaceListBox control that is used for the drop target is listening to this event. When the event is raised, the event handler checks to see if the item that is being dragged is prohibited from being dropped. If it is prohibited, the Effects property of the event arguments is set to not permit any drag-and-drop operation.

        private void OnDropTargetDragEnter(object sender, SurfaceDragDropEventArgs e)
        {
            DataItem data = e.Cursor.Data as DataItem;

            if (!data.CanDrop)
            {
                e.Effects = DragDropEffects.None;
            }
        }

When a dragged item exits a potential drop target, the attached DragEnterEvent event is raised. In the event handler, the Effects property is set back to its original value. Although it is not necessary in this example, you might need this step in a scenario that included multiple drop targets that each had separate logic for qualifying or disqualifying an item.

        private void OnDropTargetDragLeave(object sender, SurfaceDragDropEventArgs e)
        {
            // Reset the effects.
            e.Effects = e.Cursor.AllowedEffects;
        }

When the drop target changes, the TargetChangedEvent event is raised. The ContentControl object that was created for the visual of the drag cursor is listening to this event. The event handler checks to see whether the item that is being dragged is prohibited from being dropped and sets the Tag property appropriately. This setting initiates the triggers that are established in the Style object for the control, turning the border either red or green.

        private void OnTargetChanged(object sender, TargetChangedEventArgs e)
        {
            if (e.Cursor.CurrentTarget != null)
            {
                DataItem data = e.Cursor.Data as DataItem;
                e.Cursor.Visual.Tag = (data.CanDrop) ? "CanDrop" : "CannotDrop";
            }
            else
            {
                e.Cursor.Visual.Tag = null;
            }
        }

Completing the Drag-and-Drop Operation

When an item (that is allowed to drop) is dropped on the target SurfaceListBox control, the DropEvent event is raised. The event handler adds the data in the cursor to the TargetItems collection that is bound to the control.

        private void OnDropTargetDrop(object sender, SurfaceDragDropEventArgs e)
        {
            TargetItems.Add(e.Cursor.Data as DataItem);
        }

Finally, the DragCompletedEvent event is raised. The source SurfaceListBox control is listening to this event. If the Effects property of the SurfaceDragCursor object that is passed to the event handler is set to Move, the data is removed from the SourceItems collection that is bound to the control.

        private void OnDragCompleted(object sender, SurfaceDragCompletedEventArgs e)
        {
            // If the operation is Move, remove the data from drag source.
            if (e.Cursor.Effects == DragDropEffects.Move)
            {
                SourceItems.Remove(e.Cursor.Data as DataItem);
            }
        }

Testing the Code

If you are testing this code on a Windows 7 computer that does not have touch functionality, you can use the Input Simulator tool to simulate touch input.

See Also

Did you find this information useful? Please send us your suggestions and comments.

© Microsoft Corporation. All rights reserved.
Show:
© 2014 Microsoft