TreeOptionDialog を作る(2)

はじめに

前回は XAML を記述して TreeOptionControl の外観を作成しました。
今回はコントロールの中身に着手します。

今回作成したサンプルの実行画面がこちら

f:id:griefworker:20090416161855p:image

作成した手順は次の通りです。

コードを記述します

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 を参考にしました。
「結構使いやすいのでは?」と個人的に思っています。
ところどころ手を抜いていて、作り込みが不十分ですが…。

作成したプロジェクト

下のリンクからダウンロードできます。