[英]WPF/MVVM - how to handle double-click on TreeViewItems in the ViewModel?
(注意 - 這是一個重新發布,因為我的第一個問題被發布在錯誤的標題下:抱歉!)
我有一個標准的 WPF treeview 和綁定項目查看 model 類。
我現在希望處理雙擊項目時的行為(打開文檔 visual-studio-style)。
我可以讓事件處理程序在包含 treeview(顯示的 xaml)的控件中觸發,但是如何綁定到視圖 model 類上的特定行為 - 例如 ProjectViewModel?
最好綁定到 ICommand-implementer,因為它在其他地方使用......
<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>
稍微更新我的答案。
我已經嘗試了很多不同的方法,我仍然覺得附加行為是最好的解決方案。 盡管在開始時它可能看起來很多開銷但實際上並非如此。 我將ICommands
所有行為保存在同一個地方,每當我需要支持另一個事件時,只需要復制/粘貼並更改PropertyChangedCallback
的事件。
我還添加了CommandParameter
的可選支持。
在設計師中,只需選擇所需的事件即可
您可以在TreeView
, TreeViewItem
或您喜歡的任何其他位置設置此項。
例。 在TreeView
上設置它
<TreeView commandBehaviors:MouseDoubleClick.Command="{Binding YourCommand}"
commandBehaviors:MouseDoubleClick.CommandParameter="{Binding}"
.../>
例。 在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>
這是附加行為 MouseDoubleClick
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);
}
}
我遲到了,但我只是使用了不同的解決方案。 再一次,它可能不是最好的,但這就是我如何做到這一點。
首先,來自Meleak的前一個答案很酷,但我覺得被迫添加AttachedBehaviors只是為了像MouseDoubleClick一樣基本的東西是非常沉重的。 這將迫使我在我的應用程序中使用新模式,甚至會使一切變得更復雜。
我的目標是盡可能保持簡單。 因此我做了一些非常基本的事情(我的例子是針對DataGrid,但你可以在很多不同的控件上使用它):
<DataGrid MouseDoubleClick="DataGrid_MouseDoubleClick">
<!-- ... -->
</DataGrid>
在代碼隱藏中:
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);
}
為什么我覺得它沒有打破MVVM模式? 因為在我看來,你應該在代碼隱藏中放置的唯一東西是你的viewModel的橋梁,這是你的UI特有的東西。 在這種情況下,它只是說,如果你雙擊,激活相關的命令。 它幾乎與Command =“{Binding EditAppCommand}”相同,我只是模擬了這種行為。
請隨意向我發表您對此的看法,我很高興聽到一些批評者對這種思維方式的看法,但是現在我相信這是在不破壞MVVM的情況下實現它的最簡單方法。
Meleak和ígor的建議都很棒,但是當雙擊事件處理程序綁定到TreeViewItem
將為所有項目的父元素(不僅僅是單擊的元素)調用此事件處理程序。 如果不需要,還有另外一個補充:
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);
}
}
它非常簡單,這就是我在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:\\ Program Files(x86)\\ Microsoft SDKs \\ Expression \\ Blend.NETFramework \\ v4.0 \\ Libraries \\ System.Windows.Interactivity.dll或NuGet
我的觀點模型:
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("*****");
}
}
Meleak解決方案很棒!,但我添加了檢查
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);
}
我達到的最佳方法是在雙向模式下將IsSelected
屬性從TreeViewItem綁定到ViewModel,並在屬性設置器中實現邏輯。 然后,您可以定義在值為true或false時要執行的操作,因為只要用戶單擊某個項,此屬性就會更改。
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.
}
}
}
這避免了很多代碼。
此外,此技術允許您僅在真正需要它的ViewModel中實現“onclick”行為。
只是為了好奇:如果我接受Frederiks的一部分,但是直接將它作為行為來實現呢?
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);
}
}
TextBlock上的鼠標綁定
在View的TreeView.Resources中:
<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>
在該View的ViewModel中(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;
}
11年過去了。 我只是根據@Damascus 的解決方案在我的 treeview 中完成了它。
在Xaml中,有一個UserControl,里面有一個TreeView。 DataType=FileResultBrief 是我要雙擊的內容。
<resultTrees:ResultTreeView x:Class="ChiSharedFormsWpf.ResultTrees.ChiTreeView"
MouseDoubleClick="ChiTreeView_OnMouseDoubleClick"
d:DesignHeight="450" d:DesignWidth="3800">
<Grid>
<DockPanel HorizontalAlignment="Stretch">
<TreeView Name="Tree" HorizontalAlignment="Stretch">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dirTrees:FileResultBrief}"
ItemsSource="{Binding BadSmells}">
<StackPanel Orientation="Horizontal" Background="{Binding Index.SoftColorHex}"
Tag="{Binding FileName}">
<fa5:FontAwesome Icon="Regular_FileCode" Margin="0,2,5,0" />
<TextBlock Text="{Binding Index.Brief}" />
<TextBlock Text="{Binding FileName}" Margin="10 0"/>
</StackPanel>
</HierarchicalDataTemplate>
這是隱藏代碼中發生的事情:
private void ChiTreeView_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not ChiTreeView {Tree: TreeView {SelectedItem: FileResultBrief brief}})
return;
FileUtility.OpenFileWithDefaultApp(ScanTask.FullNameOf(brief.FileName));
}
我在 2 周前開始使用 Wpf 和 Xaml。 這是通過在方法的入口處放置一個斷點並越來越深入地觀察“發送者”來完成的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.