Friday, October 27, 2017

WPF Tab Control with dynamic content load in MVVM

This is written in Visual Basic with the view model in the code behind. Yes, I have to go to confession next Sunday.

This demonstrates how to dynamically load content as different tabs of a tab control are selected, using the MVVM model. The host page has a tab control and a frame. As different tabs are selected, different pages are loaded into the frame area. I did this to simplify and speed up a massive page with six unrelated tabs that has over a thousand properties. It has become too cumbersome, so I looked for a way to split the code up.

Start by creating a Visual Basic WPF project.


Let's start with the content that will be displayed. Add a Page called Page1.xaml and make the content a textblock with the words "This is page 1". The xaml looks like this...

<Page x:Class="Page1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:PagePerTab"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      Title="Page1">
    <Grid>
        <TextBlock Text="This is page 1"/>
    </Grid>
</Page>

Repeat the process for page 2. Your solution now looks like this.



Now open MainWindow.xaml. We will set the DataContext to point to the code behind and define a tab control and a frame.

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PagePerTab"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TabControl Grid.Row="0" ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}" Height="25"/>
        <Frame Grid.Row="1" Source="{Binding FrameSource}" NavigationUIVisibility="Hidden"></Frame>
    </Grid>
</Window>

The tab control is bound to a list of strings and the selected item is bound to a string property. The height is set to 25 to collapse the content area. If you know a better way of doing this, drop me a comment.

The rest of the window contains the frame that hosts the desired page. I have turned off the navigation buttons and bound the FrameSource to a URI property.

The code behind looks like this. Most of it is simply defining the properties that are used by the XAML. The only really interesting line of code is the one that sets FrameSource.

Imports System.ComponentModel

Class MainWindow
    Implements INotifyPropertyChanged

    Private _Tabs As New List(Of String) From {"Page1", "Page2"}
    Private _SelectedTab As String
    Private _FrameSource As Uri

    Public Property Tabs As List(Of String)
        Get
            Return _Tabs
        End Get
        Set(value As List(Of String))
            _Tabs = value
            NotifyPropertyChanged("Tabs")
        End Set
    End Property

    Public Property SelectedTab As String
        Get
            Return _SelectedTab
        End Get
        Set(value As String)
            _SelectedTab = value
            NotifyPropertyChanged("SelectedTab")
            FrameSource = New Uri(SelectedTab & ".xaml", UriKind.Relative)
        End Set
    End Property

    Public Property FrameSource As Uri
        Get
            Return _FrameSource
        End Get
        Set(value As Uri)
            _FrameSource = value
            NotifyPropertyChanged("FrameSource")
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Public Sub NotifyPropertyChanged(PropertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
    End Sub

End Class

Give it a try.




No comments:

Post a Comment