开发者

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 DataGrids ItemsSource to refresh.

Hope this helps.


Here is my usual solution for such situation:

  1. First of all I always have a VM which contains collection of root nodes. Let's name it RootVM. This VM contains root nodes and SelectedNode property.
  2. Your grid view datasource should be bound to RootVM.SelectedNode.GridData
  3. 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜