7

我有这个上下文菜单资源:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ContextMenu x:Key="FooContextMenu">
        <ContextMenu.CommandBindings>
            <CommandBinding Command="Help" Executed="{Binding ElementName=MainTabs, Path=HelpExecuted}" />
        </ContextMenu.CommandBindings>

        <MenuItem Command="Help">
            <MenuItem.Icon>
                <Image Source="../Resources/Icons/Help.png" Stretch="None" />
            </MenuItem.Icon>
        </MenuItem>
    </ContextMenu>
</ResourceDictionary>

我想在两个地方重复使用它。首先,我试图把它放在一个DataGrid

<DataGrid ContextMenu="{DynamicResource FooContextMenu}">...

ContextMenu本身工作正常,但Executed="..."我现在有打破应用程序并抛出:

PresentationFramework.dll 中出现“System.InvalidCastException”类型的第一次机会异常

附加信息:无法将“System.Reflection.RuntimeEventInfo”类型的对象转换为“System.Reflection.MethodInfo”类型。

如果我删除整个Executed="..."定义,那么代码就可以工作(并且命令什么都不做/变灰)。只要我右键单击网格/打开上下文菜单,就会引发异常。

DataGrid放置在几个元素下,但最终它们都位于已设置为 s 集合的TabControl(称为) 之下,并且我有一个我想要被调用的方法。MainTabsItemsSourceFooViewModelFooViewModelHelpExecuted

让我们形象化:

  • 选项卡控件 ( ItemsSource=ObservableCollection<FooViewModel>, x:Name=MainTabs)
    • 网格
      • 更多用户界面
        • DataGrid(带有上下文菜单集)

为什么会出现此错误,如何使上下文菜单命令“定位” FooViewModel'sHelpExecuted方法?

4

5 回答 5

3

不幸的是,您不能Executed为 a绑定,ContextMenu因为它是一个事件。另一个问题是,在您的应用程序的其余部分ContextMenu不存在。VisualTree这两个问题都有解决方案。

首先,您可以使用Tag父控件的属性ContextMenu来传递DataContext您的应用程序。然后你可以使用DelegateCommand你的,然后就CommandBinding可以了。这是一个显示 的小示例ViewViewModel以及DelegateCommand您必须添加到项目中的实现。

委托命令.cs

public class DelegateCommand : ICommand
{
    private readonly Action<object> execute;
    private readonly Predicate<object> canExecute;

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    { }

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        this.execute = execute;
        this.canExecute = canExecute;
    }

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return canExecute == null ? true : canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        execute(parameter);
    }

    #endregion
}

MainWindowView.xaml

<Window x:Class="Application.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindowView" Height="300" Width="300"
        x:Name="MainWindow">
    <Window.Resources>
        <ResourceDictionary>
            <ContextMenu x:Key="FooContextMenu">
                <MenuItem Header="Help" Command="{Binding PlacementTarget.Tag.HelpExecuted, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
            </ContextMenu>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <TabControl ItemsSource="{Binding FooViewModels}" x:Name="MainTabs">
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <DataGrid ContextMenu="{DynamicResource FooContextMenu}" Tag="{Binding}" />
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>
</Window>

MainWindowView.xaml.cs

public partial class MainWindowView : Window
{
    public MainWindowView()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }
}

MainWindowViewModel.cs

public class MainWindowViewModel
{
    public ObservableCollection<FooViewModel> FooViewModels { get; set; }

    public MainWindowViewModel()
    {
        FooViewModels = new ObservableCollection<FooViewModel>();
    }
}

FooViewModel.cs

public class FooViewModel
{
    public ICommand HelpExecuted { get; set; }

    public FooViewModel()
    {
        HelpExecuted = new DelegateCommand(ShowHelp);
    }

    private void ShowHelp(object obj)
    {
        // Yay!
    }
}
于 2012-04-30T20:49:53.720 回答
3

这有帮助吗?

<ContextMenu>
    <ContextMenu.ItemContainerStyle>
       <Style TargetType="MenuItem">
          <Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=HelpExecuted}" />
       </Style>
    </ContextMenu.ItemContainerStyle>
    <MenuItem Header="Help" />
</ContextMenu>
于 2012-04-30T20:17:31.297 回答
3

恐怕MatthiasG 打败了我。我的解决方案类似:

此处的帮助命令由选项卡项的视图模型处理。如果需要,将 TestViewModel 的引用传递给每个 TestItemViewModel 并让 ShowHelp 回调到 TestViewModel 会很简单。

public class TestViewModel
{
    public TestViewModel()
    {
        Items = new List<TestItemViewModel>{ 
                    new TestItemViewModel(), new TestItemViewModel() };
    }

    public ICommand HelpCommand { get; private set; }

    public IList<TestItemViewModel> Items { get; private set; }
}

public class TestItemViewModel
{
    public TestItemViewModel()
    {
        // Expression Blend ActionCommand
        HelpCommand = new ActionCommand(ShowHelp);
        Header = "header";
    }

    public ICommand HelpCommand { get; private set; }

    public string Header { get; private set; }

    private void ShowHelp()
    {
        Debug.WriteLine("Help item");
    }
}

xml

<Window.Resources>
    <ContextMenu x:Key="FooMenu">
        <MenuItem Header="Help" Command="{Binding HelpCommand}"/>
    </ContextMenu>
    <DataTemplate x:Key="ItemTemplate">
        <!-- context menu on header -->
        <TextBlock Text="{Binding Header}" ContextMenu="{StaticResource FooMenu}"/>
    </DataTemplate>
    <DataTemplate x:Key="ContentTemplate">
        <Grid Background="#FFE5E5E5">
            <!-- context menu on data grid -->
            <DataGrid ContextMenu="{StaticResource FooMenu}"/>
        </Grid>
    </DataTemplate>
</Window.Resources>

<Window.DataContext>
    <WpfApplication2:TestViewModel/>
</Window.DataContext>

<Grid>
    <TabControl 
        ItemsSource="{Binding Items}" 
        ItemTemplate="{StaticResource ItemTemplate}" 
        ContentTemplate="{StaticResource ContentTemplate}" />
</Grid>

替代视图模型,以便将帮助命令定向到根视图模型

public class TestViewModel
{
    public TestViewModel()
    {
        var command = new ActionCommand(ShowHelp);

        Items = new List<TestItemViewModel>
                    {
                        new TestItemViewModel(command), 
                        new TestItemViewModel(command)
                    };
    }

    public IList<TestItemViewModel> Items { get; private set; }

    private void ShowHelp()
    {
        Debug.WriteLine("Help root");
    }
}

public class TestItemViewModel
{
    public TestItemViewModel(ICommand helpCommand)
    {
        HelpCommand = helpCommand;
        Header = "header";
    }

    public ICommand HelpCommand { get; private set; }

    public string Header { get; private set; }
}

ActionCommand 的一个非常简单的实现

public class ActionCommand : ICommand
{
    private readonly Action _action;

    public ActionCommand(Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        _action = action;
    }

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

    public void Execute(object parameter)
    {
        _action();
    }

    // not used
    public event EventHandler CanExecuteChanged;
}
于 2012-04-30T21:05:38.570 回答
2

您收到此错误是因为 CommandBinding.Executed 不是依赖属性,因此您无法绑定到它。

相反,使用后面的 ResourceDictionary 代码为 CommandBinding.Executed 事件指定事件处理程序,并在事件处理程序代码中调用 FooViewModel.HelpExecuted() 方法,如下所示:

MainWindowResourceDictionary.xaml

<ResourceDictionary x:Class="WpfApplication.MainWindowResourceDictionary" 
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApplication">

    <DataTemplate DataType="{x:Type local:FooViewModel}">
        <Grid>
            <DataGrid ContextMenu="{DynamicResource FooContextMenu}"/>
        </Grid>
    </DataTemplate>

    <ContextMenu x:Key="FooContextMenu">
        <ContextMenu.CommandBindings>
            <CommandBinding Command="Help" Executed="HelpExecuted"/>
        </ContextMenu.CommandBindings>
        <MenuItem Command="Help"/>
    </ContextMenu>

</ResourceDictionary>

MainWindowResourceDictionary.xaml.cs

public partial class MainWindowResourceDictionary : ResourceDictionary
{
    public MainWindowResourceDictionary()
    {
        InitializeComponent();
    }

    private void HelpExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        var fooViewModel = (FooViewModel)((FrameworkElement)e.Source).DataContext;
        fooViewModel.HelpExecuted();
    }
}
于 2012-04-30T20:49:49.310 回答
1

可以创建一个适配器类,它可以配置为 XAML 中的资源,可以附加到控件以便在那里创建 CommandBindings,并且在另一端可以绑定到 ViewModel 中的方法,当命令由 Button 或 MenuItem 触发。在这种情况下,命令将是 RoutedCommand,无论您是选择预定义的 WPF 命令之一还是在应用程序中创建自定义 RoutedCommand,都没有关系。

绑定到方法的技巧是

  • 使适配器成为 Freezable,因此可以使用当前 DataContext 作为绑定源,
  • 给它一个 Delegate 类型或其子类型之一的 DependencyProperty,并且
  • 使用一个转换器,它接受方法名称作为 ConverterParameter 并检查绑定源类型,以便为应该由命令调用的方法创建一个委托。

虽然这听起来很复杂,但好处是一旦您将框架的各个部分组合在一起,您就可以只在 XAML 中简单地重用它们,并且在 ViewModel 或后面的代码中根本不会有任何胶水代码。

你可以想象,这需要一些基础设施,而且代码比我想在这里发布的要多。不过,我刚刚在我的博客上发表了一篇关于这个主题的文章,http://wpfglue.wordpress.com/2012/05/07/commanding-binding-controls-to-methods/,通过博客你可以下载框架的完整源代码和 VB.Net 中的示例。

应用于您的问题,XAML 将如下所示:

在 ContextMenu 的定义中:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ContextMenu x:Key="FooContextMenu">
    <!-- No CommandBindings needed here -->
    <MenuItem Command="Help">
        <MenuItem.Icon>
            <Image Source="../Resources/Icons/Help.png" Stretch="None" />
        </MenuItem.Icon>
    </MenuItem>
</ContextMenu>
</ResourceDictionary>

并且在DataGrid的定义中

<DataGrid c:Commanding.CommandSet="{DynamicResource helpCommand}">
    <DataGrid.Resources>
        <f:ActionConverter x:Key="actionConverter"/>
        <c:ActionCommand x:Key="helpCommand" Command="Help" ExecutedAction="{Binding Converter={StaticResource actionConverter}, ConverterParameter=HelpExecuted}"/>
<!-- DataGrid definition continued... -->
于 2012-05-07T20:38:09.817 回答