Wpf 在MVVM中,拖动完成后打开上下文菜单
在一个高度互动的软件中,用户可以在Wpf 在MVVM中,拖动完成后打开上下文菜单,wpf,xaml,mvvm,drag-and-drop,prism,Wpf,Xaml,Mvvm,Drag And Drop,Prism,在一个高度互动的软件中,用户可以在UserControls的集合上进行拖放操作。放置时,应向其显示上下文菜单,提供有关如何执行操作的一些选项,例如,复制项目,或在放置位置有其他项目时交换位置 使用Prism框架,实现这一点的理想方式是通过交互请求触发器,例如: <i:Interaction.Triggers> <prism:InteractionRequestTrigger SourceObject="{Binding SomeCustomNotificationReq
UserControl
s的集合上进行拖放操作。放置时,应向其显示上下文菜单
,提供有关如何执行操作的一些选项,例如,复制项目,或在放置位置有其他项目时交换位置
使用Prism框架,实现这一点的理想方式是通过交互请求触发器
,例如:
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding SomeCustomNotificationRequest, Mode=OneWay}" >
<!-- some subclass of TriggerAction-->
<ContextMenu>
<MenuItem Header="Copy" />
<MenuItem Header="Swap" />
</ContextMenu>
<!-- end some subclass of TriggerAction-->
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
这就产生了一个疑问,即是否要在包含拖放式用户控件的项控件的XAML中实现交互请求触发器
,或者它是否应该进入用户控件
本身。对于后者,该特定UserControl
的各种实例如何“知道”哪一个将对交互请求作出反应
其次,InteractionRequestTrigger
的子元素必须是System.Windows.Interactivity.TriggerAction
。除了打开弹出窗口之外,这似乎没有被广泛用于任何其他用途。关于TriggerAction
的文档非常稀少,我不知道如何实现它的Invoke
方法。任何指向文档的指针都将不胜感激 使用InteractionRequestTrigger
无疑是一种方法,但是由于ContextMenu
控件与定义它的控件不在同一个视觉/逻辑树中,因此必须穿过一些黑暗的小巷
在谈到实际的代码之前,我还想强调一下我不赞成@Haukinger建议使用弹出窗口而不是上下文菜单的原因:同时提供了直接使用我为自定义通知定义的属性的优势(加上回调机制)通过IInteractionRequestAware
,我必须实现一些魔法,使弹出窗口出现在鼠标光标位置。另外,在我的特殊情况下,我通过上下文菜单单击操作数据模型,这意味着我必须使用弹出窗口的依赖项注入来访问数据模型的正确实例,坦白说,我也不知道该怎么做
无论如何,我用ContextMenu
使它能够顺利工作。这就是我所做的。(我不会发布明显的样板代码;请记住,我使用的是
A) 投球手
drop处理程序类必须增加一个我们可以调用drop的事件。此事件稍后将由属于承载拖放操作的视图的视图模型使用
public class MyCustomDropHandler : IDropTarget {
public event EventHandler<DragDropContextMenuEventArgs> DragDropContextMenuEvent;
public void Drop(IDropInfo dropInfo) {
// do more things if you like to
DragDropContextMenuEvent?.Invoke(this, new DragDropContextMenuEventArgs() {
// set all the properties you need to
});
}
// don't forget about the other methods of IDropTarget
}
B.2)XAML
作为MyContainerControl
设计元素的同级,我们为通知请求定义了InteractionTrigger
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding DragDropContextMenuNotificationRequest, ElementName=MyContainerControlRoot, Mode=OneWay}">
<local:ContextMenuAction ContextMenuDataContext="{Binding Data, Source={StaticResource Proxy}}">
<local:ContextMenuAction.ContextMenuContent>
<ContextMenu>
<MenuItem Header="Move">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<prism:InvokeCommandAction Command="{Binding MoveCommand}"
CommandParameter="{Binding DragDropActionElements}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
<MenuItem Header="Copy">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<prism:InvokeCommandAction Command="{Binding CopyCommand}"
CommandParameter="{Binding DragDropActionElements}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</local:ContextMenuAction.ContextMenuContent>
</local:ContextMenuAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
C.2)具有约束力的代理
您将注意到还有第二个依赖属性,名为ContextMenuDataContext
。这是一个问题的解决方案,该问题源于ContextMenu
与视图的其余部分不在同一个视觉/逻辑树中。找出这个解决方案花了我几乎和其他所有方法加起来一样长的时间,如果没有@Cameron McFarland的答案以及
事实上,我将参考这些参考资料中的代码。只需说我们需要使用绑定代理来设置ContextMenu
的DataContext
。我通过自定义TriggerAction
中的依赖项属性以编程方式解决了这一问题,因为ContextMenu
的DataContext
属性需要PlacementTarget
机制才能正常工作,这在本例中是不可能的,因为TriggerAction
(作为包含上下文菜单的元素
)没有自己的数据上下文
D) 收尾
回顾过去,实施起来并不难。有了以上内容,就可以轻松地连接托管MyContainerControl
的视图的视图模型中定义的一些命令,并通过通常的绑定机制和依赖属性传递这些命令。这允许在数据的最根处对数据进行操作
我对这个解决方案感到高兴;我不太喜欢的是,当发出自定义交互请求通知时,通信会加倍。但这是无济于事的,因为在drop handler中收集的信息必须以某种方式到达我们对用户可以在上下文菜单上做出的不同选择作出反应的地方。这有帮助吗@Haukinger它很有帮助,因为它强调我可能应该子类化ContextMenu
实现IInteractionRequestAware
。至少我认为这就是我的drop处理程序类中提出的SomeCustomNotificationRequest
没有被使用的原因。我看到你是那个在你发布的链接中提供了公认答案的人(顺便说一句,这个链接工作完美无瑕)。有什么建议吗?好吧,看来我有什么进展了:Prism手册提到必须在与使用它的视图相关的视图模型中提出自定义通知请求。事实上,如果我将带有自定义事件参数的EventHandler
添加到我的drop处理程序类(视图模型无论如何都会引用该类)中,并将侦听器添加到视图模型中的该事件处理程序中,我可以在该处理程序中引发自定义通知请求。InteractionRequestTrigger
现在可以正确调用我的TriggerAction
。我想剩下的只是视觉效果,当然还有ContextMenu
回调。我会在这里发布成功的消息
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding DragDropContextMenuNotificationRequest, ElementName=MyContainerControlRoot, Mode=OneWay}">
<local:ContextMenuAction ContextMenuDataContext="{Binding Data, Source={StaticResource Proxy}}">
<local:ContextMenuAction.ContextMenuContent>
<ContextMenu>
<MenuItem Header="Move">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<prism:InvokeCommandAction Command="{Binding MoveCommand}"
CommandParameter="{Binding DragDropActionElements}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
<MenuItem Header="Copy">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<prism:InvokeCommandAction Command="{Binding CopyCommand}"
CommandParameter="{Binding DragDropActionElements}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</local:ContextMenuAction.ContextMenuContent>
</local:ContextMenuAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
public class ContextMenuAction : TriggerAction<FrameworkElement> {
public static readonly DependencyProperty ContextMenuContentProperty =
DependencyProperty.Register("ContextMenuContent",
typeof(FrameworkElement),
typeof(ContextMenuAction));
public FrameworkElement ContextMenuContent {
get { return (FrameworkElement)GetValue(ContextMenuContentProperty); }
set { SetValue(ContextMenuContentProperty, value); }
}
public static readonly DependencyProperty ContextMenuDataContextProperty =
DependencyProperty.Register("ContextMenuDataContext",
typeof(FrameworkElement),
typeof(ContextMenuAction));
public FrameworkElement ContextMenuDataContext {
get { return (FrameworkElement)GetValue(ContextMenuDataContextProperty); }
set { SetValue(ContextMenuDataContextProperty, value); }
}
protected override void Invoke(object parameter) {
if (!(parameter is InteractionRequestedEventArgs args)) {
return;
}
if (!(ContextMenuContent is ContextMenu contextMenu)) {
return;
}
contextMenu.DataContext = ContextMenuDataContext;
contextMenu.IsOpen = true;
}
}