Monday, January 14, 2013

WPF Calendar Control Style

How to style a WPF Calendar control?

Same as what we had discussed when designing the WPF Date Picker control. You must know how the WPF Control implement the hierarchical order of the child controls on its own template.

In the first place, you should understand how PARTS are being implemented on the template of the WPF Calendar control. After that, you need to determine what type of control the PART was used. Once you know it already, it is very simple to place the style for the specific PART control.

For your reference of WPF Calendar Control parts, please visit this link.

As per referenced, the parts of the WPF Calendar control were follow (based from MSDN):

Part

Type
PART_RootFrameworkElement
PART_HeaderButtonButton
PART_PreviousButtonButton
PART_NextButtonButton
DayTitleTemplateKey name of DataTemplate
PART_MonthViewGrid
PART_YearViewGrid

The parts listed above should be placed inside the template you will be overriding when styling the WPF Calendar control. Below are the basic template of how to customized the WPF Calendar control.

As the main important control item, the CalendarItem is the actual calendar object that host the actual PARTS of the WPF Calendar control.

<Style x:Name="styleDatePickerCalendarItem"TargetType="{x:Type CalendarItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CalendarItem}">
                <ControlTemplate.Resources>
                    <DataTemplate x:Key="{x:Static CalendarItem.DayTitleTemplateResourceKey}">
                        <Label>
                            <TextBlock Foreground="#F7F7F7"
                                       FontSize="12"
                                       HorizontalAlignment="Center"
                                       Margin="2"
                                       Text="{Binding}"
                                       VerticalAlignment="Center">
                            </TextBlock>
                        </Label>
                    </DataTemplate>
                </ControlTemplate.Resources>
                <Border Background="{StaticResource ResourceKey=gradientBrushCalendarBackground}"
                        BorderThickness="1"
                        BorderBrush="{DynamicResource ResourceKey=brushBorderControl}"
                        Padding="3">
                    <Grid Name="PART_Root">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"></RowDefinition>
                            <RowDefinition Height="*"></RowDefinition>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <DockPanel Grid.Column="0"
                                   Grid.Row="0"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="Stretch"
                                   LastChildFill="True">
                            <Button x:Name="PART_PreviousButton"
                                    DockPanel.Dock="Left"
                                    Width="25"
                                    Content="&lt;"
                                    Focusable="False"></Button>
                            <Button x:Name="PART_NextButton"
                                    DockPanel.Dock="Right"
                                    Width="25"
                                    Content="&gt;" Focusable="False">
                            </Button>
                            <Button x:Name="PART_HeaderButton"
                                    Width="135"
                                    MaxWidth="135">
                            </Button>
                        </DockPanel>
                        <Grid Grid.Column="0"
                              Grid.Row="1"
                              VerticalAlignment="Center"
                              HorizontalAlignment="Center">
                            <Grid x:Name="PART_MonthView">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                            </Grid>
                            <Grid x:Name="PART_YearView">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                            </Grid>
                        </Grid>
                        <Rectangle x:Name="PART_DisabledVisual"
                                   Opacity="0"
                                   Visibility="Collapsed"
                                   Fill="#F5F5F5"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

There you see the placement and how we design the PARTS of the WPF Calendar control with the use of the Grid control columns and rows (divisions). You will see PART_Root, PART_HeaderButton, PART_PreviousButton, PART_NextButton, DayTitleTemplate, PART_MonthView and PART_YearView.

So depends on how will you going to implement your own style on each PART, just simply create your own custom style and reference it in every PART control.

Styling the CalendarButton

The style and template of this object is very simple to implement as it is just a CalendarButton object (you can customized with the used of Grid with a TextBlock inside of it). So the choice is yours.

Sample style I created for this:

<Style x:Key="styleDatePickerCalendarButton" TargetType="{x:Type CalendarButton}">
    <Setter Property="OverridesDefaultStyle" Value="True"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CalendarButton}">
                <Grid x:Name="grid">
                    <Border x:Name="border_today"
                            CornerRadius="2"
                            Background="#FFFFFF"
                            BorderBrush="#9A9A9A"
                            BorderThickness="1"
                            Visibility="Collapsed"></Border>
                    <Border x:Name="border" CornerRadius="3">
                        <TextBlock x:Name="block"
                                   Foreground="#3A3A3A"
                                   FontSize="12"
                                   Text="{TemplateBinding Content}"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="Center">
                        </TextBlock>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

You can apply your trigger based on your desires. However, you can use the trigger below for your reference.

<ControlTemplate.Triggers>
    <Trigger Property="IsInactive" Value="True">
        <Setter TargetName="block"
                Property="Foreground"
                Value="{DynamicResource ResourceKey=brushForegroundDisabled}">
        </Setter>
        <Setter Property="IsEnabled"
                Value="False">
        </Setter>
    </Trigger>
    <Trigger Property="HasSelectedDays" Value="True">
        <Setter TargetName="border_today"
                Property="Visibility"
                Value="Visible">
        </Setter>
    </Trigger>
    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition SourceName="border"
                       Property="IsMouseOver"
                       Value="True">
            </Condition>
            <Condition Property="IsInactive"
                       Value="False">
            </Condition>
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
            <Setter TargetName="border"
                    Property="Background"
                    Value="#66CED3C4">
            </Setter>
        </MultiTrigger.Setters>
    </MultiTrigger>
</ControlTemplate.Triggers>

Just change the design of the colors based on your design.

Note: The brushForegroundDisabled is a custom style reference, so you need to implement your own.

Finished Style for CalenderButton:

<Style x:Key="styleDatePickerCalendarButton" TargetType="{x:Type CalendarButton}">
    <Setter Property="OverridesDefaultStyle" Value="True"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CalendarButton}">
                <Grid x:Name="grid">
                    <Border x:Name="border_today"
                            CornerRadius="2"
                            Background="#FFFFFF"
                            BorderBrush="#9A9A9A"
                            BorderThickness="1"
                            Visibility="Collapsed"></Border>
                    <Border x:Name="border" CornerRadius="3">
                        <TextBlock x:Name="block"
                                   Foreground="#3A3A3A"
                                   FontSize="12"
                                   Text="{TemplateBinding Content}"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="Center">
                        </TextBlock>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsInactive" Value="True">
                        <Setter TargetName="block"
                                Property="Foreground"
                                Value="{DynamicResource ResourceKey=brushForegroundDisabled}">
                        </Setter>
                        <Setter Property="IsEnabled"
                                Value="False">
                        </Setter>
                    </Trigger>
                    <Trigger Property="HasSelectedDays" Value="True">
                        <Setter TargetName="border_today"
                                Property="Visibility"
                                Value="Visible">
                        </Setter>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition SourceName="border"
                                       Property="IsMouseOver"
                                       Value="True">
                            </Condition>
                            <Condition Property="IsInactive"
                                       Value="False">
                            </Condition>
                        </MultiTrigger.Conditions>
                        <MultiTrigger.Setters>
                            <Setter TargetName="border"
                                    Property="Background"
                                    Value="#66CED3C4">
                            </Setter>
                        </MultiTrigger.Setters>
                    </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Styling the CalendarDayButton

Same with CalendarButton, you have to determine the what type of control the PART is and implement the simple style by overriding its template. See sample below for your reference.

Note: It is a CalendarDayButton object.

<Style x:Key="styleDatePickerCalendarDayButton" TargetType="{x:Type CalendarDayButton}">
    <Setter Property="OverridesDefaultStyle" Value="True"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CalendarDayButton}">
                <Grid x:Name="grid">
                    <Border x:Name="border_today"
                            CornerRadius="2"
                            Background="#FFFFFF"
                            BorderBrush="#9A9A9A"
                            BorderThickness="1"
                            Visibility="Collapsed">
                    </Border>
                    <Border x:Name="border_selected"
                            CornerRadius="2"
                            Background="#E7E7E7"
                            BorderBrush="#9A5A9A"
                            BorderThickness="1"
                            Visibility="Collapsed">
                    </Border>
                    <Border x:Name="border" CornerRadius="3">
                        <TextBlock x:Name="block"
                                   Foreground="#3A3A3A"
                                   Margin="10,3,10,3"
                                   Text="{TemplateBinding Content}"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="Center">
                        </TextBlock>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

See below the trigger that you can use for this style.

<ControlTemplate.Triggers>
    <Trigger Property="IsInactive" Value="True">
        <Setter TargetName="block"
                Property="Foreground"
                Value="{DynamicResource ResourceKey=brushForegroundDisabled}">
        </Setter>
        <Setter Property="IsEnabled"
                Value="False">
        </Setter>
    </Trigger>
    <Trigger Property="IsToday" Value="True">
        <Setter TargetName="border_today"
                Property="Visibility"
                Value="Visible">
        </Setter>
    </Trigger>
    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition Property="IsSelected" Value="True"></Condition>
            <Condition Property="IsToday" Value="False"></Condition>
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
            <Setter TargetName="border_selected" Property="Visibility" Value="Visible"></Setter>
        </MultiTrigger.Setters>
    </MultiTrigger>
    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition SourceName="border" Property="IsMouseOver" Value="True"></Condition>
            <Condition Property="IsInactive" Value="False"></Condition>
            <Condition Property="IsSelected" Value="False"></Condition>
            <Condition Property="IsToday" Value="False"></Condition>
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
            <Setter TargetName="border" Property="Background" Value="{DynamicResource ResourceKey=brushBorderControlDisabled}"></Setter>
        </MultiTrigger.Setters>
    </MultiTrigger>
</ControlTemplate.Triggers>

The actual code combination for CalendarDayButton

<Style x:Key="styleDatePickerCalendarDayButton" TargetType="{x:Type CalendarDayButton}">
    <Setter Property="OverridesDefaultStyle" Value="True"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CalendarDayButton}">
                <Grid x:Name="grid">
                    <Border x:Name="border_today"
                            CornerRadius="2"
                            Background="#FFFFFF"
                            BorderBrush="#9A9A9A"
                            BorderThickness="1"
                            Visibility="Collapsed">
                    </Border>
                    <Border x:Name="border_selected"
                            CornerRadius="2"
                            Background="#E7E7E7"
                            BorderBrush="#9A5A9A"
                            BorderThickness="1"
                            Visibility="Collapsed">
                    </Border>
                    <Border x:Name="border" CornerRadius="3">
                        <TextBlock x:Name="block" 
                                   Foreground="#3A3A3A" 
                                   Margin="10,3,10,3" 
                                   Text="{TemplateBinding Content}"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="Center"></TextBlock>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsInactive" Value="True">
                        <Setter TargetName="block"
                                Property="Foreground"
                                Value="{DynamicResource ResourceKey=brushForegroundDisabled}">
                        </Setter>
                        <Setter Property="IsEnabled" Value="False"></Setter>
                    </Trigger>
                    <Trigger Property="IsToday" Value="True">
                        <Setter TargetName="border_today" Property="Visibility" Value="Visible"></Setter>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="True"></Condition>
                            <Condition Property="IsToday" Value="False"></Condition>
                        </MultiTrigger.Conditions>
                        <MultiTrigger.Setters>
                            <Setter TargetName="border_selected" Property="Visibility" Value="Visible"></Setter>
                        </MultiTrigger.Setters>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition SourceName="border"
                                       Property="IsMouseOver"
                                       Value="True">
                            </Condition>
                            <Condition Property="IsInactive"
                                       Value="False">
                            </Condition>
                            <Condition Property="IsSelected"
                                       Value="False">
                            </Condition>
                            <Condition Property="IsToday"
                                       Value="False">
                            </Condition>
                        </MultiTrigger.Conditions>
                        <MultiTrigger.Setters>
                            <Setter TargetName="border"
                                    Property="Background"
                                    Value="{DynamicResource ResourceKey=brushBorderControlDisabled}">
                            </Setter>
                        </MultiTrigger.Setters>
                    </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Please note that other custom style like brushForegroundDisabled and brushBorderControlDisabled is just a custom style that you can modify base on your own desire.

So be on this implementation to simplify everything. ;)

Applying in Calendar

Once you're done with your styling, simply apply the actual CalendarItem Style object in the Calendar control. Below is the way how to do it.

<Style x:Key="styleDatePickerCalendar" TargetType="{x:Type Calendar}">
    <Setter Property="CalendarButtonStyle"
            Value="{StaticResource ResourceKey=styleDatePickerCalendarButton}">
    </Setter>
    <Setter Property="CalendarDayButtonStyle"
            Value="{StaticResource ResourceKey=styleDatePickerCalendarDayButton}">
    </Setter>
    <Setter Property="CalendarItemStyle"
            Value="{StaticResource ResourceKey=styleDatePickerCalendarItem}">
    </Setter>
</Style>

And that's it, we just finish styling the WPF Calendar control.

5 comments:

  1. Thanks for the posting as I am working on a project that does this exactly. The curious part is my resource.xaml that controls all of this and it isn't an exact copy of your but close. My custom DatePicker style works no problem but when I try to customize the calendar control none of my changes take affect. How does the Custom DatePicker know to use the custom Calendar style?

    I have my
    <DatePicker Style="{DynamicResource DatePickerStyle}" Height="25" HorizontalAlignment="Center" Name="DatePickerLeft"

    in my xaml file and it controls the customer datepicker no problem. Any help would be greatly appreciated.

    Thanks

    Chris

    ReplyDelete
    Replies
    1. Hi Chris, I was just very busy last week so I missed replying in your comment immediately. Anyway...

      But the best thing you can do is to override the style of you DatePicker, you can see it here (http://codesdirectory.blogspot.com/2013/01/wpf-datepicker-control-style.html).

      However, if you don't want to override it, you can use the CalendarStyle property to apply the custom style you created for your Calendar control. See below the application.

      Delete
  2. Hey.
    Awesome guide. I have tried copy in your code and mix it with the guide you wrote for the DatePicker control.

    My problem is, the calender does not refresh when "going out" from day overview to month to years and so on.
    So when im in month overview, i can still see the date numbers.

    You know how to fix it?

    -Kristian

    ReplyDelete
  3. Hi,
    I also have the same issue as Kristian Above. I am not able to get it to refresh when it switches from month to year view

    ReplyDelete
  4. Hi,

    I 've solved the problem adding Triggers :











































    It works good in my project.
    Hope it will be usefull

    ReplyDelete

Place your comments and ideas