Document tab restyling

First thing to say is that this tutorial is not a "getting started to"; this means that readers should know quite well how to build a docking interface with AvalonDock before reading this (for more info follow the base tutorial here http://avalondock.codeplex.com/wikipage?title=GettingStarted&referringTitle=Documentation)

This article is about document content restyling, in particular the document tab, which usually shows the title of contained document and (from version 1.2) a small locked icon bound to the property IsLocked of the base class ManagedContent.
For this sample we'll add a small progress bar beside the document title of each document tab. Of course the same approach works if you want to add anything else into the tab (i.e. a small cross icon to close the document etc...).

This is the final result:
SD2_2.png

First of all create a new WPF project in Visual Studio, reference the AvalonDock assembly and then add a DockingManager control into the first Window1 file. The resulting code should be something like this:

<Window x:Class="AvalonDockStyleTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:AvalonDockStyleTest"
    xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
    Title="Window1" Height="321" Width="595">
    <Grid>
        <ad:DockingManager>
            <ad:DocumentPane>
            </ad:DocumentPane>
        </ad:DockingManager>
    </Grid>
</Window>

Now we have to create a custom document (a class deriving from DocumentContent) that will hold a Progress property. The easiest way is creating a WPF UserControl, in our case called CustomDocument. VS will create two file: an XAML containing the WPF interface (CustomDocument.xaml) and a code-behind file (CustomDocument.xaml.cs) (maybe I'm too much verbose here:) ). Change the xaml code and source code in order to change the base class from UserControl to DocumentContent:

CustomDocument.xaml
<ad:DocumentContent x:Class="AvalonDockStyleTest.CustomDocument"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
    Height="300" Width="300">
    <Grid>
        <Slider Height="23" Margin="53,95,56,0" Name="slider1" VerticalAlignment="Top" Value="{Binding Path=Progress}" />
        <TextBlock Height="66" Margin="36,20,36,0" Name="textBlock1" VerticalAlignment="Top" 
Text="Please move the slider below to change the progress bar on top of the document!" TextWrapping="Wrap" />
    </Grid>
</ad:DocumentContent>

CustomDocument.xaml.cs
    /// <summary>
    /// Interaction logic for CustomDocument.xaml
    /// </summary>
    public partial class CustomDocument : DocumentContent
    {
        public CustomDocument()
        {
            this.DataContext = this;
            InitializeComponent();
        }



        public int Progress
        {
            get { return (int)GetValue(ProgressProperty); }
            set { SetValue(ProgressProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Progress.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ProgressProperty =
            DependencyProperty.Register("Progress", typeof(int), typeof(CustomDocument), new FrameworkPropertyMetadata(3));
    }

As you can see, I've added a Dependency Property called "Progress" which hold the progress value of the document. This property will be attached via WPF binding to the custom style that we're going to create for our CustomDocument. Also note from XAML that the slider located into the document is bound to the same property. In this manner when user move the slider, the Progress property will be updated and the same value will be propagated to the progress bar located beside the title of the tab.

Now add some custom document to the Window1, as children of the DocumentPane we have created above:

<Window x:Class="AvalonDockStyleTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:AvalonDockStyleTest"
    xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
    Title="Window1" Height="321" Width="595">
    <Grid>
        <ad:DockingManager>
            <ad:DocumentPane>
                <local:CustomDocument Title="TestDocument1"/>
                <local:CustomDocument Title="TestDocument2"/>
                <local:CustomDocument Title="TestDocument3"/>
                <local:CustomDocument Title="TestDocument4"/>
            </ad:DocumentPane>
        </ad:DockingManager>
    </Grid>
</Window>

Now build and run the project, you should see a window like this:

SD2_3.png

OK, now let's go ahead creating a custom style for the document tab. Using Visual Studio, create a new ResourceDictionary naming it CustomStyles.xaml. There we'll put all the custom style we'll use in our application.
We have also to reference it some where, in this sample I decided to make available it at application level. So edited the App.xaml code:

App.xaml
<Application x:Class="AvalonDock.StyleTest.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="CustomStyles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Creating a style for a AvalonDock control is quite tricky, I suggest you to start from a precompiled style. Just open the source code of AvalonDock and look into the Resources and Themes folders. There you can find a lot of styles for each control present in AvalonDock according to a specific theme.

For our purposes we'll start from the base style for the DocumentPane control (Vista theme). So open the aero.normalcolor.xaml locating the part where a style is created for the DocumentPane along with the style called DocumenttabItemStyle or simply copy&paste the following xaml into your CustomStyles.xaml:

CustomStyles.xaml
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary 
Source="/AvalonDock;component/themes/generic.xaml"/>

        <!--Aero colors-->
        <ResourceDictionary 
Source="/AvalonDock;component/themes/aero.normalcolor.brushes.xaml"/>
    </ResourceDictionary.MergedDictionaries>

<Style x:Key="CustomDocumentTabItemStyle" 
TargetType="{x:Type local:CustomDocument}">
    <Setter Property="Background"
            Value="Transparent"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:CustomDocument}">
                <Border 
					x:Name="PART_DragArea" 
					BorderBrush=
"{StaticResource ManagedContentTabControlNormalBorderBrush}"
                    Margin="-10,0,0,0"
					SnapsToDevicePixels="True"
                    ContextMenu=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:ContextMenuElement.DocumentPane}}}"
                    >
                    <Grid Margin="0,0,0,0" >
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="20"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <Path Data="M 20,0.5 Q 16,0.5 10,10 Q 5,19.5 0,19.5 L 20,19.5"
                            x:Name="tabItemIntPathBackground"  
                              Fill=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBackground}}}"/>
                        <Path 
                            x:Name="tabItemIntPathBorder"  
                            Stroke=
"{StaticResource ManagedContentTabControlNormalBorderBrush}"
                            Data=
"M 20,0.5 Q 16,0.5 10,10 Q 5,19.5 0, 19.5"
                        />
                        <Border
                                x:Name="tabItemIntBorder"
                                Grid.Column="1"
                                BorderThickness="0,1,1,0"
                                Margin="-0.5,0,0,0"
                                CornerRadius="0,3,0,0"
                                
BorderBrush=
"{StaticResource ManagedContentTabControlNormalBorderBrush}"
                                
Background=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBackground}}}"
                            >
<StackPanel Orientation="Horizontal" 
        Margin="5,0,4,0">
<ProgressBar Width="50" 
Margin="2" Minimum="1" Maximum="10" 
Value="{Binding Path=Progress, RelativeSource={RelativeSource TemplatedParent}}"></ProgressBar>
    <TextBlock 
        x:Name="tabItemTitle" 
        TextTrimming="CharacterEllipsis" 
        TextWrapping="NoWrap"
        Text="{TemplateBinding Title}" 
        Foreground=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderForeground}}}"/>
<ad:AlignedImage 
x:Name="PART_LockedIcon" Margin="2,0,0,0" Visibility="Collapsed" VerticalAlignment="Center" HorizontalAlignment="Center">
    <Image
Source="/AvalonDock;component/resources/Images/Locked.png" Width="6" Height="8" Stretch="Uniform"/>
</ad:AlignedImage>
</StackPanel>
                        </Border>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="Selector.IsSelected" Value="True">
                        <Setter Property="Background" 
                                TargetName="tabItemIntBorder" 
Value=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBackgroundSelected}}}"
                                />
                        <Setter Property="Fill" 
                                TargetName="tabItemIntPathBackground" 
Value=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBackgroundSelected}}}"
                                />
                        <Setter Property="BorderBrush" TargetName="tabItemIntBorder" 
Value=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBorder}}}"/>
                        <Setter Property="Stroke" TargetName="tabItemIntPathBorder" 
Value=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBorder}}}"/>
                        <!--<Setter Property="Panel.ZIndex" Value="1"/> DOES NOT WORK! I DON'T KNOW WHY!!???-->
                    </Trigger>
                    <DataTrigger 
Binding="{Binding Path=IsActiveDocument, RelativeSource={RelativeSource Self}}" Value="True">
                        <Setter 
Property="TextBlock.FontWeight" TargetName="tabItemTitle" Value="Bold"/>
                    </DataTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsMouseOver" SourceName="tabItemIntBorder" Value="True"/>
                            <Condition Property="Selector.IsSelected" Value="False"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" 
                                TargetName="tabItemIntBorder" 
                                Value=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBackgroundMouseOver}}}"
                                />
                        <Setter Property="Fill" 
                                TargetName="tabItemIntPathBackground" 
                               Value=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBackgroundMouseOver}}}"
                                />
                    </MultiTrigger>

                    <DataTrigger 
Binding="{Binding Path=IsLocked, RelativeSource={RelativeSource Self}}" Value="True">
                        <Setter Property="Visibility" 
Value="Visible" TargetName="PART_LockedIcon"/>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!--DocumentPane-->
<Style TargetType="{x:Type ad:DocumentPane}">
    <Setter Property="Background" 
Value="{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DefaultBackgroundBrush}}}"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ad:DocumentPane}" >
                <ControlTemplate.Resources>
                    <ContextMenu x:Key="DocumentsListMenu" StaysOpen="True" >
                        <!--ItemTemplate="{StaticResource ManagedContentHeaderDataTemplate}"-->
                        <ContextMenu.ItemContainerStyle>
                            <Style TargetType="{x:Type MenuItem}">
                                <Setter Property="CommandParameter" 
Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Header}"/>
                                <Setter Property="Command">
                <Setter.Value>
                  <RoutedCommand />
                </Setter.Value>
              </Setter>
              <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="MenuItem">
                                            <Border x:Name="intBorder" BorderThickness="1" 
Background="{TemplateBinding Background}" CornerRadius="2">
                                                <Grid>
                                                    <Grid.ColumnDefinitions>
                                                        <ColumnDefinition Width="Auto" MinWidth="24"/>
                                                        <ColumnDefinition Width="*"/>
                                                    </Grid.ColumnDefinitions>
                                                    <ContentPresenter x:Name="Icon" Margin="2" 
Content="{Binding Path=Icon}" Grid.Column="0" VerticalAlignment="Center"/>
                                                    <TextBlock x:Name="intMenuTitle" Margin="5,2,20,2" 
Text="{Binding Path=Title}" Grid.Column="1" VerticalAlignment="Center"/>
                                                </Grid>
                                            </Border>
                                            <ControlTemplate.Triggers>
                                                <Trigger Property="IsMouseOver" Value="true">
                                                    <Setter Property="Background" TargetName="intBorder">
                                                        <Setter.Value>
                                                            <SolidColorBrush 
Color="{DynamicResource {x:Static SystemColors.ActiveCaptionColorKey}}" Opacity="0.3"/>
                                                        </Setter.Value>
                                                    </Setter>
                                                    <Setter Property="BorderBrush" TargetName="intBorder" 
Value="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}"/>
                                                </Trigger>
                                                <DataTrigger Binding="{Binding Path=IsActiveDocument}" Value="True">
                                                    <Setter Property="FontWeight" Value="Bold" TargetName="intMenuTitle"/>
                                                </DataTrigger>
                                                <!--<Trigger Property="Icon"
                                                        Value="{x:Null}">
                                                    <Setter TargetName="Icon"
                                                        Property="Visibility"
                                                        Value="Collapsed"/>
                                                </Trigger>-->
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </ContextMenu.ItemContainerStyle>
                    </ContextMenu>
                </ControlTemplate.Resources>
                <Border 
                    Focusable="False"
                    Background="{TemplateBinding Background}"
                    >
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="20"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Grid Grid.Row="1" Margin="0,-1,0,0">
                            <Border 
                                BorderThickness="5" 
                                BorderBrush=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBorder}}}" 
                                CornerRadius="3" 
                                Focusable="False">
                            </Border>
                            <Border BorderThickness="2" 
                                    BorderBrush=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderBorder2}}}" 
                                    CornerRadius="3" 
                                    Margin="1" 
                                    Focusable="False"
                                    Background=
"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.Background}"   
                                    >
                                <ContentPresenter 
                                    Content=
"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.Content}"
                                    Margin="2"
							        KeyboardNavigation.TabNavigation="Local"
							        KeyboardNavigation.DirectionalNavigation="Contained"
							        KeyboardNavigation.TabIndex="1"
                                    />
                            </Border>
                        </Grid>
                        <Border x:Name="PART_Header" 
						    Grid.Row="0" 
						    Focusable="False" 
						    BorderThickness="1,1,1,0">
                            <DockPanel LastChildFill="True">
                                <Button DockPanel.Dock="Right" Width="15" Height="15" Margin="2,0,2,0" 
Style="{StaticResource PaneHeaderCommandStyle}" Command="ApplicationCommands.Close">
                                    <Image 
Source="/AvalonDock;component/resources/Images/PinClose.png" Width="13" Height="13" Stretch="None"/>
                                </Button>
                                <Button 
x:Name="PART_ShowContextMenuButton" DockPanel.Dock="Right" Width="15" Height="15" 
Style="{StaticResource PaneHeaderCommandStyle}">
                <Button.Command>
                  <RoutedCommand />
                </Button.Command>
                <Image x:Name="ShowContextMenuIcon" 
Source="/AvalonDock;component/resources/Images/PinMenu.png" Width="13" Height="13" Stretch="None"/>
                                </Button>
                                <ad:DocumentTabPanel 
                                  x:Name="paneTabsPanel" 
                                  Panel.ZIndex ="1" 
                                  KeyboardNavigation.TabIndex="2"								
							      IsItemsHost="True" 
							      Margin="10,2,0,0"
							      TabItemStyle="{StaticResource CustomDocumentTabItemStyle}" />
                            </DockPanel>
                        </Border>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasItems" Value="False">
                        <Setter Property="Visibility" Value="Hidden"/>
                    </Trigger>
                    <DataTrigger 
Binding="{Binding Path=IsMainDocumentPane, RelativeSource={RelativeSource Self}}" Value="True">
                        <Setter Property="Source" 
Value="/AvalonDock;component/resources/Images\PinDocMenu.png" TargetName="ShowContextMenuIcon"/>
                    </DataTrigger>
                    <EventTrigger RoutedEvent="Window.Loaded">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Opacity"
                                    From="0" To="1"  Duration="0:0:0.200" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


In our case we have to restyle only one control: the DocumentPane class. Looking at style above you can see that a DocumentPane is simply a container of documents that shows only one document content each time. All the documents are visible each one with a tab on top (just like a TabControl). The selected document has a highlighted tab. The tab is styled with a custom style called "DocumentTabItemStyle".

Now let's change the target type from ad:ManagedContent to local:CustomDocument (our custom document class) and, just to be more clear, rename the tab item style from "DocumentTabItemStyle" to "CustomDocumentTabItemStyle". Also you have accordingly rename all the reference to those names into the file.

The next step is to add a progress bar on the tab: this is very easy in this case. Locate the stackpanel control into the CustomDocumentTabItemStyle, that stack the title of the document and the icon locked. There, just before the TextBlock showing the title add a progress bar and bind it to the Progress proprety of the tmplated parent (object of type CustomDocument).

This a the part affected:

<StackPanel Orientation="Horizontal" 
            Margin="5,0,4,0">
    <ProgressBar Width="50" Margin="2" Minimum="1" Maximum="10" 
Value="{Binding Path=Progress, RelativeSource={RelativeSource TemplatedParent}}"></ProgressBar>
        <TextBlock 
            x:Name="tabItemTitle" 
            TextTrimming="CharacterEllipsis" 
            TextWrapping="NoWrap"
            Text="{TemplateBinding Title}" 
            Foreground=
"{DynamicResource {ComponentResourceKey {x:Type ad:DockingManager}, {x:Static ad:AvalonDockBrushes.DocumentHeaderForeground}}}"/>
    <ad:AlignedImage x:Name="PART_LockedIcon" Margin="2,0,0,0" 
Visibility="Collapsed" VerticalAlignment="Center" HorizontalAlignment="Center">
        <Image Source="/AvalonDock;component/resources/Images/Locked.png" Width="6" Height="8" Stretch="Uniform"/>
    </ad:AlignedImage>
</StackPanel>

Build and run the project, you should be able to move the sliders and see the progress bar of each document tab change its value accordingly.

(Some people asked me why I'm not publishing some video tutorial instead of written articles. I perfectly know the importance of a video tutorial, the problem at the moment is that I need a free license of some screen recording program and also I'm not sure that my spoken english will be understable outside my office :). Anyway I'm working on that!)

This is the code:
AvalonDock.StyleTest.rar

Last edited Jun 22, 2010 at 1:20 PM by adospace, version 16

Comments

mikejr83 Oct 6, 2011 at 2:11 PM 
- "Now let's change the target type from ad:ManagedContent to local:CustomDocument (our custom document class) and, just to be more clear, rename the tab item style from "DocumentTabItemStyle" to "CustomDocumentTabItemStyle". Also you have accordingly rename all the reference to those names into the file."

This through me off for a bit. Using the above example you need to make sure the ad:DocumentTabPanel in the DocumentPane style has its TabItemStyle="{StaticResource CustomDocumentTabItemStyle}"

vdangwal Jun 1, 2011 at 7:23 PM 
These controls does not support direct styling like Other WPF controls. So its not possible to Change DocumentPane.TabItemStyle directly. So user will have to Subclass existing controls (Ex. DocumentContent) first and then copy base styles from themes and create new one.

vdangwal Jun 1, 2011 at 7:22 PM 
These controls does not support direct styling like Other WPF controls. So its not possible to Change DocumentPane.TabItemStyle directly. So user will have to Subclass existing controls (Ex. DocumentContent) first and then copy base styles from themes and create new one.

Woshiernog Jun 16, 2010 at 10:09 PM 
You need to uncomment a line of code in App.xaml.

simmotech Mar 20, 2010 at 11:49 AM 
Downloaded the .rar file and ran the exe directly - no sliders just normal tabs!

cuipengfei Jan 22, 2010 at 8:08 AM 
nicely done!keep it real!