【WPF】ユーザーコントロールをカスタムコントロールに変える手順

C#

こんにちは、働くC#プログラマーのさんさめです。

WPFで開発を進めていくと、
コントロールを使いまわしたくなるケースが生じてきます。

  • 異なるビューで共通の見た目を使いたくなった
  • 定型文的に配置している組み合わせがある
  • 似たようなxamlを打つのがしんどくなってきた

WPFにおいてコントロールを使いまわしたくなった時、
もっとも手軽に使いまわせるようにする方法は
ユーザーコントロールとして切り出すことです。

しかし、様々な事情により、
一度ユーザーコントロールとして作ってしまったものの、
カスタムコントロールとして
作り直したくなるケースがありました。

さんさめ
さんさめ

さんさめのケースでは
大量に配置されるので処理コストが気になってきた

しかし、何も考えずにプロジェクトを作っていたため、
カスタムコントロール化するために、
かなりの手順を踏む必要がありました。

本記事では、

「既存のユーザーコントロールを
カスタムコントロールに変更したい!」

という人に向けて、ユーザーコントロールを
カスタムコントロールに変えるやり方を解説します。

スポンサーリンク

ユーザーコントロールならどうなるのか

さて、手順を解説するために、
まずはコントロールを使いまわしたくなったところから
シミュレートしていきましょう。

ここでは、
「ボタンと説明文をセットにしたコントロール」
を使いまわしたくなったとします。

ファーストステップとしては、
やはりユーザーコントロールを作るのが手早いです。

以下のようなユーザーコントロールになったとします。

<UserControl x:Class="WpfCustomControlLibrary1.ButtonWithComment"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfCustomControlLibrary1">
    <DockPanel>
        <TextBlock DockPanel.Dock="Right"
                   Text="{Binding Comment, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ButtonWithComment}}}"/>
        <Button Content="{Binding ButtonText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ButtonWithComment}}}"
                Command="{Binding Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ButtonWithComment}}}"/>
    </DockPanel>
</UserControl>

Buttonがメインで、
TextBlockが右側にあるような配置です。

コードビハインドは以下の通りです。

public partial class ButtonWithComment : UserControl
{
    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(ButtonWithComment), new PropertyMetadata(null));

    public string Comment
    {
        get { return (string)GetValue(CommentProperty); }
        set { SetValue(CommentProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Comment.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CommentProperty =
        DependencyProperty.Register("Comment", typeof(string), typeof(ButtonWithComment), new PropertyMetadata(string.Empty));

    public string ButtonText
    {
        get { return (string)GetValue(ButtonTextProperty); }
        set { SetValue(ButtonTextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ButtonText.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ButtonTextProperty =
        DependencyProperty.Register("ButtonText", typeof(string), typeof(ButtonWithComment), new PropertyMetadata(string.Empty));

    public ButtonWithComment()
    {
        InitializeComponent();
    }
}

Bindingを可能にするために、
3つほど依存プロパティを追加しています。

それ以外の処理は特に行っていません。

実際に配置してみて、
ちゃんと使いまわせているかどうか確認してみましょう。

サンプルコードを載せます。

<Window x:Class="WpfCustomControlLibrary1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfCustomControlLibrary1"
        Title="MainWindow" Height="450" Width="800">
    <DockPanel>
        <local:ButtonWithComment ButtonText="終了"
                                 Comment="保存せずに終了します"
                                 DockPanel.Dock="Bottom"
                                 HorizontalAlignment="Center"/>
        <local:ButtonWithComment ButtonText="保存"
                                 Comment="編集内容を保存します"
                                 DockPanel.Dock="Bottom"
                                 HorizontalAlignment="Center"/>
        <StackPanel>
            <TextBlock Text="Hogeプロパティ"/>
            <TextBox Text="ほげ"/>
            <TextBlock Text="Fugaプロパティ"/>
            <TextBox Text="ふが"/>
        </StackPanel>
    </DockPanel>
</Window>

実行した結果は以下のようになります。

ちゃんとコメント付きボタンが2つ表示されていますね。
(中央寄せなのでレイアウトが残念ですが)

さて、前提条件を整えたところで、
いよいよ本題です。

このコントロールを実際に
カスタムコントロールに変えていきたいと思います。

カスタムコントロールを作る準備

カスタムコントロールを作るためには、
そのコントロール自体を編集するだけでなく、
プロジェクトの設定にも変更を加える必要があります。

(※最初から『カスタムコントロールライブラリ』で
プロジェクトを作っている場合は不要です)

準備その1:ThemeInfo属性の設定

まずは、プロジェクト内にあるAssemblyInfo.csを開いてください。

…いや、そんなファイル無いけど。
まさかさっそく悩む羽目に…?

…はい、しれっと「開いてください」
と書きましたが、
.csprojが新形式仕様の場合、
AssemblyInfo.csは自動生成されないので、
まだ存在しません。

その場合は、AssemblyInfo.csを新規作成してください。
プロジェクト直下でも、
「Properties」フォルダを掘っても構いません。

旧形式のcsprojだと、
Propertiesフォルダを掘ったところに生成されるのがデフォルトですね。

そして、中身に以下のように書きます。

using System.Windows;

[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]

AssemblyInfo.csが最初からあった場合は、
ファイルの一番下などに、
ThemeInfo属性の方だけを追記してください。

(※「using System.Windows;」はおそらくすでにあると思います。
なければ足してください)

準備その2:Generic.xamlの作成

さて、次は「Generic.xaml」を作りましょう。
まず、「Themes」フォルダをプロジェクト直下に作り、
次に「Generic.xaml」というファイル(リソースディクショナリ)を
そのフォルダの中に作成します。

ソリューションエクスプローラで見た時に、
以下のような見た目になってればOKです。

そして、Generic.xamlの中には色々記述していくことになるのですが、
ひとまずは以下のように空のResourceDictionaryとしておいてください。

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

</ResourceDictionary>

さて、これでカスタムコントロールを作る準備が整いました。

ここら辺で一度ビルドしてみて、
正常にビルドが終了するかチェックしておきましょう。

ここでビルド失敗するようなら、
既に何かおかしいです。
今までの手順を見直してみてください。

では次に、既存のユーザーコントロールに手を加えていきます。

コードビハインドの変更。継承元を変えてpartialを外す

コードビハインドにおける変更は主に3点です。

  • 継承元をUserControlからControlに変更する
  • クラス宣言部分からpartialを外す
  • デフォルトスタイルが当たるようにメタデータを設定する
  • コンストラクタのInitializeComponentを削除

先ほどのコードビハインドを例に、
実際に手を加えていきましょう。

継承元をUserControlからControlに変更する

まず、UserControlではなくなるので、
UserControl継承である必要が無くなります。

とりあえずControl継承にしておきましょう。
実は、今回の例であれば、Button継承などの方が
見通しやカスタマイズ性の面で適格なのですが、
本筋から逸れてしまうので割愛します。

クラス宣言部分からpartialを外す

xamlとセットではなくなるので、
partial修飾子を削ります。

デフォルトスタイルが当たるようにメタデータを設定する

これは少しややこしいですね。
staticコンストラクタを作り、
そこでDefaultStyleKeyProperty.OverrideMetadataメソッドを呼び出します。

ほとんどおまじないみたいなものなので、
以下を参考に設定してください。

static ButtonWithComment()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(ButtonWithComment), new FrameworkPropertyMetadata(typeof(ButtonWithComment)));
}

コンストラクタのInitializeComponentを削除

カスタムコントロールではInitializeComponentは不要です。

コンストラクタごと削除してしまいましょう。

コンストラクタで何かやっていた場合は、
コンストラクタ自体は残しておいてもいいですが、
カスタムコントロールの場合は
OnApplyTemplateメソッドでやるべきかもしれませんね。

あれー…。
なんかエラーの波線出まくっているけど…?

ここまで終わってみても、
明らかにビルドが通っていないと思います。

それもそのはず、
まだ同名の.xamlが残っており、
「partial」修飾子付き同名クラスが、
プロジェクト内に存在するのです。

というわけで次は、
その.xamlを削除する工程です。
これが終われば全工程完了です。あとちょっと!

レイアウトの変更。既存xamlは削除しGeneric.xamlに移植

さて、.xamlをいきなり削除する前に、
カスタムコントロールの準備で作った
Generic.xamlを作りこんでいきます。

カスタムコントロールではGeneric.xamlに
レイアウトなどを作りこんでいきます。

というわけで、移植してみた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:WpfCustomControlLibrary1">

    <Style TargetType="{x:Type local:ButtonWithComment}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel>
                        <TextBlock DockPanel.Dock="Right"
                                   Text="{Binding Comment, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ButtonWithComment}}}"/>
                        <Button Content="{Binding ButtonText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ButtonWithComment}}}"
                                Command="{Binding Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ButtonWithComment}}}"/>
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

やっていることは、
StyleによるTemplateプロパティの上書きですね。

「ControlTemplate」タグの内部が、
移植元のxamlに書いてあった内容そのままとなっています。

最後に、元々の.xamlを削除します。
VisualStudio上で削除すると、
コードビハインド(だったもの)の.csも
巻き込まれて消えてしまう可能性があるので、
念のためエクスプローラ上で削除します。

カスタムコントロール化完了。動作確認する

さて、これで晴れてカスタムコントロール化の工程は完了しました!

さっそくビルドして実行してみましょう。

ちゃんと実行できましたし、
最初の実行結果と同じ見た目になりました。

ここまでくれば、
あとは通常のカスタムコントロールを編集する手順に
載ることができます。

よりカスタマイズ性を高めても良し、
シンプルなビジュアル構造にして
ビジュアルツリーの負荷を低減するも良し、
快適なカスタムコントロール作成ライフを満喫しましょう!

まとめ

まとめです。

  • コントロールを使いまわしたいなら、
    ユーザーコントロールかカスタムコントロール
  • 一度作ってしまったユーザーコントロールを
    カスタムコントロールに移植するのは意外と手間
  • ユーザーコントロールが
    カスタムコントロール化されるまでの工程を解説

最後までお読みいただき、ありがとうございました。

コメント

タイトルとURLをコピーしました