Wednesday 28 July 2010

WPF Master Page and Dependency Properties in Action

Our Task for This Post:
To implement an application (wizard) that looks like the mockup below:

Fig1. Application's mockup.

As you could see, we'd like to have Title P-holder, Message Bar P-holder, Content P-holder and Footer P-holder.
Everything is clear for now, lets go to do our task.

Abstract
There is no Master Page (MP) concept implemented in WPF(Windows Presentation Foundation) and XAML(Extensible Application Markup Language). Well, the main point of this post is to show you how to create MP, and the last but not least - understand what is Dependency Properties(DP) are. I'm quite sure that MP example - is a good way to explain what DP are. Taking into account what I said before we'll kill two birds with one stone. So, here we go.

Overview
WPF Master Page Common Vision
There is nothing special or/and unusual in the MP concept - everything is simply done. The MP structure is the following one:


Fig.2 WPF Master Page Common Vision

The top element of diagram is WPF Master Page object, and our pages ( Page(1) , Page(n) ) that are derived from them, and they have the same style and content, they are twins :)



The MP in our case consist of from three content - components:

1.Master Control.
2.Master Page Template.
3.Page Template Style.



Fig.3 WPF Master Page Overview

Master Control





The main role in our MP take Dependency Properties. Well, What is that?

From Matthew MacDonald "Pro WPF in C# 2008 Windows Presentation Foundation with .NET 3.5": "Dependency properties are a completely new implementation of properties—one that has a significant amount of added value. You need dependency properties to plug into core WPF features, such as animation, data binding, and styles. Most of the properties that are exposed by WPF elements are dependency properties. In all the examples you’ve seen up to this point, you’ve been using dependency properties with- out realizing it. That’s because dependency properties are designed to be consumed in the same way as normal properties. Dependency properties are a completely new implementation of properties—one that has asignificant amount of added value. You need dependency properties to plug into core WPFfeatures, such as animation, data binding, and styles.Most of the properties that are exposed by WPF elements are dependency properties. Inall the examples you’ve seen up to this point, you’ve been using dependency properties with-out realizing it. That’s because dependency properties are designed to be consumed in thesame way as normal properties."

I highly recommend this book for those who are just start to work with this BRILLIANT technology.

So, enough words, lats write some code to give a live to our MP.
Now, you have some understanding what DP is, and we could continue with the practical part.

From the code snippet below you could see that we have three dependency properties, namely: TitleProperty, ContentProperty and FooterProperty.

Implementation:

public class Master : Control
    {
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(object), typeof(Master), new UIPropertyMetadata());

        public static readonly DependencyProperty MessageBarProperty =
            DependencyProperty.Register("MessageBar", typeof(object), typeof(Master), new UIPropertyMetadata());

        public static readonly DependencyProperty ContentProperty =
            DependencyProperty.Register("Content", typeof(object), typeof(Master), new UIPropertyMetadata());

        public static readonly DependencyProperty FooterProperty =
            DependencyProperty.Register("Footer", typeof(object), typeof(Master), new UIPropertyMetadata());

        /// <summary>
        /// Initializes the <see cref="Master"/> class.
        /// </summary>
        static Master()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Master), new FrameworkPropertyMetadata(typeof(Master)));
        }

        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        /// <value>The title.</value>
        public object Title
        {
            get { return GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the MessageBar.
        /// </summary>
        /// <value>The message bar.</value>
        public object MessageBar
        {
            get { return GetValue(MessageBarProperty); }
            set { SetValue(MessageBarProperty, value); }
        }

        /// <summary>
        /// Gets or sets the content.
        /// </summary>
        /// <value>The content.</value>
        public object Content
        {
            get { return GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        /// <summary>
        /// Gets or sets the footer.
        /// </summary>
        /// <value>The footer.</value>
        public object Footer
        {
            get { return GetValue(FooterProperty); }
            set { SetValue(FooterProperty, value); }
        }
    }

Each property represents one area in our Master Page. The type for the dependency properties should be an Object. This ensures that we can add different types of controls (TextBox, Grid, StackPanel, Button etcetera) to each area on the page.

Master Page Template



• WPF doesnot add layout information into the class implementing a custom control like in our case - control Master. The content of the file generic.xaml defines the look of the control (template). This file will be automatically created by Visual Studio as soon as you add a custom control to your project.



Fig.4 Add new custom control

Note. In our case we aren't creating Master Page custom control, automatically, we do that manually. The file generic.xaml, mentioned above, must be inside of the folder Themes, otherwise generic.xaml will be unaccessible.

Implementation:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:MasterPage1="clr-namespace:WPFMasterPage.MasterPage">
    
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="../Themes/Master.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <Style TargetType="{x:Type MasterPage1:Master}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type MasterPage1:Master}">
                    <Grid>
                        <Border CornerRadius="5">
                            <Border.Background>
                                <SolidColorBrush Color="WhiteSmoke" />
                            </Border.Background>
                        </Border>

                        <Grid ShowGridLines="False" Margin="5">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>

                            <Grid Grid.Column="0">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="60" />
                                </Grid.RowDefinitions>

                                <!--TITLE PLACE HOLDER-->
                                <Grid Grid.Row="0">
                                    <ContentPresenter Content="{TemplateBinding Title}" Style="{StaticResource TitlePlaceHolderStyle}" />
                                </Grid>

                                <!--MESSAGE BAR-->
                                <Grid Grid.Row="1">
                                    <ContentPresenter Content="{TemplateBinding MessageBar}" Style="{StaticResource MessageBarPlaceHolderStyle}" />
                                </Grid>
 
                                <!--CONTENT-->
                                <StackPanel Grid.Row="1" >
                                    <ContentPresenter Content="{TemplateBinding Content}" Style="{StaticResource ContentPlaceHolderStyle}" />
                                </StackPanel>   

                                <!--FOOTER-->
                                <StackPanel Grid.Row="2">
                                    <ContentPresenter Content="{TemplateBinding Footer}" Style="{StaticResource FooterPlaceHolderStyle}" />
                                </StackPanel>
                                                                
                            </Grid>
                        </Grid>
                    </Grid>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Page Template Style



• Finally, we are able to separate the styles of Title, Message Bar, Content and Footer, into separate file, to make our template source code (generic.xaml) more understandable and good-looking :)

Implementation:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <!--TITLE PLACEHOLDER-->
    <Style x:Key="TitlePlaceHolderStyle" TargetType="{x:Type ContentPresenter}">
        <Setter Property="Control.FontSize" Value="24" />
        <Setter Property="Control.FontFamily" Value="Verdana" />
        <Setter Property="Control.FontWeight" Value="Bold" />
        <Setter Property="Control.Foreground" Value="#FFC9CBCC" />
        <Setter Property="Margin" Value="0,0,0,10" />
    </Style>

    <!--MESSAGE BAR PLACEHOLDER-->
    <Style x:Key="MessageBarPlaceHolderStyle" TargetType="{x:Type ContentPresenter}">
        <Setter Property="Control.Height" Value="50" />
        <Setter Property="Control.VerticalAlignment" Value="Top" />
        <Setter Property="Margin" Value="0" />
    </Style>

    <!--CONTENT PLACEHOLDER-->
    <Style x:Key="ContentPlaceHolderStyle" TargetType="{x:Type ContentPresenter}">
        <Setter Property="Control.Background" Value="Transparent" />
        <Setter Property="Control.MaxWidth" Value="760"/>
        <Setter Property="Control.MaxHeight" Value="515"/>
        <Setter Property="Control.VerticalAlignment" Value="Top" />
        <Setter Property="Control.HorizontalAlignment" Value="Left" />
        <Setter Property="Margin" Value="0,50,5,0" />
    </Style>
    
    <!--FOOTER PLACEHOLDER-->
    <Style x:Key="FooterPlaceHolderStyle" TargetType="{x:Type ContentPresenter}">
        <Setter Property="StackPanel.HorizontalAlignment" Value="Right"/>
        <Setter Property="StackPanel.Orientation" Value="Horizontal"/>
        <Setter Property="Margin" Value="10"/> 
    </Style>
    
</ResourceDictionary>

So, we are done with our task.
Let's see what we have in conclusion.


Fig.5 Master Page in action.


Let's take a look on XAML:

<UserControl x:Class="WPFMasterPage.UserControls.FirstPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             xmlns:MasterPage="clr-namespace:WPFMasterPage.MasterPage" 
             xmlns:MessageBar="clr-namespace:WPFMasterPage.MessageBar">
    <MasterPage:Master>

        <!--PAGE TITLE PLACEHOLDER-->
        <MasterPage:Master.Title>
            Personal Data
        </MasterPage:Master.Title>

        <!--MESSAGE BAR PLACEHOLDER-->
        <MasterPage:Master.MessageBar>
            <MessageBar:MessageBar x:Name="PersonalDataMessageBar" Height="20" Text="Hello, Mr. Julian Ustiyanovych" Foreground="Red" FontSize="14" FontFamily="Verdana" />
        </MasterPage:Master.MessageBar>

        <!--CONTENT PLACEHOLDER-->
        <MasterPage:Master.Content>
            <StackPanel>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="360" />
                    </Grid.ColumnDefinitions>

                    <!--Full Name-->
                    <TextBlock x:Name="FullNameTextBlock" Text="Full Name:" FontFamily="Verdana" FontSize="14" FontWeight="Normal" VerticalAlignment="Center" Margin="5" Grid.Column="0" />

                    <ComboBox x:Name="MrMrsTextBox" FontFamily="Verdana" FontSize="12" Width="55" Height="20" Margin="5" VerticalAlignment="Center" SelectedIndex="0" Grid.Column="1">
                        <ComboBoxItem>Mr.</ComboBoxItem>
                        <ComboBoxItem>Mrs.</ComboBoxItem>
                        <ComboBoxItem>Miss.</ComboBoxItem>
                        <ComboBoxItem>Ms.</ComboBoxItem>
                    </ComboBox>

                    <TextBox x:Name="FullNameTextBox" FontFamily="Verdana" FontSize="12" Width="350" Height="20" Margin="5" VerticalAlignment="Center" Grid.Column="2" />
                    
                    <!--Email-->                    
                    <TextBlock x:Name="EmailTextBlock" Text="Email:" FontFamily="Verdana" FontSize="14" FontWeight="Normal" VerticalAlignment="Center" Margin="5" Grid.Row="1" Grid.Column="0" />
                    <TextBox x:Name="EmailTextBox" FontFamily="Verdana" FontSize="12" Width="350" Height="20" Margin="5" VerticalAlignment="Center" Grid.Column="3" Grid.Row="1" />
                    
                    <!--LinkedIn-->
                    <TextBlock x:Name="LinkedInTextBlock" Text="LinkedIn:" FontFamily="Verdana" FontSize="14" FontWeight="Normal" VerticalAlignment="Center" Margin="5" Grid.Row="2" Grid.Column="0" />
                    <TextBox x:Name="LinkedInTextBox" FontFamily="Verdana" FontSize="12" Width="350" Height="20" Margin="5" VerticalAlignment="Center" Grid.Column="3" Grid.Row="2" />
                    
                    <!--Blog-->
                    <TextBlock x:Name="BlogTextBlock" Text="Blog:" FontFamily="Verdana" FontSize="14" FontWeight="Normal" VerticalAlignment="Center" Margin="5" Grid.Row="3" Grid.Column="0" />
                    <TextBox x:Name="BlogTextBox" FontFamily="Verdana" FontSize="12" Width="350" Height="20" Margin="5" VerticalAlignment="Center" Grid.Column="3" Grid.Row="3" />
                </Grid>
            </StackPanel>
        </MasterPage:Master.Content>

        <!--FOOTER PLACEHOLDER-->
        <MasterPage:Master.Footer>
            <StackPanel Orientation="Horizontal">
                <Button x:Name="btnNext" Content="Next" Width="100" Height="35" FontWeight="Normal" TabIndex="2" Click="btnNext_Click"/>                  
            </StackPanel>
        </MasterPage:Master.Footer>

    </MasterPage:Master>

</UserControl>


Well, folks, it was my version of Master Page, hope you like it :)




I'll upload source code latter on, hopefully till the end of this weekend.

2 comments:

  1. Very good post, but honestly I expected to hear more about DP.

    ReplyDelete
  2. Thanks dude, I'll write new post soon, related to DP for 100 per cent.

    Julian

    ReplyDelete