Search code examples
c#wpfdata-bindingtreeviewselecteditem

Binding different types of selected item in hierarchical treeview?


I have a hierarchical list of objects whose children are objects of a different type. The classes for them are described as follows:

public class Production
{
    public string name { get; set; }
    public string id { get; set; }
    public List<Plant> plants { get; set; }
}

public class Plant
{
    public string name { get; set; }
    public string id { get; set; }
    public List<Scheme> schemes { get; set; }
}

public class Scheme
{
    public string name { get; set; }
    public string id { get; set; }
}

And the main class which contains a list of productions and methods for filling the main menu:

public class DocumentProviderMenu
{
    public List<Production> productions { get; set; }
    public DocumentProviderMenu()
    {
        ExecuteUpdateMenu();
    }
private void ExecuteUpdateMenu() {/*Uploding menu method}

Finally, the TreeView xaml:

<TreeView ItemsSource="{Binding Menu.productions}">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type mod:Production}"
                                      ItemsSource="{Binding plants}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding name}"/>
                </StackPanel>
            </HierarchicalDataTemplate>
            <HierarchicalDataTemplate DataType="{x:Type mod:Plant}"
                                      ItemsSource="{Binding schemes}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding name}"/>
                </StackPanel>
            </HierarchicalDataTemplate>
            <HierarchicalDataTemplate DataType="{x:Type mod:Scheme}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding name}"/>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>

Where Menu is a property of ViewModel.So I declared the fields in ViewModel like:

public Production SelectedProduction
    {
        get => _SelectedProduction;
        set
        {
            _SelectedProduction = value;
            OnPropertyChanged(nameof(SelectedProduction));
        }
    }
private Production _SelectedProduction;

for 3 types - Production,Plant,Scheme.I can bind the selected item but only to one type (this question helped me Data binding to SelectedItem in a WPF Treeview). Is there a way to bind 3 of my types to the selected item?


Solution

  • These elements must have something in common, e.g. an interface:

    enter image description here

    XAML:

    <Window x:Class="WpfApp1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1" Width="640" Height="480"
            mc:Ignorable="d" d:DataContext="{d:DesignInstance local:Model}">
        <Window.DataContext>
            <local:Model />
        </Window.DataContext>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition />
            </Grid.RowDefinitions>
            <TextBlock Text="{Binding SelectedProduction, StringFormat='{}Selected item: {0}' }" />
            <TreeView ItemsSource="{Binding Productions}" SelectedItemChanged="TreeView_OnSelectedItemChanged" Grid.Row="1">
                <TreeView.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type local:Production}"
                                              ItemsSource="{Binding Plants}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" />
                        </StackPanel>
                    </HierarchicalDataTemplate>
                    <HierarchicalDataTemplate DataType="{x:Type local:Plant}"
                                              ItemsSource="{Binding Schemes}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" />
                        </StackPanel>
                    </HierarchicalDataTemplate>
                    <HierarchicalDataTemplate DataType="{x:Type local:Scheme}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" />
                        </StackPanel>
                    </HierarchicalDataTemplate>
                </TreeView.Resources>
            </TreeView>
        </Grid>
    </Window>
    

    Code:

    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    
    namespace WpfApp1;
    
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            (DataContext as Model)!.SelectedProduction = e.NewValue as ITreeElement;
        }
    }
    
    internal class Model : INotifyPropertyChanged
    {
        private ITreeElement? _selectedProduction;
    
        public Model()
        {
            Productions = new List<ITreeElement>
            {
                new Production
                {
                    Name = "production",
                    Plants = new List<Plant>
                    {
                        new()
                        {
                            Name = "plant 1",
                            Schemes = new List<Scheme>
                            {
                                new()
                                {
                                    Name = "scheme 1"
                                },
                                new()
                                {
                                    Name = "scheme 2"
                                }
                            }
                        },
                        new()
                        {
                            Name = "plant 2",
                            Schemes = new List<Scheme>
                            {
                                new()
                                {
                                    Name = "scheme 3"
                                },
                                new()
                                {
                                    Name = "scheme 4"
                                }
                            }
                        }
                    }
                }
            };
        }
    
        public List<ITreeElement> Productions { get; }
    
        public ITreeElement? SelectedProduction
        {
            get => _selectedProduction;
            set => SetField(ref _selectedProduction, value);
        }
    
        public event PropertyChangedEventHandler? PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }
    
    public interface ITreeElement
    {
        string Name { get; set; }
    
        string Id { get; set; }
    }
    
    public class Production : ITreeElement
    {
        public List<Plant> Plants { get; set; }
    
        public string Name { get; set; }
    
        public string Id { get; set; }
    }
    
    public class Plant : ITreeElement
    {
        public List<Scheme> Schemes { get; set; }
    
        public string Name { get; set; }
    
        public string Id { get; set; }
    }
    
    public class Scheme : ITreeElement
    {
        public string Name { get; set; }
    
        public string Id { get; set; }
    }