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構成にせず、このようなシンプルな分割でも
十分に見通しの良いコードを書くことができます。

コメント