Set property of one control from the HierarchicalDataTemplate of another
I've got a viewmodel like so:
public abstract class ViewModelBase : INotifyPropertyChanged {
public abstract string Header { get; }
public abstract IEnumerable<ViewModelBase> Nodes { get; }
public abstract IEnumerable GridData { get; }
// following are hooked up to PropertyChanged event in the standard way
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
}
This represents the nodes in a TreeView. Now, whenever the selecteditem changes, I want the selected item's GridData
property to be copied to a separate DataGri开发者_开发百科d's ItemSource property.
I've got a DataTrigger Setter that should be doing what I want:
<DataGrid Name="dataGrid" />
<TreeView Name="treeView" ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type ViewModel:ViewModelBase}" ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter TargetName="dataGrid" Property="DataContext" Value="{Binding GridData}" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
However, this is giving an error 'Cannot find the Trigger target 'dataGrid'. How do I get this working, and what am I doing wrong?
The reason why the dataGrid
name cannot be found is that you are trying to access an element that is located in a different Name Scope, meaning that elements inside a data template cannot "see" elements that are outside that template.
To fix that you could do the following. You will need another view model for your UserControl
(or a Window
, whatever contains you TreeView
and DataGrid
). Lets call it MyTreeViewModel
:
public class MyTreeViewModel : INotifyPropertyChanged // Better to implement this interface in some base class for all your view models
{
private TreeItemViewModelBase _selectedNode;
public IEnumerable<TreeItemViewModelBase> TopLevelNodes
{
get { yield return new TopLevelTreeNodeViewModel(); // Subclass of your base class for tree nodes (ViewModelBase) }
}
public TreeItemViewModelBase SelectedNode
{
get { return _selectedNode; }
set
{
_selectedNode = value;
RaisePropertyChanged("SelectedNode");
}
}
}
Set an instance of this view model as DataContext
of your UserControl
that contains you TreeView
and DataGrid
. Then, you XAML will look like this:
<DataGrid Name="dataGrid" ItemsSource="{Binding SelectedNode.GridData}" />
<TreeView Name="treeView" ItemsSource="{Binding TopLevelNodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type ViewModel:ViewModelBase}" ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Then, you need to modify you ViewModelBase (I suggest rename it to TreeItemViewModelBase
to avoid confusion):
public abstract class TreeItemViewModelBase : INotifyPropertyChanged
{
private bool _isSelected;
public TreeItemViewModelBase(MyTreeViewModel parentViewModel)
{
ParentViewModel = parentViewModel;
}
public abstract string Header { get; }
public abstract IEnumerable<TreeItemViewModelBase> Nodes { get; }
public abstract IEnumerable GridData { get; }
public MyTreeViewModel ParentViewModel { get; private set; }
// following are hooked up to PropertyChanged event in the standard way
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
if (_isSelected) {
ParentViewModel.SelectedNode = this;
}
}
}
public bool IsExpanded { get; set; }
}
As you see, it maintains a reference to the parent view model and whenever IsSelected
is set to true
it sets itself as SelectedNode
on the parent view model, which triggers data binding on DataGrid
s ItemsSource
to refresh.
Hope this helps.
Here is my usual solution for such situation:
- First of all I always have a VM which contains collection of root nodes. Let's name it
RootVM
. This VM contains root nodes andSelectedNode
property. - Your grid view datasource should be bound to
RootVM.SelectedNode.GridData
- Then you have to bind selected node in tree to the
SelectedNode
property. The problem here is that it cannot be bound directly (SelectedItem
property in treeview is readonly), but this can be solved. I prefer to use attached behavior for this, some prefer inheriting treeView - it's up to you.
This seems to be a good way how to get it working.
精彩评论