for WPF developers
Home Profile Tips 全記事一覧

WPF で画面遷移する方法 3

(2017/05/19 8:31:45 created.)

(2017/05/24 15:09:21 modified.)

コンテンツを切り替えるコントロールといえば真っ先に TabControl を想像しますよね。というわけで今回は TabControl を少しカスタマイズしてみます。

まずはプロジェクトのファイル構成から。

構成は単純で、MainView ウィンドウに対する MainViewModel と、各コンテンツに対する ViewModel として Content01ViewModel、Content02ViewModel、Content03ViewModel があります。 Content01ViewModel などに対する View は MainView.xaml の中に記述するため、ファイルとしては分かれていません。

それではまず MainViewModel のソースから説明します。

MainViewModel.cs
  1. namespace TabSample.ViewModels
  2. {
  3.     using YKToolkit.Bindings;
  4.  
  5.     internal class MainViewModel : NotificationObject
  6.     {
  7.         private ViewModelBase _content01ViewModel = new Content01ViewModel();
  8.         public ViewModelBase Content01ViewModel { get { return this._content01ViewModel; } }
  9.  
  10.         private ViewModelBase _content02ViewModel = new Content02ViewModel();
  11.         public ViewModelBase Content02ViewModel { get { return this._content02ViewModel; } }
  12.  
  13.         private ViewModelBase _content03ViewModel = new Content03ViewModel();
  14.         public ViewModelBase Content03ViewModel { get { return this._content03ViewModel; } }
  15.     }
  16. }

愚鈍に各コンテンツの ViewModel のインスタンスを保持、公開しているだけです。各コンテンツの ViewModel は Caption プロパティを持っています。例えば Content01ViewModel クラスは次のようになっています。

Content01ViewModel.cs
  1. namespace TabSample.ViewModels
  2. {
  3.     internal class Content01ViewModel : ViewModelBase
  4.     {
  5.         public string Caption { get { return "Content01"; } }
  6.     }
  7. }

さて、MainView ウィンドウに TabControl を配置しましょう。

MainView.xaml
  1. <YK:Window x:Class="TabSample.Views.MainView"
  2.            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.            xmlns:YK="clr-namespace:YKToolkit.Controls;assembly=YKToolkit.Controls"
  5.            Title="MainView"
  6.            Width="300" Height="200">
  7.     <DockPanel>
  8.         <TabControl>
  9.             <TabItem DataContext="{Binding Content01ViewModel}" Header="{Binding Caption}">
  10.                 <CheckBox Content="{Binding Caption}" />
  11.             </TabItem>
  12.  
  13.             <TabItem DataContext="{Binding Content02ViewModel}" Header="{Binding Caption}">
  14.                 <TextBlock Text="{Binding Caption}" />
  15.             </TabItem>
  16.  
  17.             <TabItem DataContext="{Binding Content03ViewModel}" Header="{Binding Caption}">
  18.                 <TextBlock Text="{Binding Caption}" />
  19.             </TabItem>
  20.         </TabControl>
  21.     </DockPanel>
  22. </YK:Window>

各コンテンツのレイアウトを MainView.xaml 内に書いてしまいます。あまり長くなるのが嫌だったり、明確にファイルを別にしたい場合は独自のユーザコントロールを置くなどして対処すればいいかと思います。ここでは簡略化のため同一 xaml 内で、しかもコントロールを 1 つだけ置いています。
また、ここではあえてすべてのコンテンツの DataContext に別物を指定していますが、指定しなくてもいいし、同じものを指定してもいいと思います。

とりあえず実行してみるとこんな感じ。

あ、うん。TabControl ってこんな感じだよね。

これでいい場合はこれでいいんですが、そうじゃないですよね。つまり、TabControl のタブの部分が邪魔です。というわけで Template プロパティをいじって TabPanel の部分を表示しないようにしてしまいます。

MainView.xaml
  1. <YK:Window x:Class="TabSample.Views.MainView"
  2.            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.            xmlns:YK="clr-namespace:YKToolkit.Controls;assembly=YKToolkit.Controls"
  5.            Title="MainView"
  6.            Width="300" Height="200">
  7.     <DockPanel>
  8.         <TabControl SelectedIndex="0">
  9.             <TabControl.Template>
  10.                 <ControlTemplate TargetType="{x:Type TabControl}">
  11.                     <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
  12.                 </ControlTemplate>
  13.             </TabControl.Template>
  14.  
  15.             <TabItem DataContext="{Binding Content01ViewModel}">
  16.                 <CheckBox Content="{Binding Caption}" />
  17.             </TabItem>
  18.  
  19.             <TabItem DataContext="{Binding Content02ViewModel}">
  20.                 <TextBlock Text="{Binding Caption}" />
  21.             </TabItem>
  22.  
  23.             <TabItem DataContext="{Binding Content03ViewModel}">
  24.                 <TextBlock Text="{Binding Caption}" />
  25.             </TabItem>
  26.         </TabControl>
  27.     </DockPanel>
  28. </YK:Window>

TabControl のカスタマイズについては MSDN のサイトが参考になります。

本来は TabControl の Template に TabPanel コントロールを配置することでタブ部分を表示させますが、これを配置せずに ContentPresenter だけを配置します。タブ部分が表示されなくなるので、各 TabItem コントロールの Header プロパティは不要になります。また、デフォルトで 0 番目のコンテンツが選択されるように TabControl の SelectedIndex プロパティを指定しています。

実行するとこんな感じ。

狙い通りタブ部分が表示されなくなりました。

さらにコンテンツを選択できるようにすれば見た目で TabControl を使っているとは思えない外観になります。

MainView.xaml
  1. <YK:Window x:Class="TabSample.Views.MainView"
  2.            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.            xmlns:YK="clr-namespace:YKToolkit.Controls;assembly=YKToolkit.Controls"
  5.            Title="MainView"
  6.            Width="300" Height="200">
  7.     <DockPanel>
  8.         <ComboBox x:Name="combobox" DockPanel.Dock="Bottom" SelectedIndex="0">
  9.             <ComboBoxItem>Content01</ComboBoxItem>
  10.             <ComboBoxItem>Content02</ComboBoxItem>
  11.             <ComboBoxItem>Content03</ComboBoxItem>
  12.         </ComboBox>
  13.         <TabControl SelectedIndex="{Binding SelectedIndex, ElementName=combobox}">
  14.             <TabControl.Template>
  15.                 <ControlTemplate TargetType="{x:Type TabControl}">
  16.                     <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
  17.                 </ControlTemplate>
  18.             </TabControl.Template>
  19.  
  20.             <TabItem DataContext="{Binding Content01ViewModel}">
  21.                 <CheckBox Content="{Binding Caption}" />
  22.             </TabItem>
  23.  
  24.             <TabItem DataContext="{Binding Content02ViewModel}">
  25.                 <TextBlock Text="{Binding Caption}" />
  26.             </TabItem>
  27.  
  28.             <TabItem DataContext="{Binding Content03ViewModel}">
  29.                 <TextBlock Text="{Binding Caption}" />
  30.             </TabItem>
  31.         </TabControl>
  32.     </DockPanel>
  33. </YK:Window>


Content01View のチェックボックスを入れた状態で画面遷移し、戻ってきてもちゃんとチェックが入った状態になっています。MainView.xaml に各コンテンツのインスタンスを配置しているため、View のインスタンスが保持されるからですね。

ところが、これでは前回までのように画面遷移時のアニメーションが実現できません。次回は TabControl による画面遷移でアニメーションをおこなってみます。