user575219 user575219 - 2 months ago 13
C# Question

WPF treeview and datagrid selection changed

I am working on this WPF project with a treeview and datagrid.

On the left side is a tree structure with some documents shown on the right side in the data grid.
If we change the selected item in the tree view, the datagrid display should change.
All Documents should show all and so on.
Can someone point me in the right direction? I have this working in winforms with some view models.
Just doing this in WPF seems hard. I am just learning WPF. Here is what I have done so far. Now the part where I bring in the tree is where some help or websites would be good



<Grid ShowGridLines="False">


<DataGrid Name="DataGrid1" AutoGenerateColumns="False" AlternatingRowBackground="AliceBlue">
<DataGrid.Columns>

<DataGridTextColumn Header="Document Type Name" Binding="{Binding DocumentType.DocumentTypeName}" IsReadOnly="True"></DataGridTextColumn>
<DataGridTextColumn Header="Status" Binding="{Binding Name}" IsReadOnly="True"></DataGridTextColumn>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" IsReadOnly="True"></DataGridTextColumn>

<DataGridTextColumn Header="Type" Binding="{Binding Type}" IsReadOnly="True"></DataGridTextColumn>

</DataGrid.Columns>

</DataGrid>
</Grid>

</StackPanel>


enter image description here

Answer

Since the default TreeView does not support a Binding on SelectedItem, you have to perform an ugly Workaround. Subclass the Treeview and make it bindable first:

 public class TreeViewA : TreeView {

    public new static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeViewA), new FrameworkPropertyMetadata(null, OnSelectedItemChanged));

    public TreeViewA() {
      base.SelectedItemChanged += this.OnTreeViewSelectedItemChanged;
      this.ItemContainerGenerator.StatusChanged += this.ItemContainerGeneratorOnStatusChanged;
    }

    public new object SelectedItem {
      get {
        return this.GetValue(SelectedItemProperty);
      }
      set {
        this.SetValue(SelectedItemProperty, value);
      }
    }

    private void ItemContainerGeneratorOnStatusChanged(object sender, EventArgs eventArgs) {
      if (this.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
        return;
      if (this.SelectedItem != null) {
        this.VisualSelectItem();
      }
    }

    private void VisualSelectItem() {
      var xx = (TreeViewItem)this.ItemContainerGenerator.ContainerFromItem(this.SelectedItem);
      if (xx == null)
        return;
      xx.IsSelected = true;
      xx.BringIntoView();
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {
      this.SelectedItem = e.NewValue;
    }

    private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
      if (e.NewValue != null) {
        (sender as TreeViewA)?.VisualSelectItem();
      }
    }

  }

Next, the Gui (Example)

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <local:TreeViewA ItemsSource="{Binding Documents}" SelectedItem="{Binding SelectedDocument, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
            <TreeView.ItemTemplate>
                <DataTemplate>
                    <WrapPanel>
                        <Image Source="whereever" Width="40" Height="40"/>
                        <TextBlock Text="{Binding Name}"/>
                    </WrapPanel>
                </DataTemplate>
            </TreeView.ItemTemplate>
        </local:TreeViewA>

        <DataGrid Grid.Column="1" ItemsSource="{Binding SubDocuments, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" AutoGenerateColumns="False">
            <DataGrid.Columns>

                <DataGridTextColumn Header="Document Type Name" Binding="{Binding DocumentTypeName}" IsReadOnly="True"></DataGridTextColumn>
                <DataGridTextColumn Header="Status" Binding="{Binding Name}" IsReadOnly="True"></DataGridTextColumn>
                <DataGridTextColumn Header="Description" Binding="{Binding Description}" IsReadOnly="True"></DataGridTextColumn>

                <DataGridTextColumn Header="Type" Binding="{Binding Type}" IsReadOnly="True"></DataGridTextColumn>

            </DataGrid.Columns>

        </DataGrid>
    </Grid>

My Example Model for the Documents:

public class Document {

    public string Name {
      get; set;
    }

    public string DocumentTypeName {
      get; set;
    }

    public string Description {
      get; set;
    }

    public string Type {
      get; set;
    }

  }

Code-Behind of my Example-Window:

public partial class Window1 : INotifyPropertyChanged {

    public Window1() {
      InitializeComponent();
      this._docs.Add(new Document { Name = "Doc1", Type = "docx", Description = "Important Doc", DocumentTypeName = "Word-Document" });
      this._docs.Add(new Document { Name = "Doc2", Type = "xlsx", Description = "Important Calculation", DocumentTypeName = "Excel-Document" });
      this._docs.Add(new Document { Name = "Doc3", Type = "pdf", Description = "Important Contract", DocumentTypeName = "Pdf-Document" });
      this.DataContext = this;


    }

    public Document SelectedDocument {
      get {
        return this._selectedDocument;
      }
      set {
        if (Equals(value, this._selectedDocument))
          return;
        this._selectedDocument = value;
        this.SubDocuments.Clear();
        this.SubDocuments.Add(value);
        this.OnPropertyChanged();
      }
    }

    public ObservableCollection<Document> SubDocuments
    {
      get { return this._subDocuments; }
      set
      {
        if (Equals(value, this._subDocuments)) return;
        this._subDocuments = value;
        this.OnPropertyChanged();
      }
    }

    private readonly ObservableCollection<Document> _docs = new ObservableCollection<Document>();
    private Document _selectedDocument;
    private ObservableCollection<Document> _subDocuments = new ObservableCollection<Document>();
    public ObservableCollection<Document> Documents => this._docs;

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }


  }

Closure

This snippet should provide you an idea, how it can be done. It surely differs from your actual objects and implementations. But since you stated that you only need a push in the right direction, this might give you a Jumpstart.

Note

I've made this with Code-Behind and the Model does not implement INotifyPropertyChanged because it doesnt really matter in this example.

Hope this helps.