Tuesday, October 24, 2023

Deferring TabItem creation

Let's suppose you have a TabControl with several TabItems. The definition might look like this.

<Window x:Class="DeferredTabItemContentCreation.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:DeferredTabItemContentCreation"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <local:Search x:Key="Search"/>
        <local:Add x:Key="Add"/>
        <local:Edit x:Key="Edit"/>
    </Window.Resources>
    <Grid>
        <TabControl SelectionChanged="TabControl_SelectionChanged">
            <TabItem Header="Search" Content="{StaticResource Search}"/>
            <TabItem Header="Add" Content="{StaticResource Add}"/>
            <TabItem Header="Edit" Content="{StaticResource Edit}"/>
        </TabControl>
    </Grid>
</Window>

The problem here is that when this window is loaded the constructors for Search, Add, and Edit are all called. You only really need the Search UserControl to be created. If you could defer the creation of the Add and Edit UserControls until the user asks for them, that would speed up the initial load.

It's fairly easy to achieve this by not specifying Content for the Add and Edit tabs and adding a SelectionChanged event handler.

<Window x:Class="DeferredTabItemContentCreation.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:DeferredTabItemContentCreation"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <local:Search x:Key="Search"/>
    </Window.Resources>
    <Grid>
        <TabControl SelectionChanged="TabControl_SelectionChanged">
            <TabItem Header="Search" Content="{StaticResource Search}"/>
            <TabItem Header="Add"/>
            <TabItem Header="Edit"/>
        </TabControl>
    </Grid>
</Window>

The SelectionChanged event handler detects that the TabItem we are changing to has no Content and creates it on the fly. If we have visited this tab before, we just reuse the Content we created on the first visit.

    private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        TabControl? tc = (sender as TabControl);
        if (tc != null && e.AddedItems != null && e.AddedItems.Count > 0)
        {
            TabItem? ti = e.AddedItems[0] as TabItem;
            if (ti != null)
            {
                if (ti.Content == null)
                {
                    switch (ti.Header?.ToString()?.ToLower())
                    {
                        case "add": ti.Content = new Add();break;
                        case "edit":ti.Content = new Edit();break;
                    }
                }
            }
        }
    }

For more complex scenarios we could subclass the TabItem and add a class property. Then we could use Reflection CreateInstance to dynamically create the Content from the class property.

No comments:

Post a Comment