普通、カスタムコントロールを作成するときはソリューションエクスプローラーから新しい項目を追加しますよね? その方法で追加すると「Themes/Generic.xaml」と「カスタムコントロール名.cs」のファイルが追加されます。 これを編集していくのが普通のやり方です。
ですが、コレだとやりたい事ができないケースもあるようです。 今回は、Generic.xamlのStyleにEventSetterを追加したらx:Classが無いとダメだと怒られてしまいました。 それをキッカケに「x:Classの設定ってどう書くの?」と色々検索して試してみたらカスタムコントロールを追加するもう1つの方法が見つかりました。 この方法なら問題なくEventSetterも使えます。
その方法はこんな感じ。
- ソリューションエクスプローラーから新規「ウィンドウ」を追加。 ファイル名は「カスタムコントロール名.xaml」でよい。
- カスタムコントロール名.xamlを開き、ルート要素のタグをWindowから継承元コントロール名に変える。 ルート要素の属性からWindow用の属性(Title、Width、Heightなど)を削除。 ルート要素の子のContent(Grid)も削除。
- カスタムコントロール名.xaml.csを開き、カスタムコントロールクラスの継承元をWindowから継承元コントロール名に書きかえる。
ちょっとだけ面倒だけど簡単です。
実際にやってみるときの様子はこうなります。 例えば、ListBoxを継承したTestListBoxを作る場合は。
- ソリューションエクスプローラーから新規ウィンドウを追加。 ファイル名は「TestListBox.xaml」とする。
-
TestListBox.xamlを開き、ルート要素のタグをWindowからListBoxに変える。
ルート要素の属性からWindow用の属性(Title、Width、Height)を削除。
ルート要素の子のContent(Grid)も削除。
<ListBox x:Class="WpfApplication1.TestListBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestListBox" Height="300" Width="300"><Grid></Grid></ListBox> -
TestListBox.xaml.csを開き、TestListBoxクラスの継承元をWindowからListBoxに書きかえる。
using ~略~ namespace WpfApplication1 { public partial class TestListBox : ListBox { public TestListBox() { InitializeComponent(); } } }
これでOK。 この書き換えをした直後のビルドでエラーが出ることもあるようですが、2度続けてビルドしたらエラーは出なくなりました。 名前解決の何かでしょうか? まぁとにかく、1度エラーなしで通るようになれば後は普通に編集できます。
カスタムコントロールクラスのコンストラクタにあるInitializeComponentメソッドは「そのままでいいの?」と思ったけど大丈夫でした。 カスタムコントロールどころか、適当なテキストファイルにxamlを書いてからリネームしてもルート要素がコントロールでさえあれば自動生成されるみたいです。 自動生成されたコードはobjフォルダの「コントロール名.g.i.cs」というファイルを見れば確認できます。
あと、普通にカスタムコントロールを追加したときには、静的コンストラクタにメタデータの上書きコードが書かれていますよね?
static カスタムコントロールクラス名()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(カスタムコントロールクラス名),
new FrameworkPropertyMetadata(
typeof(カスタムコントロールクラス名)
)
);
}
コレについては勉強不足でよく分かってません。 ただ、親クラスとなるコントロールを元にした改造コントロールを作るだけなら不要のようです。
とにかくこれで土台は完成。 あとは、MainWindowを書くようにカスタムコントロールを書いて行きます。 ↓は適当な例です。
<ListBox
x:Class="WpfApplication1.TestListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
SelectionChanged="ListBox_SelectionChanged"
>
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<EventSetter Event="MouseDoubleClick" Handler="OnMouseDoubleClick"/>
<Setter Property="FontSize" Value="24"/>
</Style>
</ListBox.Resources>
<ListBox.Template>
<ControlTemplate>
<Border Name="border" BorderBrush="DarkGreen" BorderThickness="2" CornerRadius="4">
<StackPanel IsItemsHost="True" />
</Border>
</ControlTemplate>
</ListBox.Template>
</ListBox>
クラスライブラリを作りたいときは前の投稿も参照。
------------------------
2012年5月12日 追記
どうやらこの書き方でカスタムコントロールを作ると制限があるようです。 カスタムコントロールと使う側、両方のxamlでResourcesを書くと例外が発生しました。
例外が出るのはこういう書き方をした場合です。 まずはカスタムコントロール側で普通にResourcesを記述。
<TabControl
x:Class="UserControls.MyTabControl"
~その他属性~
>
<TabControl.Resources>
~リソースを書く~
</TabControl.Resources>
</TabControl>
カスタムコントロールを使う側で、カスタムコントロールのResourcesを「上書き」すると例外が発生します。
<Window x:Class="~略~"
xmlns:appCtrls="clr-namespace:UserControls"
~その他属性~
>
<appCtrls:MyTabControl>
<appCtrls:MyTabControl.Resources>
~リソースを書く~
</appCtrls:MyTabControl.Resources>
</appCtrls:MyTabControl>
</Window>
発生するのはXamlParseExceptionです。 InnerExceptionを見るとInvalidOperationException(ResourceDictionaryインスタンスを再初期化することはできません)との記述が。
もちろん、Window.Resourcesなど他のリソースには影響はありません。 ダメなのはカスタムコントロール.Resourcesの重複だけです。 カスタムコントロールのResources以外のプロパティが重複しても影響なし、使う側の値で上書きされます。
というわけでこの追加方法をとるならカスタムコントロール.xamlでResourcesは書かないようにしましょう。 リソースが必要な場合はC#のコードで読み込んだり、マージしたりする必要があります。 カスタムコントロールを使う側のResourcesを禁止にするっていう仕様もあり ... 無いか?
それにしても、普通にGeneric.xamlに書くよりファイルの整理がしやすいと思ったんですが、思わぬ落とし穴ですなぁ。
------------------------
2012年5月15日 追記

