簡體   English   中英

如何在WPF中實現命令以使用祖先方法?

[英]How to implement commands to use ancestor methods in WPF?

我有這個上下文菜單資源:

<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放在幾個元素下面,但最終它們都在TabControl (稱為MainTabsMainTabs ,它將ItemsSource設置為FooViewModel的集合,並且在FooViewModel我有一個我想要調用的方法HelpExecuted

讓我們想象一下:

  • TabControl( ItemsSource=ObservableCollection<FooViewModel>x:Name=MainTabs
      • 更多UI
        • DataGrid(帶上下文菜單集)

為什么我會收到此錯誤,如何使上下文菜單命令“定位” FooViewModelHelpExecuted方法?

這有幫助嗎?

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

遺憾的是,您無法為ContextMenu綁定Executed ,因為它是一個事件。 另一個問題是,在應用程序的其余部分存在的VisualTree中, ContextMenu不存在。 這兩個問題都有解決方案。

首先,您可以使用ContextMenu的父控件的Tag屬性來傳遞應用程序的DataContext 然后你可以使用DelegateCommand為你的CommandBinding ,你去。 這是一個小樣本,顯示了您必須添加到項目中的ViewViewModelDelegateCommand實現。

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

我擔心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");
    }
}

xaml

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

您收到此錯誤,因為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();
    }
}

可以創建一個適配器類,可以將其配置為XAML中的資源,可以附加到Control以在那里創建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... -->

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM