はじめに
前回は XAML を記述して TreeOptionControl の外観を作成しました。
今回はコントロールの中身に着手します。
コードを記述します
using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace TreeOptionControlSample { /// <summary> /// ツリー形式のオプションコントロールを表します。 /// </summary> [TemplatePart(Name = TreeOptionControl.LabelPart, Type = typeof(Label))] [TemplatePart(Name = TreeOptionControl.TreeViewPart, Type = typeof(TreeView))] [TemplatePart(Name = TreeOptionControl.OKButtonPart, Type = typeof(Button))] [TemplatePart(Name = TreeOptionControl.CancelButtonPart, Type = typeof(Button))] [TemplatePart(Name = TreeOptionControl.ApplyButtonPart, Type = typeof(Button))] [TemplatePart(Name = TreeOptionControl.ContentPart, Type = typeof(ContentControl))] public class TreeOptionControl : ItemsControl { internal const string TreeViewPart = "TreeViewPart"; internal const string LabelPart = "LabelPart"; internal const string OKButtonPart = "OKButtonPart"; internal const string CancelButtonPart = "CancelButtonPart"; internal const string ApplyButtonPart = "ApplyButtonPart"; internal const string ContentPart = "ContentPart"; private TreeView _treeView; private ContentControl _content; public static RoutedCommand OKCommand { get; private set; } public static RoutedCommand CancelCommand { get; private set; } public ObservableCollection<Category> Categories { get; set; } public event RoutedEventHandler OK; public event RoutedEventHandler Cancel; static TreeOptionControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeOptionControl), new FrameworkPropertyMetadata(typeof(TreeOptionControl))); OKCommand = new RoutedCommand("OKCommand", typeof(TreeOptionControl)); CommandManager.RegisterClassCommandBinding(typeof(TreeOptionControl), new CommandBinding(OKCommand, OnOKCommand)); CancelCommand = new RoutedCommand("CancelCommand", typeof(TreeOptionControl)); CommandManager.RegisterClassCommandBinding(typeof(TreeOptionControl), new CommandBinding(CancelCommand, OnCancelCommand)); // Category 添付プロパティの登録 CategoryProperty = DependencyProperty.RegisterAttached( "Category", typeof(string), typeof(TreeOptionControl), new FrameworkPropertyMetadata()); } public TreeOptionControl() { Categories = new ObservableCollection<Category>(); } public override void OnApplyTemplate() { base.OnApplyTemplate(); _content = GetTemplateChild(ContentPart) as ContentControl; _treeView = GetTemplateChild(TreeViewPart) as TreeView; if (_treeView != null) { // TreeView にイベントハンドラ追加 _treeView.SelectedItemChanged += (sender, e) => { Category category = (Category)e.NewValue; if (category.Tag == null) { // ページ数が多いと初回起動に時間がかかるので、 // 項目が選択されたときにページを検索して表示している。 // 一度表示したページは Tag にキャッシュしておく。 var page = (from item in Items.Cast<UIElement>() where (string)category.Header == (string)item.GetValue(CategoryProperty) select item).FirstOrDefault(); // 対応するページが無い場合はダミーをセットする category.Tag = page != null ? page : new Grid(); } _content.Content = category.Tag; }; // TreeView にカテゴリ追加。 // TreeViewItem を継承しているのでそのまま追加できる。 foreach (Category c in Categories) { _treeView.Items.Add(c); } // 最初のページを表示 if (_treeView.HasItems) { ((TreeViewItem)_treeView.Items[0]).IsSelected = true; } } } protected virtual void OnOK(RoutedEventArgs e) { RoutedEventHandler handler = OK; if (handler != null) { handler(this, e); } } protected virtual void OnCancel(RoutedEventArgs e) { RoutedEventHandler handler = Cancel; if (handler != null) { handler(this, e); } } private static void OnOKCommand(object sender, ExecutedRoutedEventArgs e) { TreeOptionControl control = sender as TreeOptionControl; if (control != null) { control.OnOK(new RoutedEventArgs()); } } private static void OnCancelCommand(object sender, ExecutedRoutedEventArgs e) { TreeOptionControl control = sender as TreeOptionControl; if (control != null) { control.OnCancel(new RoutedEventArgs()); } } #region Category 添付プロパティ public static readonly DependencyProperty CategoryProperty; public static string GetCategory(DependencyObject target) { return (string)target.GetValue(CategoryProperty); } public static void SetCategory(DependencyObject target, string value) { target.SetValue(CategoryProperty, value); } #endregion } /// <summary> /// ツリーに表示するカテゴリを表します。 /// </summary> public class Category : TreeViewItem { } }
試しに使ってみます
<Window x:Class="TreeOptionControlSample.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TreeOptionControlSample" Title="Window1" Height="400" Width="500"> <Grid> <local:TreeOptionControl> <!--カテゴリを登録。--> <local:TreeOptionControl.Categories> <local:Category Header="クライアント"> <local:Category Header="全般1"/> <local:Category Header="全般2"/> <local:Category Header="終了処理"/> </local:Category> <local:Category Header="ツールバー"> <local:Category Header="アドレスバー"/> <local:Category Header="検索バー"/> </local:Category> </local:TreeOptionControl.Categories> <!--添付プロパティでコンテンツがどのカテゴリに対応するかを指定。--> <Canvas Background="Blue" local:TreeOptionControl.Category="クライアント"> <ComboBox Canvas.Left="50" Canvas.Top="50" Width="100"/> </Canvas> <DockPanel local:TreeOptionControl.Category="全般1" LastChildFill="True" Background="Yellow"> <Menu DockPanel.Dock="Top"> <MenuItem Header="_File"/> </Menu> <ListView DockPanel.Dock="Top"></ListView> </DockPanel> <StackPanel local:TreeOptionControl.Category="アドレスバー"> <Button Content="button1"/> <Button Content="button2"/> </StackPanel> </local:TreeOptionControl> </Grid> </Window>
添付プロパティを使ってカテゴリツリーと対応付けを行っているところがポイント。
まとめ
カテゴリを追加しておいて、添付プロパティでページとカテゴリを結びつけるというアイデアは Grid を参考にしました。
「結構使いやすいのでは?」と個人的に思っています。
ところどころ手を抜いていて、作り込みが不十分ですが…。