【问题标题】:How to force ContextMenu binding custom WPF/XAML behavior?如何强制 ContextMenu 绑定自定义 WPF/XAML 行为?
【发布时间】:2023-04-02 12:24:01
【问题描述】:

问题: 我们如何在自定义行为中访问分配给 MenuItem 的绑定命令? ContextMenu 不是可视化树的一部分,并且不会绑定,直到由于在自定义行为中被抑制而永远不会发生的单击事件。

目的 我有一个使用 Microsoft.Xaml.Behaviors 的自定义行为,旨在仅在用户单击 ListView 中的对象时显示上下文菜单。我想在访问视图标记中的引用 ICommand 时使用自定义行为修改命令参数。

代码:

自定义行为 (Microsoft.Xaml.Behaviors):

public class RightClickContextMenuBehavior : Behavior<ListView>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.ContextMenuOpening += AssociatedObject_ContextMenuOpening;
        AssociatedObject.PreviewMouseRightButtonDown += AssociatedObject_PreviewMouseRightButtonDown;
        AssociatedObject.PreviewMouseRightButtonUp += AssociatedObject_PreviewMouseRightButtonUp;
    }
    protected override void OnDetached()
    {
        base.OnDetached();
        AssociatedObject.ContextMenuOpening -= AssociatedObject_ContextMenuOpening;
        AssociatedObject.PreviewMouseRightButtonDown -=
        AssociatedObject_PreviewMouseRightButtonDown;
        AssociatedObject.PreviewMouseRightButtonUp -= AssociatedObject_PreviewMouseRightButtonUp;
    }
    private void AssociatedObject_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        //Store the click locations so that we can determine drag on mouse move.
        originPoint = e.GetPosition(null);
    
        //Grab the item from the ListView
        var listViewItem = TryFindFromPoint<ListViewItem>((UIElement)sender, 
            e.GetPosition(AssociatedObject));

        if (listViewItem == null)
            return;
    
        itemReference = listViewItem;
    }
    private void AssociatedObject_PreviewMouseRightButtonUp(object sender, 
        MouseButtonEventArgs e)
    {
        if (itemReference != null)
        {
            // Only display the context menu if the user clicked on the object
            // inside the listview, not the listview itself.
            var targetPosition = e.GetPosition((UIElement)itemReference);
            HitTestResult hitResult = VisualTreeHelper.HitTest((UIElement)itemReference, 
                targetPosition);

            if (hitResult != null && sender is ListView)
            {
                var listView = sender as ListView;
                
                //Set the context menu's visibility flag
                listView.ContextMenu.IsOpen = true; 
    
                foreach (var item in listView.ContextMenu.Items)
                {
                    if (item is MenuItem)
                    {
                        var customMemuItem = item as MenuItem;

                        //Apply the item the was in our hitbox as context.
                        customMemuItem.CommandParameter = itemReference.DataContext; 
                        /*
                            ISSUE:
                            Bindings here are always null, how do I force the bound 
                            ICommand in the XAML markup to be available here.  I'd like
                            to use the Command binding from the markup, and functionally 
                            apply my DataContext customMemuItem.Command is null
                        */
                    }
                }
                e.Handled = true; //Handle the bubble
            }
        }
        else
        {
            if (sender is ListView)
            {
                //Hide the context menu.
                var listView = sender as ListView;
                listView.ContextMenu.IsOpen = false;
                e.Handled = true;
            }
        }
    }
}

查看:

<ListView Grid.Row="1"
          ItemsSource="{Binding Path= ItemQueue, IsAsync=True}"
          ScrollViewer.CanContentScroll="True"
          ScrollViewer.VerticalScrollBarVisibility="Auto"
          ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <behaviors:Interaction.Behaviors>
        <commandbehaviors:RightClickContextMenuBehavior />
    </behaviors:Interaction.Behaviors>
    <ListView.ContextMenu>
        <ContextMenu DataContext="{Binding PlacementTarget.DataContext,
                                           RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Context Menu Command"
                      Command="{Binding Source={x:Type models:MyViewModel}, 
                      Path=BindingContext.MenuTestCommand}"
                      CommandParameter="{Binding}"/>
        </ContextMenu>
    </ListView.ContextMenu>
    [...]
</ListView>

编辑#1: 我们正在使用 ListView 所在的 MVVM:

public partial class MyViewModelView : UserControl
{
    public DocumentTileManagementView()
    {
        InitializeComponent();
    }
}
public class MyViewModel : ViewModelBase
{
    public MyViewModel()
    {
        MenuTestCommand = new ApplicationRelayCommand(MenuTestCommandBehavior);
    }


    public ObservableCollection<ObjectViewModel> ItemQueue
    {
        get
        {
            return _ItemQueue;
        }
        set
        {
            this.MutateVerbose(ref _ItemQueue, value, this.RaisePropertyChanged());
        }
    }
    private ObservableCollection<MyDataObject> _ItemQueue= new ObservableCollection<MyDataObject>();
   
    public ICommand MenuTestCommand { get; }
    private async void MenuTestCommandBehavior(object obj)
    {

    }
}

我还没有找到一种在不允许 ListView 上的初始右键单击事件链的情况下调用绑定的方法。

我有一个可行的解决方案,我允许第一次右键单击事件并覆盖每次后续单击中的行为。我想在自定义 (Microsoft.Xaml.Behaviors) 行为中调用第一次右键单击期间发生的任何事情。

【问题讨论】:

  • MenuTestCommandItemQueue 是否定义在同一个视图模型类中?
  • 两个项目都在同一个视图模型上,其中MenuTestCommandICommandItem QueueObservableCollection

标签: c# wpf xaml mvvm asp.net-core-3.1


【解决方案1】:

我没有尝试覆盖整个事件链,而是附加到 ContextMenuOpening 事件。在第一个PreviewMouseRightButtonUp 上,我们允许事件链从标记完成绑定所有参数和命令。 ContextMenuOpening 事件允许初始命中框配置和CommandParameter 分配。

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.ContextMenuOpening += AssociatedObject_ContextMenuOpening;
        [...]  
    }

    private void AssociatedObject_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (!isFirstClick)
        {
            isFirstClick = true;
            e.Handled = false;
            AssociatedObject.ContextMenu.Visibility = Visibility.Collapsed;
            return;
        }
        else 
        {
            [...]        
        }
    }
    private void AssociatedObject_ContextMenuOpening(object sender, ContextMenuEventArgs e)
    {
        if (AssociatedObject.Items.Count > 0)
        {
            var listViewItem = TryFindFromPoint<ListViewItem>((UIElement)e.OriginalSource, new Point(e.CursorLeft, e.CursorTop));
            foreach (var item in AssociatedObject.ContextMenu.Items)
            {
                if (item is MenuItem && listViewItem != null)
                {
                    var customMemuItem = item as MenuItem;
                    customMemuItem.CommandParameter = listViewItem.DataContext;
                }
            }
            AssociatedObject.ContextMenu.Visibility = Visibility.Visible;
            AssociatedObject.ContextMenu.IsOpen = true;
        }
        else
        {
            AssociatedObject.ContextMenu.Visibility = Visibility.Collapsed;
            AssociatedObject.ContextMenu.IsOpen = false;
        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 1970-01-01
    • 2013-07-09
    • 2014-07-05
    • 2011-08-06
    • 2012-06-18
    相关资源
    最近更新 更多