Friday, June 13, 2008

Silverlight Master Page

Since I'm old school and so are most of my users, I wanted to create a simple Silverlight app that had a title bar across the top, nav menu on the left side and a content area on the right. You can modify this for mutiple zones and do some pretty slick stuff. Note: This is Silverlight 2 Beta 2 and VS 2008.

image

The key is to create a Canvas that gets changed when you want to make changes. Here's the code for the above -- notice the Canvas named ContentHolder.

<UserControl x:Class="SilverLightMasterPageTest.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:SilverLightMasterPageTest">
    <Grid x:Name="LayoutRoot" Background="#FF5C7590">
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Border Grid.Row="0" CornerRadius="10" Background="#FFDEDEDE" Margin="2,2,2,2" Padding="10,2,10,2">
            <TextBlock Text="Master Page Test" VerticalAlignment="Center" Foreground="#FF14517B" />
        </Border>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="110" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <local:NavBar Grid.Column="0" />
            <!-- This canvas is what we change -->
            <Canvas x:Name="ContentHolder" Grid.Column="1">
                <local:Page1 />
            </Canvas>
        </Grid>
    </Grid>
</UserControl>

We need to use a Canvas because it has the Children property that allows us to add / remove child controls. This code shows how to use the Children property to change the page.

Partial Public Class Page
    Inherits UserControl

    Public Sub New()
        InitializeComponent()
    End Sub

    Public Sub ChangePage(ByVal newPage As UserControl)
        ' Removes the currently displayed page
        ' (including from memory)
        Me.ContentHolder.Children.RemoveAt(0)
        ' Adds our new page
        Me.ContentHolder.Children.Add(newPage)
    End Sub
End Class

The Remove option gets rid of the currently displayed control while Add shows the new control. Now all we need to do is have our navigation bar create the correct control and call ChangePage. This is encapsulated in PagePicker (in the code behind for NavBar).

' Encapsulate the logic to select
' the page to display
Private Sub PagePicker(ByVal pageId As PageIds)
    ' Make sure this goes to the top page so that you can get
    ' to your change method.
    ' We're nested in two grids at this point so we need
    ' to go back up the tree.
    Dim pg As Page = CType(CType(Me.Parent, Grid).Parent, Grid).Parent
    Dim newPage As UserControl = Nothing
    Select Case pageId
        Case PageIds.Page1
            newPage = New Page1
        Case PageIds.Page2
            newPage = New Page2
        Case PageIds.Page3
            newPage = New Page3
        Case PageIds.Page4
            newPage = New Page4
    End Select
    ' This is where the actual change
    ' occurs.
    pg.ChangePage(newPage)
End Sub

Private Sub Page1Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page1)
End Sub

Private Sub Page2Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page2)
End Sub

Private Sub Page3Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page3)
End Sub

Private Sub Page4Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page4)
End Sub

' Just makes it easy to identify
' our pages
Private Enum PageIds
    Page1
    Page2
    Page3
    Page4
End Enum

Notice that PagePicker has to get a reference to the top control so that it can call ChangePage. This will probably be one of the things that bites you as you change the layout. Here's the XAML for NavBar and one of the pages.

<UserControl x:Class="SilverLightMasterPageTest.NavBar"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <Border CornerRadius="10" Background="#FFDEDEDE" Width="110" Padding="4,4,4,4">
        <StackPanel>
            <Button x:Name="Page1Button" Content="Page 1" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page1Button_Click" />
            <Button x:Name="Page2Button" Content="Page 2" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page2Button_Click" />
            <Button x:Name="Page3Button" Content="Page 3" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page3Button_Click" />
            <Button x:Name="Page4Button" Content="Page 4" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page4Button_Click" />
        </StackPanel>
    </Border>
</UserControl>
<UserControl x:Class="SilverLightMasterPageTest.Page1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot">
        <Border CornerRadius="10" Background="AliceBlue" Width="300"
         HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,10,10,10" Padding="5" >
            <TextBlock Text="This is page 1" FontSize="22" />
        </Border>
    </Grid>
</UserControl>

7 comments:

Konstantinidis Konstantinos said...
This comment has been removed by the author.
Konstantinidis Konstantinos said...

good approach m8, there are is also a solution to this problem by another fellow here

pooran said...

Nice.. found it useful. Do you twitter?

PretzelSteelersFan said...

Thanks, pooran! As for tweeting, that's one of my goals for this month. At least three tweets a day. You can find me at http://twitter.com/paulgbrown

PretzelSteelersFan said...

Thanks Konstantinidis and sorry for the late post. I thought I had replied when you posted but apparently not!

Bharat said...

i am getting error in the line "Dim pg As Page = CType(CType(Me.Parent, Grid).Parent, Grid).Parent
"Cannot implicitly convert type 'System.Windows.DependencyObject' to 'LibraryMan.Page'. An explicit conversion exists (are you missing a cast?).

Please suggest me.. pls pls......

PretzelSteelersFan said...

Bharat, do you mean Library.Main.Page or actually LibraryMan.Page? Also, what version of SL are you working with? I've pretty much moved to the navigation stuf in SL 3 & 4.