0

I am trying to implement a countries list as per this link. Basically it has a id:country with 3 levels of data.

I have the tree view displaying as required using this class:

using System.Collections.ObjectModel;

namespace ckd.Library
{
  /// <summary>
  /// implementation of the hierarchical data from the ABS SACC 2016
  /// @link https://www.abs.gov.au/statistics/classifications/standard-australian-classification-countries-sacc/latest-release 
  /// </summary>
  public static class Sacc2016
  {
    public static  ObservableCollection<MajorGroup> Countries { get; set; }

    static Sacc2016()
    {
      Countries = new ObservableCollection<MajorGroup>();

      var majorGroup1 = new MajorGroup(1, "OCEANIA AND ANTARCTICA");

      var minorGroup11 = new MinorGroup(11, "Australia (includes External Territories)");
      var minorGroup12 = new MinorGroup(12, "New Zealand");
      var minorGroup13 = new MinorGroup(13, "Melanesia");

      minorGroup11.Countries.Add(new Country(1101, "Australia"));
      minorGroup11.Countries.Add(new Country(1102, "Norfolk Island"));
      minorGroup11.Countries.Add(new Country(1199, "Australian External Territories, nec"));
      minorGroup12.Countries.Add(new Country(1201, "New Zealand"));
      minorGroup13.Countries.Add(new Country(1301, "New Caledonia"));

      Countries.Add(majorGroup1);
     
    }
  }

  public class MajorGroup
  {
    public MajorGroup(int id, string name)
    {
      Id = id;
      Name = name;
      MinorGroups = new ObservableCollection<MinorGroup>();
    }

    public int                              Id          { get; set; }
    public string                           Name        { get; set; }
    public ObservableCollection<MinorGroup> MinorGroups { get; set; }
  }

  public class MinorGroup
  {
    public MinorGroup(int id, string name)
    {
      Id = id;
      Name = name;
      Countries = new ObservableCollection<Country>();
    }

    public int                           Id        { get; set; }
    public string                        Name      { get; set; }
    public ObservableCollection<Country> Countries { get; set; }
  }

  public class Country
  {
    public Country(int id, string name)
    {
      Id = id;
      Name = name;
    }

    public int    Id   { get; set; }
    public string Name { get; set; }
  }
}

My ViewModel implements INotifyPropertyChanged and in part is:


    private int? _CountryOfBirth;
    public int? CountryOfBirth
    {
      get => _CountryOfBirth;
      set => SetProperty(ref _CountryOfBirth, value);
    }
    public ObservableCollection<MajorGroup>                CountriesObservableCollection   { get; private set; }
    void ViewModelConstructor(){
       ...
       CountriesObservableCollection = Sacc2016.Countries;
       ...
    }



Finally, the xaml section is:

<TreeView x:Name="CountriesTreeView" HorizontalAlignment="Stretch" Margin="10" VerticalAlignment="Stretch" 
                      ItemsSource="{Binding CountriesObservableCollection}"
                      SelectedValue="{Binding CountryOfBirth, Mode=OneWayToSource }"
                      SelectedValuePath="Id"
                      >  
                <TreeView.ItemTemplate>  
                    <HierarchicalDataTemplate ItemsSource="{Binding MinorGroups}" DataType="{x:Type library:MajorGroup}">  
                        <Label Content="{Binding Name}"/>  
                        <HierarchicalDataTemplate.ItemTemplate>  
                            <HierarchicalDataTemplate ItemsSource="{Binding Countries}" DataType="{x:Type library:MinorGroup}">  
                                <Label Content="{Binding Name}"/>  
                                <HierarchicalDataTemplate.ItemTemplate>  
                                    <DataTemplate DataType="{x:Type library:Country}">  
                                        <Label Content="{Binding Name}"/>  
                                    </DataTemplate>  
                                </HierarchicalDataTemplate.ItemTemplate>  
                            </HierarchicalDataTemplate>  
                        </HierarchicalDataTemplate.ItemTemplate>  
                    </HierarchicalDataTemplate>  
                </TreeView.ItemTemplate>  
            </TreeView>  

this xaml gives the error: View.xaml(260, 23): [MC3065] 'SelectedValue' property is read-only and cannot be set from markup. Line 260 Position 23. removing the selectedValue shows: image of treeview

so my question is, how can I link the Id field (from MajorGroup, MinorGroup and Country) to the CountryOfBirth property in my viewmodel?

pgee70
  • 3,707
  • 4
  • 35
  • 41
  • Does this answer your question? [Get SelectedItem from TreeView?](https://stackoverflow.com/questions/1238304/get-selecteditem-from-treeview) – user2250152 Mar 27 '22 at 13:20

1 Answers1

0

There exist many solutions. One simple MVVM ready solution is to handle the TreeView.SelectedItemChanged event to set a local dependency property which you can bind to the view model class:

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static readonly DependencyProperty SelectedTreeItemProperty = DependencyProperty.Register(
    "SelectedTreeItem", 
    typeof(object), 
    typeof(MainWindow), 
    new PropertyMetadata(default));

  public object SelectedTreeItem
  {
    get => (object)GetValue(SelectedTreeItemProperty);
    set => SetValue(SelectedTreeItemProperty, value); 
  }

  public MainWindow()
  {
    InitializeComponent();
    this.DataContext = new MainViewModel();
  }

  private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
  {
    var treeView = sender as TreeView;
    this.SelectedTreeItem = treeView.SelectedItem;
  }
}

MainWindow.xaml

<Window>
  <Window.Resources>
    <Style TargetType="local:MainWindow">
      <Setter Property="SelectedTreeItem"
              Value="{Binding SelectedDataModel, Mode=OneWayToSource}" />
    </Style> 
  </Window.Resources>

  <TreeView SelectedItemChanged="TreeView_SelectedItemChanged" />
</Window>

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged
{
  public object SelectedDataModel { get; set; }
}

Alternatively, you can also move the logic from the MainWindow.xaml.cs to an attached behavior.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thanks for responding - Your answer shows how to bind an object to the view model, but not an int? CountryOfBirth - So as I read your answer, object could be a Country, MinorGroup or a MajorGroup. After posting this I was able to use the itemSelected event to bind the selected value to a separate TextBlock, but this only works one way, it doesn't bind the TextBlock to the TreeView – pgee70 Mar 27 '22 at 22:11
  • The example shows the general pattern. You can read any property from the selected item and sign it to a local property that you can bind to. If you want to make TwoWay, you must register a dependency property changed callback with the dependency property. Then find the item in the TreeView and select it. It's a little complicated, that's why it isn't supported by the TreeView by default. It will be impossible to identify the item of you bind to e.g. integer values instead of the complete item unless the value is unique. – BionicCode Mar 27 '22 at 22:25
  • Note that you can always read the value like CountryOfBirth directly in the view model from the selected item. If you want to sent a property value to the TreeView simply set it on the selected item in your view model. If you want to select an item, set an IsSelected property that you bind to the TreeViewItem via the ItemContainerStyle. In this case you must expand the complete tree from the root to the selected item in order to make it visible – BionicCode Mar 27 '22 at 22:28