TabItemと各TabItemの中身を動的に生成する

やりたいこと

タブと各タブページにボタンを配置する。
タブページの個数と各ページの個数はデータによって変動するのでデータバインドで動的に生成できるようにする。

↓こんな画面を作りたい

f:id:hkou:20150524091521p:plain

XAML

とりあえず全文、各要素の説明は下記。

MainWindow.xaml

<TabControl x:Name="tabControl" Grid.Row="2" Grid.Column="3" Margin="5" ItemsSource="{Binding TabPanels}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Header}" />
        </DataTemplate>
    </TabControl.ItemTemplate>

    <TabControl.ContentTemplate>
        <DataTemplate>
            <ItemsControl ItemsSource="{Binding Buttons}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button Content="{Binding Name}" Margin="4" FontSize="16">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="Click">
                                    <i:InvokeCommandAction Command="{Binding ClickCommand}" />
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </Button>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>

                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel ItemHeight="40" ItemWidth="100" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

TabControl.ItemTemplate

タブページのヘッダー(タブの名前が表示されるところ)のデザイン設定
名前が表示されればいいのでTextBlock

<TabControl.ItemTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding Header}" />
    </DataTemplate>
</TabControl.ItemTemplate>

TabControl.ContentTemplate

各タブページの内容を記述する

<TabControl.ContentTemplate>
    <DataTemplate>
    ~省略~
    </DataTemplate>
</TabControl.ContentTemplate>

省略の中身、まず画面上に生成するボタンのリストを一段下の階層にItemsControlItemsSourceにバインドする。
ItemsControl.ItemTemplateDataTemplateにタブページに表示するボタンを記述する。
ボタンを複数個生成したときに折り返して表示させるためにItemsControl.ItemsPanelItemsPanelTemplateにWrapPanelを指定する。

<ItemsControl ItemsSource="{Binding Buttons}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding Name}" Margin="4" FontSize="16">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <i:InvokeCommandAction Command="{Binding ClickCommand}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel ItemHeight="40" ItemWidth="100" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

ViewModel

ButtonItem.cs

一つのButtonにバインドするクラス
Name:Buttonに表示する文言を格納
ClickCommand:Buttonがクリックされたことを受け取る

public class ButtonItem
{
    /// <summary>
    /// ボタン名称
    /// </summary>
    public string Name { get; set; }

    #region ClickCommand
    private Livet.Commands.ViewModelCommand _ClickCommand;
    public Livet.Commands.ViewModelCommand ClickCommand
    {
        get
        {
            if (_ClickCommand == null)
            {
                _ClickCommand = new Livet.Commands.ViewModelCommand(Click);
            }
            return _ClickCommand;
        }
    }

    public void Click()
    {
        if (ClickEvent != null)
        {
            ClickEvent(this);
        }
    }
    #endregion

    /// <summary>
    /// ボタン押下時のイベント
    /// </summary>
    public Action<ButtonItem> ClickEvent { get; set; }
}

CustomTabItem.cs

一つのTabItemにバインドするクラス
Header:タブページのヘッダー(タブの名前が表示されるところ) Buttons:このページに表示するButtonの情報を保持

public class CustomTabItem
{
    public string Header { get; set; }

    private ObservableCollection<ButtonItem> Buttons_ = new ObservableCollection<ButtonItem>();
    public ObservableCollection<ButtonItem> Buttons
    {
        get
        {
            return Buttons_;
        }
        set
        {
            Buttons_ = value;
        }
    }
}

MainWindowViewModel.cs

TabControlにバインドするオブジェクト

private ObservableCollection<CustomTabItem> TabPanels_ = new ObservableCollection<CustomTabItem>();
public ObservableCollection<CustomTabItem> TabPanels
{
    get
    {
        return TabPanels_;
    }
    set
    {
        TabPanels_ = value;
        RaisePropertyChanged("TabPanels");
    }
}

表示内容を設定

public void Initialize()
{
    TabPanels.Add(new CustomTabItem { Header = "TabPanel1", 
        Buttons = { 
            new ButtonItem{Name="Button1", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
            new ButtonItem{Name="Button2", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
            new ButtonItem{Name="Button3", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
            new ButtonItem{Name="Button4", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
        }
    });

    TabPanels.Add(new CustomTabItem
    {
        Header = "TabPanel2",
        Buttons = { 
            new ButtonItem{Name="Button1", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
            new ButtonItem{Name="Button2", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
            new ButtonItem{Name="Button3", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
            new ButtonItem{Name="Button4", ClickEvent = (x)=>{Debug.WriteLine(x.Name);}},
        }
    });
}

参考ページ

非常に参考にさせてもらいました

grabacr.net