2012-04-28 22 views
7

Tôi có này tài nguyên menu ngữ cảnh:Làm thế nào để thực hiện các lệnh để sử dụng các phương thức tổ tiên trong 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> 

Tôi muốn tái sử dụng nó ở hai nơi. Trước hết tôi đang cố gắng để đặt nó trong một DataGrid:

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

Các ContextMenu bản thân hoạt động tốt, nhưng với sự Executed="..." ngay bây giờ tôi đã phá vỡ ứng dụng và ném:

Một cơ hội ngoại lệ đầu tiên của gõ 'System.InvalidCastException' xảy ra trong PresentationFramework.dll

Thông tin bổ sung: Không thể truyền đối tượng loại 'System.Reflection.RuntimeEventInfo' để nhập 'System.Reflection.MethodInfo'.

Nếu tôi xóa toàn bộ định nghĩa Executed="...", thì mã hoạt động (và lệnh không làm gì/xám). Ngoại lệ được ném ngay sau khi tôi nhấp chuột phải vào lưới/mở menu ngữ cảnh.

Các DataGrid được đặt dưới một vài yếu tố, nhưng cuối cùng tất cả họ đều dưới một TabControl (gọi tắt là MainTabs) trong đó có ItemsSource thiết lập để một bộ sưu tập của FooViewModel s, và trong FooViewModel rằng tôi có một phương pháp HelpExecuted mà tôi muốn trở thành gọi là.

Hãy hình dung:

  • TabControl (ItemsSource=ObservableCollection<FooViewModel>, x:Name=MainTabs)
    • Lưới
      • More UI
        • DataGrid (với thiết lập menu ngữ cảnh)

Tại sao tôi nhận được lỗi này và làm thế nào tôi có thể làm cho lệnh menu ngữ cảnh để "mục tiêu" HelpExecuted phương pháp 's FooViewModel?

+0

Tôi nghĩ điều khiển MainTabs không chứa thuộc tính HelpExecuted. Nó chỉ chứa một danh sách các FooViewModel. –

Trả lời

3

Đáng tiếc là bạn không thể ràng buộc Executed cho một ContextMenu vì nó là một sự kiện. Một vấn đề khác là ContextMenu không tồn tại trong số VisualTree phần còn lại của đơn đăng ký của bạn tồn tại. Có những giải pháp cho cả hai vấn đề này.

Trước hết, bạn có thể sử dụng thuộc tính Tag của kiểm soát gốc của số ContextMenu để chuyển qua số DataContext của đơn đăng ký của bạn. Sau đó, bạn có thể sử dụng số DelegateCommand cho số CommandBinding của mình và tại đó bạn sẽ sử dụng. Dưới đây là một mẫu nhỏ hiển thị View, ViewModel và triển khai DelegateCommand bạn sẽ phải thêm vào dự án của mình.

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

Các thẻ và đại biểu có thực sự là con đường để đi không? 'Stipo' có một cách làm việc và ngắn ngủi để thực hiện điều này, cách tiếp cận của bạn có cái gì đó mà 'Stipo' không? Tôi đang cố gắng tìm ra cách tiếp cận nào có ý nghĩa nhất, bởi vì các câu trả lời được đưa ra ở đây là khá khác nhau. :) – Tower

+0

Thực ra câu trả lời từ Stipo nên giải quyết vấn đề của bạn. Nhưng nếu tôi đã học được một điều về WPF và MVVM: Không bao giờ sử dụng code-behind nếu bạn không có một lý do rất tốt để làm điều đó vì nó hủy bỏ toàn bộ khuôn mẫu.Điều này đôi khi có nghĩa là để đi một cách rất đá :) – MatthiasG

+0

Tôi không đồng ý với MatthiasG rằng code-đằng sau bãi bỏ mô hình MVVM. Điều quan trọng nhất trong MVVM là tất cả logic ứng dụng và trạng thái phải ở dạng xem và không có tham chiếu trực tiếp đến các khung nhìn từ các mô hình khung nhìn. Ràng buộc xem và xem mô hình nên được thực hiện thông qua XAML, nếu có thể (nếu WPF/XAML API hỗ trợ nó), nếu không nó phải được thực hiện thông qua mã, như trong câu trả lời của tôi. Nếu các ràng buộc thông qua mã được lặp đi lặp lại, chúng có thể được thực hiện XAMLish với các hành vi kèm theo. – Stipo

3

Điều này có hữu ích không?

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

Bạn nhận được lỗi này vì CommandBinding.Executed không phải là thuộc tính phụ thuộc để bạn không thể liên kết với nó.

Thay vào đó, sử dụng mã ResourceDictionary phía sau để chỉ định xử lý sự kiện cho sự kiện CommandBinding.Executed, và trong các cuộc gọi mã xử lý sự kiện FooViewModel.HelpExecuted() phương pháp như thế này:

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(); 
    } 
} 
+0

Điều này hoạt động độc đáo! Bạn có nghĩ rằng có bất kỳ hạn chế nào đối với phương pháp này không? Tất cả các câu trả lời tôi đã có cho đến nay là khá khác nhau, có vẻ như không có cách nào để xác định điều này ...? – Tower

+0

Cách tiếp cận của tôi thực sự sử dụng lệnh ApplicationsCommands.Help (http://msdn.microsoft.com/en-us/library/system.windows.input.applicationcommands.help.aspx), trong đó bạn nhận được Cử chỉ khóa và Văn bản giao diện người dùng theo mặc định miễn phí. Một lợi ích khác của việc sử dụng ApplicationCommands là chúng là các lệnh nổi tiếng (được giới thiệu qua API WPF) để các tác giả kiểm soát có thể sử dụng chúng trong các điều khiển của chúng mà không biết ai xử lý chúng (người cung cấp thực thi logic lệnh). Các cách tiếp cận khác không sử dụng ApplicationCommands và không cần thiết làm phức tạp mã bằng cách chuyển DataContext qua Thẻ. – Stipo

+0

Điều này dường như không hoạt động với các lệnh tùy chỉnh? – Tower

3

Tôi sợ MatthiasG đánh tôi với nó. Giải pháp của tôi tương tự:

Tại đây lệnh Trợ giúp được xử lý theo mô hình chế độ xem của mục tab. Nó sẽ đơn giản để chuyển một tham chiếu đến TestViewModel tới từng TestItemViewModel và có ShowHelp gọi lại vào TestViewModel nếu cần.

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

Các 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> 

mô hình xem Alternative để các lệnh giúp đỡ là hướng đến mô hình xem gốc

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

Một thực hiện rất đơn giản của 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; 
} 
+0

Nó không tìm thấy 'ActionCommand'. Cái gì thế này? – Tower

+0

ActionCommand khả dụng sau khi bạn cài đặt Blend Expression. Bạn sẽ cần phải thêm một tham chiếu đến Microsoft.Expression.Interactions. Hoặc sử dụng một DelegateCommand từ PRISM hoặc một RelayCommand từ MVVM ánh sáng, hoặc thực hiện của riêng bạn. – Phil

1

I Có thể tạo một lớp adapter có thể được cấu hình như một tài nguyên trong XAML, có thể được gắn vào một Control để tạo CommandBindings ở đó, và ở đầu kia có thể liên kết với một phương thức trong ViewModel. lệnh được kích hoạt bởi một Button hoặc MenuItem. Lệnh trong trường hợp này sẽ là một RoutedCommand, và nó sẽ không quan trọng cho dù bạn chọn một trong các lệnh WPF được xác định trước hoặc tạo một RoutedCommand tùy chỉnh trong ứng dụng của bạn.

Bí quyết để liên kết với một phương pháp là

  • Làm adapter một Freezable, vì vậy người ta có thể sử dụng các DataContext hiện như là một nguồn ràng buộc,
  • Cho nó một thuộc tính phụ thuộc của loại đại biểu hoặc một trong của nó các loại phụ và
  • Sử dụng trình chuyển đổi chấp nhận tên phương thức là một ConverterParameter và kiểm tra loại nguồn ràng buộc để tạo Đại biểu cho phương thức cần được lệnh gọi.

Trong khi điều này nghe có vẻ phức tạp, điều tốt là khi bạn có các phần của khung công tác, bạn chỉ có thể sử dụng lại chúng trong XAML và bạn sẽ không có bất kỳ mã keo nào trong ViewModel hoặc mã ẩn.

Như bạn có thể tưởng tượng, điều này có một số cơ sở hạ tầng và mã này nhiều hơn số lượng tôi muốn đăng ở đây. Tuy nhiên, tôi vừa mới xuất bản một bài viết trong blog của tôi về chủ đề, http://wpfglue.wordpress.com/2012/05/07/commanding-binding-controls-to-methods/ và thông qua blog, bạn có thể tải xuống mã nguồn hoàn chỉnh cho khung công tác và ví dụ trong VB.Net.

Áp dụng cho vấn đề của bạn, XAML sau đó sẽ trông như thế này:

Trong định nghĩa của 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> 

Và trong định nghĩa của 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... --> 
Các vấn đề liên quan