気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

私は「Visual Web Developer 2008」を使用して「Silverlight」の開発を行っているのですが、
1つ非常に不便に感じることがあります。

それは、作成したUserControlの親を変更したり、UserControlにインタフェースを実装させたりすることが
できない点です。

Visual StudioでUserControlを追加すると、「xxxx.xaml」ファイルと「xxxx.xaml.cs」ファイルが自動生成されます。
そして、実はこの他に「obj」ディレクトリの下に「****.g.cs」というファイルが作成されます。
(ソリューションエクスプローラーの上部のボタン「すべてのファイルを表示」をクリックすると
ソリューションエクスプローラーに表示されるようになります。)

「****.g.cs」は、UserControlの部分クラス定義ファイルです。自動生成された「xxxx.xaml.cs」ファイルのコンストラクタを見ると、
「InitializeComponent()」メソッドがコールされていますよね。これは「****.g.cs」の中に定義されているメソッドで
XAMLファイルを読み込んで、ツリー要素に該当するコンポーネントを作成する処理を行っています。

コード例)

[Page.xaml.cs]
  public Page()
  {
      InitializeComponent();
  }

[Page.g.cs]
  internal System.Windows.Controls.Grid LayoutRoot = null;
  private bool _contentLoaded;

  public void InitializeComponent() {
      if (_contentLoaded) {
          return;
      }

      _contentLoaded = true;
      //解説:LoadComponent()はXAML内で定義されているツリー要素のインスタンスを生成して、UserControlに追加します
      System.Windows.Application.LoadComponent(this, new System.Uri("/GuestBook;component/Page.xaml", System.UriKind.Relative));

      this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
  }

「x:Name」属性が指定された要素がコードの中から利用できるように、インスタンス変数として自動的に定義され、
FindName()メソッドにより該当するインスタンスが代入されています。

「****.g.cs」のおかげで、面倒な処理の記述をする必要が無くなっている反面一点問題があります。
例えば、UserControlを継承して作成したクラス「BasicControl」を拡張して、新しいUserControlを作成しようとした場合、
クラス宣言をする際、「public partial class ChildControl : BasicControl」のように記述します。

しかし、これが対応する自動生成クラス「xxxx.g.cs」の中では依然、UserControlを継承しているため、
ビルド時にコンパイルエラーが発生してしまいます。

「xxxx.g.cs」を直接編集して、その場でコンパイルを通しても、XAMLを編集すると再作成されて、元に戻って
しまうので、根本的な解決にはなりません。

そこで、部分クラスに記述されている処理を「xxxx.xaml.cs」に移し、部分クラスが自動生成されないようにしてみました。

まず、XAMLファイルのプロパティウィンドウで「カスタムツール」の値を空白に、「ビルドアクション」を「Resource」に変更します。
こうすることで、「****.g.cs」が自動生成されなくなります。既に作成されたファイルは削除します。



そして「xxxx.xaml.cs」ファイル内にInitializeComponent()を定義し、以下の処理を記述しました。

[Page.xaml.cs」

  private System.Windows.Controls.Grid LayoutRoot = null;

  private void InitializeComponent()
  {
      System.Windows.Application.LoadComponent(this, new System.Uri("/Kronos;component/Page.xaml", System.UriKind.Relative));
      this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
  }

クラス宣言から「partial」を消すこともお忘れなく。
そして、継承先クラスを「BasicControl」に変更し完了です。
もちろんコンパイルもとおりますし、XAMLファイルをデザイナで編集することも可能です。
ただ一点、「x:Name」属性を持つ要素をXAMLファイルに追加した場合は、
それに該当するインスタンス変数を宣言し、FindName()を使ってインスタンスを設定する必要がありますので
ご注意ください。

たくさんの画面を持つ業務システムを作成する場合、「ポリモーフィズム」を活用するため、
抽象クラスやインターフェースを使用するケースが考えられます。

そんな場合、この方法がお役に立つかと思います。

一方で、Visual Studioには、この点を考慮して欲しいです。

コメント

初めまして。
実はこの事についてずっと悩んでいました。
本当に助かりました。ありがとうございます。
楽になるのはいいですけど、もう少し自由度を考えて欲しいですよね。

Re: タイトルなし

たろみーさん、こんにちは。
mykonosです。コメントありがとうございました!

記事が役に立ってうれしいです。

私も、これじゃオブジェクト指向の意味がないじゃんと思ってしまいました。
Visual Studioに少し工夫をしてほしいですね。

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://csfun.blog49.fc2.com/tb.php/42-c4e848c0