【C# WPF】カスタムコントロールで追加削除ボタンつきのTabControl を作成

今回はカスタムコントロールをで追加削除ボタン付きのTabControlを作成します。

カスタムコントロールの名前を、AddDelTabCustomControl とした場合、

Xamlファイルにカスタムコントロールのタグを追記するだけで、

追加ボタン、削除ボタンを実装したTabControlを作成できます。

仕様
  • 追加ボタンを押すたび、初期設定のtitle+全タブ数をタイトルとしたタブを生成
  • タブのコンテンツは初期設定のものを複製
  • 先頭のタブは削除ボタンを非表示
  • タブを削除すると、順番通りになるようにタブのヘッダーを修正

なお、TabControlに追加ボタン、削除ボタンの実装の説明は下記リンクを参照してください。

【C# WPF】追加ボタンでタブを複製する方法

2020.02.29

【C# WPF】削除ボタンでタブを削除する

2020.03.01
開発環境
  • Windows10
  • Microsoft Visual Studio Community2019
  • .NET Framework 4.7.2
  • WPF アプリ( .NET Framework )

タブのヘッダーのユーザーコントロールを実装

追加削除ボタン付きのTabControlの作成前に、タブのヘッダーを作成します。ヘッダーも複製できるようにユーザーコントロールを使用します。

ソリューションエクスプローラーでプロジェクト名を右クリック→追加→新しい項目 で項目のダイアログが表示され、ユーザーコントロールを作成できます。

ヘッダーのXAMLファイルは下記のようになります。( ファイル名は、TabHeaderUserControl.xaml )

<UserControl x:Class="TabAddDelTest.TabHeaderUserControl" 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:TabAddDelTest" mc:Ignorable="d" Height="25" Width="85">
    <Grid>
        <Label x:Name="label" Content="Label" HorizontalAlignment="Left" VerticalAlignment="Top" Width="60" Height="25"/>
        <Button x:Name="button" Content="×" Margin="60,0,0,0" VerticalAlignment="Top" Height="25" Click="ClickedDelete" Background="#00000000" BorderBrush="Transparent"/>

    </Grid>
</UserControl>

ラベルと削除ボタンだけです。これがタブのヘッダーになります。

デザイナで見ると↑のように表示されます。

cs ファイルを以下のようにします。

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

namespace TabAddDelTest
{
    /// <summary>
    /// TabHeaderUserControl.xaml の相互作用ロジック
    /// </summaruy>
    public partial class TabHeaderUserControl : UserControl
    {
        public TabHeaderUserControl(string title, Visibility visibility)
        {
            InitializeComponent();

            // タブのタイトルを設定
            label.Content = title;

            // 削除ボタンの表示状態
            button.Visibility = visibility;
        }

        private void ClickedDelete(object sender, RoutedEventArgs e)
        {
            // 削除ボタンを押す
            TabItem item = (TabItem)this.Parent;
            AddDelTabCustomControl tabControl = (AddDelTabCustomControl)item.Parent;

            // タブコントロールで削除を実行する
            tabControl.ClickDelButton(item);
        }
    }
}

TabHeaderUserControl(string title, Visibility visibility)

コンストラクタです。このヘッダーが作成される、つまりタブを追加するとき呼ばれます。タイトル、削除ボタンの表示状態を設定します。削除ボタンの表示状態は、先頭のタブだけ非表示にするために必要です。

ClickedDelete(object sender, RoutedEventArgs e)

削除ボタンを押したときに呼ばれます。この関数は、先ほどのXAMLファイルで登録します。

この関数内では、親のClickDelButtonを呼び出します。親の関数でタブを削除します。

このヘッダーは、ここでしか使えません。汎用性はないですね。。。

タブコントロールのカスタムコントロールを実装

いよいよ、カスタムコントロールを実装します。ソリューションエクスプローラーでプロジェクト名を右クリック→追加→新しい項目 で項目のダイアログが表示され、カスタムコントロールを作成できます。

カスタムコントロールを作成すると、ソリューションエクスプローラーでプロジェクト名→Themes→Generic.xaml にコードが自動生成されます。コードを以下のように修正します。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TabAddDelTest">


    
<Style TargetType="{x:Type local:AddDelTabCustomControl}" BasedOn="{StaticResource {x:Type TabControl}}">
        <!-- 
         <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:AddDelTabCustomControl}">
                    <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        -->
    </Style>

</ResourceDictionary>

今回は、AddDelTabCustomControl.cs というカスタムファイルを作成しました。

  • TargetType : 今回作成したカスタムコントロール
  • BasedOn : TabControl をベース

次は、AddDelTabCustomControl.cs の中身です。

 

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

namespace TabAddDelTest
{
    public class AddDelTabCustomControl : TabControl, ISupportInitialize
    {
        static AddDelTabCustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(AddDelTabCustomControl), new FrameworkPropertyMetadata(typeof(AddDelTabCustomControl)));
        }

        /// <summary>
        /// タブのコンテンツのクラス名
        /// </summary>
        public static readonly DependencyProperty ContentClassNameProperty =
            DependencyProperty.Register(
                "ContentClassName",
                typeof(String),
                typeof(AddDelTabCustomControl),
                new PropertyMetadata("")
            );

        public static string GetContentClassName(DependencyObject obj)
        {
            return (string)obj.GetValue(ContentClassNameProperty);
        }

        public static void SetContentClassName(DependencyObject obj, string value)
        {
            obj.SetValue(ContentClassNameProperty, value);
        }

        /// <summary>
        /// タブのタイトル
        /// </summary>
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register(
                "Title",
                typeof(String),
                typeof(AddDelTabCustomControl),
                new PropertyMetadata("")
            );

        public static string GetTitle(DependencyObject obj)
        {
            return (string)obj.GetValue(TitleProperty);
        }
        
        public static void SetTitle(DependencyObject obj, string value)
        {
            obj.SetValue(TitleProperty, value);
        }

        /// <summary>
        /// コントロールの初期化後の処理
        /// </summary>
        public override void EndInit()
        {
            base.EndInit();

            // 追加ボタン
            TabItem itemAdd = new TabItem();
            itemAdd.Header = "+";
            Items.Add(itemAdd);

            // 最初のタブを追加
            AddTabItemCustom(Visibility.Hidden);

            // イベントを登録
            SelectionChanged += SelectedAddDelTab;
        }

        /// <summary>
        /// タブの切り替え時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectedAddDelTab(object sender, SelectionChangedEventArgs e)
        {
            // 追加ボタン(+)を選択する
            if (SelectedIndex == Items.Count - 1)
            {
                // 最初のタブを追加
                AddTabItemCustom(Visibility.Visible);

                // 追加したタブを選択状態にする
                SelectedIndex = Items.Count - 2;

                // 追加したタブにフォーカスを設定する
                ((TabItem)(Items[SelectedIndex])).Focus();

                e.Handled = true;
            }
        }

        /// <summary>
        /// 削除ボタンをクリック
        /// </summary>
        /// <param name="item">削除ボタンクリックしたアイテム</param>
        public void ClickDelButton(TabItem item)
        {
            // インデックスを1つ戻す
            SelectedIndex--;

            // タブを削除する
            Items.Remove(item);

            // タブのタイトルを再設定する
            for( int i=1; i < Items.Count - 1; i++ )
            {
                ((TabHeaderUserControl)((TabItem)Items[i]).Header).label.Content = GetTitle(this) + (i + 1);
            }
        }

        /// <summary>
        /// タブの追加
        /// </summary>

        /// <param name="visibility">削除ボタンの表示状態</param>
        private void AddTabItemCustom(Visibility visibility)
        {
            // タブを作成し、追加ボタンの前に追加
            TabItem item = new TabItem();
            item.Header = new TabHeaderUserControl(GetTitle(this) + Items.Count, visibility);
            item.Content = GetContentFromContentClassName();
            Items.Insert(Items.Count - 1, item);
        }

        /// <summary>
        /// クラス名からオブジェクト作成
        /// </summary>

        /// <returns>オブジェクト。失敗時はnull</returns>
        private Control GetContentFromContentClassName()
        {
            // プロパティで設定したクラス名からコンテンツのオブジェクトを作成する
            Type t = Type.GetType(GetContentClassName(this));
            if (t != null)
            {
                return (Control)Activator.CreateInstance(t);
            }
            else
            {
                return null;
            }
        }
    }
}

 

プロパティ変数の追加

ContentClassNameProperty、TitleProperty を追加します。ここの記述で、このカスタムコントールを使うとき、XAMLファイルのプロパティとして設定できます。

追加されたプロパティ
  • ContentClassName : タブのコンテンツに表示するクラス
  • Title : タブのヘッダーの文字列

コントロールの初期化後の処理

EndInit関数でコントロールの初期化後の処理を行います。ISupportInitialize インタフェースを使用します。

やること
  • 追加ボタン「+」( 追加用のタブ )と最初のタブを作成
  • タブを選択したときに呼ばれるイベントの登録

タブを選択したときの処理

SelectedAddDelTabはタブを選択するたびに呼ばれます。追加ボタンを押したとき、タブの追加をします。

タブを切り替えた後は、このタブにフォーカスをあてます。フォーカスをあてないと、2つ目のタブが追加できないという問題が発生する。

タブを削除したときの処理

ClickDelButton はタブの削除ボタンを押したときに呼ばれます。呼び出し元は、ヘッダーのユーザーコントロールです。

タブの削除をした後、ヘッダーのタイトルが順番通りになるようにします。

メイン画面に表示

最後に、メイン画面に追加削除ボタンを実装したタブコントロールを表示します。

<Window x:Class="TabAddDelTest.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:TabAddDelTest" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:AddDelTabCustomControl HorizontalAlignment="Left" Height="370" Width="580" Margin="10,10,0,0" VerticalAlignment="Top" ContentClassName="TabAddDelTest.TabItemUserControl" Title="タブ">
        </local:AddDelTabCustomControl>
    </Grid>
</Window>
  • ContentClassName : コンテンツに表示するユーザーコントロール
  • Title : タブのヘッダーのタイトル

コンテンツに表示するユーザーコントロールは、ラベルとテキストボックスだけのものです。追加ボタンを押すたびに複製されます。

 

<UserControl x:Class="TabAddDelTest.TabItemUserControl" 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:TabAddDelTest" mc:Ignorable="d" d:DesignHeight="350" d:DesignWidth="700">
    <Grid>
        <Label x:Name="label" Content="Label" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="100"/>
        <TextBox x:Name="textbox" HorizontalAlignment="Left" Height="23" Margin="150,14,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="100"/>
    </Grid>
</UserControl>

 

まとめ

今回はカスタムコントロールをで追加削除ボタン付きのTabControlを作成しました。

開発の参考にしていただけると嬉しく思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です