Top > Programming > .NetFramework > WPF > Custon and UserControl > TechniqueOfAddingCustomControl
Last-modified: Tue, 23 Apr 2013 14:35:43 JST
Counter:9204 Today:1 Yesterday:7 Online:14
このエントリーをはてなブックマークに追加

カスタムコントロールをスマートに追加するテクニック

About

WPFでカスタムコントロール(CustomControl)を実装る場合には、[新しい項目の追加]からカスタムコントロールを選ぶと思います。するとソリューションの中には、外観を決定する[Themes/Generic.xaml]ファイルと、[CustomControl.cs]ファイルが生成されます。

しかしながらこの状態では何かと不都合が発生します。発生する不都合は、カスタムコントロールが多くの場合に汎用性を重視して設計されることに起因します。

この記事に掲載する問題の解決方法や実装方法がMSの提唱する実装方法から外れたものである可能性は十分にありますし、この記事のWriterである私は、プログラミングに関して高い技術力や知識を持った人間ではありません。

問題を端的に

少々問題提起が長いので端的に示します。これを読んで問題が分かる場合には解決策を読んでもらった方が早いです。

  1. Generic.xamlとCustomControl.csに分割されているのが分かりにくい。
  2. Generic.xamlにすべてのカスタムコントロールを定義するのが気に入らない。ソースが長くなり読みにくい。

1 - コンポーネントの定義と、その実動を定義するファイルが分離されている。

実際に挙動を設定したりプロパティを実装するのは、[CustomConrol.cs]ファイルの方、つまりソースコード側になります。しかしながら、実装するコントロールの外観や構成コンポーネント(ボタンやスライダ、パネルなど)は、[Themes/Generic.xaml]ファイルに定義することになります。

私はこれを問題だと感じています。なぜならば、コントロールの実装とそのコンポーネントに素早くアクセスできないからです。UserControlから独自のコントロールを実装する場合を考えてみてください。UserControlを構成するコンポーネントを定義したファイル(xaml)と、その挙動を実装するファイル(cs)は、ソリューションエクスプローラ上からすばやく確認し、行き来することができます。対応関係を間違えることもほとんどないですし、他者が実装していた場合でも素早くその関係を理解することができます。

2 - Generic.xamlの肥大化

1つのカスタムコントロールの実装だけであれば、先の項目の問題1もそれほど問題にならないでしょう。しかしながら複数のカスタムコントロールを実装する場合には、徐々に問題が大きくなります。なぜならば、通常(初期状態で)は、Generic.xamlに、そのプロジェクトに含まれるすべてのカスタムコントロールのコンポーネントを定義するようになっているからです。

5のカスタムコントロールをプロジェクトに追加したとします。するとその実装形態を確認する場合には、まず挙動が実装されるソースコードを開き、縦に長くなったxamlの中から対象のカスタムコントロールを定義する箇所を探し出さなければいけません。5つであればまだ良いでしょう。しかしながら数を増すたびに必要なコストは増えていきます。

1と2は両方そろって発生する問題です。ここでは次の項目でこれらの問題を解決します。

問題の解決

先に紹介した問題を解決する例を紹介します。ここでは次の2つによって解決します。

  1. Generix.xamlに記述するResource(カスタムコントロールの実装)は分離して記述することができる。
  2. xaml/csファイルはソリューションエクスプローラ上で階層構造にできる。

説明は、カスタムコントロールのライブラリを実装する、というシチュエーションを想定しています。

ResourceDictionaryの分割と統合

[Themes/Generic.xaml]ファイルは、カスタムコントロールの実装と利用に必要なものです。これを異なるファイル名やパスに変更することなどはできません。

普通は次のようにGeneric.xamlを記述していくことになります。

一般的なThemes/Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

…いくつかの長い長いコントロールの定義

</ResourceDictionary>

これを改善するために分割したファイル[CustomControl1.xaml]をプロジェクトに追加し、カスタムコントロールを構成するコンポーネントを定義していきます。このとき、このカスタムコントロールの処理を実装するソースコード(csファイル)はそのままです。定義方法や実装方法は従来のカスタムコントロールと同様です。つまり次のようになります。

CustomControl1.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfCustomControlLibrary">

    <Style TargetType="{x:Type local:CustomControl1}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomControl1}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>    
</ResourceDictionary>

CustomControl1.cs

namespace WpfCustomControlLibrary
{
    public class CustomControl1 : Control
    {
        static CustomControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), 
            new FrameworkPropertyMetadata(typeof(CustomControl1)));
        }
    }
}

分割して定義したカスタムコントロール(CustomControl1.xaml)を、[Themes/Generic.xaml]から参照し、統合するように記述します。次に[CustomControl2]なども追加している例を示します。この様にCustomControlを定義するxamlファイルは分割して管理することができます。

改善されたThemes/Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfCustomControlLibrary">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/WpfCustomControlLibrary;component/CustomControl1.xaml"/>
        <ResourceDictionary Source="/WpfCustomControlLibrary;component/CustomControl2.xaml"/>
…
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

重要なのは、定義方法です。まず参照するxamlファイルの先頭には必ず"component/"をつける必要があります。またあるフォルダ(ディレクトリ以下)に位置するxamlファイルを参照するバイは"component/フォルダ名/CustomControl1.xaml"となることに注意してください。

XAMLファイルとCSファイルの階層化

XAMLファイルとCSファイルが、ソリューションエクスプローラ内で階層構造になっている方が、見た目がコンパクトですっきりします。ちょうどUserControlがソリューションエクスプローラないで階層構造になっているのと同じです。そこで、CustomControlでも次のような階層構造にします。

  • CustomControl1.xaml
    • CustomControl1.xaml.cs

実際にはUserControlなどを追加して、それぞれxamlファイルの中身とソースコードの中身をCustomControlのものに書き換える必要があります。少々手間ですが、最初にしてしまえばあとは従来の開発工程と同じなので、特に苦にはならないかと思います。書き換えたあとはThemes/Generic.xamlに参照を通せばよいだけです。

このとき、csファイル側にpartialを付けない点に注意する必要があります。UserControlやxamlとついになったクラスを新規に追加した場合、そのソースコード(csファイル)にはpartialが与えられていますが、カスタムコントロールの場合にはparticalを宣言しません。