WPF UIEventHub register a number of events and distributed to registered UIEventProcessor
, available UIEventProcessor included MultiSelectEventProcessor
and DragDropEventProcessor
, which is an update for SelectionHelper
and FileDragDropHelper
static class.
How to use?
UIEventHub is created to support ListBox/View and TreeView, you can use xaml syntax or cs code to register the UIProcessors.
Xaml:
<ListView Grid.Column="1" Grid.Row="2" >
<bc:UIEventAdapter.Processors>
<bc:DragDropEventProcessor EnableDrag="True" EnableDrop="True" />
<bc:MultiSelectEventProcessor UnselectAllCommand="{Binding UnselectAllCommand}" />
</bc:UIEventAdapter.Processors>
</ListView>
Cs:
treeView.RegisterEventProcessors(new DragDropEventProcessor());
listView.RegisterEventProcessors(new DragDropEventProcessor(), new MultiSelectEventProcessor(vm1.UnselectAllCommand));
Please noted that TreeView does not support Multi-Select.
For Drag and Drop, in addition to register the UIProcessor, you also need to implement ISupportDrag/Drop/DragHelper/DropHelper for the system to work, it will be described below.
UIEventProcessor
public interface IUIEventProcessor
{
IEnumerable<RoutedEvent> ProcessEvents { get; }
IScriptCommand OnEvent(RoutedEvent eventId);
IScriptCommand OnMouseDrag { get; } IScriptCommand OnMouseDragOver { get; }
....
}
public interface IScriptCommand
{
string CommandKey { get; }
IScriptCommand Execute(ParameterDic pm);
bool CanExecute(ParameterDic pm);
}
UIEventProcessor
includes a number of IScriptCommand
s has OnEvent()
method. When certain event is triggered, a ScriptRunner is created and run the appropriate IScriptCommand
(e.g. Control.MouseMove -> OnEvent() -> ContinueDrag). Because IScriptCommand.Execute()
can return another IScriptCommand
, complex operations are broken up into multiple IScriptCommand
s.
public class ScriptRunner : IScriptRunner
{
public void Run(Queue<IScriptCommand> cmds, ParameterDic initialParameters)
{
ParameterDic pd = initialParameters;
while (cmds.Any())
{
var current = cmds.Dequeue();
if (current.CanExecute(pd))
{
var retCmd = current.Execute(pd);
if (retCmd != null)
cmds.Enqueue(retCmd);
}
else throw new Exception(String.Format("Cannot execute {0}", current));
}
}
}
Using UIEventHub
, Drag and Drop Helper and MultiSelect Helper (static classes) in FileExplorer is converted to DragDropEventProcessor
and MultiSelectEventProcessor
.
DragDropEventProcessor
DragDropEventProcessor enable dragging one or multi object from one DataContext (which implements ISupportDrag) to another DataContext (which implements ISupportDrop).
Control does not have Drag event, it’s trigger when MouseMove (while a mouse button is pressed) more than SystemParameters.MinimumHorizontalDragDistance or MinimumVerticalDragDistance.
public interface ISupportDragHelper
{
ISupportDrag DragHelper { get; }
}
public interface ISupportDrag
{
bool HasDraggables { get; }
IEnumerable<IDraggable> GetDraggables();
DragDropEffects QueryDrag(IEnumerable<IDraggable> draggables);
IDataObject GetDataObject(IEnumerable<IDraggable> draggables);
void OnDragCompleted(IEnumerable<IDraggable> draggables, IDataObject da, DragDropEffects effect);
}
So whenever user trying to drag, this processor will check if DataContext supports ISupportDrag
or ISupportDragHelper
, and if HasDraggables
equals true, it then calls GetDraggables()
and GetDataObject() to get an dataobject, and initialize the drag (using System.Windows.DragDrop.DoDragDrop
).
Because it uses DoDragDrop
, you can create a DataObject
for shell file drop (to Windows Explorer, VS, but doesn’t work on notepad). For files that's not exists in the file system, you can use the included VirtualDataObject
to delay creation of the files as well.
That’s the drag operation, then we have drop operation.
Whenever items (DataObject
) is dragged over or dropped on a registered control, the processor looks up the LogicalTree for a DataContext that implements ISupportDrop
and it’s IDroppable
equals true, so If DataContext of the current item, for examples, ListViewItem, does not support ISupportDrop
, it will lookup the ListView.
public interface ISupportDropHelper
{
ISupportDrop DropHelper { get; }
}
public interface ISupportDrop
{
bool IsDraggingOver { set; }
bool IsDroppable { get; }
string DropTargetLabel { get; }
QueryDropResult QueryDrop(IDataObject da, DragDropEffects allowedEffects);
IEnumerable<IDraggable> QueryDropDraggables(IDataObject da);
DragDropEffects Drop(IEnumerable<IDraggable> draggables, IDataObject da, DragDropEffects allowedEffects);
}
DragDropEventProcessor
show an adorner (DragAdorner
) under your mouse cursor, it has an ItemsControl which loaded from ISupportDrop.QueryDropDraggables()
. You have to specify a ItemTemplate for it, without template it shows the items in text.
<DataTemplate x:Key="dragnDropHintTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}" />
<TextBlock Text="[drag]" Foreground="Gray" />
</StackPanel>
</DataTemplate>
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}" >
<Setter Property="ItemTemplate" Value="{StaticResource dragnDropItemTemplate}" />
<Setter Property="ItemsSource" Value="{Binding Items}" />
<Setter Property="AllowDrop" Value="True" />
<Setter Property="bc:AttachedProperties.DragItemTemplate"
Value="{StaticResource dragnDropHintTemplate}" />
</Style>
The drag adorner also contains a textblock that shown “Copy n items to {target}”, the target text is loaded from ISupportDrop.DropTargetLabel.
This drag adorner is reside in an adorner layer named PART_DragDropAdorner
, this adorner can be shared, so it’s ideal to put it under root control like Windows or Page.
<AdornerDecorator x:Name="PART_DragDropAdorner" />
QueryDrop allow you to return multiple supported DragDropEffects and one preferred DragDropEffect. If user initiate the drag and drop in the same application using right mouse, a context menu is shown (ShowAdornerContextMenu) for choosing the desired DragDropEffect. In this case, ISupportDrag.OnDragCompleted()
is called after user select a DragDropEffect, instead of immediately after Drop.
MultiSelectEventProcessor
MultiSelectEventProcessor allow you to choose multiple items by dragging with mouse on ListView, this is very basic functionality and it’s wired that the framework still doesn’t provide it.
When dragging, an adorner (SelectionAdorner) is shown, it has 0.5 opacity so the items below are visible. The adorner is reside in adorner layer inside the control. The items under the SelectionAdorner have it’s AttachedProperties.IsSelecting set to true, you can highlight your items when selecting.
<DataTemplate x:Key="dragnDropItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}" />
<TextBlock Text="[ing]" Foreground="Blue"
Visibility="{Binding (bc:AttachedProperties.IsSelecting),
RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}},
Converter={StaticResource btvc}}" />
<TextBlock Text="[ed]" Foreground="Green"
Visibility="{Binding IsSelected, Converter={StaticResource btvc}}" />
</StackPanel>
</DataTemplate>
The selection lookup routines (FindSelectedItems) is a combination of the first and second articles, it uses first/last selected item for GridView, IChildInfo for panels that support IChildInfo, otherwise HitTest.
public interface IChildInfo
{
Rect GetChildRect(int itemIndex);
}
If HitTest, MultiSelectEventProcessor cannot correctly return all selected items (they cannot be hit test if destroyed or not shown), so Highlight/SelectItemsByUpdate are used, which add or remove items from the selection list. For the another two methods, HighlightItems/SelectItems are used, which replace the selection list.
Conclusion
The reason for rewriting this two static class is to simplify it so I can implement touch related code. I cannot implement them yet because I don’t have a machine that support touch right now, but I found implement the code in a scriptable way (learned from REST in Pratice) can make the code reusable and easy to read.
I hope this rewrite can reduce the effort needed to implement other touch based code and other gesture.
This article has been posted on CodeProject. You can find a list of my articles here.