2010-12-21 34 views
25

(Lưu ý - đây là một bài đăng lại khi câu hỏi đầu tiên của tôi được đăng dưới tiêu đề sai: Here Xin lỗi!)WPF/MVVM - làm thế nào để xử lý bấm đúp vào TreeViewItems trong ViewModel?

Tôi có một khung nhìn WPF chuẩn và có các mục bị ràng buộc để xem các lớp mô hình.

Bây giờ tôi muốn xử lý hành vi khi các mục được nhấp đúp (mở tài liệu kiểu studio-studio).

Tôi có thể xử lý sự kiện để kích hoạt nhà kiểm soát treeview (xaml được hiển thị), nhưng làm cách nào để liên kết với hành vi cụ thể trên các lớp mô hình chế độ xem - ví dụ: ProjectViewModel?

Ưu tiên ràng buộc để ICommand-người thực hiện, như thế này được sử dụng ở những nơi khác ...

<TreeView ItemsSource="{Binding Projects}" MouseDoubleClick="TreeView_MouseDoubleClick"> 
    <TreeView.ItemContainerStyle> 
     <!-- 
This Style binds a TreeViewItem to a TreeViewItemViewModel. 
--> 
     <Style TargetType="{x:Type TreeViewItem}"> 
      <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> 
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
      <Setter Property="FontWeight" Value="Normal" /> 
      <Style.Triggers> 
       <Trigger Property="IsSelected" Value="True"> 
        <Setter Property="FontWeight" Value="Bold" /> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
    </TreeView.ItemContainerStyle> 

    <TreeView.Resources> 
     <HierarchicalDataTemplate DataType="{x:Type Implementations:ProjectViewModel}" ItemsSource="{Binding Children}"> 
      <StackPanel Orientation="Horizontal"> 
       <Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" /> 
       <TextBlock Text="{Binding DisplayName}" /> 
      </StackPanel> 
     </HierarchicalDataTemplate> 

     <HierarchicalDataTemplate DataType="{x:Type Implementations:PumpViewModel}" ItemsSource="{Binding Children}"> 
      <StackPanel Orientation="Horizontal"> 
       <Image Width="16" Height="16" Margin="3,0" Source="Images\State.png" /> 
       <TextBlock Text="{Binding Name}" /> 
      </StackPanel> 
     </HierarchicalDataTemplate> 

     <DataTemplate DataType="{x:Type Implementations:PumpDesignViewModel}"> 
      <StackPanel Orientation="Horizontal"> 
       <Image Width="16" Height="16" Margin="3,0" Source="Images\City.png" /> 
       <TextBlock Text="{Binding Name}" /> 
      </StackPanel> 
     </DataTemplate> 
    </TreeView.Resources> 
</TreeView> 
+0

Tôi có các đối tượng viewmodel xử lý các cây riêng lẻ liên quan đến hiển thị, tải chậm ... Tuy nhiên, khi tôi muốn 'kích hoạt'/nhấp đúp vào một mục trong treeview, tôi muốn hành động này sẽ được xử lý bởi cùng một đối tượng viewmodel xử lý hiển thị - nhưng làm cách nào để làm điều đó? –

+0

Bạn đã thử xem các Hành vi được đính kèm chưa? –

+0

Câu hỏi tương tự sử dụng ListView, nhưng câu trả lời là http://stackoverflow.com/questions/1035023/firing-a-double-click-event-from-a-wpf-listview-item-using-mvvm/1510592# 1510592 – surfen

Trả lời

49

Đang cập nhật câu trả lời của tôi một chút.

Tôi đã thử rất nhiều cách tiếp cận khác nhau cho điều này và tôi vẫn cảm thấy như Hành vi được đính kèm là giải pháp tốt nhất. Mặc dù nó có thể trông giống như rất nhiều chi phí trong đầu nó thực sự là không. Tôi giữ tất cả các hành vi của mình cho ICommands ở cùng một nơi và bất cứ khi nào tôi cần hỗ trợ cho một sự kiện khác, đó chỉ là vấn đề sao chép/dán và thay đổi sự kiện trong số PropertyChangedCallback.

Tôi cũng đã thêm hỗ trợ tùy chọn cho CommandParameter.

Trong nhà thiết kế nó chỉ là một vấn đề của việc lựa chọn sự kiện mong muốn

enter image description here

Bạn có thể thiết lập này hoặc TreeView, TreeViewItem hoặc bất kỳ nơi nào khác mà bạn thích.

Ví dụ. Đặt hình ảnh trên TreeView

<TreeView commandBehaviors:MouseDoubleClick.Command="{Binding YourCommand}" 
      commandBehaviors:MouseDoubleClick.CommandParameter="{Binding}" 
      .../> 

Ví dụ sau. Đặt nó trên TreeViewItem

<TreeView ItemsSource="{Binding Projects}"> 
    <TreeView.ItemContainerStyle> 
     <Style TargetType="{x:Type TreeViewItem}"> 
      <Setter Property="commandBehaviors:MouseDoubleClick.Command" 
        Value="{Binding YourCommand}"/> 
      <Setter Property="commandBehaviors:MouseDoubleClick.CommandParameter" 
        Value="{Binding}"/> 
     </Style> 
    </TreeView.ItemContainerStyle> 
</TreeView> 

Và đây là hành vi đính kèmMouseDoubleClick

public class MouseDoubleClick 
{ 
    public static DependencyProperty CommandProperty = 
     DependencyProperty.RegisterAttached("Command", 
     typeof(ICommand), 
     typeof(MouseDoubleClick), 
     new UIPropertyMetadata(CommandChanged)); 

    public static DependencyProperty CommandParameterProperty = 
     DependencyProperty.RegisterAttached("CommandParameter", 
              typeof(object), 
              typeof(MouseDoubleClick), 
              new UIPropertyMetadata(null)); 

    public static void SetCommand(DependencyObject target, ICommand value) 
    { 
     target.SetValue(CommandProperty, value); 
    } 

    public static void SetCommandParameter(DependencyObject target, object value) 
    { 
     target.SetValue(CommandParameterProperty, value); 
    } 
    public static object GetCommandParameter(DependencyObject target) 
    { 
     return target.GetValue(CommandParameterProperty); 
    } 

    private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) 
    { 
     Control control = target as Control; 
     if (control != null) 
     { 
      if ((e.NewValue != null) && (e.OldValue == null)) 
      { 
       control.MouseDoubleClick += OnMouseDoubleClick; 
      } 
      else if ((e.NewValue == null) && (e.OldValue != null)) 
      { 
       control.MouseDoubleClick -= OnMouseDoubleClick; 
      } 
     } 
    } 

    private static void OnMouseDoubleClick(object sender, RoutedEventArgs e) 
    { 
     Control control = sender as Control; 
     ICommand command = (ICommand)control.GetValue(CommandProperty); 
     object commandParameter = control.GetValue(CommandParameterProperty); 
     command.Execute(commandParameter); 
    } 
} 
+0

@Anders Juul: Bạn có gặp bất kỳ lỗi liên kết nào trong cửa sổ đầu ra không? Xem câu hỏi này: http://stackoverflow.com/questions/4051713/what-are-the-tools-available-for-troubleshooting-wpf-and-silverlight-databinding/4051953#4051953 để biết cách đặt Đầu ra Ràng buộc Dữ liệu thành Tất cả trong trường hợp bạn không bật chức năng này –

+0

Bạn cũng có thể tạo một sự kiện đính kèm có tên ItemActivated, nó cũng xử lý khóa Enter: http://serialseb.blogspot.com/2007/01/attached-events-by-example- adding.html. Bạn sẽ liên kết sự kiện đó với một lệnh theo cách tương tự như được đề xuất trong câu trả lời này. – surfen

+0

Tôi đã sử dụng cách tiếp cận của bạn cho kiểu thùng chứa TreeView, nó hoạt động OK trong ứng dụng, nhưng trình thiết kế hiển thị lỗi: "Thuộc tính" Lệnh "không phải là DependencyProperty. Được sử dụng trong đánh dấu, các thuộc tính không đính kèm phải được hiển thị trên loại mục tiêu với thuộc tính thể hiện có thể truy cập "Lệnh" .Đối với các thuộc tính được đính kèm, kiểu khai báo phải cung cấp các phương thức "GetCommand" và "SetCommand" tĩnh. " Nhưng lớp của bạn chứa các phương thức SetCommand và GetCommand, vì vậy tôi không biết tại sao lỗi này. Làm thế nào để sửa chữa nó? Không sửa lỗi này, tất cả các kiểu XAML của tôi không được tải trong toàn bộ giải pháp. –

9

Tôi trễ này, nhưng tôi chỉ được sử dụng một giải pháp khác nhau. Một lần nữa, nó có thể không phải là tốt nhất, nhưng đây là cách tôi đã làm điều đó.

Trước hết, câu trả lời trước đó của Meleak rất thú vị, nhưng tôi cảm thấy nó rất nặng nề khi buộc phải thêm AttachedBehaviors cho một thứ cơ bản như MouseDoubleClick. Điều này sẽ buộc tôi phải sử dụng một mẫu mới trong ứng dụng của tôi và thậm chí sẽ làm phức tạp hơn mọi thứ.

Mục tiêu của tôi là đơn giản nhất có thể.Vì vậy, tôi đã làm một cái gì đó rất cơ bản (ví dụ của tôi là dành cho một DataGrid, nhưng bạn có thể sử dụng trên rất nhiều các điều khiển khác nhau):

<DataGrid MouseDoubleClick="DataGrid_MouseDoubleClick"> 
    <!-- ... --> 
</DataGrid> 

Trong code-behind:

private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) 
{ 
    //Execute the command related to the doubleclick, in my case Edit 
    (this.DataContext as VmHome).EditAppCommand.Execute(null); 
} 

Tại sao tôi cảm thấy như nó không phá vỡ mô hình MVVM? Bởi vì theo ý kiến ​​của tôi, những điều duy nhất bạn nên đặt trong code-behind là các bridge cho viewModel của bạn, mọi thứ rất cụ thể cho UI của bạn. Trong trường hợp này, nó chỉ nói rằng nếu bạn nhấp đúp chuột vào, hãy kích hoạt lệnh liên quan. Nó gần giống với Command = "{Binding EditAppCommand}", tôi vừa mô phỏng hành vi này.

Hãy cho tôi biết ý kiến ​​của bạn về điều này, tôi rất vui khi nghe một số nhà phê bình cho cách suy nghĩ này, nhưng bây giờ tôi tin rằng đó là cách dễ nhất để thực hiện nó mà không vi phạm MVVM.

+1

@Demascus: Cảm ơn một giải pháp thay thế. Theo tôi, tt thực sự phụ thuộc vào mức độ thường xuyên bạn sẽ cần phải làm điều đó. Lưu ý rằng một giải pháp kèm theoBehaviour là generic - kiểm soát indepentent để nó thúc đẩy khả năng tái sử dụng và bảo trì, bởi vì bạn sẽ có tất cả các lệnh của bạn bị ràng buộc khai báo trong XAML. Với tất cả các WPF đã cung cấp, viết codebehind để ánh xạ các sự kiện cho các lệnh cảm thấy như quá nhiều chi phí cho tôi. Tôi muốn tất cả các sự kiện hỗ trợ Command ràng buộc ra khỏi hộp. – surfen

+0

Trong trường hợp của một TreeView, bạn thực sự quan tâm đến DataContext của TreeViewItem mà trên đó kích đúp đã được thực hiện, nhưng giải pháp của bạn không đưa thông tin đó cho ViewModel. – JoanComasFdz

0

Cách tiếp cận tốt nhất mà tôi đã đạt được là chỉ ràng buộc thuộc tính IsSelected từ TreeViewItem đến ViewModel trong chế độ Hai chiều và triển khai logic trong trình thiết lập thuộc tính. Sau đó, bạn có thể xác định phải làm gì nếu giá trị là đúng hoặc sai, vì thuộc tính này sẽ thay đổi bất cứ khi nào người dùng nhấp vào một mục.

class MyVM 
{ 
    private bool _isSelected; 
    public bool IsSelected 
    { 
    get { return _isSelected; } 
    set 
    { 
     if (_isSelected == null) 
     return; 

     _isSelected = vale; 

     if (_isSelected) 
     { 
     // Your logic goes here. 
     } 
     else 
     { 
     // Your other logic goes here. 
     } 
    } 
} 

Điều này tránh được nhiều mã.

Ngoài ra, kỹ thuật này cho phép bạn triển khai hành vi "onclick" chỉ trong ViewModels thực sự cần đến nó.

+1

Nhưng bạn đã bỏ lỡ câu hỏi, vì OP muốn xử lý sự kiện "nhấp đúp". Mã của bạn rất tuyệt, nhưng chỉ hoạt động cho sự kiện "click". –

+0

@JulienN Có, bạn đã đúng. – JoanComasFdz

2

giải pháp Meleak là rất tốt !, nhưng tôi thêm séc

private static void OnMouseDoubleClick(object sender, RoutedEventArgs e) 
    { 
     Control control = sender as Control; 
     ICommand command = (ICommand)control.GetValue(CommandProperty); 
     object commandParameter = control.GetValue(CommandParameterProperty); 
     //Check command can execute!! 
     if(command.CanExecute(commandParameter)) 
     command.Execute(commandParameter); 
    } 
5

Cả Meleak và Igor của khuyến nghị là rất lớn, nhưng khi nhấn đúp chuột handler sự kiện là ràng buộc để TreeViewItem sau đó handler sự kiện này được gọi là cho tất cả các phần tử cha của mục (không chỉ là phần tử được nhấp). Nếu không được mong muốn, đây là một bổ sung khác:

private static void OnMouseDoubleClick(object sender, RoutedEventArgs e) 
{ 
    Control control = sender as Control; 
    ICommand command = (ICommand)control.GetValue(CommandProperty); 
    object commandParameter = control.GetValue(CommandParameterProperty); 

    if (sender is TreeViewItem) 
    { 
     if (!((TreeViewItem)sender).IsSelected) 
      return; 
    } 

    if (command.CanExecute(commandParameter)) 
    { 
     command.Execute(commandParameter); 
    } 
} 
+0

Điều này rất hữu ích đối với tôi. Vấn đề nhỏ duy nhất là nó giả định rằng 'người gửi' thực sự được chọn, điều này luôn đúng cho một nhấp đúp, nhưng không phải khi bạn thay đổi loại sự kiện thành một cái gì đó khác (tức là 'MouseHover' hoặc những thứ tương tự). – Yellow

0

Chỉ vì tò mò: nếu tôi lấy Frederik một phần, nhưng thực hiện trực tiếp như hành vi thì sao?

public class MouseDoubleClickBehavior : Behavior<Control> 
{ 
    public static readonly DependencyProperty CommandProperty = 
     DependencyProperty.Register("Command", typeof (ICommand), typeof (MouseDoubleClickBehavior), new PropertyMetadata(default(ICommand))); 

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

    public static readonly DependencyProperty CommandParameterProperty = 
     DependencyProperty.Register("CommandParameter", typeof (object), typeof (MouseDoubleClickBehavior), new PropertyMetadata(default(object))); 

    public object CommandParameter 
    { 
     get { return GetValue(CommandParameterProperty); } 
     set { SetValue(CommandParameterProperty, value); } 
    } 

    protected override void OnAttached() 
    { 
     base.OnAttached(); 
     AssociatedObject.MouseDoubleClick += OnMouseDoubleClick; 
    } 

    protected override void OnDetaching() 
    { 
     AssociatedObject.MouseDoubleClick -= OnMouseDoubleClick; 
     base.OnDetaching(); 
    } 

    void OnMouseDoubleClick(object sender, RoutedEventArgs e) 
    { 
     if (Command == null) return; 
     Command.Execute(/*commandParameter*/null); 
    } 
} 
2

Nó thực sự đơn giản và đây là cách tôi xử lý nhấp đúp chuột vào TreeView:

<Window x:Class="TreeViewWpfApplication.MainWindow" 
    ... 
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 
    ...> 

     <TreeView ItemsSource="{Binding Departments}" > 
     <i:Interaction.Triggers> 
      <i:EventTrigger EventName="MouseDoubleClick"> 
       <ei:CallMethodAction MethodName="SomeMethod" TargetObject="{Binding}"/> 
      </i:EventTrigger> 
     </i:Interaction.Triggers> 
     </TreeView> 
</Window> 

System.Windows.Interactivity.dll được lấy từ C: \ Program Files (x86) \ Microsoft SDK \ Biểu \ Blend.NETFramework \ v4.0 \ Libraries \ System.Windows.Interactivity.dll hoặc bằng NuGet

xem mô hình của tôi:

public class TreeViewModel : INotifyPropertyChanged 
{ 
    private List<Department> departments; 
    public TreeViewModel() 
    { 
     Departments = new List<Department>() 
     { 
      new Department("Department1"), 
      new Department("Department2"), 
      new Department("Department3") 
     }; 
    } 

    public List<Department> Departments 
    { 
     get 
     { 
      return departments; 
     } 
     set 
     { 
      departments = value; 
      OnPropertyChanged("Departments"); 
     } 
    } 

    public void SomeMethod() 
    { 
     MessageBox.Show("*****"); 
    } 
} 
+0

+1. Điều này làm việc cho tôi. Tôi luôn thích phương pháp tiếp cận dựa trên tương tác. Và điều này làm việc như bạn nói - cài đặt các gói thông qua NuGet, chèn i: Interaction.Triggers phần bit, thêm phương pháp thử nghiệm cho lớp ViewModel và bạn đang đi ... – AndyUK

0

Chuột Ràng buộc trên TextBlock

Trong TreeView.Resources của Xem:

<HierarchicalDataTemplate 
     DataType="{x:Type treeview:DiscoveryUrlViewModel}" 
     ItemsSource="{Binding Children}"> 

     <StackPanel Orientation="Horizontal"> 
      <Image Width="16" Height="16" Margin="3,0" Source="../Images/ic_search.png" /> 

      <TextBlock Text="{Binding DisplayText}" > 
       <TextBlock.InputBindings> 
        <MouseBinding Gesture="LeftDoubleClick" 
            Command="{Binding DoubleClickCopyCommand}" 
            CommandParameter="{Binding }" /> 
       </TextBlock.InputBindings> 
      </TextBlock> 
     </StackPanel> 
</HierarchicalDataTemplate> 

Trong ViewModel của Xem đó (DiscoveryUrlViewModel.cs):

private RelayCommand _doubleClickCommand; 
public ICommand DoubleClickCopyCommand 
     { 
      get 
      { 
       if (_doubleClickCommand == null) 
        _doubleClickCommand = new RelayCommand(OnDoubleClick); 
       return _doubleClickCommand; 
      } 
     } 

     private void OnDoubleClick(object obj) 
     { 
      var clickedViewModel = (DiscoveryUrlViewModel)obj; 
     } 
Các vấn đề liên quan