听起来很简单?我有一个TreeView,我希望在扩展其中一个节点时发生一些事情。我正在使用MVVM,因此“某些东西”是ViewModel中的一个命令。
好吧,我发现它毕竟不是那么简单。我环顾四周,尝试了一些事情。例如,使用MVVM Light的EventToCommand:
<i:Interaction.Triggers>
<i:EventTrigger EventName="TreeViewItem.Expanded">
<cmd:EventToCommand Command="{Binding Path=FolderNodeToggledCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
此代码(基于this和this)不起作用(没有触发;命令绑定在ViewModel中,但在展开节点时永远不会触发相应的方法)。我也尝试用cmd:EventToCommand
替换i:InvokeCommandAction
,结果是一样的。第二个链接中的“解决方案”显然有点过分,我不想更改ToggleButton,因为我想使用具有自己的ToggleButton的WPF TreeView WinForms Style。第二个链接中的第二个答案表明我可能正在尝试在TreeView上使用不存在的事件。
另一个possible solution可能是绑定TreeViewItem的IsExpanded
属性。但是我想将我绑定的对象保持为干净DTOs并在ViewModel中执行操作,而不是在绑定的对象中执行。
那么在扩展TreeViewItem时,如何在ViewModel中调用命令呢?
答案 0 :(得分:7)
要实现这一点,您可以使用附加行为,并且您会发现它是一个干净的MVVM策略。
创建一个WPF应用并添加此Xaml ...
<Grid>
<TreeView>
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour" Value="{Binding ExpandingCommand}"/>
</Style>
</TreeView.Resources>
<TreeViewItem Header="this" >
<TreeViewItem Header="1"/>
<TreeViewItem Header="2"><TreeViewItem Header="Nested"></TreeViewItem></TreeViewItem>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
</TreeViewItem>
<TreeViewItem Header="that" >
<TreeViewItem Header="1"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
</TreeViewItem>
</TreeView>
</Grid>
然后创建一个像这样的视图模型......
public class ViewModel : INotifyPropertyChanged
{
public ICommand ExpandingCommand { get; set; }
public ViewModel()
{
ExpandingCommand = new RelayCommand(ExecuteExpandingCommand, CanExecuteExpandingCommand);
}
private void ExecuteExpandingCommand(object obj)
{
Console.WriteLine(@"Expanded");
}
private bool CanExecuteExpandingCommand(object obj)
{
return true;
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
我使用Relay Command,但您可以互换地使用Delegate Command。中继命令的源位于http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
然后创建一个看起来像这样的单独的类......
public static class Behaviours
{
#region ExpandingBehaviour (Attached DependencyProperty)
public static readonly DependencyProperty ExpandingBehaviourProperty =
DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(OnExpandingBehaviourChanged));
public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
{
o.SetValue(ExpandingBehaviourProperty, value);
}
public static ICommand GetExpandingBehaviour(DependencyObject o)
{
return (ICommand) o.GetValue(ExpandingBehaviourProperty);
}
private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewItem tvi = d as TreeViewItem;
if (tvi != null)
{
ICommand ic = e.NewValue as ICommand;
if (ic != null)
{
tvi.Expanded += (s, a) =>
{
if (ic.CanExecute(a))
{
ic.Execute(a);
}
a.Handled = true;
};
}
}
}
#endregion
}
然后将此类的名称空间导入Xaml ...
的xmlns:bindTreeViewExpand =&#34; CLR-名称空间:BindTreeViewExpand&#34; (你的名字空间会有所不同!)
Resharper将为您执行此操作,或者为您提供智能提示。
最后连接View Model。使用这样快速而肮脏的方法...
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
然后,在解析名称空间并且接线正确后,它将开始工作。将调试器锚定在Execute方法中,并观察您是否获得了RoutedEvent参数。您可以解析它以获取扩展的树视图项。
此解决方案的关键方面是STYLE中指定的行为!因此它适用于每个TreeViewItem。没有任何代码(除了行为)。
我上面列出的行为将事件标记为已处理。您可能希望根据您所追求的行为进行更改。