Example 2: TreeViewItem template - Athari/Alba.Jaml Wiki

The following template is based on default TreeViewItem template. It modifies horizontal content alignment to make text wrapping in tree items possible.

JAML (10,024 bytes)

_={
    $: 'Window root',
    Resources: [{
        $: 'Style ExpandCollapseToggleStyle ToggleButton',
        set: {
            Width: 16, Height: 16, Focusable: false,
            Template: {
                TargetType: 'ToggleButton',
                _: [{
                    $: 'Border',
                    Width: 16, Height: 16, Background: 'Transparent', Padding: '3 4 3 3',
                    _: [{
                        $: 'Path ExpandPath', Fill: 'Transparent', Stroke: '#FF989898', Data: 'M 4,0 L 8,4 4,8 Z'
                    }]
                }],
                on: {
                    '{=this.IsChecked}': {set: {
                        'ref.ExpandPath.RenderTransform': {
                            $: 'RotateTransform', Angle: 45, CenterX: 4, CenterY: 4
                        },
                        'ref.ExpandPath.Fill': '#FF595959', 'ref.ExpandPath.Stroke': '#FF262626'
                    }},
                    '{=this.IsMouseOver}': {set: {
                        'ref.ExpandPath.Fill': '#FF1BBBFA', 'ref.ExpandPath.Stroke': 'Transparent'
                    }},
                    '{= (bool)${=this.IsMouseOver} && (bool)${=this.IsChecked} }': {set: {
                        'ref.ExpandPath.Fill': '#FF262626', 'ref.ExpandPath.Stroke': '#FF595959'
                    }}
                }
            }
        }
    }, {
        $: 'Style TreeViewItem',
        set: {
            HorizontalContentAlignment: 'Stretch',
            Template: {
                TargetType: 'TreeViewItem',
                _: [{
                    $: 'Grid',
                    ColumnDefinitions: [ { Width: 'Auto', MinWidth: 20 }, { Width: 'Auto' }, { Width: '*' }],
                    RowDefinitions: [ { Height: 'Auto' }, { Height: 'Auto' } ],
                    _: [{
                        $: 'ToggleButton chkExpand', Grid$: '0 0',
                        ClickMode: 'Press', IsChecked: '{=tpl.IsExpanded}', Style: '{@ExpandCollapseToggleStyle}'
                    }, {
                        $: 'Border brdBG', Grid$: '0 1 1 2', Padding: '{tpl.Padding}', Background: '{tpl.Background}',
                        BorderBrush: '{tpl.BorderBrush}', BorderThickness: '{tpl.BorderThickness}',
                        _: {
                            $: 'ContentPresenter PART_Header', ContentSource: 'Header',
                            HorizontalAlignment: '{tpl.HorizontalContentAlignment}'
                        }
                    }, {
                        $: 'ItemsPresenter preItems', Grid$: '1 1 1 2'
                    }]
                }],
                on: {
                    '{= !(bool)${=this.IsExpanded} }': {set: {
                        'ref.preItems.Visibility': 'Collapsed'
                    }},
                    '{= !(bool)${=this.HasItems} }': {set: {
                        'ref.chkExpand.Visibility': 'Hidden'
                    }},
                    '{=this.IsSelected}': {set: {
                        'ref.brdBG.Background': '{@={static.SystemColors.HighlightBrushKey}}',
                        'Foreground': '{@={static.SystemColors.HighlightTextBrushKey}}'
                    }},
                    '{= (bool)${=this.IsSelected} && !(bool)${=this.IsSelectionActive} }': {set: {
                        'ref.brdBG.Background': '{@={static.SystemColors.InactiveSelectionHighlightBrushKey}}',
                        'Foreground': '{@={static.SystemColors.InactiveSelectionHighlightTextBrushKey}}'
                    }},
                    '{= !(bool)${=this.IsEnabled} }': {set: {
                        'Foreground': '{@={static.SystemColors.GrayTextBrushKey}}'
                    }}
                }
            }
        }
    }, {
        $: 'Style TreeView',
        set: {
            'ScrollViewer.HorizontalScrollBarVisibility': 'Disabled'
        }
    }],
    _: [{
        $: 'Grid',
        _: [{
            $: 'TreeView',
            Items: [{
                $: 'TreeViewItem', Header: 'Root', Items: [{
                    $: 'TreeViewItem', Header: 'Item 1', Items: [{
                        $: 'TreeViewItem', Header: 'Item 1 1'
                    }, {
                        $: 'TreeViewItem', Header: 'Item 1 2'
                    }, {
                        $: 'TreeViewItem', Header: 'Item 1 3'
                    }]
                }, {
                    $: 'TreeViewItem', Header: 'Item 2', Items: [{
                        $: 'TreeViewItem', Header: 'Item 2 1', Items: [{
                            $: 'TreeViewItem', Header: 'Item 2 1 1'
                        }]
                    }]
                }, {
                    $: 'TreeViewItem', Header: 'Item 3'
                }, {
                    $: 'TreeViewItem', Header: 'Item 4'
                }, {
                    $: 'TreeViewItem', Header: 'Item 5'
                }]
            }]
        }]
    }]
}

Manually written XAML (16,958 bytes)

<Window x:Class="Alba.JamlTestApp.TreeViewWindow" x:Name="root"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <TreeView>
            <TreeView.Items>
                <TreeViewItem Header="Root">
                    <TreeViewItem.Items>
                        <TreeViewItem Header="Item 1">
                            <TreeViewItem.Items>
                                <TreeViewItem Header="Item 1 1"/>
                                <TreeViewItem Header="Item 1 2"/>
                                <TreeViewItem Header="Item 1 3"/>
                            </TreeViewItem.Items>
                        </TreeViewItem>
                        <TreeViewItem Header="Item 2">
                            <TreeViewItem.Items>
                                <TreeViewItem Header="Item 2 1">
                                    <TreeViewItem.Items>
                                        <TreeViewItem Header="Item 2 1 1"/>
                                    </TreeViewItem.Items>
                                </TreeViewItem>
                            </TreeViewItem.Items>
                        </TreeViewItem>
                        <TreeViewItem Header="Item 3"/>
                        <TreeViewItem Header="Item 4"/>
                        <TreeViewItem Header="Item 5"/>
                    </TreeViewItem.Items>
                </TreeViewItem>
            </TreeView.Items>
        </TreeView>
    </Grid>
    <Window.Resources>
        <Style x:Key="ExpandCollapseToggleStyle" TargetType="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 Width="16" Height="16" Background="Transparent" Padding="3 4 3 3">
                            <Path x:Name="ExpandPath" Fill="Transparent" Stroke="#FF989898"
                                    Data="M 4,0 L 8,4 4,8 Z"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="ExpandPath" Property="RenderTransform">
                                    <Setter.Value>
                                        <RotateTransform Angle="45" CenterX="4" CenterY="4"/>
                                    </Setter.Value>
                                </Setter>
                                <Setter TargetName="ExpandPath" Property="Fill" Value="#FF595959"/>
                                <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF262626"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF1BBBFA"/>
                                <Setter TargetName="ExpandPath" Property="Fill" Value="Transparent"/>
                            </Trigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsMouseOver" Value="True"/>
                                    <Condition Property="IsChecked" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF262626"/>
                                <Setter TargetName="ExpandPath" Property="Fill" Value="#FF595959"/>
                            </MultiTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style TargetType="TreeViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TreeViewItem}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" MinWidth="20"/>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <ToggleButton x:Name="chkExpand" Grid.Column="0" ClickMode="Press"
                                    IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                                    Style="{StaticResource ExpandCollapseToggleStyle}"/>
                            <Border x:Name="brdBG" Grid.Column="1" Grid.ColumnSpan="2"
                                    Padding="{TemplateBinding Padding}"
                                    Background="{TemplateBinding Background}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}">
                                <ContentPresenter x:Name="PART_Header" ContentSource="Header"
                                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                            </Border>
                            <ItemsPresenter x:Name="preItems" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsExpanded" Value="False">
                                <Setter TargetName="preItems" Property="Visibility" Value="Collapsed"/>
                            </Trigger>
                            <Trigger Property="HasItems" Value="False">
                                <Setter TargetName="chkExpand" Property="Visibility" Value="Hidden"/>
                            </Trigger>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter TargetName="brdBG" Property="Background"
                                        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 TargetName="brdBG" Property="Background"
                                        Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
                                <Setter Property="Foreground"
                                        Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
                            </MultiTrigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Foreground"
                                        Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style TargetType="{x:Type TreeView}">
            <Style.Setters>
                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
            </Style.Setters>
        </Style>
    </Window.Resources>
</Window>

Generated XAML (reformatted for readability) (23,500 bytes)

<Window
    x:Name="root"
    x:Class="Alba.JamlTestApp.TreeViewWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:Alba.JamlTestApp">
    <Grid>
        <TreeView>
            <TreeView.Items>
                <TreeViewItem Header="Root">
                    <TreeViewItem.Items>
                        <TreeViewItem Header="Item 1">
                            <TreeViewItem.Items>
                                <TreeViewItem Header="Item 1 1" />
                                <TreeViewItem Header="Item 1 2" />
                                <TreeViewItem Header="Item 1 3" />
                            </TreeViewItem.Items>
                        </TreeViewItem>
                        <TreeViewItem Header="Item 2">
                            <TreeViewItem.Items>
                                <TreeViewItem Header="Item 2 1">
                                    <TreeViewItem.Items>
                                        <TreeViewItem Header="Item 2 1 1" />
                                    </TreeViewItem.Items>
                                </TreeViewItem>
                            </TreeViewItem.Items>
                        </TreeViewItem>
                        <TreeViewItem Header="Item 3" />
                        <TreeViewItem Header="Item 4" />
                        <TreeViewItem Header="Item 5" />
                    </TreeViewItem.Items>
                </TreeViewItem>
            </TreeView.Items>
        </TreeView>
    </Grid>
    <Window.Resources>
        <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
            <Style.Setters>
                <Setter Property="Width" Value="16" />
                <Setter Property="Height" Value="16" />
                <Setter Property="Focusable" Value="False" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ToggleButton">
                            <Border Width="16" Height="16" Background="Transparent" Padding="3 4 3 3">
                                <Path x:Name="ExpandPath" Fill="Transparent" Stroke="#FF989898"
                                    Data="M 4,0 L 8,4 4,8 Z" />
                            </Border>
                            <ControlTemplate.Triggers>
                                <DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
                                    Value="True">
                                    <DataTrigger.Setters>
                                        <Setter TargetName="ExpandPath" Property="RenderTransform">
                                            <Setter.Value>
                                                <RotateTransform Angle="45" CenterX="4" CenterY="4" />
                                            </Setter.Value>
                                        </Setter>
                                        <Setter TargetName="ExpandPath" Property="Fill" Value="#FF595959" />
                                        <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF262626" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}"
                                    Value="True">
                                    <DataTrigger.Setters>
                                        <Setter TargetName="ExpandPath" Property="Fill" Value="#FF1BBBFA" />
                                        <Setter TargetName="ExpandPath" Property="Stroke" Value="Transparent" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                                <DataTrigger
                                    Value="True">
                                    <DataTrigger.Binding>
                                        <MultiBinding Converter="{x:Static my:TreeViewWindow._jaml_TreeViewWindowConverter}">
                                            <Binding Path="IsMouseOver" RelativeSource="{RelativeSource Mode=Self}" />
                                            <Binding Path="IsChecked" RelativeSource="{RelativeSource Mode=Self}" />
                                        </MultiBinding>
                                    </DataTrigger.Binding>
                                    <DataTrigger.Setters>
                                        <Setter TargetName="ExpandPath" Property="Fill" Value="#FF262626" />
                                        <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF595959" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style.Setters>
        </Style>
        <Style TargetType="{x:Type TreeViewItem}">
            <Style.Setters>
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="TreeViewItem">
                            <Grid>
                                <ToggleButton x:Name="chkExpand" Grid.Row="0" Grid.Column="0" ClickMode="Press"
                                    IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                                    Style="{StaticResource ExpandCollapseToggleStyle}" />
                                <Border x:Name="brdBG" Grid.Row="0" Grid.Column="1" Grid.RowSpan="1" Grid.ColumnSpan="2"
                                    Padding="{TemplateBinding Padding}"
                                    Background="{TemplateBinding Background}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}">
                                    <ContentPresenter x:Name="PART_Header" ContentSource="Header"
                                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
                                </Border>
                                <ItemsPresenter x:Name="preItems" Grid.Row="1" Grid.Column="1"
                                    Grid.RowSpan="1" Grid.ColumnSpan="2" />
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" MinWidth="20" />
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <DataTrigger
                                    Binding="{Binding IsExpanded, RelativeSource={RelativeSource Self},
                                        Converter={x:Static my:TreeViewWindow._jaml_TreeViewWindowConverter1}}"
                                    Value="True">
                                    <DataTrigger.Setters>
                                        <Setter TargetName="preItems" Property="Visibility" Value="Collapsed" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                                <DataTrigger
                                    Binding="{Binding HasItems, RelativeSource={RelativeSource Self},
                                        Converter={x:Static my:TreeViewWindow._jaml_TreeViewWindowConverter2}}"
                                    Value="True">
                                    <DataTrigger.Setters>
                                        <Setter TargetName="chkExpand" Property="Visibility" Value="Hidden" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                                <DataTrigger
                                    Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}"
                                    Value="True">
                                    <DataTrigger.Setters>
                                        <Setter TargetName="brdBG" Property="Background"
                                            Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                                        <Setter Property="Foreground"
                                            Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                                <DataTrigger
                                    Value="True">
                                    <DataTrigger.Binding>
                                        <MultiBinding Converter="{x:Static my:TreeViewWindow._jaml_TreeViewWindowConverter3}">
                                            <Binding Path="IsSelected" RelativeSource="{RelativeSource Mode=Self}" />
                                            <Binding Path="IsSelectionActive" RelativeSource="{RelativeSource Mode=Self}" />
                                        </MultiBinding>
                                    </DataTrigger.Binding>
                                    <DataTrigger.Setters>
                                        <Setter TargetName="brdBG" Property="Background" Value="{DynamicResource {x:Static
                                            SystemColors.InactiveSelectionHighlightBrushKey}}" />
                                        <Setter Property="Foreground" Value="{DynamicResource {x:Static
                                            SystemColors.InactiveSelectionHighlightTextBrushKey}}" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                                <DataTrigger
                                    Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self},
                                        Converter={x:Static my:TreeViewWindow._jaml_TreeViewWindowConverter4}}"
                                    Value="True">
                                    <DataTrigger.Setters>
                                        <Setter Property="Foreground"
                                            Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style.Setters>
        </Style>
        <Style TargetType="{x:Type TreeView}">
            <Style.Setters>
                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
            </Style.Setters>
        </Style>
    </Window.Resources>
</Window>

Generated CS (unnecessary usings removed)

// ReSharper disable RedundantUsingDirective
// ReSharper disable RedundantCast
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;

namespace Alba.JamlTestApp
{
    public partial class TreeViewWindow
    {
        public static IMultiValueConverter _jaml_TreeViewWindowConverter = new _jaml_TreeViewWindowConverter_Class();
        public static IValueConverter _jaml_TreeViewWindowConverter1 = new _jaml_TreeViewWindowConverter1_Class();
        public static IValueConverter _jaml_TreeViewWindowConverter2 = new _jaml_TreeViewWindowConverter2_Class();
        public static IMultiValueConverter _jaml_TreeViewWindowConverter3 = new _jaml_TreeViewWindowConverter3_Class();
        public static IValueConverter _jaml_TreeViewWindowConverter4 = new _jaml_TreeViewWindowConverter4_Class();
 
        private class _jaml_TreeViewWindowConverter_Class : IMultiValueConverter
        {
            public object Convert (object[] values, Type targetType, object param, CultureInfo culture)
            {
                if (values.Any(v => ReferenceEquals(v, DependencyProperty.UnsetValue)))
                    return DependencyProperty.UnsetValue;
                return (bool)(values[0]) && (bool)(values[1]);
            }

            public object[] ConvertBack (object value, Type[] targetTypes, object param, CultureInfo culture)
            {
                throw new NotSupportedException("Converter supports only one-way binding.");
            }
        }
 
        private class _jaml_TreeViewWindowConverter1_Class : IValueConverter
        {
            public object Convert (object value, Type targetType, object param, CultureInfo culture)
            {
                if (ReferenceEquals(value, DependencyProperty.UnsetValue))
                    return DependencyProperty.UnsetValue;
                return !(bool)value;
            }

            public object ConvertBack (object value, Type targetType, object param, CultureInfo culture)
            {
                throw new NotSupportedException("Converter supports only one-way binding.");
            }
        }
 
        private class _jaml_TreeViewWindowConverter2_Class : IValueConverter
        {
            public object Convert (object value, Type targetType, object param, CultureInfo culture)
            {
                if (ReferenceEquals(value, DependencyProperty.UnsetValue))
                    return DependencyProperty.UnsetValue;
                return !(bool)value;
            }

            public object ConvertBack (object value, Type targetType, object param, CultureInfo culture)
            {
                throw new NotSupportedException("Converter supports only one-way binding.");
            }
        }
 
        private class _jaml_TreeViewWindowConverter3_Class : IMultiValueConverter
        {
            public object Convert (object[] values, Type targetType, object param, CultureInfo culture)
            {
                if (values.Any(v => ReferenceEquals(v, DependencyProperty.UnsetValue)))
                    return DependencyProperty.UnsetValue;
                return (bool)(values[0]) && !(bool)(values[1]);
            }

            public object[] ConvertBack (object value, Type[] targetTypes, object param, CultureInfo culture)
            {
                throw new NotSupportedException("Converter supports only one-way binding.");
            }
        }
 
        private class _jaml_TreeViewWindowConverter4_Class : IValueConverter
        {
            public object Convert (object value, Type targetType, object param, CultureInfo culture)
            {
                if (ReferenceEquals(value, DependencyProperty.UnsetValue))
                    return DependencyProperty.UnsetValue;
                return !(bool)value;
            }

            public object ConvertBack (object value, Type targetType, object param, CultureInfo culture)
            {
                throw new NotSupportedException("Converter supports only one-way binding.");
            }
        } 
    }
}