Implementation of Embedding TreeView in WPF ComboBox (MVVM)

Implementation of Embedding TreeView in WPF ComboBox (MVVM)

Because of project requirements, a tree structure is needed inside the ComboBox control.

Last updated 11/1/2022 9:31 PM
hlpinghcg
5 min read
Category
WPF
Topic
WPF MVVM Framework Prism Series
Tags
.NET C# WPF MVVM

This article is reproduced from a reprint.

Author: hlpinghcg

Original title: Implementation of embedding TreeView in WPF ComboBox (MVVM)

Original link: https://blog.csdn.net/qq_28149763/article/details/126539635

Introduction

Due to project requirements, the ComboBox control needed a tree structure. I also saw many implementations online, both complex and simple. I encountered some issues during my own implementation, and I'd like to share my approach here. If you have any questions, feel free to message me to discuss and provide guidance.

First, I referenced a netizen's implementation: WPF TreeView MVVM Two-Way Binding

Without further ado, let's get into the topic...

XAML file

<StackPanel Orientation="Vertical">
  <ComboBox Name="com" SelectedIndex="{Binding ComboSelected}">
    <i:Interaction.Triggers>
      <i:EventTrigger EventName="SelectionChanged">
        <i:InvokeCommandAction
          Command="{Binding SelectionChangedCommand}"
          CommandParameter="{Binding ElementName=com,Path=SelectedItem}"
        />
      </i:EventTrigger>
    </i:Interaction.Triggers>
    <ComboBoxItem Content="{Binding ShowName}" Visibility="Collapsed" />
    <ComboBoxItem FocusVisualStyle="{x:Null}">
      <ItemsControl>
        <TreeView
          x:Name="treeView"
          ItemsSource="{Binding TypeList}"
          Height="200"
          Width="{Binding ElementName=com, Path=ActualWidth}"
        >
          <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectedItemChanged">
              <i:InvokeCommandAction
                Command="{Binding SelectItemChangeCommand}"
                CommandParameter="{Binding ElementName=treeView,Path=SelectedItem}"
              />
            </i:EventTrigger>
          </i:Interaction.Triggers>
          <TreeView.Resources>
            <HierarchicalDataTemplate
              DataType="{x:Type m:TypeTreeModel}"
              ItemsSource="{Binding ChildList}"
            >
              <StackPanel Orientation="Horizontal">
                <!--<Image Source="/images/OIP-C.jpg" Width="15" Height="15"/>-->
                <TextBlock Text="{Binding Name}" Margin="3,2" />
                <TextBlock Text=" [" Foreground="Blue" />
                <TextBlock Text="{Binding ChildList.Count}" Foreground="Blue" />
                <TextBlock Text="]" Foreground="Blue" />
              </StackPanel>
              <!--<TextBlock Text="{Binding Name}" Margin="3,2"/>-->
            </HierarchicalDataTemplate>
            <DataTemplate DataType="{x:Type m:TypeModel}">
              <StackPanel Orientation="Horizontal">
                <!--<Image Source="/images/OIP-D.jpg" Width="15" Height="15"/>-->
                <!--<TextBlock Text="{Binding Name}" ToolTip="{Binding Id}" Margin="3,2"/>-->
                <TextBlock Text="{Binding Name}" Margin="3,2" />
                <TextBlock Text=" (" Foreground="Green" />
                <TextBlock Text="{Binding Id}" Foreground="Green" />
                <TextBlock Text=" )" Foreground="Green" />
              </StackPanel>
              <!--<TextBlock Text="{Binding Name}" ToolTip="{Binding Id}" Margin="3,2"></TextBlock>-->
            </DataTemplate>
          </TreeView.Resources>
        </TreeView>
      </ItemsControl>
    </ComboBoxItem>
  </ComboBox>
  <!--<TreeView x:Name="treeView" ItemsSource="{Binding TypeList}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectedItemChanged">
                <i:InvokeCommandAction Command="{Binding SelectItemChangeCommand}"
                          CommandParameter="{Binding ElementName=treeView,Path=SelectedItem}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type m:TypeTreeModel}" ItemsSource="{Binding ChildList}">
                <TextBlock Text="{Binding Name}" Margin="3,2"/>
            </HierarchicalDataTemplate>
            <DataTemplate DataType="{x:Type m:TypeModel}">
                <TextBlock Text="{Binding Name}" ToolTip="{Binding Id}" Margin="3,2"></TextBlock>
            </DataTemplate>
        </TreeView.Resources>
    </TreeView>-->
</StackPanel>

Note that the direct event binding has been switched to binding via Command:

  • Add the GuNet package (search for Interactivity)
  • Introduce namespace in XAML: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
  • Add the following code
<i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged">
        <i:InvokeCommandAction Command="{Binding SelectionChangedCommand}"
                  CommandParameter="{Binding ElementName=com,Path=SelectedItem}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>
  • Reference the MVVM library; you can check: WPF MVVM General Library
  • Backend logic (ViewModel) command declaration and usage
SelectItemChangeCommand = new RelayCommand<TypeModel>(onSelectItemChange);
SelectionChangedCommand = new RelayCommand(onSelectionChanged);
  public RelayCommand<TypeModel> SelectItemChangeCommand { get; set; }
  public RelayCommand SelectionChangedCommand { get; set; }

Backend logic (ViewModel)

public class ViewModel:ViewModelBase
{

    public ObservableCollection<TypeTreeModel> TypeList { get; set; } = new ObservableCollection<TypeTreeModel>();

    public ViewModel()
    {
        TypeList = new ObservableCollection<TypeTreeModel>(GetData());

        SelectItemChangeCommand = new RelayCommand<TypeModel>(onSelectItemChange);
        SelectionChangedCommand = new RelayCommand(onSelectionChanged);
    }

    public RelayCommand<TypeModel> SelectItemChangeCommand { get; set; }
    public RelayCommand SelectionChangedCommand { get; set; }

    private TypeModel selectItem;
    public TypeModel SelectItem
    {
        get { return selectItem; }
        set
        {
            selectItem = value;
            RaisePropertyChanged(() => SelectItem);
        }
    }

    private string showName;
    public string ShowName
    {
        get { return showName; }
        set
        {
            showName = value;
            RaisePropertyChanged(() => ShowName);
        }
    }


    private int comboSelected;
    public int ComboSelected
    {
        get { return comboSelected; }
        set
        {
            comboSelected = value;
            RaisePropertyChanged(() => ComboSelected);
        }
    }


    private List<TypeTreeModel> GetData()
    {
        List<TypeTreeModel> typeTrees = new List<TypeTreeModel>()
        {
            new TypeTreeModel()
            {
                Id = 1,
                Name = "Phone",
                ChildList = new ObservableCollection<TypeTreeModel>()
                {
                    new TypeTreeModel(){ Id=2, Name="Apple" },
                    new TypeTreeModel(){ Id=3, Name="Huawei",
                        ChildList = new ObservableCollection<TypeTreeModel>()
                        {
                            new TypeTreeModel(){Id=4, Name="Honor" },
                        }},
                    new TypeTreeModel(){ Id=5, Name="Xiaomi",
                        ChildList = new ObservableCollection<TypeTreeModel>()
                        {
                            new TypeTreeModel(){Id=6, Name="Redmi" }
                        }}
                }
            },
            new TypeTreeModel()
            {
                Id=7,
                Name="Laptop",
                ChildList = new ObservableCollection<TypeTreeModel>()
                {
                    new TypeTreeModel(){Id=8, Name="Lenovo"}
                }
            },
            new TypeTreeModel()
            {
                Id=9,
                Name="Headphones"
            }
        };
        return typeTrees;
    }

    private void onSelectItemChange(TypeModel type)
    {
        SelectItem=type;
        ShowName = SelectItem.Name;

    }

    private void onSelectionChanged()
    {
        ComboSelected = 0;
    }
}

Data classes

public class TypeTreeModel: TypeModel
{
    public ObservableCollection<TypeTreeModel> ChildList { get; set; }
        = new ObservableCollection<TypeTreeModel>();
}

public class TypeModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsSelected { get; set; }
}

Final Result

Site Admin Tried the Above Code

The site admin tested the above code and it runs normally. The complete code is hosted on GitHub

Keep Exploring

Related Reading

More Articles