実際使うにあたり、以前の記事のソースコードにコメントで解説を追記してみました。


WPFでUserControlを追加するだけでタブが増える自動タブシステム
タブでUserControlを切り替えるサンプルコードを以前に記事にしました。こちらの方法だと、UserControlが増えるたびタブを追加するコードを記述する必要があります。今回のプログラムでは、UserControlを作ると、タブに自動...
AutoTab.cs
using System.Reflection;
using System.Windows.Controls;
using System.Globalization;
using System.Windows.Data;
namespace Maywork.WPF.AutoTab;
// =========================
// タブ定義用Attribute
// =========================
// UserControlに付与し、タブのメタ情報(ヘッダー・順序)を定義する
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class TabAttribute : Attribute
{
public string Header { get; } // タブに表示するヘッダー文字
public int Order { get; } // タブの表示順(小さい順)
public TabAttribute(string header, int order)
{
Header = header;
Order = order;
}
}
/*
// 使用例:UserControlに付与するだけでタブとして認識される
[Tab("ホーム", 10)]
public partial class HomeTabView : UserControl
{
public HomeTabView()
{
InitializeComponent();
}
}
*/
// =========================
// タブ情報クラス
// =========================
// タブ生成に必要な情報をまとめたデータクラス
public class TabItemInfo
{
public int Order { get; set; } // 表示順
public string Header { get; set; } = ""; // ヘッダー文字
public Type ViewType { get; set; } = null!; // ビューの型
}
// =========================
// タブ情報ロード処理
// =========================
// リフレクションでUserControlを列挙し、TabAttribute付きのものだけ抽出する
public static class TabLoader
{
public static List<TabItemInfo> LoadTabs()
{
// 実行中のアセンブリを取得
var asm = Assembly.GetExecutingAssembly();
// アセンブリ内の全型からタブ対象を抽出
var tabs = asm.GetTypes()
.Where(t =>
t.IsClass && // クラスであること
!t.IsAbstract && // 抽象クラスではない
typeof(UserControl).IsAssignableFrom(t)) // UserControlか、その派生
.Select(t => new
{
Type = t, // 型
Attribute = t.GetCustomAttribute<TabAttribute>() // TabAttribute取得
})
.Where(x => x.Attribute is not null) // 属性が付いているものだけ対象
.Select(x => new TabItemInfo
{
Header = x.Attribute!.Header, // 表示名
Order = x.Attribute.Order, // 並び順
ViewType = x.Type // インスタンス生成用の型
})
.OrderBy(x => x.Order) // 第1キー:Order
.ThenBy(x => x.Header) // 第2キー:Header(ソート)
.ToList(); // Listとして確定
return tabs;
}
}
// =========================
// 型 → インスタンス変換
// =========================
// ViewType (Type) を UserControl インスタンスに変換するコンバータ
// XAMLバインディング用
public class ViewTypeToInstanceConverter : IValueConverter
{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Typeでなければ対象外
if (value is not Type type)
return null;
// UserControlでなければ対象外
if (!typeof(UserControl).IsAssignableFrom(type))
return null;
// リフレクションでインスタンス生成
return Activator.CreateInstance(type) as UserControl;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// 逆変換は不要
throw new NotSupportedException();
}
}
ライブラリとして流用可能
MainWindowViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Maywork.WPF.AutoTab;
namespace WpfSample01;
// =========================
// メインウィンドウ用ViewModel
// =========================
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
// 自動生成されたタブ情報のコレクション
public ObservableCollection<TabItemInfo> Tabs { get; }
// 現在選択されているタブ
private TabItemInfo? _selectedTab;
public TabItemInfo? SelectedTab
{
get => _selectedTab;
set
{
if (_selectedTab == value) return;
_selectedTab = value;
OnPropertyChanged();
}
}
// コンストラクタ
public MainWindowViewModel()
{
// TabAttribute付きUserControlを自動検出してコレクション化
Tabs = new ObservableCollection<TabItemInfo>(TabLoader.LoadTabs());
// 初期選択:先頭タブを選択状態にする
SelectedTab = Tabs.FirstOrDefault();
}
}
MainWindow.xaml
<Window x:Class="WpfSample01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSample01"
xmlns:at="clr-namespace:Maywork.WPF.AutoTab"
Title="AutoTab Demo" Height="200" Width="300">
<Window.Resources>
<at:ViewTypeToInstanceConverter x:Key="ViewTypeToInstanceConverter"/>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content=
"{Binding ViewType, Converter={StaticResource ViewTypeToInstanceConverter}}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
TabControlはListViewなどと同様にItemsControlを継承したコントロールです。
そのため、ItemsSourceやSelectedItemはListViewと同じようにデータバインディングで扱うことが出来ます。
また、DataTemplateを利用することで、各タブの表示内容(HeaderやContent)を柔軟に定義することが出来ます。
本サンプルでは、DataTemplate内でConverterを使用し、型情報(Type)からUserControlのインスタンスを生成しています。
以下タブに表示されるUserControlです。
追加すると自動的にタブが増えます。
HomeTabView.xaml
<UserControl x:Class="WpfSample01.Views.HomeTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinWidth="200"
MinHeight="100">
<Grid Margin="16">
<StackPanel>
<TextBlock FontSize="24" Text="Home" />
<TextBlock Margin="0,8,0,0" Text="これはホームタブです。" />
<Button Width="140" Margin="0,16,0,0" Content="ボタン" />
</StackPanel>
</Grid>
</UserControl>
HomeTabView.xaml.cs
using System.Windows.Controls;
using Maywork.WPF.AutoTab;
namespace WpfSample01.Views;
[Tab("ホーム", 10)]
public partial class HomeTabView : UserControl
{
public HomeTabView()
{
InitializeComponent();
}
}

コメント