Click here to Skip to main content
15,860,859 members
Articles / Desktop Programming / WPF

Basic Understanding of Tree View in WPF

Rate me:
Please Sign up or sign in to vote.
4.90/5 (114 votes)
3 Nov 2010CPOL6 min read 485K   16.9K   150   34
This article explains different ways to show contents in the Tree View control.
Image 1

Introduction

This article describes the use of TreeView control provided by WPF. It will give you a knowledge to create simple Tree, customization, Template and Binding. It mainly focuses on how to show contents in the tree view. This article will help you to understand the basics of tree view and also gives you deep knowledge to show content on tree according to your requirement.

I had to use tree view in one of my projects where I had to show image and text as most of tree has in windows. This problem started my journey to learn features provided by WPF. At the start of my journey, I found many difficulties and sometimes I said Windows application contains more user friendly controls to work with, but with the passage of time I found WPF controls more user friendly.

This article will cover the following 6 main areas:

Create Simple Tree

Image 2

If you want to create a simple tree then WPF provides you an easy way draw tree. Just add TreeView control on your page and add items in either using XAML or from code behind.

Using XAML

You can easily create a tree using XAML.

XML
<TreeView>
   <TreeViewItem Header="North America">
       <TreeViewItem Header="USA"></TreeViewItem>
       <TreeViewItem Header="Canada"></TreeViewItem>
       <TreeViewItem Header="Mexico"></TreeViewItem>
   </TreeViewItem>
   <TreeViewItem Header="South America">
       <TreeViewItem Header="Argentina"></TreeViewItem>
       <TreeViewItem Header="Brazil"></TreeViewItem>
       <TreeViewItem Header="Uruguay"></TreeViewItem>
   </TreeViewItem>
</TreeView>

Using code

If you want to populate tree from code behind, then simply place your tree on form and add tree item according to your tree hierarchy.

XML
<TreeView Name="tvMain">
</TreeView>
C#
TreeViewItem treeItem = null;

// North America
treeItem = new TreeViewItem();
treeItem.Header = "North America";

treeItem.Items.Add(new TreeViewItem() { Header = "USA" });
treeItem.Items.Add(new TreeViewItem() { Header = "Canada" });
treeItem.Items.Add(new TreeViewItem() { Header = "Mexico" });

tvMain.Items.Add(treeItem);

Customize Tree

Image 3

If you want to add some other controls with the content e.g. checkbox, image, etc., then you can easily design your tree without any big effort. You just need to customize the HeaderTemplate of the TreeViewItem. You can also create class derived from TreeViewItem and change its Header according to your requirement.

Using XAML

For customizing the Tree item, simply change item’s Header.

XML
<TreeView >
   <TreeViewItem >
       <TreeViewItem.Header>
           <StackPanel Orientation="Horizontal">
               <Border Background="Green" Width="8" Height="12"
                       BorderBrush="#00000000"></Border>
               <Label Content="North America"></Label>
           </StackPanel>
       </TreeViewItem.Header>

       <!-- Child Item -->

       <TreeViewItem>
           <TreeViewItem.Header>
               <StackPanel Orientation="Horizontal">
                   <Image Source="../Images/usa.png"></Image>
                   <Label Content="USA"></Label>
               </StackPanel>
           </TreeViewItem.Header>
       </TreeViewItem>
   </TreeViewItem>
</TreeView>

Using Code

If you want to create header from code behind, then WPF will not disappoint you. You can change header template very smartly.

C#
private TreeViewItem GetTreeView(string text, string imagePath)
{
   TreeViewItem item = new TreeViewItem();

   item.IsExpanded = true;

   // create stack panel
   StackPanel stack = new StackPanel();
   stack.Orientation = Orientation.Horizontal;

   // create Image
   Image image = new Image();
   image.Source = new BitmapImage
       (new Uri("pack://application:,,/Images/" + imagePath));

   // Label
   Label lbl = new Label();
   lbl.Content = text;


   // Add into stack
   stack.Children.Add(image);
   stack.Children.Add(lbl);

   // assign stack to header
   item.Header = stack;
   return item;
}

Using overriding TreeViewItem

You can also customize TreeViewItem by writing a new derived class for custom item. It is also pretty easy. Just create header template and assign it to Header property if TreeViewItem.

C#
public class ImageTreeViewItem : TreeViewItem
{
   #region Data Member

   Uri _imageUrl = null;
   Image _image = null;
   TextBlock _textBlock = null;

   #endregion

   #region Properties

   public Uri ImageUrl
   {
       get { return _imageUrl; }
       set
       {
           _imageUrl = value;
           _image.Source = new BitmapImage(value);
       }
   }

   public string Text
   {
       get { return _textBlock.Text; }
       set { _textBlock.Text = value; }
   }

   #endregion

   #region Constructor

   public ImageTreeViewItem()
   {
       CreateTreeViewItemTemplate();
   }

   #endregion

   #region Private Methods

   private void CreateTreeViewItemTemplate()
   {
       StackPanel stack = new StackPanel();
       stack.Orientation = Orientation.Horizontal;

       _image = new Image();
       _image.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
       _image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
       _image.Width = 16;
       _image.Height = 16;
       _image.Margin = new Thickness(2);

       stack.Children.Add(_image);

       _textBlock = new TextBlock();
       _textBlock.Margin = new Thickness(2);
       _textBlock.VerticalAlignment = System.Windows.VerticalAlignment.Center;

       stack.Children.Add(_textBlock);

       Header = stack;
   }

   #endregion
}

Header Template

Image 4

If the style of all the elements is the same, then it is better to create header template at once. Because the problem with the last example was for the same design, we add template for each tree item.

Using XAML

For creating general TreeViewItem item template, create Template resource at application level, window level or at control level resource. In this example, I have created resource at control level and set the TargetType=”TreeViewItem” and also set the “HeaderTemplate” property of the TreeViewItem.

XML
<TreeView Name="tvMain">
   <TreeView.Resources>
       <Style TargetType="{x:Type TreeViewItem}">
           <Setter Property="HeaderTemplate">
               <Setter.Value>
                   <DataTemplate>
                       <StackPanel Orientation="Horizontal">
                           <CheckBox Name="chk" Margin="2" Tag="{Binding}" >
           </CheckBox>
                           <Image  Margin="2"  Source="{Binding Converter=
           {StaticResource CustomImagePathConvertor}}"></Image>
                           <TextBlock Text="{Binding}"></TextBlock>
                       </StackPanel>
                   </DataTemplate>
               </Setter.Value>
           </Setter>
       </Style>
   </TreeView.Resources>

   <TreeViewItem Header="North America" IsExpanded="True">
       <TreeViewItem Header="USA"></TreeViewItem>
       <TreeViewItem Header="Canada"></TreeViewItem>
       <TreeViewItem Header="Mexico"></TreeViewItem>
   </TreeViewItem>

   <TreeViewItem Header="South America"  IsExpanded="True">
       <TreeViewItem Header="Argentina"></TreeViewItem>
       <TreeViewItem Header="Brazil"></TreeViewItem>
       <TreeViewItem Header="Uruguay"></TreeViewItem>
</TreeView>

It is a very interested point here that I did not pass Image path for each country, but TreeView shows flag with each country. I achieved by writing custom converter CustomImagePathConverter.

XML
<Image  Margin="2"  Source="{Binding Converter=
  {StaticResource CustomImagePathConverter}}"></Image>

Implement CustomImagePathConverter from IValueConverter. You can associate a value converter with binding. In this example, I am getting image path from the country name, as you can see in code.

C#
public class CustomImagePathConverter : IValueConverter
{
   #region IValueConverter Members

   public object Convert(object value, Type targetType, object parameter,
                                   System.Globalization.CultureInfo culture)
   {
       return "../Images/" + GetImageName(value.ToString());
   }

   public object ConvertBack(object value, Type targetType, object parameter,
                                   System.Globalization.CultureInfo culture)
   {
       return "";
   }

   #endregion

   private string GetImageName(string text)
   {
       string name = "";
       name = text.ToLower() + ".png";
       return name;
   }
}

Using Code

You can easily create template from code behind. FrameworkElementFactory provides you a facility to create templates. Let's see how can we achieve this exciting feature.

C#
private DataTemplate GetHeaderTemplate()
{
   //create the data template
   DataTemplate dataTemplate = new DataTemplate();

   //create stack pane;
   FrameworkElementFactory stackPanel = new FrameworkElementFactory(typeof(StackPanel));
   stackPanel.Name = "parentStackpanel";
   stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);

   // Create check box
   FrameworkElementFactory checkBox = new FrameworkElementFactory(typeof(CheckBox));
   checkBox.Name = "chk";
   checkBox.SetValue(CheckBox.NameProperty, "chk");
   checkBox.SetValue(CheckBox.TagProperty , new Binding());
   checkBox.SetValue(CheckBox.MarginProperty, new Thickness(2));
   stackPanel.AppendChild(checkBox);

   // Create Image
   FrameworkElementFactory image = new FrameworkElementFactory(typeof(Image));
   image.SetValue(Image.MarginProperty, new Thickness(2));
   image.SetBinding(Image.SourceProperty, new Binding()
       { Converter = new CustomImagePathConverter() });
   stackPanel.AppendChild(image);

   // create text
   FrameworkElementFactory label = new FrameworkElementFactory(typeof(TextBlock));
   label.SetBinding(TextBlock.TextProperty, new Binding());
   label.SetValue(TextBlock.ToolTipProperty, new Binding());
   stackPanel.AppendChild(label);


   //set the visual tree of the data template
   dataTemplate.VisualTree = stackPanel;

   return dataTemplate;
}

Simply assign this template to HeaderTemplate of each TreeViewitem.

C#
DataTemplate template = GetHeaderTemplate();

foreach (WorldArea area in WorldArea.GetAll())
{
   TreeViewItem item = new TreeViewItem();
   item.HeaderTemplate = template;
   item.Header = area.Name;

   .
   .
   .
   .
}

Get selected checked items

You can easily get the child items from the template. Just for the example, I am showing you how to get the selected check boxes from the tree view. WPF manage control in hierarchical structure, you can access any child using VisualTreeHelper class.

C#
private List<CheckBox> GetSelectedCheckBoxes(ItemCollection items)
{
   List<CheckBox> list = new List<CheckBox>();
   foreach (TreeViewItem item in items)
   {
       UIElement elemnt = GetChildControl(item, "chk");
       if (elemnt != null)
       {
           CheckBox chk = (CheckBox)elemnt;
           if (chk.IsChecked.HasValue && chk.IsChecked.Value)
           {
               list.Add(chk);
           }
       }

       List<CheckBox> l = GetSelectedCheckBoxes(item.Items);
       list = list.Concat(l).ToList();
   }

   return list;
}

private UIElement GetChildControl(DependencyObject parentObject, string childName)
{

   UIElement element = null;

   if (parentObject != null)
   {
       int totalChild = VisualTreeHelper.GetChildrenCount(parentObject);
       for (int i = 0; i < totalChild; i++)
       {
           DependencyObject childObject = VisualTreeHelper.GetChild(parentObject, i);

           if (childObject is FrameworkElement &&
       ((FrameworkElement)childObject).Name == childName)
           {
               element = childObject as UIElement;
               break;
           }

           // get its child
           element = GetChildControl(childObject, childName);
           if (element != null) break;
       }
   }

   return element;
}

Custom Objects

Image 5

WPF provides you many ways to populate tree. You can directly add your object as a TreeViewItem in the tree and WPF gives respect to your objects and display it as you want. You just need to tell him which field will be shown in item.

Using XAML

For populating custom object in tree, you just need to create template for your object. I used HierarchicalDataTemplate for designing template.

XML
<TreeView Name="tvMain">
   <TreeView.ItemTemplate>
       <HierarchicalDataTemplate ItemsSource="{Binding Path=Countries}">
           <StackPanel Orientation="Horizontal" Margin="4" Background="LightSeaGreen">
               <CheckBox Name="chk" Margin="2" Tag="{Binding Path=Name}" ></CheckBox>
               <Image  Margin="2" Source="{Binding Path=ImageUrl}" ></Image>
               <TextBlock Text="{Binding Path=Name}" Margin="2" >
               </TextBlock>
               <StackPanel.Effect>
                   <DropShadowEffect BlurRadius="2" Color="LightGray"
            Opacity=".2" ></DropShadowEffect>
               </StackPanel.Effect>
           </StackPanel>
       </HierarchicalDataTemplate>
   </TreeView.ItemTemplate>
</TreeView>

After creating template, you just need to add custom object from your code behind file, as you can see code below. I am just putting parent object in the tree. But when you will run this code, you will also see child countries are also being shown. The reason is that because I have define template in XAML for child items using ItemsSource="{Binding Path=Countries}".

C#
private void FillTree()
{
   foreach (WorldArea area in WorldArea.GetAll())
   {
       tvMain.Items.Add(area);
   }
}

Using Code

You can also create template for your object from code behind file, as we created in previous example. The tricky part here, how can we add custom objects in the hierarchical way? Because using XAML we can write create hierarchical template. We can also create hierarchical template using code behind, but in this example I am not doing that, I am achieving the solution from other way. This technique will give you a new way to work and you can implement it in other Items controls like ListView, ListBox etc. But in the last example, I will create hierarchical template from code behind.

C#
private void FillTree()
{
   tvMain.ItemTemplate = GetHeaderTemplate();
   tvMain.ItemContainerGenerator.StatusChanged +=
       new EventHandler(ItemContainerGenerator_StatusChanged);

   foreach (WorldArea area in _list)
   {
       tvMain.Items.Add(area);
   }
}

void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
   if (tvMain.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
   {
       foreach (WorldArea area in _list)
       {
           TreeViewItem item =
       (TreeViewItem)tvMain.ItemContainerGenerator.ContainerFromItem(area);
           if (item == null) continue;
           item.IsExpanded = true;
           if (item.Items.Count == 0)
           {

               foreach (Country country in area.Countries)
               {
                   item.Items .Add(country);
               }
           }
       }
   }
}

As you can see in code after adding setting template, I have registered tvMain.ItemContainerGenerator.StatusChanged event. ItemContainerGenerator generates the containers for each custom object. When we add custom object in the TreeView ItemContainerGenerator starts to generate container in separate thread. So we cannot get container in the next line after adding object. So you need to register StatusChanged event, which fires after the status change and you can get container after that.

Data Binding

Image 6

You can also bind your tree with any source as you can bind DataGrid, ListView, etc. You just need to create a template for your items as you create in other binding controls.

Using XAML

Create your hierarchical template as you created in the previous example. You may need to add inner hierarchical template for different example. But it is working fine for my example.

XML
<TreeView Name="tvMain"  >
   <TreeView.ItemTemplate>
       <HierarchicalDataTemplate ItemsSource="{Binding Path=Countries}">

           <Grid Background="LightSkyBlue"  Margin="2" Width="100" Height="24">
               <Image Margin="2" Width="32" Height="18"
           Source="{Binding Path=ImageUrl}"
       HorizontalAlignment="Right"
               VerticalAlignment="Center" ></Image>
               <TextBlock Margin="2" Text="{Binding Path=Name}"
           VerticalAlignment="Center" FontWeight="Bold" />
           </Grid>

       </HierarchicalDataTemplate>
   </TreeView.ItemTemplate>
</TreeView>

Simply bind tree using ItemsSource property:

C#
private void BindTree()
{
   tvMain.ItemsSource = WorldArea.GetAll();
}

Using Code

For creating Hierarchical template from code behind simply create object of HierarchicalDataTemplate class and fill childs according to your requirement and assign this template to tree.

C#
private void BindTree()
{
   tvMain.ItemTemplate = GetTemplate();
   tvMain.ItemsSource = WorldArea.GetAll();
}

private HierarchicalDataTemplate GetTemplate()
{
   //create the data template
   HierarchicalDataTemplate dataTemplate = new HierarchicalDataTemplate();

   //create stack pane;
   FrameworkElementFactory grid = new FrameworkElementFactory(typeof(Grid));
   grid.Name = "parentStackpanel";
   grid.SetValue(Grid.WidthProperty, Convert.ToDouble(100));
   grid.SetValue(Grid.HeightProperty, Convert.ToDouble(24) );
   grid.SetValue(Grid.MarginProperty, new Thickness(2));
   grid.SetValue(Grid.BackgroundProperty, new SolidColorBrush( Colors.LightSkyBlue));

   // Create Image
   FrameworkElementFactory image = new FrameworkElementFactory(typeof(Image));
   image.SetValue(Image.MarginProperty, new Thickness(2));
   image.SetValue(Image.WidthProperty, Convert.ToDouble(32));
   image.SetValue(Image.HeightProperty, Convert.ToDouble(24));
   image.SetValue(Image.VerticalAlignmentProperty, VerticalAlignment.Center );
   image.SetValue(Image.HorizontalAlignmentProperty, HorizontalAlignment.Right);
   image.SetBinding(Image.SourceProperty, new Binding()
       { Path = new PropertyPath("ImageUrl") });

   grid.AppendChild(image);

   // create text
   FrameworkElementFactory label = new FrameworkElementFactory(typeof(TextBlock));
   label.SetBinding(TextBlock.TextProperty,
       new Binding() { Path = new PropertyPath("Name") });
   label.SetValue(TextBlock.MarginProperty, new Thickness(2));
   label.SetValue(TextBlock.FontWeightProperty, FontWeights.Bold);
   label.SetValue(TextBlock.ToolTipProperty, new Binding());

   grid.AppendChild(label);

   dataTemplate.ItemsSource = new Binding("Countries");

   //set the visual tree of the data template
   dataTemplate.VisualTree = grid;

   return dataTemplate;
}

Template By Data Type

Image 7

A very nice flexibility provided by WPF is you can create your template by data type. Suppose you want have to show different type of objects in tree and you want to differentiate them on UI. It is not a big problem in WPF. Simply create Template by Data type and bind source with tree or manually add objects. Your tree will pick template according to data type.

Using Data Template

Simply create data template in any resource as I created in tree resource. And set its data type as I did using DataType="{x:Type loc:WorldArea}".

XML
<TreeView Name="tvMain">
   <TreeView.Resources>

       <DataTemplate DataType="{x:Type loc:WorldArea}">
           <Border Width="150" BorderBrush="RoyalBlue"
       Background="RoyalBlue"  BorderThickness="1"
       CornerRadius="2" Margin="2" Padding="2" >
               <StackPanel Orientation="Horizontal" >
                   <TextBlock  Text="{Binding Path=Name}"
           FontWeight="Bold" Foreground="White"></TextBlock>
               </StackPanel>
           </Border>
       </DataTemplate>

       <DataTemplate  DataType="{x:Type loc:Country}">
           <Border Width="132"  Background="LightBlue" CornerRadius="2" Margin="1" >
               <StackPanel Orientation="Horizontal" >
                   <Image Margin="2" Source="{Binding Path=ImageUrl}"></Image>
                   <TextBlock Margin="2"  Text="{Binding Path=Name}"></TextBlock>
               </StackPanel>
           </Border>
       </DataTemplate>

   </TreeView.Resources>
</TreeView>

Using Hierarchical Template

You can also create hierarchical template by data type.

XML
<TreeView Name="tvMain">
   <TreeView.Resources>

       <HierarchicalDataTemplate DataType="{x:Type loc:WorldArea}"
           ItemsSource="{Binding Path=Countries}">
           <Border Width="150" BorderBrush="RoyalBlue" Background="RoyalBlue"
        BorderThickness="1" CornerRadius="2" Margin="2" Padding="2" >
               <StackPanel Orientation="Horizontal" >
                   <TextBlock  Text="{Binding Path=Name}"
           FontWeight="Bold" Foreground="White"></TextBlock>
               </StackPanel>
           </Border>
       </HierarchicalDataTemplate>

       <HierarchicalDataTemplate DataType="{x:Type loc:Country}">
           <Border Width="132"  Background="LightBlue" CornerRadius="2" Margin="1" >
               <Grid>
                   <Grid.ColumnDefinitions>
                       <ColumnDefinition Width="*"></ColumnDefinition>
                       <ColumnDefinition Width="26"></ColumnDefinition>
                   </Grid.ColumnDefinitions>

                   <TextBlock Margin="2"  Text="{Binding Path=Name}"></TextBlock>

                   <Image Grid.Column="1" Margin="2"
           Source="{Binding Path=ImageUrl}"></Image>
               </Grid>
           </Border>
       </HierarchicalDataTemplate>

   </TreeView.Resources>
</TreeView>

License

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


Written By
Chief Technology Officer
Pakistan Pakistan
Passion and positive dedication is essential part of success. I believe on hardworking and sharing knowledge with others. I always try to be a better than I am and think positive for positive result.

My Blogs

My Linked-In Profile

Comments and Discussions

 
QuestionIdeal for beginners who want to learn XAML and C#. Pin
Member 1544056926-Sep-22 23:47
Member 1544056926-Sep-22 23:47 
QuestionSet the FrameworkElementFactory image based on an image from ressources (resx) ? Pin
Julien Dev7-Nov-17 5:44
Julien Dev7-Nov-17 5:44 
GeneralMy vote of 5 Pin
wwwx28-Feb-17 21:21
wwwx28-Feb-17 21:21 
PraiseThanks... Pin
Member 1295288616-Jan-17 8:57
Member 1295288616-Jan-17 8:57 
QuestionHow to add more level Pin
Member 1118642029-Aug-15 20:58
Member 1118642029-Aug-15 20:58 
Questiontreeview Code behind template Check Checkbox Pin
leighdar5-Aug-15 1:02
leighdar5-Aug-15 1:02 
QuestionItem.Header = stack; doesn't work in customize tree section Pin
Member 1100366118-Oct-14 3:35
Member 1100366118-Oct-14 3:35 
GeneralMy vote of 5 Pin
viler8413-Aug-14 13:31
viler8413-Aug-14 13:31 
Absolutely great. Clear and simple. So that a first timer can easy understand it. I like it that you have shown the XAML and code way. Thx a lot!
QuestionVery complete, very understandable. Thanks a lot. Pin
Aykut Eyileten27-Oct-13 4:02
Aykut Eyileten27-Oct-13 4:02 
GeneralMy vote of 5 Pin
itsho3-Aug-13 19:31
itsho3-Aug-13 19:31 
GeneralMy vote of 5 Pin
Abhijeet Desai 10124-Jul-13 23:38
Abhijeet Desai 10124-Jul-13 23:38 
GeneralMy vote of 5 Pin
Sinan Guler7-Jun-13 3:26
Sinan Guler7-Jun-13 3:26 
GeneralMy vote of 5 Pin
Thornik5-Jun-13 9:44
Thornik5-Jun-13 9:44 
GeneralMy vote of 5 Pin
gymbrall4-Jun-13 5:19
gymbrall4-Jun-13 5:19 
QuestionMy vote of 5 Pin
sbarnes15-May-13 14:19
sbarnes15-May-13 14:19 
QuestionGreat Article But Need Help in Check Box Section Pin
Hanish JJ26-Mar-13 13:55
Hanish JJ26-Mar-13 13:55 
Questiongreat article Pin
tomas42111-Mar-13 11:25
tomas42111-Mar-13 11:25 
AnswerRe: great article Pin
Shakeel Iqbal11-Mar-13 23:37
Shakeel Iqbal11-Mar-13 23:37 
GeneralMy vote of 5 Pin
Member 780137916-Nov-12 1:28
Member 780137916-Nov-12 1:28 
QuestionMy Vote Of 5 Pin
Serge Desmedt2-Oct-12 9:17
professionalSerge Desmedt2-Oct-12 9:17 
GeneralMy vote of 5 Pin
Saravanan from Chennai Tamil Nadu, India27-Jun-12 20:43
Saravanan from Chennai Tamil Nadu, India27-Jun-12 20:43 
GeneralMy vote of 5 Pin
Filip D'haene18-Dec-11 3:55
Filip D'haene18-Dec-11 3:55 
GeneralThanks Pin
Jaikrishan7-Jun-11 1:37
Jaikrishan7-Jun-11 1:37 
GeneralMy vote of 5 Pin
Jaikrishan7-Jun-11 1:37
Jaikrishan7-Jun-11 1:37 
GeneralNeed three level of hierarchy Pin
Tarun Dudhatra28-Feb-11 2:22
Tarun Dudhatra28-Feb-11 2:22 

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.