Showing posts with label TreeView. Show all posts
Showing posts with label TreeView. Show all posts

Friday, August 3, 2012

WPF Tree view binding how to stuff.

Introduction

Treeview is a very useful control, yet not that much used in real life scenario, however; it has a very good potential and the control is very customizable. Even though it has some disadvantages over some special category of data but none the less this control is a great addition to WPF default control set. In this particular article we will look at some basic functionality that WPF Tree view can provides, in particular we will take a look at the binding options. 

Basic Uses

Bellow I have given a simple code, that shows how to add a treeview in XAML. The Treeview Items are also defined as as a child nodes.

<Window x:Class="WPFTreeView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView Height="287"
HorizontalAlignment="Left"
Margin="12,12,0,0"
Name="treeView1"
VerticalAlignment="Top"
Width="479">
<TreeViewItem Header="Bing">
<TreeViewItem Header="A" />
<TreeViewItem Header="B" />
<TreeViewItem Header="C" />
</TreeViewItem>
<TreeViewItem Header="Google" >
<TreeViewItem Header="D" />
<TreeViewItem Header="E" />
</TreeViewItem>
<TreeViewItem Header="Yahoo" >
<TreeViewItem Header="F" />
</TreeViewItem>
</TreeView>
</Grid>
</Window>


 

If you want to Populate the tree view and its node from c# code bellow I have listed few line of code to demonstrate how to do that.


using System.Windows;
using System.Windows.Controls;

namespace WPFTreeView
{
public partial class WindowTreeViewFromCodexaml
{
private TreeView _treeView;

public WindowTreeViewFromCodexaml()
{
InitializeComponent();
BuildTreeView();
BuildTreeViewNodes(0,_treeView.Items);
}

private void BuildTreeViewNodes(int level, ItemCollection itemCollection)
{
if(level==3)
return;

for (int i = 0; i < 3; i++)
{
var item = new TreeViewItem();
item.Header = string.Format("Item {0}", i + 1);
BuildTreeViewNodes(level+1,item.Items);
itemCollection.Add(item);
}
}

private void BuildTreeView()
{
TreeViewContainer.Children.Clear();
_treeView = new TreeView();
_treeView.HorizontalAlignment = HorizontalAlignment.Left;
_treeView.Margin = new Thickness(12, 12, 0, 0);
_treeView.Name = "treeView1";
_treeView.VerticalAlignment = VerticalAlignment.Top;
TreeViewContainer.Children.Add(_treeView);
}
}
}


Binding to a source


In this section we would see how we can bind a Treeview to a data source which has a hierarchical structure. For this scenario let’s Consider the following example. “ We want to display employee information as hierarchical data. The corresponding xaml and c# code is given bellow. 


XAML code


<Window x:Class="WPFTreeView.WindowTreeViewWithItemSource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowTreeViewWithItemSource" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate x:Key="ItemTemplate" ItemsSource="{Binding Employees}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"></CheckBox>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<DockPanel LastChildFill="True">
<TreeView
Name="treeView1"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding Employees}">
</TreeView>
</DockPanel>
</Grid>
</Window>


Above  Xaml code shows declaration of WPF treeview where ItemSource is Employee Dependency Property. But we would assign the item source from code. And this would work perfectly as we override the binding with hard code source. We have also defined a item template for each tree view item node. Bellow the corresponding c# code is given.


C# code


public partial class WindowTreeViewWithItemSource
{
public WindowTreeViewWithItemSource()
{
InitializeComponent();
LoadData();
}

private void LoadData()
{
var employees = new List();
for (int i =1; i <= 100; i++)
{
var manager = new Employee {Name = string.Format("Manager {0}", i.ToString())};
for (int j = 1; j <= 100; j++)
{
var lead = new Employee { Name = string.Format("Team Lead {0}", i.ToString()) };
for (int k = 0; k < 100; k++)
{
var junior = new Employee { Name = string.Format("Developer {0}", i.ToString())};
lead.Employees.Add(junior);
}
manager.Employees.Add(lead);
}
employees.Add(manager);
}
treeView1.ItemsSource = employees;
}
}

public class Employee
{
public Employee()
{
Employees = new List();
}
public string Name { get; set; }
public string Department { get; set; }
public int Age { get; set; }
public string Designation { get; set; }
public string Email { get; set; }
public DateTime DateOfJoin { get; set; }
public double Salary { get; set; }
public List Employees { get; private set; }
}


In above code we have defined a custom user type named “Employee” and constructed a demo hierarchy data, and finally assigned to the item source of the treeview. Note that here we did assigned itemsouce directly. If we want to use binding in that case we have to use mvp (model view presenter) or MVVM ( Model View ViewModel) pattern.


public partial class WindowTreeViewWithItemSourceBinding
{
public WindowTreeViewWithItemSourceBinding()
{
InitializeComponent();
this.DataContext = new TreeViewPresenter();
}
}

public class TreeViewPresenter:DependencyObject
{
public TreeViewPresenter()
{
LoadData();
}
private void LoadData()
{
var employees = new List<Employee>();
for (int i = 1; i <= 100; i++)
{
var manager = new Employee { Name = string.Format("Manager {0}", i.ToString()) };
for (int j = 1; j <= 100; j++)
{
var lead = new Employee { Name = string.Format("Team Lead {0}", i.ToString()) };
for (int k = 0; k < 100; k++)
{
var junior = new Employee { Name =
                    string.Format("Developer {0}", i.ToString()) };
lead.Employees.Add(junior);
}
manager.Employees.Add(lead);
}
employees.Add(manager);
}
Employees = employees;
}
public List<Employee> Employees
{
get { return (List<Employee>)GetValue(EmployeesProperty); }
set { SetValue(EmployeesProperty, value); }
}

public static readonly DependencyProperty EmployeesProperty =
DependencyProperty.Register("Employees", typeof(List<Employee>),
typeof(TreeViewPresenter), new UIPropertyMetadata(null));
}


Summery


In this short article we have seen how to use WPF tree view and some usages of WPF tree view as far as the binding and populating tree view is concern.


References


MSDN Help


Friday, July 27, 2012

How to Customize the look and feel of WPF Tree view? [Part 2]

Styling the expend collapse icon

This part is not easy, we have to open the box and see for our self what’s inside? fear not!! we can open its internal structure using expression blend and then modify the required stuff, our goal in this section is to replace the default expand collapse icon with something more friendly, for instance a plus and minus icon would do for now I guess.
Lets open the project in expression blend navigate to our desired project and then open the desired window, in our case we have main window, a simple tree view control is added. navigate to the tree view just like the image shown here, then select one of the TreeViewItem , if there is no add a demo one. Then right click and select edit template, and select edit a copy.
TreeViewTemplate_thumb2
It will quickly extend the style and bring out the desired styles in a resource file. if you mention it. well here is the xaml looks like after I have edited the default template.
image_thumb5
Looks rusty! but you have to consider that I am a developer not a designer, designers will come out with awesome design to look it good. well our main concern is the know now. Bello I have listed the complete xaml for the edited TreeViewItem Template.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Canvas x:Key="FolderIcon" x:Shared="false" Width="16" Height="16" ClipToBounds="True">
        <Rectangle Fill="BlanchedAlmond" Canvas.Left="0" Canvas.Top="0" Height="15" Width="15">
        </Rectangle>
    </Canvas>
    <Style x:Key="TreeViewItemFocusVisual">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Rectangle/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
    <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
        <Setter Property="Focusable" Value="False"/>
        <Setter Property="Width" Value="16"/>
        <Setter Property="Height" Value="16"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Border Height="16" Width="16" BorderBrush="Gray" BorderThickness="1" Background="Transparent">
                        <Canvas Name="ExpandPath">
                            <Rectangle x:Name="virticalbar" Canvas.Top="1" Canvas.Left="6.5" Width="1" Height="12" Fill="Gray"></Rectangle>
                            <Rectangle Canvas.Left="1" Canvas.Top="7" Width="12" Height="1" Fill="Gray"></Rectangle>
                        </Canvas>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                        </Trigger>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="Visibility" TargetName="virticalbar" Value="Hidden"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="{x:Type TreeViewItem}">
        <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" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="4,0,0,0"/>
                        </Border>
                        <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="false">
                            <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
                        </Trigger>
                        <Trigger Property="HasItems" Value="false">
                            <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
                        </Trigger>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="true"/>
                                <Condition Property="IsSelectionActive" Value="false"/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </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>  
</ResourceDictionary>


Style the selected item


Now its time to style the selected item, if you carefully notice that by default when you select any items in WPF tree view you would see that there is a blue background appear behind the selected item. There is a good chance that the client or users will not like it in customized business solutions. Again since we have the whole skeleton of the TreeViewItem we would simply modify the default style.

Bellow I have listed the xaml part of the above full style but with a simple modification in one line only, note that we have a trigger were IsSelected Property is been queried with value True, where we have changed the default value to our customized value “Silver”.

<ControlTemplate.Triggers>
    <Trigger Property="IsExpanded" Value="false">
        <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
    </Trigger>
    <Trigger Property="HasItems" Value="false">
        <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
    </Trigger>
    <Trigger Property="IsSelected" Value="true">
        <Setter Property="Background" TargetName="Bd" Value="Silver"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
    </Trigger>
    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition Property="IsSelected" Value="true"/>
            <Condition Property="IsSelectionActive" Value="false"/>
        </MultiTrigger.Conditions>
        <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    </MultiTrigger>
    <Trigger Property="IsEnabled" Value="false">
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
    </Trigger>
</ControlTemplate.Triggers>


The result is given bellow

Customize outer looks


Most easy part may be customize the outer look we can achieve this with the basic WPF knowledge's. All the container property such as Border Brush, Border thickness and Margin or Padding is available for modification. One interesting this is modification of the scroll bars, in this particular scenario you might want to again expression blend to customize the look and feel of the scroll content and scroll viewer scrollbars.

Summery


In this short article we have seen that how we can customize WPF tree view for our own way and they way we want it to be look like. The methods that has been used in this article is not prefect but one of the solution that can be implemented. Hopefully in future we can look more into it and customize even more to achieve our desired look and feel. If your have any feed back and suggestion please feel free to add in the comment section.

References


Friday, July 20, 2012

How to customize the look and feel of WPF tree view? [part1]

Why we need to customize Tree view?

Intention is to modify default look and feel so that we can use the tree view as custom purpose control not just hierarchical data structure, a good example could be a folder explorer.

In this short article we are going to see how to customize tree view look and feel. first we will look at how to customize the style of a tree view item, then move on to the expand and collapse icon, and lastly we would see how to style a selected item.

image_thumb11_thumb2

Styling the items

Lets say we want to display a icon before each tree view “item node” so that the user knows what type of file item it is, right now we have drive, directory and file, so we would add thee resources to display. Bellow a simple code is given to demonstrate how to read a file system, and then populate in a dependency object so that it can be bind to a tree view.

[Sample c# code]

using System.Collections.Generic;
using System.IO;
using System.Windows;

namespace WPFFolderExplorer
{
public class FileSystemPresenter : DependencyObject
{
public FileSystemPresenter()
{
LoadFileSystem();
}

private void LoadFileSystem()
{
var liteDirectoryInfos = new List<LiteDirectoryInfo>();
var driveInfos = DriveInfo.GetDrives();
foreach (var driveInfo in driveInfos)
{
if (driveInfo.DriveType == DriveType.Fixed)
{
var liteDirectoryInfo = new LiteDirectoryInfo
{
FileName = driveInfo.Name,
FileType = FileType.Drive
};
var info = new DirectoryInfo(driveInfo.Name);
var directoryInfos = info.GetDirectories();
foreach (var directoryInfo in directoryInfos)
{
liteDirectoryInfo.Files.Add(new LiteDirectoryInfo
{ FileName = directoryInfo.Name,
FileType = FileType.Folder });
}
liteDirectoryInfos.Add(liteDirectoryInfo);
}
}
FileSystem = liteDirectoryInfos;
}

public List<LiteDirectoryInfo> FileSystem
{
get { return (List<LiteDirectoryInfo>)GetValue(FileSystemProperty); }
set { SetValue(FileSystemProperty, value); }
}

public static readonly DependencyProperty FileSystemProperty =
DependencyProperty.Register("FileSystem", typeof(List<LiteDirectoryInfo>),
typeof(FileSystemPresenter), new UIPropertyMetadata(null));
}

public class LiteDirectoryInfo
{
public LiteDirectoryInfo()
{
Files = new List<LiteDirectoryInfo>();
}
public string FileName { get; set; }
public FileType FileType { get; set; }
public List<LiteDirectoryInfo> Files { get; set; }
}

public enum FileType
{
Drive,
Folder,
File
}
}

In the above code we have a enum FileType for distinguishing the file type. And a composite LiteDirectoryInfo where we have populated the directory structure. Finally After construction we preserved the data on FileSystem dependency property.


[Sample c# code for view c# part]


namespace WPFFolderExplorer
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new FileSystemPresenter();
}
}
}

Above code shows only the part where I have assigned our custom presenter to the WPFFolderExplorer’s DataContext to follow MVP Pattern (Model,View,Presenter) where model is our own local drives and file system.


[Sample code for XAML is Given here]


<Window x:Class="WPFFolderExplorer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter Property="Background" Value="Gray"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<DockPanel LastChildFill="True">
<Border DockPanel.Dock="Top" Height="30">
<!--tool bar will go here-->
</Border>
<TreeView DockPanel.Dock="Left" MinWidth="200" ItemsSource="{Binding FileSystem}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Files}">
<Border x:Name="itemBorder">
<DockPanel Margin="2">
<ContentControl Content="{DynamicResource FolderIcon}" DockPanel.Dock="Left"/>
<TextBlock Text="{Binding FileName}"></TextBlock>
</DockPanel>
</Border>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<ContentControl>
</ContentControl>
</DockPanel>
</Grid>
</Window>

In above xaml code its shown that how the binding with the presenter dependency is done. Also note that the item template is defined for a tree view.

Friday, November 12, 2010

Prevent WPF Tree View nodes from getting selected.

By default all the nodes of the WPF tree view is selectable. If some how we had a requirement that the roots nodes can not be selected rather only the child nodes will be selectable we need to customize one single property “focusable”. If we set the focusable property of any node to false then the node will not be selectable. pretty small thing but worth sharing because in MSDN forum one developer asked for this.

Bello a simple code sample is given

        <TreeView Height="251" HorizontalAlignment="Left" Margin="45,30,0,0" 
Name="treeView1" VerticalAlignment="Top" Width="197">
<TreeViewItem Header="Yahoo" IsManipulationEnabled="False" Focusable="False">
<TreeViewItem Header="d" />
<TreeViewItem Header="c" />
<TreeViewItem Header="b" />
<TreeViewItem Header="A" />
</TreeViewItem>
</TreeView>






Hope this will help, Note that if you have some binding with the tree grid and you want to achieve the same as above you might need to use triggers.