普通、カスタムコントロールを作成するときはソリューションエクスプローラーから新しい項目を追加しますよね? その方法で追加すると「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日 追記