WPFのタブコントロールでViewを分割・切り替える方法

コンピュータ

WPFのViewはXAMLで作ることが多く、
1画面に多くのコントロールを配置すると、XAMLファイルが肥大化しがちです。

対策としてUserControlに切り出す方法がありますが、
今回の記事では、複数のUserControlをTabControlで切り替える構成を紹介します。

ソースコード

ファイル名:TabViewChanger.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

ファイル名:MainWindow.xaml

<Window x:Class="TabViewChanger.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TabViewChanger Demo"
        Width="400" Height="300">
        <TabControl x:Name="TabHost"/>    
</Window>

タブコントロールが一つだけ配置。


ファイル名:DemoRegistry.cs

// DemoRegistry.cs
using TabViewChanger.DemoViews;

namespace TabViewChanger;

public static class DemoRegistry
{
    public static IReadOnlyList<Func<IDemoView>> Demos { get; } =
        [
            () => new CanvasDemoView(),
            () => new ButtonDemoView(),
        ];
}

Viewの生成処理をコンテナ化しています。
ここではFuncのリストとして定義し、
MainWindow側で一括してViewを生成できるようにしています。

もし、View(UserControl)を追加したい場合、こちらのコンテナに要素を追加します。


ファイル名:MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace TabViewChanger;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // タブのアイテムを動的に追加するループ
        foreach (var factory in DemoRegistry.Demos)
        {
            var demo = factory();   // ← ここで new CanvasDemoView(),new ButtonDemoView()が実行

            var tab = new TabItem
            {
                Header = demo.Title,
                Content = demo   // ← UserControl
            };

            TabHost.Items.Add(tab);
            TabHost.SelectedItem = tab;
        }
    }
}

メインウィンドウのコードビハインドです。

コンストラクタ内でDemoRegistryに登録されたViewを生成し、
それぞれをTabItemとしてTabControlに追加しています。

ContentにはUserControlを直接割り当てているため、
XAML側はTabControlを1つ配置するだけで済みます。


ファイル名:DemoViews\IDemoView.cs

// DemoViews/IDemoView.cs
namespace TabViewChanger.DemoViews;

public interface IDemoView
{
    string Title { get; }
}

View生成用の共通インターフェースです。

TitleプロパティはTabItemのHeaderとして使用されます。
この仕組みにより、Tabの表示名をView側に持たせることができます。


ファイル名:DemoViews\ButtonDemoView.xaml.cs

using System.Windows.Controls;

namespace TabViewChanger.DemoViews;

public partial class ButtonDemoView : UserControl, IDemoView
{
    public string Title => "Button Demo";

    public ButtonDemoView()
    {
        InitializeComponent();        
    }
}

ファイル名:DemoViews\ButtonDemoView.xaml

<UserControl x:Class="TabViewChanger.DemoViews.ButtonDemoView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid>
        <Button>
            <Button.FontSize>16</Button.FontSize>
            <Button.HorizontalAlignment>Center</Button.HorizontalAlignment>
            <Button.VerticalAlignment>Center</Button.VerticalAlignment>

            <Button.Content>Click Me!</Button.Content>
        </Button>
    </Grid>
</UserControl>

UserControlとしてViewを作成しています。
このViewはTabControl上で切り替えて表示されます。

シンプルなButton配置のデモViewです。


ファイル名:DemoViews\CanvasDemoView.xaml.cs

using System.Windows.Controls;

namespace TabViewChanger.DemoViews;

public partial class CanvasDemoView : UserControl, IDemoView
{
    public string Title => "Canvas Demo";

    public CanvasDemoView()
    {
        InitializeComponent();        
    }
}

ファイル名:DemoViews\CanvasDemoView.xaml

<UserControl x:Class="TabViewChanger.DemoViews.CanvasDemoView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid>
        <Canvas x:Name="CanvasRoot">
            <Canvas.Background>
                <SolidColorBrush Color="#333"/>
            </Canvas.Background>

            <!-- Path図形 -->
            <Path Data="M 10,10 L 100,10 L 100,60 L 10,60 Z">
                    <Canvas.Left>100</Canvas.Left>
                    <Canvas.Top>100</Canvas.Top>
                    <Path.Stroke>
                    <SolidColorBrush Color="Red"/>
                    </Path.Stroke>
                    <Path.StrokeThickness>5</Path.StrokeThickness>
                    <Path.Fill>#40FF0000</Path.Fill>
            </Path>

        </Canvas>
    </Grid>
</UserControl>

UserControlでViewを作ります。

CanvasとPathを使った描画のデモViewです。

プロジェクト構成

プロジェクトディレクトリ
|   App.xaml
|   App.xaml.cs
|   AssemblyInfo.cs
|   DemoRegistry.cs
|   MainWindow.xaml
|   MainWindow.xaml.cs
|   TabViewChanger.csproj
|   
+---DemoViews
        ButtonDemoView.xaml
        ButtonDemoView.xaml.cs
        CanvasDemoView.xaml
        CanvasDemoView.xaml.cs
        IDemoView.cs
       

実行例

起動すると、TabControl上でButton / CanvasのViewが切り替わります。

まとめ

この構成では、

・MainWindowのXAMLを最小限に保てる
・Viewごとにファイルを分割できる
・タブの追加/削除がDemoRegistryの編集だけで済む

といった利点があります。

小規模~中規模のツール系WPFアプリでは、
無理にMVVM構成にせず、このようなシンプルな分割でも
十分に見通しの良いコードを書くことができます。

コメント