ナビゲーターとしてのインターフェイス + Styleによるパンくずリストの外観を
併せ持つパンくずリストクラスです。
ベースクラスを ItemsControl とするカスタム コントロールとして作成しました。
上部にメニューを配置し、中央部がメインコンテンツという最もシンプルなパターンです。
いつものごとく、Styleを利用して グリッド レイアウトで区切っていきます。
ここで、メインコンテンツと右上部のアイコンは増減しませんが、左上部のパンくずメニューは遷移によって
コンテンツが増減する、 ItemsControl の実体の部分になります。
この実態部のコンテンツを表すのが ItemsPresenter クラスです。
ItemsControl.Items プロパティにButtonを10個投入した場合、この ItemsPresenter の箇所にButtonが10個表示されます。
ちなみに ItemsControl の実体が ItemsPresenter であり、
ContentControl の実体は ContentPresenter という、シンプルな名前です。
次に Item をどのようなパネルに展開するのかを決定するのが、 ItemsControl.ItemsPanel プロパティです。
メニューなので横に展開することができる <WrapPanel Orientation="Horizontal" />
を指定することで、要件を満たせます。
あとは右上に アイコンボタン(Designed by Alex Peattie )
中央に ViewModel を指定するとそのViewを表示することができるカスタムの DataTemplateSelector
を指定した ContentPresenter を配置すると カスタム コントロールとしての外観は完成です。
60行程度のとてもシンプルな定義です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| <ui:ViewDataTemplateSelector x:Key="templateSelector" />
<Style TargetType="{x:Type local:Breadcrumb}">
<Setter Property="Focusable" Value="False" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="Local" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Breadcrumb}">
<Grid Margin="7">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsPresenter Name="MyPresenter" Margin="20,0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="1">
<Button x:Name="HomeButton" Width="35" Height="35"
HorizontalContentAlignment="Right"
Foreground="{DynamicResource AppForeground}"
BorderBrush="Transparent"
Style="{DynamicResource EllipseButtonStyle}"
>
<Viewbox Stretch="Fill">
<Path Style="{DynamicResource PathButtonStyle}"
Data="F1 M 24.0033,56.0078L 24.0033,38.0053L 22.0031,40.0056L 19.0027,35.0049L 38.0053,20.0028L 45.0063,25.5299L 45.0063,21.753L 49.0068,21.0029L 49.0068,28.6882L 57.008,35.0049L 54.0075,40.0056L 52.0073,38.0053L 52.0073,56.0078L 24.0033,56.0078 Z M 38.0053,26.9204L 27.0038,36.005L 27.0038,53.0074L 33.0046,53.0074L 33.0046,42.006L 43.006,42.006L 43.006,53.0074L 49.0068,53.0074L 49.0068,36.005L 38.0053,26.9204 Z " />
</Viewbox>
</Button>
<Button x:Name="NewWindowButton" Width="35" Height="35"
HorizontalContentAlignment="Right"
Foreground="{DynamicResource AppForeground}"
BorderBrush="Transparent"
Style="{DynamicResource EllipseButtonStyle}"
>
<Viewbox Stretch="Fill">
<Path Style="{DynamicResource PathButtonStyle}"
Data="F1 M 44.3333,19L 57,19L 57,31.6667L 52.25,36.4167L 52.25,27.7083L 34.8333,45.125L 30.875,41.1667L 48.2917,23.75L 39.5833,23.75L 44.3333,19 Z M 19,25.3333L 42.75,25.3333L 38,30.0833L 23.75,30.0833L 23.75,52.25L 45.9167,52.25L 45.9167,38L 50.6667,33.25L 50.6667,57L 19,57L 19,25.3333 Z " />
</Viewbox>
</Button>
</StackPanel>
<ContentPresenter x:Name="MainContent" Grid.Row="1" Grid.ColumnSpan="2" Margin="10"
Content="{TemplateBinding MainContent}"
ContentTemplateSelector="{StaticResource templateSelector}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
|
ナビゲーターとしてのインターフェイス + いつものごとくアニメーションをC#のコード上で実装すれば完成です。
ポイントがいくつかあるので紹介します。
MEFの利用
Use添付プロパティを公開し、trueが設定されると自分自身をMEF経由でインスタンス化するようにしています。
設定側
アニメーション
パンくずメニューに新しくアイテムが追加されると、画面右側からButtonが飛んでくるアニメーションです。
これもシンプルで ItemsControl.Items プロパティ に Button を Add するタイミングで、 Storyboard を適用すればOKです。
NewWindowボタン
LOBアプリでは画面を見比べることもあるだろうと、NewWindowボタンを用意しました。
ボタン押下で表示しているメインコンテンツが新しいWindowsとして起動する機能です。
今は単純に新しいWindow表示していますが、どこかのタイミングでガバーフローとして
画面をスタックし、アニメーション表示する機能に差し替えたいと思います。
最後に、WeakReferenceについてです。
過去に紹介しましたが、親ViewModel が 子ViewModel を New し、それを ListCollectionView 経由で Tab 風に表示させたりしています。
ライフサイクル管理の基本は、Newした人が破棄まで担当するので、この場合は 親ViewModel が子ViewModelの破棄までを担当します。
このときに、ナビゲーターが直接子ViewModelのインスタンスへの参照を持ってしまうと、
メモリに残り続けてガベージコレクタの対象にならないようなことも十分に起こりえます。
ナビゲーターは指示されればViewModelを参照して遷移しますが、その参照は”弱い参照”である必要があります。
そこで利用するのが、WeakReferenceです。
このクラス経由で参照する場合、ガベージコレクタは参照にカウントしないので、不要なメモリリークを防ぐことができます。
あと、いくつかの基本的な機能(TextBox、DataGrid、ToggleButton系)のModern Style化と拡張を紹介できれば、アプリケーション作成に入りたいと思います。
サンプルはコチラ