連載:Microsoft AJAX Library&JavaScriptプログラミング

第3回 MS AJAX LibでAJAX対応コントロールを開発しよう(前編)

山田 祥寛(http://www.wings.msn.to/
2007/12/21

[3]エクステンダ・クラスを編集する

 最初に、エクステンダ・クラスから編集を行っていくことにしよう。前述したように、エクステンダ・クラスはExtenderコントロールのサーバ側での挙動――主にサーバ・コントロールとして設定可能なプロパティ情報を定義するためのクラスだ。

 具体的なコードを以下に示す。最低限の骨組みとなる部分は自動的に生成されているはずなので、自分で追記しなければならないのは、リストの太字の部分だけである。

using System;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.ComponentModel;
using System.ComponentModel.Design;
using AjaxControlToolkit;

[assembly: System.Web.UI.WebResource(
  "MyAjaxLibCs.DialogButtonBehavior.js", "text/javascript")]

namespace MyAjaxLibCs {

  [Designer(typeof(DialogButtonDesigner))]
  [ClientScriptResource("MyAjaxLibCs.DialogButtonBehavior",
                        "MyAjaxLibCs.DialogButtonBehavior.js")]

  [TargetControlType(typeof(IButtonControl))]
  public class DialogButtonExtender : ExtenderControlBase {

    // Messageプロパティを定義
    [ExtenderControlProperty]
    [DefaultValue("")]
    public string Message {
      get {
        return GetPropertyValue("Message", "");
      }
      set {
        SetPropertyValue("Message", value);
      }
    }
  }
}
Imports System
Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports AjaxControlToolkit

#Region "Assembly Resource Attribute"
<Assembly: System.Web.UI.WebResource(
  "MyAjaxLib.DialogButtonBehavior.js", "text/javascript")>

#End Region

Namespace MyAjaxLib

  <Designer(GetType(DialogButtonDesigner))> _
  <ClientScriptResource("MyAjaxLib.DialogButtonBehavior", _
                        "MyAjaxLib.DialogButtonBehavior.js")> _

  <TargetControlType(GetType(IButtonControl))> _
  Public Class DialogButtonExtender
    Inherits ExtenderControlBase

    ' Messageプロパティを定義
    <ExtenderControlProperty()> _
    <DefaultValue("")> _
    Public Property Message() As String
      Get
        Return GetPropertyValue("Message", "")
      End Get
      Set(ByVal value As String)
        SetPropertyValue("Message", value)
      End Set
    End Property

  End Class

End Namespace
リスト3 DialogButtonコントロールのサーバ側機能を定義するエクステンダ・クラス(上:DialogButtonExtender.cs、下:DialogButtonExtender.vb)
 

 いくつか見慣れない属性が宣言されていることを除いては、ごくシンプルなクラス定義なので、クラス全体を見渡すのはさほど難しいことではないだろう。ここでは、Visual Studioによって自動生成された部分も含めて、エクステンダ・クラス固有の構文にフォーカスして解説を進めるものとする。

エクステンダ・クラスはExtenderControlBaseクラスを継承する

 ExtenderControlBaseクラス(AjaxControlToolkit名前空間)は、エクステンダ・クラスの動作に最低限必要な以下の機能を提供するものだ。

  • ScriptManager(ToolkitScriptManager)コントロールと連動したクライアントサイド・スクリプトの管理
  • クライアントサイド(Behaviorオブジェクト)とのプロパティ値の受け渡し
  • ターゲット要素を特定するTargetControlIDプロパティを公開*4
*4 厳密には、TargetControlIDプロパティを公開しているのは、ExtenderControlBaseクラスの基底クラスであるExtenderControlクラス(System.UI.Web名前空間)。

 ExtenderControlBaseクラスを継承することで、JavaScriptとの連携を開発者がほとんど意識することなく、コーディングできるようになる。

 ちなみに、ExtenderControlBaseクラスを使わずに(Control Toolkitに依存せずに)コントロールの開発を行うことも可能ではある。ややコードは複雑になるが、その方法について興味のある方は、別記事「ASP.NET AJAXを使いこなす」も併せて参照いただくとよいだろう。

エクステンダ・クラスでよく利用する数々の属性宣言

 エクステンダ・クラスでは、Extenderコントロールの動作に必要な関連クラスやリソースとの関連付けのほとんどを属性でもって行っている。エクステンダ・クラスの役割は、これら属性宣言を束ねることにあるといってもよいだろう。

 以下の表7に、エクステンダ・クラスで利用可能な主な属性をまとめておこう。先のリスト3を見ても分かるように、最低限必要な属性はデフォルトで自動的に付与されているので、殊更に属性の存在を意識しなくてもそれなりのコントロールはできてしまう。が、今後、より高度なコントロールを開発するときのためにも、これらの意味を理解しておくことは無駄ではないはずだ。

 なお、表7内のレベルとは、属性を付与できるコード内要素を表すものとする(例えば、クラス・レベルの属性はクラス宣言の直前で指定する必要がある)。

レベル 属性 宣言内容
アセンブリ System.Web.UI.WebResource(path, mime) アセンブリに組み込むリソースのパスとコンテンツ・タイプ
クラス AjaxControlToolkit.ClientScriptResource(type, path) 利用するBehaviorオブジェクトの型とパス
AjaxControlToolkit.ClientCssResource(path) 利用するCSSスタイルシートのパス
AjaxControlToolkit.RequiredScript(type [,index]) インクルードする外部スクリプト
System.ComponentModel.Designer(type) デザイナ・クラスの型
System.Drawing.ToolboxBitmap(type, path) ツールボックス上に表示するアイコン画像のパス
System.Web.UI.TargetControlType(type) ターゲット要素の型
プロパティ AjaxControlToolkit.ClientPropertyName(name) Behaviorオブジェクトでのプロパティ名(省略時はサーバ側と同名)
AjaxControlToolkit.ExtenderControlProperty() Behaviorオブジェクトに関連付けるか(クライアントサイドに公開するか)
AjaxControlToolkit.RequiredProperty() 必須プロパティか
System.ComponentModel.DefaultValue(default) デフォルト値
表7 エクステンダ・クラスで利用可能な主な属性

 属性を記述する際に注意していただきたいのは、パス(それぞれの属性の引数path)の指定方法だ。アセンブリ内のパスを指定するに際しては、「プロジェクト名.フォルダ名.ファイル名」の形式で指定する必要がある。

 また、本サンプルで唯一変更しているTargetControlType属性についても要注目だ。この属性は、ターゲット要素として関連付けることのできるコントロールの型を制限するもので、デフォルトはSystem.Web.UI.Controlクラスである。Controlクラスはすべてのサーバ・コントロールの基底クラスであるので、デフォルトでExtenderコントロールはすべてのサーバ・コントロールをターゲットとして指定できることを意味する。

 しかし、本サンプルで扱うDialogButtonコントロールのように、最初からボタン系コントロールだけに適用したいなど、ターゲットが想定されているケースも多くあるはずだ。そこでここでは、TargetControlType属性にIButtonControlインターフェイス(System.Web.UI名前空間)を指定することで、Extenderコントロールを適用できるコントロールをボタン系コントロール(Button、LinkButton、ImageButton)に限定することができる。

プロパティ値の取得/設定を行うのはGetPropertyXxxxxValue/SetPropertyXxxxxValueメソッドの役割

 前述したExtenderControlBaseクラスは、プロパティ値を設定/取得するためのGetPropertyXxxxxValue/SetPropertyXxxxxValueメソッドを提供している。これらメソッドでは、プロパティ値をBehaviorオブジェクト(JavaScript)に受け渡しするのに必要な処理を内部的に行っており、クライアント側に公開するプロパティを読み書きする場合には、必ずこれらのメソッドを介して行わなければならない。

 GetPropertyXxxxxValue/SetPropertyXxxxxValueメソッドは、それぞれ扱うプロパティのデータ型に応じて、以下のようなものが用意されている。

メソッド名 データ型
GetPropertyValue/SetPropertyValue 任意のデータ型
GetPropertyBoolValue/SetPropertyBoolValue ブール型
GetPropertyIntValue/SetPropertyIntValue 整数型
GetPropertyStringValue/SetPropertyStringValue 文字列型
表8 ExtenderControlBaseクラスが公開するGetPropertyXxxxxValue/SetPropertyXxxxxValueメソッド

[4]Behaviorオブジェクトを定義する

 サーバ側の挙動を定義するエクステンダ・クラスが定義できたところで、次にクライアント側の挙動を定義するBehaviorオブジェクトを定義してみよう。まずは、具体的なコードを以下に示す。

// MyAjaxLib名前空間を宣言
Type.registerNamespace("MyAjaxLib");

// MyAjaxLib.DialogButtonBehaviorクラスのコンストラクタを定義
MyAjaxLib.DialogButtonBehavior = function(element) {

  // 基底クラスのコンストラクタをコール
  MyAjaxLib.DialogButtonBehavior.initializeBase(this, [element]);

  // Messageプロパティの値を格納するための
  // プライベート変数_Messageを宣言
  this._Message = '';
}

// MyAjaxLib.DialogButtonBehaviorクラスのメンバを宣言
MyAjaxLib.DialogButtonBehavior.prototype = {

  // コンポーネントを初期化するための
  // initializeメソッドをオーバーライド
  initialize : function() {

    // 基底クラスSys.Componentのinitializeメソッドをコール
    MyAjaxLib.DialogButtonBehavior.callBaseMethod(this, 'initialize');

    // ターゲット要素にclickイベント・ハンドラ(_onClick)を適用
    $addHandler(
      this.get_element(),
      'click',
      Function.createDelegate(this, this._onClick)
    );
  },

  // Messageプロパティにアクセスするためのアクセサ・メソッドを定義
  get_Message : function() {
    return this._message;
  },

  set_Message : function(value) {

    // 設定値valueが元の値と異なる場合のみ、値をセット
    if (this._message !== value) {
      this._message = value;

      // Messageプロパティが変更されたことを通知
      this.raisePropertyChanged('Message');
    }
  },

  // Closedイベントを定義
  add_Closed : function(handler) {
    this.get_events().addHandler('Closed', handler);
  },

  remove_Closed : function(handler) {
    this.get_events().removeHandler('Closed', handler);
  },

  // ターゲット要素がクリックされたときの実処理を定義
  _onClick : function(e) {

    // Messageプロパティの値をダイアログ表示
    window.alert(this._message);

    // Closedイベント・ハンドラを取得し、
    // 存在する場合にのみハンドラを呼び出し
    var h = this.get_events().getHandler('Closed');
    if (h) {
      h(this, Sys.EventArgs.Empty);
    }
  }
};

// Sys.UI.Behaviorクラスを継承する
// MyAjaxLib.DialogButtonBehaviorクラスを登録
MyAjaxLib.DialogButtonBehavior.registerClass(
  'MyAjaxLib.DialogButtonBehavior',
  AjaxControlToolkit.BehaviorBase);
リスト4 DialogButtonコントロールのクライアント側の挙動を定義するBehaviorオブジェクト(DialogButtonBehavior.js)

 本来、Extenderコントロールにおいて、Behaviorオブジェクトは処理の中心を担うものであるのだが、今回は先ほど作成したDialogButtonBehavior.js(リスト1)をほとんどそのまま使えてしまうので、説明すべきポイントはほとんどない。

 ここでは、以下の3つのポイントに絞って解説しておくことにしよう。

AjaxControlToolkit.BehaviorBaseを継承すること

 AjaxControlToolkit.BehaviorBaseクラスは、Sys.UI.Behaviorクラスを継承する派生クラスで、エクステンダ・クラスとのプロパティ値の受け渡しをはじめ、Behaviorオブジェクトの挙動に最低限必要な機能を提供するものだ。Extenderコントロールを開発する場合には、(Sys.UI.Behaviorクラスではなく)AjaxControlToolkit.BehaviorBaseクラスを継承するようにすることで、サーバ側との連携をほとんど意識することなく、コードを記述できる(実際、前述のリスト1とこのリスト4を比べてみれば、実に最後の1行――クラス宣言の部分にしか違いがないことが分かるはずだ)。

エクステンダ・クラスのプロパティと対応していること

 エクステンダ・クラスで定義されたプロパティをBehaviorオブジェクトで利用するには、Behaviorオブジェクトの側で同名のプロパティ(正しくは、対応するアクセサ・メソッド)を定義する必要がある。つまり、ここではMessageプロパティの値を受け取るために、get_Message/set_Messageメソッドを定義しているというわけだ。この対応関係が正しく取れていない場合には、プロパティ値がBehaviorオブジェクトに渡されないので要注意。

 ちなみに、エクステンダ・クラスとBehaviorオブジェクトとで異なるプロパティ名を対応させたい場合には、エクステンダ・クラスの側でClientPropertyName属性を指定しておく必要がある(例えば、命名規則の統一性を考えれば、サーバ側では大文字で始まるMessageプロパティを、クライアント側では小文字で始まるmessageプロパティを、それぞれ定義したいというケースはままあるだろう)。

[ClientPropertyName("message")]
<ClientPropertyName("message")>
ClientPropertyName属性の記述例(上:C#、下:VB)

■埋め込みリソースとして定義すること

 Behaviorオブジェクト(JavaScript)だけではなく、CSSスタイルシートや画像などのリソースは、デフォルトではアセンブリには組み込まれないので要注意*5。これらリソースをアセンブリに組み込みたい場合には、ソリューション・エクスプローラから対象のリソース(ここではDialogButtonBehavior.js)を選択したうえで、プロパティ・ウィンドウから[ビルドアクション]として「埋め込まれたリソース」を選択すればよい。


図7 アセンブリへのリソース埋め込みのための設定(プロパティ・ウィンドウ)
CSSスタイルシートや画像などのリソースをアセンブリに組み込む場合には、[ビルドアクション]として[埋め込まれたリソース]を選択する。

*5 ただし、プロジェクト作成時に自動生成された「.js」ファイルだけはデフォルトで組み込みの設定になっている。


 INDEX
  Microsoft AJAX Library&JavaScriptプログラミング
  第3回 MS AJAX LibでAJAX対応コントロールを開発しよう(前編)
    1.コンポーネント開発のための基本クラス/シンプルなコンポーネント
    2. MyAjaxLib.DialogButtonBehaviorを定義する/Webフォームから利用する
    3.Control Toolkitを利用したサーバ連携コントロールの開発
  4.エクステンダ・クラスを編集する/Behaviorオブジェクトを定義する
    5.Extenderコントロールを利用しよう
 
インデックス・ページヘ  「Microsoft AJAX Library&JavaScriptプログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間