WPF UIEventHub – 多选和拖放






4.86/5 (3投票s)
我希望这次重写可以减少实现其他基于触摸的代码和其他手势的工作量。
WPF UIEventHub 注册了许多事件,并将它们分发给已注册的 UIEventProcessor
,可用的 UIEventProcessor 包括 MultiSelectEventProcessor
和 DragDropEventProcessor
,它们是对 SelectionHelper
和 FileDragDropHelper
静态类的更新。
如何使用?
UIEventHub 用于支持 ListBox/View 和 TreeView,您可以使用 xaml 语法或 cs 代码注册 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));
请注意,TreeView 不支持多选。
对于拖放,除了注册 UIProcessor 之外,您还需要为系统实现 ISupportDrag/Drop/DragHelper/DropHelper,这将在下面描述。
UIEventProcessor
/// /// Allow one application (e.g. dragging) to handle events from an control. /// public interface IUIEventProcessor { IEnumerable<RoutedEvent> ProcessEvents { get; } IScriptCommand OnEvent(RoutedEvent eventId);IScriptCommand OnMouseDrag { get; }//Previous methodIScriptCommand OnMouseDragOver { get; } ....} public interface IScriptCommand { string CommandKey { get; } IScriptCommand Execute(ParameterDic pm); bool CanExecute(ParameterDic pm); }
UIEventProcessor
包含多个 具有 IScriptCommand
OnEvent()
方法。当触发特定事件时,会创建一个 ScriptRunner 并运行相应的 IScriptCommand
(例如,Control.MouseMove -> OnEvent() -> ContinueDrag)。由于 IScriptCommand.Execute()
可以返回另一个 IScriptCommand
,因此将复杂的操作分解为多个 IScriptCommand
。
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)); } } }
使用 UIEventHub
,FileExplorer 中的拖放助手和多选助手(静态类)被转换为 DragDropEventProcessor
和 MultiSelectEventProcessor
。
DragDropEventProcessor
DragDropEventProcessor 允许您将一个或多个对象从一个 DataContext(实现 ISupportDrag)拖动到另一个 DataContext(实现 ISupportDrop)。
Control 没有 Drag 事件,当 MouseMove(按住鼠标按钮)超过 SystemParameters.MinimumHorizontalDragDistance 或 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); }
因此,每当用户尝试拖动时,此处理器将检查 DataContext 是否支持 ISupportDrag
或 ISupportDragHelper
,如果 HasDraggables
为 true,则调用 GetDraggables()
和 GetDataObject() 以获取数据对象,并初始化拖动(使用 System.Windows.DragDrop.DoDragDrop
)。
因为它使用 DoDragDrop
,您可以为 shell 文件拖放(到 Windows 资源管理器、VS,但在记事本上不起作用)创建一个 DataObject
。 对于文件系统中不存在的文件,您可以使用随附的 VirtualDataObject
来延迟创建文件。
这是拖动操作,然后我们有放置操作。
每当项目 (DataObject
) 被拖到或放置在已注册的控件上时,处理器会在 LogicalTree 中查找一个实现 ISupportDrop
且其 IDroppable
为 true 的 DataContext,因此如果当前项目的 DataContext(例如 ListViewItem)不支持 ISupportDrop
,它将查找 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
在您的鼠标光标下显示一个装饰器 (DragAdorner
),它有一个从 ISupportDrop.QueryDropDraggables()
加载的 ItemsControl。 您必须为其指定一个 ItemTemplate,如果没有模板,它会以文本形式显示项目。
<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>
拖动装饰器还包含一个文本块,显示“将 n 个项目复制到 {target}”,目标文本是从 ISupportDrop.DropTargetLabel
加载的。
此拖动装饰器位于名为 PART_DragDropAdorner
的装饰器层中,此装饰器可以共享,因此将其放在根控件(如 Windows 或 Page)下是理想的选择。
<AdornerDecorator x:Name="PART_DragDropAdorner" />
QueryDrop 允许您返回多个 支持的 DragDropEffects 和一个 首选的 DragDropEffect。 如果用户使用鼠标右键在同一应用程序中启动拖放操作,则会显示一个上下文菜单 (ShowAdornerContextMenu) 以供选择所需的 DragDropEffect。 在这种情况下,ISupportDrag.OnDragCompleted()
在用户选择 DragDropEffect 之后被调用,而不是在 Drop 之后立即调用。
MultiSelectEventProcessor
MultiSelectEventProcessor 允许您通过在 ListView 上使用鼠标拖动来选择多个项目,这非常基本的功能,框架仍然没有提供它真是奇怪。
拖动时,会显示一个装饰器(SelectionAdorner),它的透明度为 0.5,因此下面的项目是可见的。 装饰器位于控件内的装饰器层中。 SelectionAdorner 下的项目将其 AttachedProperties.IsSelecting 设置为 true,您可以在选择时突出显示您的项目。
<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>
选择查找例程 (FindSelectedItems) 是 第一篇文章 和 第二篇文章 的组合,它使用 GridView 的第一个/最后一个选定项目,IChildInfo 用于支持 IChildInfo 的面板,否则使用 HitTest。
public interface IChildInfo { Rect GetChildRect(int itemIndex); }
如果使用 HitTest,MultiSelectEventProcessor 无法正确返回所有选定项目(如果被销毁或未显示,则无法进行命中测试),因此使用 Highlight/SelectItemsByUpdate,它们从选择列表中添加或删除项目。 对于另外两种方法,使用 HighlightItems/SelectItems,它们会替换选择列表。
结论
重写这两个静态类的原因是为了简化它们,以便我可以实现与触摸相关的代码。 我目前无法实现它们,因为我现在没有支持触摸的机器,但我发现以脚本化方式实现代码(从 Pratice 中的 REST 学习)可以使代码可重用且易于阅读。
我希望这次重写可以减少实现其他基于触摸的代码和其他手势的工作量。
本文已发布在 CodeProject 上。 您可以在 此处 找到我的文章列表。