在MVVM中,拖动完成后打开上下文菜单 [英] In MVVM, open context menu upon drag completion

查看:79
本文介绍了在MVVM中,拖动完成后打开上下文菜单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在高度交互的软件中,用户可以对 UserControl s的集合进行拖放操作。放下后,应该为他们提供一个 ContextMenu ,提供有关如何执行操作的一些选择,例如,复制该项目,或者如果放置另一个项目,则交换位置位置。

In a software that is highly interactive, the user can do drag and drop operations on a collection of UserControls. Upon drop, they should be presented with a ContextMenu offering some choices on how to perform the action, e.g., copy the item, or swap positions if there is another item at the drop location.

使用棱镜框架,实现此目标的理想方法是使用 InteractionRequestTrigger 实例:

Using the Prism framework, the ideal way of implementing this would be by means of the InteractionRequestTrigger, for instance:

<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>

这使人们怀疑是否要实施 InteractionRequestTrigger ItemsControl 的XAML中的c>包含可拖放的 UserControl ,或者是否应该放入 UserControl 本身。如果是后者,该特定 UserControl 的各个实例如何知道哪个将对交互请求做出反应?

This raises a doubt on whether to implement the InteractionRequestTrigger in the XAML of the ItemsControl containing the drag-and-droppable UserControl, or if it should go into the UserControl itself. In case of the latter, how would the various instances of that particular UserControl "know" which one is to react on the interaction request?

第二, InteractionRequestTrigger 的子元素必须是 System.Windows.Interactivity.TriggerAction 。似乎除了打开弹出窗口外,它还没有广泛使用。 TriggerAction 的文档非常稀疏,我不知道如何实现其 Invoke 方法。

Second, the child element of an InteractionRequestTrigger must be a System.Windows.Interactivity.TriggerAction. It seems that this is not widely used for anything other than opening popup windows. The documentation on TriggerAction is quite sparse, and I don’t know how to go about implementing its Invoke method. Any pointer to documentation would be much appreciated!

推荐答案

使用 InteractionRequestTrigger 非常感谢!肯定是 的解决方法,但是由于 ContextMenu 控件与定义它的控件不在同一个视觉/逻辑树中,必须走过一些黑暗的小巷。

Using an InteractionRequestTrigger definitely is the way to go here, but since the ContextMenu control doesn’t reside in the same visual/logical tree as the control that defines it, one has to walk through some dark alleys.

在进入实际代码之前,我还要强调指出我不赞成使用@Haukinger的建议的原因。一个弹出窗口而不是 ContextMenu :同时具有直接使用我为自定义 Notification (加上回调机制),通过 IInteractionRequestAware ,我不得不实现一些魔术,以使弹出窗口出现在鼠标光标位置。另外,在我的特定情况下,由于上下文菜单的单击,我正在操纵数据模型,这意味着我必须在弹出窗口中使用依赖项注入才能访问我的数据模型的正确实例,

Before coming to the actual code, I’d also highlight the reason I didn’t go for @Haukinger’s suggestion to use a popup window instead of a ContextMenu: while providing the advantage of making direct use of the properties I define for my custom Notification (plus the callback mechanism) by means of IInteractionRequestAware, I’d have had to implement some magic to make the popup window appear at the mouse cursor location. Plus, in my particular case, I’m manipulating the data model as a result of the context menu click, meaning that I’d have had to use dependency injection with the popup window in order to access the correct instance of my data model, which I frankly don’t know how to do, either.

无论如何,我可以通过 ContextMenu 。这就是我所做的。 (我不会发布明显的样板代码;请记住,我正在将 Prism GongSolutions拖放库

Anyway, I got it to work smoothly with a ContextMenu. Here’s what I did. (I won’t post the obvious boilerplate code; just keep in mind that I’m using Prism with the GongSolutions Drag and Drop Library.

放置处理程序类必须增加一个我们可以在放置时调用的事件。稍后,属于托管拖放操作的视图的视图模型将使用此事件。

The drop handler class must be augmented with an event that we can call upon drop. This event will later be consumed by the view model belonging to the view that’s hosting the drag and drop action.

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
}

DragDropContextMenuEventArgs 很简单;如果需要帮助,请参阅棱镜手册。

The DragDropContextMenuEventArgs is straightforward; refer to the Prism manual if you need assistance.

对于我来说,已经有一个自定义的 UserControl ,该托管了我要拖放的元素。它的视图模型需要 InteractionRequest 以及一个对象,该对象收集自变量并与 ContextMenu 。这是因为 ContextMenu 没有实现 IInteractionRequestAware ,这意味着我们必须使用标准的调用方式命令动作。我只是使用了上面定义的 DragDropContextMenuEventArgs ,因为它是一个已经包含所有必需属性的对象。

In my case, I’ve got a custom UserControl that’s hosting the elements I want to drag and drop. Its view model needs an InteractionRequest as well as an object that gathers the arguments to pass along with a click command on the ContextMenu. This is because the ContextMenu doesn’t implement IInteractionRequestAware, which means we’ll have to use the standard way of invoking command actions. I’ve simply used the DragDropContextMenuEventArgs defined above, since it’s an object that already hosts all the required properties.

这利用了带有相应接口的自定义通知请求,该接口的实现非常简单。为了使该条目更易于管理,我将在此处跳过代码。关于StackExchange的话题很多。例如,请参阅@Haukinger链接,作为对我原始问题的评论。

This makes use of a custom notification request with a corresponding interface, the implementation of which is straightforward. I’ll skip the code here to keep this entry more manageable. There’s a lot on StackExchange on the topic; see, for instance, the link @Haukinger provided as a comment to my original question.

public InteractionRequest<IDragDropContextMenuNotification> DragDropContextMenuNotificationRequest { get; set; }

public DragDropContextMenuEventArgs DragDropActionElements { get; set; }

public MyContainerControlConstructor() {
  DragDropContextMenuNotificationRequest = new InteractionRequest<IDragDropContextMenuNotification>();
  MyCustomDropHandler.DragDropContextMenuEvent += OnDragDropContextMenuShown;
}

private void OnDragDropContextMenuShown(object sender, DragDropContextMenuEventArgs e) {
  DragDropActionElements = e;
  DragDropContextMenuNotificationRequest.Raise(new DragDropContextMenuNotification {
    // you can set your properties here, but it won’t matter much
    // since the ContextMenu can’t consume these
  });
}



B.2)XAML



作为 MyContainerControl 的设计元素的同级,我们为通知请求定义 InteractionTrigger 。 / p>

B.2) XAML

As a sibling to the design elements of MyContainerControl, we define the InteractionTrigger for the notification request.

<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)触发动作和其他魔法



这是棘手的地方。首先,我们需要定义一个自定义 TriggerAction 来调用 ContextMenu

ContextMenuContent 依赖项属性可确保我们可以定义 ContextMenu 作为自定义 TriggerAction 的内容。在 Invoke 方法中,经过几次安全检查后,我们可以弹出上下文菜单。 (用户单击选项后,鼠标位置和破坏上下文菜单由WPF处理。)

The ContextMenuContent dependency property makes sure that we can define a ContextMenu as content of our custom TriggerAction. In the Invoke method, after a couple of safety checks, we can make the context menu pop up. (Mouse location and destroying the context menu after the user clicked an option is handled by WPF.)

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;
  }
}



C.2)绑定代理



您将注意到,还有另一个依赖项属性,称为 ContextMenuDataContext 。这是由于 ContextMenu 与视图其余部分不在同一个视觉/逻辑树之内而引起的问题的解决方案。弄清楚这个解决方案花了我几乎所有其他东西的总和,如果不是@ Cameron-McFarland对找不到参考'RelativeSource FindAncestor'以及上下文菜单上的WPF教程

C.2) Binding Proxy

You’ll note that there’s a second dependency property called ContextMenuDataContext. This is the solution to a problem that arises from the fact that a ContextMenu doesn’t live inside the same visual/logical tree as the rest of the view. Figuring out this solution took me almost as long as all the rest of this combined, and I wouldn’t have gotten there if it wasn’t for @Cameron-McFarland’s answer to Cannot find source for binding with reference 'RelativeSource FindAncestor' as well as the WPF Tutorial on Context Menus.

实际上,我将参考那些代码的资源。只需说我们需要使用绑定代理来设置 ContextMenu DataContext 。我决定通过自定义 TriggerAction 中的依赖项属性以编程方式执行此操作,因为<$ c $的 DataContext 属性c> ContextMenu 需要 PlacementTarget 机制才能正常工作,在这种情况下这是不可能的,因为 TriggerAction (作为包含 ContextMenu 的元素)没有自己的数据上下文。

In fact, I’ll refer to those resources for the code. Suffice it to say that we need to use a binding proxy to set the ContextMenu’s DataContext. I resolved doing this programmatically via a dependency property in my custom TriggerAction, since the DataContext property of the ContextMenu needs the PlacementTarget mechanism to work properly, which isn’t possible in this case, since the TriggerAction (as element containing the ContextMenu) doesn’t have its own data context.

回顾起来,实现起来并不难。有了上面的内容,就可以钩住托管 MyContainerControl 的视图的视图模型中定义的一些命令,并通过通常的绑定机制和依赖项属性来传递这些命令。

In retrospective, it wasn’t that hard to implement. With the above in place, it’s child’s play to hook up some commands defined in the view model of the view that hosts the MyContainerControl and pass those via the usual binding mechanism and dependency properties. This allows for manipulation of the data at its very root.

我对这种解决方案感到高兴;我不太喜欢的是,当自定义互动请求通知发出时,交流会加倍。但这无济于事,因为在放置处理程序中收集的信息必须以某种方式到达我们对用户可以在上下文菜单上做出的不同选择做出反应的地方。

I’m happy about this solution; what I don’t like that much is that communication is doubled when the custom interaction request notification is raised. But this can’t be helped, since the information gathered in the drop handler must somehow reach the place where we react upon the different choices the user can make on the context menu.

这篇关于在MVVM中,拖动完成后打开上下文菜单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆