Click here to Skip to main content
15,880,608 members
Articles / Desktop Programming / WPF

WPF Treeview: Styling and Template Binding using MVVM

Rate me:
Please Sign up or sign in to vote.
4.40/5 (12 votes)
8 Aug 2011CPOL4 min read 112.4K   6K   33   6
Describes how to change appearance of Treeview using custom style

Introduction

In the development world, sometimes we have a requirement to change the appearance of the control. In this article, we walk through how to change appearance of Treeview control using Templates and Styles.

We will also focus on MVVM (Model-View-ViewModel) pattern in this article.

Background

First let's look at MVVM.

MVVM (Model-View-ViewModel)

The Model View ViewModel (MVVM) is an architectural pattern used in software engineering that originated from Microsoft which is specialized in the Presentation Model design pattern. The Model-View-ViewModel (MVVM) pattern provides a flexible way to build WPF/Silverlight applications that promotes code re-use, simplifies maintenance and supports testing.

Model/View/ViewModel also relies on one more thing: a general mechanism for data binding.

Image 1

View

View in MVVM is defined in XAML. It contains visual controls and rich UI using styles and templates and storyboard. View is used for Data binding and displaying data.

ViewModel

Viewmodel is the mediator between view and viewmodel. It has properties, commands and propertychanged notification logic to execute when any property raise or changed. All business logic related to view is written in viewmodel.

Model

Model is like a Entity. It defines properties to be exposed.

TREEVIEW

Treeview is used to display data in a hierarchical series with parent child relationship. One parent node has multiple child nodes.

WPF has HierarchicalDataTemplate to bind treeviewitem in hierarchical data by setting ItemsSource property. You can change the style of treeview using custom template style.

Using the Code

This example is for a Company that has many departments and each department has employees.

We wish to bind departments in parent node and employees for each department in child nodes in Treeview.

Step 1: Create Models for Department and Employee

C#
public class Employee
{
    public int EmployeeID { get; set; }
   public string EmployeeName { get; set; }
}
C#
public class Department
{
    public int DepartmentID { get; set; }
    public string DepartmentName { get; set; }
    public List<Employee> EmployeeList { get; set; }
} 

Step 2: Create One Class for NotifyProperty Change

ViewModelBase.cs
C#
public abstract class ViewModelBase:INotifyPropertyChanged
    {
        #region Constructor

        protected ViewModelBase()
        {
           
        }

        #endregion

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Event Handlers

        /// <summary>
        /// Get name of property
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="e"></param>
        /// <returns></returns>
        public static string GetPropertyName<T>(Expression<Func<T>> e)
        {
            var member = (MemberExpression)e.Body;
            return member.Member.Name;
        }
        /// <summary>
        /// Raise when property value propertychanged or override propertychage
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyExpression"></param>
        protected virtual void RaisePropertyChanged<T>
			(Expression<Func<T>> propertyExpression)
        {
            RaisePropertyChanged(GetPropertyName(propertyExpression));
        }
        /// <summary>
        /// Raise when property value propertychanged
        /// </summary>
        /// <param name="propertyName"></param>
        protected void RaisePropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion      
    }

If we want to notify viewmodels property change, then we must inherit this class in every viewmodel, and override RaisePropertyChanged method to notify property change.

Step 3: Create viewmodels for Each Model

EmployeeViewModel.cs
C#
public class EmployeeViewModel : ViewModelBase
   {
       public Employee Employee { get; protected set; }

       public int EmployeeID
       {
           get { return Employee.EmployeeID; }
           set
           {
               if (Employee.EmployeeID != value)
               {
                   Employee.EmployeeID = value;
                   RaisePropertyChanged(() => EmployeeID);
               }
           }
       }
       public string EmployeeName
       {
           get { return Employee.EmployeeName; }
           set
           {
               if (Employee.EmployeeName != value)
               {
                   Employee.EmployeeName = value;
                   RaisePropertyChanged(() => EmployeeName);
               }
           }
       }
       private bool _isChecked;
       public bool IsChecked
       {
           get { return _isChecked; }
           set
           {
               if (_isChecked != value)
               {
                   _isChecked = value;
                   RaisePropertyChanged(() => IsChecked);
               }
           }
       }
       public EmployeeViewModel(Employee employee)
       {
           this.Employee = employee;
           IsChecked = false;
       }
   }

This viewmodel holds employee model properties and one more property is IsChecked.

IsChecked property is used to check/uncheck child tree nodes and holds value of check/uncheck state for current employee of selected department.

In this viewmodel, I am binding Employee model properties because I have to set logic when property value is changed.

DepartmentViewModel.cs
C#
public class DepartmentViewModel:ViewModelBase
  {
      public Department Department { get; protected set; }

      private ObservableCollection<EmployeeViewModel> _employeeCollection;
      public ObservableCollection<EmployeeViewModel> EmployeeCollection
      {
          get { return _employeeCollection; }
          set {
              if (_employeeCollection != value)
              {
                  _employeeCollection = value;
                  RaisePropertyChanged(() => EmployeeCollection);
              }
          }
      }
      public int DepartmentID
      {
          get { return Department.DepartmentID; }
          set {
              if (Department.DepartmentID != value)
              {
                  Department.DepartmentID = value;
                  RaisePropertyChanged(() => DepartmentID);
              }
          }
      }
      public string DepartmentName
      {
          get { return Department.DepartmentName; }
          set {
              if (Department.DepartmentName != value)
              {
                  Department.DepartmentName = value;
                  RaisePropertyChanged(() => DepartmentName);
              }
          }
      }
      private bool _isChecked;
      public bool IsChecked
      {
          get { return _isChecked; }
          set
          {
              if (_isChecked != value)
              {
                  _isChecked = value;
                  RaisePropertyChanged(() => IsChecked);
                  OnCheckChanged();
              }
          }
      }
      private void OnCheckChanged()
      {
          foreach (EmployeeViewModel employeeViewModel in EmployeeCollection)
          {
              employeeViewModel.IsChecked = IsChecked;
          }
      }
      public DepartmentViewModel(Department department)
      {
          this.Department = department;
          EmployeeCollection = new ObservableCollection<EmployeeViewModel>();
          foreach (Employee employee in Department.EmployeeList)
          {
              EmployeeCollection.Add(new EmployeeViewModel(employee));
          }
      }
  }

DepartmentViewModel consists of Department Model Properties and Collections of EmployeeViewmodel.

Why I have created property ObservableCollection<EmployeeViewModel> EmployeeCollection is because each department has a list of employees so it should contain a list of employees.

ObservableCollection provides dynamic data collections and it also provides notification when any items are added/deleted/updated.

IsChecked property is used to check/uncheck department.

OnCheckChanged() method is used to check/uncheck all employees when related department is check/uncheck.

Step 4: Create viewmodel for UI Page

CompanyDetailViewModel.cs
C#
public class CompanyDetailViewModel : ViewModelBase
    {
        private ObservableCollection<DepartmentViewModel> _departmentCollection;
        public ObservableCollection<DepartmentViewModel> DepartmentCollection
        {
            get { return _departmentCollection; }
            set
            {
                if (_departmentCollection != value)
                {
                    _departmentCollection = value;
                    RaisePropertyChanged(() => DepartmentCollection);
                }
            }
        }
        public CompanyDetailViewModel()
        {
            DepartmentCollection = new ObservableCollection<DepartmentViewModel>();
            List<Department> departmentList = GetDepartmentList();
            foreach (Department department in departmentList)
            {
                DepartmentCollection.Add(new DepartmentViewModel(department));
            }
        }

        #region Methods
        List<Employee> GetEmployeeList()
        {
            List<Employee> employeeList = new List<Employee>();
            employeeList.Add(new Employee() { EmployeeID = 1, EmployeeName = "Hiren" });
            employeeList.Add(new Employee() { EmployeeID = 2, EmployeeName = "Imran" });
            employeeList.Add(new Employee() { EmployeeID = 3, EmployeeName = "Shivpal" });
            employeeList.Add(new Employee() { EmployeeID = 4, EmployeeName = "Prabhat" });
            employeeList.Add(new Employee() { EmployeeID = 5, EmployeeName = "Sandip" });
            employeeList.Add(new Employee() { EmployeeID = 6, EmployeeName = "Chetan" });
            employeeList.Add(new Employee() { EmployeeID = 7, EmployeeName = "Jayesh" });
            employeeList.Add(new Employee() { EmployeeID = 8, EmployeeName = "Bhavik" });
            employeeList.Add(new Employee() { EmployeeID = 9, EmployeeName = "Amit" });
            employeeList.Add(new Employee() { EmployeeID = 10, EmployeeName = "Brijesh" });
            return employeeList;
        }
        List<Department> GetDepartmentList()
        {
            List<Employee> employeeList = GetEmployeeList();
            List<Department> departmentList = new List<Department>();
            departmentList.Add(new Department() { DepartmentID = 1, 
		DepartmentName = "Mocrosoft.Net", 
		EmployeeList = employeeList.Take(3).ToList() });
            departmentList.Add(new Department() { DepartmentID = 2, 
		DepartmentName = "Open Source", 
		EmployeeList = employeeList.Skip(3).Take(3).ToList() });
            departmentList.Add(new Department() { DepartmentID = 3, 
		DepartmentName = "Other", 
		EmployeeList = employeeList.Skip(6).Take(4).ToList() });
            return departmentList;
        }
        #endregion

This viewmodel is for CompanyDetailView.xaml page declared ObservableCollection<DepartmentViewModel> DepartmentCollection property to hold collection of departments to bind in treeviewitems.

I created two methods for getting a list of Departments and List of Employees and bind to each viewmodel properties.

Step 5: Create XAML Page to Display treeview

CompanyDetailView.xaml
XML
<Grid Grid.Row="1"
                 MaxHeight="250">
               <TreeView ScrollViewer.VerticalScrollBarVisibility="Auto"
                         BorderThickness="0"
                         Background="#FFF"
                         ItemContainerStyle="{DynamicResource TreeViewItemStyle}"
                         ItemsSource="{Binding DepartmentCollection}"
                         ItemTemplate="{DynamicResource DepartmentTemplate}" />
 </Grid>

Put Treeview control in page and setting ItemsSource to DepartmentCollection property of CompanyDetailViewModel.

Set ItemContainerStyle to TreeViewItemStyle created in resourcedictionary.

Set ItemTemplate to DepartmentTemplate, i.e., template created in resourcedictionary.

Step 6: Create TreeViewItemStyle Style

XML
<Style x:Key="ExpandCollapseToggleStyle"
          TargetType="{x:Type ToggleButton}">
       <Setter Property="Focusable"
               Value="False" />
       <Setter Property="Width"
               Value="17" />
       <Setter Property="Height"
               Value="17" />
       <Setter Property="Template">
           <Setter.Value>
               <ControlTemplate TargetType="{x:Type ToggleButton}">
                   <Border Width="17"
                           Height="17"
                           Background="Transparent">
                       <Border Width="17"
                               Height="17"
                               SnapsToDevicePixels="true"
                               Background="{DynamicResource
               {x:Static SystemColors.WindowBrushKey}}"
                               BorderBrush="{DynamicResource
               {x:Static SystemColors.GrayTextBrushKey}}"
                               BorderThickness="1">
                           <Grid>
                               <Rectangle>
                                   <Rectangle.Fill>
                                       <LinearGradientBrush EndPoint="0,1"
                                                            StartPoint="0,0">
                                           <GradientStop Color="#7FD4FF"
                                                         Offset="0" />
                                           <GradientStop Color="#00AAFF"
                                                         Offset="1" />
                                       </LinearGradientBrush>
                                   </Rectangle.Fill>
                               </Rectangle>
                               <Line Stroke="#112844"
                                     x:Name="ExpandImg"
                                     StrokeThickness="1"
                                     X1="8"
                                     Y1="2"
                                     X2="8"
                                     Y2="14" />
                               <Line Stroke="#112844"
                                     StrokeThickness="1"
                                     X1="2"
                                     Y1="8"
                                     X2="14"
                                     Y2="8" />
                           </Grid>
                       </Border>
                   </Border>
                   <ControlTemplate.Triggers>
                       <Trigger Property="IsChecked"
                                Value="True">
                           <Setter Property="Visibility"
                                   TargetName="ExpandImg"
                                   Value="Collapsed" />
                       </Trigger>
                   </ControlTemplate.Triggers>
               </ControlTemplate>
           </Setter.Value>
       </Setter>
   </Style>

In the above code, I have created style for toggle button (Expand/Collapsed) in treeview.

<Rectangle> control is used to create a square.

<Line> control is used to create + (Collapsed) and - (Expand) symbol.

<Trigger> is for change state of toggle button when expand/collapsed.

XML
<Style x:Key="TreeViewItemStyle"
         TargetType="{x:Type TreeViewItem}">
      <Setter Property="IsExpanded"
              Value="{Binding IsExpanded, Mode=TwoWay}" />
      <Setter Property="Background"
              Value="Transparent" />
      <Setter Property="HorizontalContentAlignment"
              Value="{Binding HorizontalContentAlignment,
      RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
      <Setter Property="VerticalContentAlignment"
              Value="{Binding VerticalContentAlignment,
      RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
      <Setter Property="Padding"
              Value="1,0,0,0" />
      <Setter Property="Foreground"
              Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
      <Setter Property="FocusVisualStyle"
              Value="{StaticResource TreeViewItemFocusVisual}" />
      <Setter Property="Template">
          <Setter.Value>
              <ControlTemplate TargetType="{x:Type TreeViewItem}">
                  <Grid>
                      <Grid.ColumnDefinitions>
                          <ColumnDefinition MinWidth="19"
                                            Width="Auto" />
                          <ColumnDefinition Width="Auto" />
                          <ColumnDefinition Width="*" />
                      </Grid.ColumnDefinitions>
                      <Grid.RowDefinitions>
                          <RowDefinition Height="Auto" />
                          <RowDefinition />
                      </Grid.RowDefinitions>
                      <ToggleButton x:Name="Expander"
                                    ClickMode="Press"
                                    IsChecked="{Binding IsExpanded,
                 RelativeSource={RelativeSource TemplatedParent}}"
                                    Style="{StaticResource ExpandCollapseToggleStyle}" />
                      <Border x:Name="Bd"
                              BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="{TemplateBinding BorderThickness}"
                              Background="{TemplateBinding Background}"
                              Grid.Column="1"
                              Padding="{TemplateBinding Padding}"
                              SnapsToDevicePixels="true">
                          <ContentPresenter x:Name="PART_Header"
                                            ContentSource="Header"
                                            VerticalAlignment=
                  "{TemplateBinding VerticalContentAlignment}"
                                            HorizontalAlignment=
                  "{TemplateBinding HorizontalContentAlignment}"
                                            SnapsToDevicePixels=
                  "{TemplateBinding SnapsToDevicePixels}" />
                      </Border>
                      <ItemsPresenter x:Name="ItemsHost"
                                      Grid.ColumnSpan="2"
                                      Grid.Column="1"
                                      Grid.Row="1" />
                  </Grid>
              </ControlTemplate>
          </Setter.Value>
      </Setter>
      <Style.Triggers>
          <Trigger Property="VirtualizingStackPanel.IsVirtualizing"
                   Value="true">
              <Setter Property="ItemsPanel">
                  <Setter.Value>
                      <ItemsPanelTemplate>
                          <VirtualizingStackPanel />
                      </ItemsPanelTemplate>
                  </Setter.Value>
              </Setter>
          </Trigger>
      </Style.Triggers>
  </Style>

Above is the style for treeviewitem to set Background color property, setting controlTemplate, Padding, Alignment, etc. properties.

Step 7: Create Templates for Display TreeviewItes

XML
<DataTemplate x:Key="EmployeeDateTemplate">
       <StackPanel Orientation="Horizontal">
           <CheckBox Focusable="False"
                     IsChecked="{Binding IsChecked,Mode=TwoWay}"
                     VerticalAlignment="Center" Margin="5" />
           <TextBlock Text="{Binding EmployeeName}"
                      VerticalAlignment="Center" Margin="5" />
       </StackPanel>
   </DataTemplate>
   <HierarchicalDataTemplate x:Key="DepartmentTemplate"
                             ItemsSource="{Binding EmployeeCollection}"
                             ItemTemplate="{StaticResource EmployeeDateTemplate}">
       <StackPanel Orientation="Horizontal">
           <CheckBox Focusable="False" Margin="5"
                     IsChecked="{Binding IsChecked,Mode=TwoWay}"
                     VerticalAlignment="Center" />
           <TextBlock Text="{Binding DepartmentName}"
                      Margin="5 5 10 10" />
       </StackPanel>

HierarchicalDataTemplate Template binds data in treeview items as parent nodes and holds child data template. Its ItemsSource property holds data of Collection of Employee related to Department.

ItemTemplate property has template for how to display employee data in treeview items. It is DataTemplate for Employee.

Step 8: Setting DataContext to View

C#
this.DataContext = new CompanyDetailViewModel();

The above line is written in code behind file of MainWindow.xaml to set datacontent of CompanyDetailViewModel which contains Company Data.

At the end, put CompanyDetailView.xaml control in MainWindow.xaml and run the code. You will see the below output:

Image 2

History

  • 8th August, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Reputed IT Company
India India
Having 9+ years of experience in Microsoft.Net Technology.
Experience in developing applications on Microsoft .NET Platform ( Asp.Net, WPF, Silverlight, Windows Phone 7/8).
Experience and knowledge of software design methodologies (Agile), object oriented design, and software design patterns (MVVM).
Experience in Developing android mobile application using Xamarin (mono for android) framework.

http://hirenkhirsaria.blogspot.com/

Comments and Discussions

 
QuestionBug in check selection Pin
Shabana Parveen25-Sep-19 3:42
professionalShabana Parveen25-Sep-19 3:42 
QuestionDownload for this file does not work!? Pin
TurgayTürk1-Oct-15 12:02
TurgayTürk1-Oct-15 12:02 
QuestionModel change notification Pin
last.try6-Nov-11 17:27
last.try6-Nov-11 17:27 
AnswerRe: Model change notification Pin
Hiren Khirsaria12-Dec-11 0:03
professionalHiren Khirsaria12-Dec-11 0:03 
QuestionNice work Pin
appxdev8-Aug-11 23:45
appxdev8-Aug-11 23:45 
AnswerRe: Nice work Pin
Hiren Khirsaria18-Aug-11 21:00
professionalHiren Khirsaria18-Aug-11 21:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.