0

我有一个定义上下文菜单的 DataTemplate:

<DataTemplate>
    <TextBlock>
        <TextBlock.ContextMenu>
            <ContextMenu>
                <MenuItem Command="{Binding SendToRecycleBin}" Header="Delete">
            </ContextMenu>
        </TextBlock.ContextMenu>
    </TextBlock>
</DataTemplate>

我想将另一个菜单项添加到上下文菜单中,仅当用户在打开上下文菜单时按住 Shift 时才显示,仅使用 XAML(也许创建一个新的附加属性 App.PowerUserOnly?):

<MenuItem Command="{Binding Delete}" Header="Permanently Delete"
                                     local:App.PowerUserOnly="true">

这可以仅在 XAML 中完成(如果可以,如何?),还是必须使用后面的代码?

编辑:当在打开上下文菜单时按住 Shift 时,Windows shell 还会显示高级选项。我试图模仿这种行为。例如,应用程序的高级选项之一是以不同用户身份运行它。

我简化了我的代码来帮助测试人们的建议。该项目是在 VS2010 中使用名为 ShiftContextMenu 的默认 WPF 应用程序创建的。App.xaml 和 App.xaml.cs 文件未修改。

MainWindow.xaml:

<Window x:Class="ShiftContextMenu.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="DummyItemTemplate">
            <TextBlock Text="{Binding Name}">
                <TextBlock.ContextMenu>
                    <ContextMenu>
                        <MenuItem Command="{Binding SendToRecycleBin}" Header="Delete" />
                        <MenuItem Command="{Binding Delete}" Header="Permanently Delete" />
                    </ContextMenu>
                </TextBlock.ContextMenu>
            </TextBlock>
        </DataTemplate>
    </Window.Resources>
    <TreeView Name="tvMain" ItemTemplate="{StaticResource DummyItemTemplate}" ItemsSource="{Binding DummyItems}" />
</Window>

MainWindow.xaml.cs:

using System.Collections.Generic;
using System.Windows;
using System.Collections.ObjectModel;

namespace ShiftContextMenu
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DummyItem[] dummyItems = new DummyItem[] {
                new DummyItem("First"),
                new DummyItem("Second"),
                new DummyItem("Third")
            };
            DummyItems = new ReadOnlyCollection<DummyItem>(new List<DummyItem>(dummyItems));
            this.DataContext = this;
            InitializeComponent();
        }

        public ReadOnlyCollection<DummyItem> DummyItems { get; protected set; }
    }
}

ViewModelBase.cs:

using System.ComponentModel;

namespace ShiftContextMenu
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        protected PropertyChangedEventHandler _propertyChangedEvent;

        protected void SendPropertyChanged(string propertyName)
        {
            if (_propertyChangedEvent != null)
            {
                _propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                _propertyChangedEvent += value;
            }
            remove
            {
                _propertyChangedEvent -= value;
            }
        }
    }
}

DummyItem.cs:

using System;
using System.Windows.Input;
using System.Windows;

namespace ShiftContextMenu
{
    public class DummyItem : ViewModelBase
    {
        public string Name { get; protected set; }

        public DummyItem(string name)
        {
            Name = name;
            _sendToRecycleBinCommand = new SendToRecycleBinCommand();
            _deleteCommand = new DeleteCommand();
        }

        protected SendToRecycleBinCommand _sendToRecycleBinCommand;
        protected DeleteCommand _deleteCommand;

        public ICommand SendToRecycleBin { get { return _sendToRecycleBinCommand; } }
        public ICommand Delete { get { return _deleteCommand; } }

        protected class SendToRecycleBinCommand : ICommand
        {
            public void Execute(object parameter)
            {
                MessageBox.Show("Send To Recycle Bin");
            }

            public bool CanExecute(object parameter)
            {
                return true;
            }

            public event EventHandler CanExecuteChanged { add { } remove { } }
        }

        protected class DeleteCommand : ICommand
        {
            public void Execute(object parameter)
            {
                MessageBox.Show("Permanently Delete");
            }

            public bool CanExecute(object parameter)
            {
                return true;
            }

            public event EventHandler CanExecuteChanged { add { } remove { } }
        }
    }
}
4

2 回答 2

2

Windows shell 行为解决方案:

在此解决方案中,我使用了两个程序集:

  1. System.Windows.Interactivity
  2. System.Windows.Forms

将以下命名空间添加到窗口:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

数据模板应如下所示:

<DataTemplate x:Key="DummyItemTemplate">
            <TextBlock Text="{Binding Name}">
                <TextBlock.ContextMenu>
                    <ContextMenu> 
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Opened">
                                <i:InvokeCommandAction Command="{Binding ShowMoreOptions}" />
                            </i:EventTrigger>
                            <i:EventTrigger EventName="Closed">
                                <i:InvokeCommandAction Command="{Binding HideMoreOptions}" />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                        <MenuItem Command="{Binding SendToRecycleBin}" Header="Delete" />
                        <MenuItem Command="{Binding Delete}" Header="Permanently Delete">
                        <MenuItem.Style>
                            <Style TargetType="MenuItem">
                                <Setter Property="Visibility" Value="Collapsed" />
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding IsVisibleDelete}" Value="True">
                                        <Setter Property="Visibility" Value="Visible" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </MenuItem.Style>    
                    </MenuItem>
                    </ContextMenu>
                </TextBlock.ContextMenu>
            </TextBlock>
        </DataTemplate>

DummyItem课堂上,您必须添加两个命令和一个属性:

private bool _isVisibleDelete;
public bool IsVisibleDelete
{
    get { return _isVisibleDelete; }
    set { _isVisibleDelete = value; SendPropertyChanged("IsVisibleDelete"); }
}

public ICommand ShowMoreOptions { get; private set; }
private void OnShowMoreOptions()
{
    if (System.Windows.Forms.Control.ModifierKeys == System.Windows.Forms.Keys.Shift)
        IsVisibleDelete = true;
}

public ICommand HideMoreOptions { get; private set; }
private void OnHideMoreOptions()
{
    IsVisibleDelete = false;
}

在我的示例中,我DelegateCommandMicrosoft.Practices.Prism程序集中使用。

所以在ctor中DummyItem我有:

 ShowMoreOptions = new DelegateCommand(this.OnShowMoreOptions);
 HideMoreOptions = new DelegateCommand(this.OnHideMoreOptions);

第二种解决方案允许您动态更改此菜单:

你可以尝试这样的事情:

XAML 文件:

<TextBlock>
            <TextBlock.ContextMenu>
                <ContextMenu>
                    <ContextMenu.InputBindings>
                        <KeyBinding Modifiers="Shift" Key="Shift" Command="{Binding ShowMoreOptions}" />
                    </ContextMenu.InputBindings>
                    <MenuItem Command="{Binding SendToRecycleBin}" Header="Delete" />
                    <MenuItem Command="{Binding Delete}" Header="Permanently Delete">
                        <MenuItem.Style>
                            <Style TargetType="MenuItem">
                                <Setter Property="Visibility" Value="Collapsed" />
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding IsVisibleDelete}" Value="True">
                                        <Setter Property="Visibility" Value="Visible" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </MenuItem.Style>    
                    </MenuItem>
                </ContextMenu>
            </TextBlock.ContextMenu>
        </TextBlock>

在您的 ViewModel 类中,您应该添加属性和命令来更改MenyItem可见性属性:

private bool _isVisibleDelete;
public bool IsVisibleDelete
{
    get { return _isVisibleDelete; }
    set { _isVisibleDelete = value; RaisePropertyChanged(() => IsVisibleDelete); }
}

public ICommand ShowMoreOptions { get; private set; }
private void OnShowMoreOptions()
{
    IsVisibleDelete = !IsVisibleDelete;
}
于 2012-10-25T15:57:01.193 回答
2

kmatyaszek 的回答有效,但我不喜欢修改我的 ViewModel。所以我最终创建了我在问题中提出的附加依赖属性:

public static readonly DependencyProperty PowerUserOnlyProperty =
    DependencyProperty.RegisterAttached(
        "PowerUserOnly", 
        typeof(bool), 
        typeof(App), 
        new UIPropertyMetadata(false, new PropertyChangedCallback(PUOChanged)));

public static bool GetPowerUserOnly(MenuItem obj)
{
    return (bool)obj.GetValue(PowerUserOnlyProperty);
}

public static void SetPowerUserOnly(MenuItem obj, bool value)
{
    obj.SetValue(PowerUserOnlyProperty, value);
}

public static void PUOChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    MenuItem menuItem = sender as MenuItem;
    if (menuItem == null) return;

    bool value = (bool)e.NewValue;
    if (!value) return;

    new PowerUserOnlyHelper(menuItem);
}

public class PowerUserOnlyHelper
{
    public MenuItem Item { get; protected set; }

    public PowerUserOnlyHelper(MenuItem menuItem)
    {
        Item = menuItem;

        ContextMenu parent = VisualUpwardSearch<ContextMenu>(menuItem);
        if (parent != null)
        {
            parent.Opened += new RoutedEventHandler(OnContextMenuOpened);
        }
    }

    protected void OnContextMenuOpened(object sender, RoutedEventArgs e)
    {
        Visibility v;
        if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
        {
            v = Visibility.Visible;
        }
        else v = Visibility.Collapsed;

        Item.Visibility = v;
    }
}

public static T VisualUpwardSearch<T>(DependencyObject source)
    where T : DependencyObject
{
    DependencyObject returnVal = source;
    DependencyObject tempReturnVal;

    while (returnVal != null && !(returnVal is T))
    {
        tempReturnVal = null;
        if (returnVal is Visual || returnVal is Visual3D)
        {
            tempReturnVal = VisualTreeHelper.GetParent(returnVal);
        }
        if (tempReturnVal == null)
        {
            returnVal = LogicalTreeHelper.GetParent(returnVal);
        }
        else
        {
            returnVal = tempReturnVal;
        }
    }

    return returnVal as T;
}
于 2012-10-30T20:48:30.367 回答