2010-04-06 33 views
12

Tôi đang gặp một số khó khăn với lệnh Trình đơn ngữ cảnh trên Kiểu xem của tôi.WPF ViewModel Lệnh CanExecute vấn đề

Tôi đang triển khai giao diện ICommand cho từng lệnh trong Mô hình xem, sau đó tạo ContextMenu trong tài nguyên của Chế độ xem (MainWindow) và sử dụng CommandReference từ MVVMToolkit để truy cập các lệnh DataContext (ViewModel) hiện tại. Khi tôi gỡ lỗi ứng dụng, có vẻ như phương thức CanExecute trên lệnh không được gọi ngoại trừ khi tạo cửa sổ, do đó các MenuItext Ngữ cảnh của tôi không được bật hoặc tắt như tôi mong đợi.

Tôi đã nấu một mẫu đơn giản (attached here), đây là biểu hiện của ứng dụng thực tế của tôi và được tóm tắt bên dưới. Mọi sự trợ giúp sẽ rất được trân trọng!

Đây là ViewModel

namespace WpfCommandTest 
{ 
    public class MainWindowViewModel 
    { 
     private List<string> data = new List<string>{ "One", "Two", "Three" }; 

     // This is to simplify this example - normally we would link to 
     // Domain Model properties 
     public List<string> TestData 
     { 
      get { return data; } 
      set { data = value; } 
     } 

     // Bound Property for listview 
     public string SelectedItem { get; set; } 

     // Command to execute 
     public ICommand DisplayValue { get; private set; } 

     public MainWindowViewModel() 
     { 
      DisplayValue = new DisplayValueCommand(this); 
     } 

    } 
} 

Các DisplayValueCommand là ví dụ:

public class DisplayValueCommand : ICommand 
{ 
    private MainWindowViewModel viewModel; 

    public DisplayValueCommand(MainWindowViewModel viewModel) 
    { 
     this.viewModel = viewModel; 
    } 

    #region ICommand Members 

    public bool CanExecute(object parameter) 
    { 
     if (viewModel.SelectedItem != null) 
     { 
      return viewModel.SelectedItem.Length == 3; 
     } 
     else return false; 
    } 

    public event EventHandler CanExecuteChanged; 

    public void Execute(object parameter) 
    { 
     MessageBox.Show(viewModel.SelectedItem); 
    } 

    #endregion 
} 

Và cuối cùng, quan điểm được định nghĩa trong XAML:

<Window x:Class="WpfCommandTest.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WpfCommandTest" 
    xmlns:mvvmtk="clr-namespace:MVVMToolkit" 
    Title="Window1" Height="300" Width="300"> 

    <Window.Resources> 

     <mvvmtk:CommandReference x:Key="showMessageCommandReference" Command="{Binding DisplayValue}" /> 

     <ContextMenu x:Key="listContextMenu"> 
      <MenuItem Header="Show MessageBox" Command="{StaticResource showMessageCommandReference}"/> 
     </ContextMenu> 

    </Window.Resources> 

    <Window.DataContext> 
     <local:MainWindowViewModel /> 
    </Window.DataContext> 

    <Grid> 
     <ListBox ItemsSource="{Binding TestData}" ContextMenu="{StaticResource listContextMenu}" 
       SelectedItem="{Binding SelectedItem}" /> 
    </Grid> 
</Window> 
+1

thứ tốt ... tôi đã tìm cách để làm điều này hôm nay! +1 – kevchadders

Trả lời

21

Để hoàn thành câu trả lời của Will, đây là một "tiêu chuẩn" thực hiện sự kiện CanExecuteChanged:

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

(từ lớp RelayCommand Josh Smith)

Nhân tiện, bạn có lẽ nên xem xét sử dụng RelayCommand hoặc DelegateCommand: bạn sẽ nhanh chóng mệt mỏi khi tạo các lớp lệnh mới cho mỗi và mọi c ommand của bạn ViewModels ...

4

Bạn phải theo dõi khi trạng thái của CanExecute đã thay đổi và kích hoạt sự kiện ICommand.CanExecuteChanged.

Ngoài ra, bạn có thể thấy rằng nó không phải lúc nào cũng hiệu quả, và trong những trường hợp này, yêu cầu gọi CommandManager.InvalidateRequerySuggested() để bắt đầu trình quản lý lệnh trong ass.

Nếu bạn thấy rằng đây mất quá lâu, check out the answer to this question.

2

Cảm ơn bạn đã trả lời nhanh chóng. Cách tiếp cận này không hoạt động nếu bạn đang ràng buộc các lệnh vào một Nút tiêu chuẩn trong Cửa sổ (có quyền truy cập vào Mô hình Xem thông qua DataContext của nó), ví dụ; CanExecute được hiển thị để được gọi khá thường xuyên khi sử dụng CommandManager như bạn đề xuất trên các lớp thực hiện ICommand hoặc bằng cách sử dụng RelayCommand và DelegateCommand.

Tuy nhiên, việc liên kết các lệnh giống nhau thông qua CommandReference trong ContextMenu không hoạt động theo cùng một cách.

Để có hành vi tương tự, tôi cũng phải bao gồm EventHandler từ RelayCommand của Josh Smith, trong CommandReference, nhưng khi làm như vậy tôi phải nhận xét một số mã từ bên trong phương thức OnCommandChanged. Tôi không hoàn toàn chắc chắn lý do tại sao nó có, có lẽ nó đang ngăn chặn rò rỉ bộ nhớ sự kiện (tại một đoán!)?

public class CommandReference : Freezable, ICommand 
    { 
     public CommandReference() 
     { 
      // Blank 
     } 

     public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged))); 

     public ICommand Command 
     { 
      get { return (ICommand)GetValue(CommandProperty); } 
      set { SetValue(CommandProperty, value); } 
     } 

     #region ICommand Members 

     public bool CanExecute(object parameter) 
     { 
      if (Command != null) 
       return Command.CanExecute(parameter); 
      return false; 
     } 

     public void Execute(object parameter) 
     { 
      Command.Execute(parameter); 
     } 

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

     private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      CommandReference commandReference = d as CommandReference; 
      ICommand oldCommand = e.OldValue as ICommand; 
      ICommand newCommand = e.NewValue as ICommand; 

      //if (oldCommand != null) 
      //{ 
      // oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged; 
      //} 
      //if (newCommand != null) 
      //{ 
      // newCommand.CanExecuteChanged += commandReference.CanExecuteChanged; 
      //} 
     } 

     #endregion 

     #region Freezable 

     protected override Freezable CreateInstanceCore() 
     { 
      throw new NotImplementedException(); 
     } 

     #endregion 
    } 
+0

Vui lòng xem [câu trả lời của tôi] (http://stackoverflow.com/a/10924171/435522) – alexei

1

Tuy nhiên, ràng buộc các lệnh tương tự thông qua một CommandReference trong ContextMenu không hành động theo cách tương tự.

Đó là lỗi trong triển khai CommandReference. Nó sau từ hai điểm sau đây:

  1. Đó là khuyến cáo rằng những người thực hiện các ICommand.CanExecuteChanged giữ chỉ tham khảo yếu để xử lý (xem this answer).
  2. tiêu dùng của ICommand.CanExecuteChanged nên mong đợi (1) và do đó nên giữ tham chiếu mạnh mẽ với bộ xử lý họ đăng ký với ICommand.CanExecuteChanged

Việc triển khai chung của RelayCommand và DelegateCommand tuân theo (1). Việc thực hiện CommandReference không tuân theo (2) khi nó đăng ký newCommand.CanExecuteChanged. Vì vậy, các đối tượng xử lý được thu thập và sau đó CommandReference không còn nhận được bất kỳ thông báo rằng nó đã được đếm trên.

Việc sửa chữa là để giữ một ref mạnh để xử lý trong CommandReference:

private EventHandler _commandCanExecuteChangedHandler; 
    public event EventHandler CanExecuteChanged; 

    ... 
    if (oldCommand != null) 
    { 
     oldCommand.CanExecuteChanged -= commandReference._commandCanExecuteChangedHandler; 
    } 
    if (newCommand != null) 
    { 
     commandReference._commandCanExecuteChangedHandler = commandReference.Command_CanExecuteChanged; 
     newCommand.CanExecuteChanged += commandReference._commandCanExecuteChangedHandler; 
    } 
    ... 

    private void Command_CanExecuteChanged(object sender, EventArgs e) 
    { 
     if (CanExecuteChanged != null) 
      CanExecuteChanged(this, e); 
    } 

Để cho hành vi tương tự, tôi cũng phải bao gồm các EventHandler từ RelayCommand Josh Smith, trong CommandReference, nhưng đang thực hiện vì vậy tôi phải nhận xét một số mã trong phương thức OnCommandChanged . Tôi không hoàn toàn chắc chắn lý do tại sao nó có, có lẽ nó là ngăn chặn rò rỉ bộ nhớ sự kiện (tại một đoán!)?

Lưu ý rằng cách tiếp cận đăng ký chuyển tiếp tới CommandManager.RequerySuggested cũng loại bỏ lỗi (không có trình xử lý bắt buộc), nhưng nó làm mất chức năng CommandReference. Lệnh mà CommandReference được liên kết là miễn phí để tăng CanExecuteChanged trực tiếp (thay vì dựa vào CommandManager để đưa ra một yêu cầu requery), nhưng sự kiện này sẽ bị nuốt chửng và không bao giờ đến được nguồn lệnh gắn với CommandReference. Điều này cũng nên trả lời câu hỏi của bạn là tại sao CommandReference được thực hiện bằng cách đăng ký newCommand.CanExecuteChanged.

UPDATE: nộp an issue on CodePlex

+0

cảm ơn bạn, điều này đã giải quyết được sự cố của tôi – Tobias

0

Một giải pháp dễ dàng hơn đối với tôi, là để thiết lập CommandTarget trên MenuItem. Thông tin

<MenuItem Header="Cut" Command="Cut" CommandTarget=" 
     {Binding Path=PlacementTarget, 
     RelativeSource={RelativeSource FindAncestor, 
     AncestorType={x:Type ContextMenu}}}"/> 

thêm: http://www.wpftutorial.net/RoutedCommandsInContextMenu.html

Các vấn đề liên quan