コントロールによるHTML出力
ヘルパーメソッドであるGetTableTag、OutputCategoryRow、およびOutputCheckBoxを詳しく調べてみれば、TABLEタグ、TRおよびTDタグ、それにチェックボックスフィールドタグにCSSの「クラス」属性が含まれていることにおそらく気がつくでしょう。コントロールのパブリックプロパティにおいて指定されたCSSクラスをこうしたタグのために利用します。そうすれば、CategorizedCheckBoxListコントロールの外観と印象をASPXページによって管理できます。
また、TABLE、TR、およびTDの各タグのHTMLに改行(\n)とタブ(\t)が追加されていることにも気が付いたかもしれません。こうした記号の追加は、どうしても必要なものではありませんし、かえってWebブラウザがダウンロードするHTMLのサイズを増やすことになります。しかし、結果として得られるHTMLは非常に読みやすいものになります(このページのソースをご覧になればわかります)。ただし、製品版アプリケーションでこのコントロールを実装する場合には、こうした余白文字は削除しておくのがよいでしょう。
デザイン時サポート
さて、ここまでは順調です。しかし依然として、Visual Studio .NETのページデザイナでは、このコントロールは汎用的なグレーボックスとして表示されたままです。さらに、プロパティウィンドウには、このコントロールのプロパティが1つも表示されません。このコントロールにデザイン時サポートを加えるためには、まずクラスおよびプロパティの宣言部にコード属性を追加する必要があります。
Visual Studioのデザイン時サポートを加える際には、パブリックプロパティにコード属性を設定します。
[Category("Appearance"), DefaultValue(""), Description("The CSS Class name for the table row" + "containing each item.")] public string RowCssClass {
プロパティ宣言部の上に記述した大カッコ内のコードは、このプロパティをプロパティウィンドウにどのように表示するかをVisual Studio .NETに指示しています。

Category | プロパティが現れるカテゴリを定めます。次の共通オプションがあります。 ●・ Appearance・ Behavior・ Data |
Description | プログラマがプロパティの設定を行う際の参考としてプロパティボックスの最下部に表示される説明です。 |
DefaultValue | デフォルト値を表します。 |
Browsable | プロパティをプロパティボックスに表示するかどうかを指定します。次のいずれかの値をとります。 ・ true・ false |
ご覧のとおり、プロパティウィンドウのサポートは簡単に実現できます。
しかし、コントロール自身のデザイン時サポート、つまりブラウザ上でのコントロールの描画をより正確に示すためのデザイン時サポートを追加するには、もう少し複雑な作業が必要です。そのためには、System.Web.UI.Design.ControlDesigner型から派生させた新しいクラスを作成し、CategorizedCheckBoxListクラスにDesigner属性を追加しなければなりません。Designer属性は、このコントロールのデザイン時HTMLを出力するときに特別なクラスを使用するようVisual Studioに指示します。
ControlDesigner基底クラスには、GetDesignTimeHtmlというメソッドが用意されています。このメソッドは、Visual Studio .NETによって使用されるHTMLを作成し、デザインビューのページ上にコントロールを描画します。このメソッドをオーバーライドすることによって、独自のHTMLを書き出し、コントロールの外観を自在に制御することができます。
ただし、Visual Studio .NET IDE内でのCategorizedCheckBoxListの外観と実際のWebアプリケーション上での外観を完全に一致させようとは考えないでください。そうすると、必要以上にコードが複雑になってしまうからです。しかし、何百行もコードを書かなくても、コントロールの基本的なルックアンドフィールを再現することは可能です。Microsoftお気に入りの言葉に従い、「Do more with less(最小のコストで最大の効果を得よ)」という路線で考えてみましょう。
ControlDesignerのコードを次に示します。
/// <summary> /// Provides a moderate level of fidelity /// for the CategorizedCheckBoxList control /// in the VS.net IDE. /// </summary> internal class CategorizedCheckBoxListControlDesigner : System.Web.UI.Design.ControlDesigner { /// <summary> /// Provides easy access the properties set in the IDE. /// </summary> protected CategorizedCheckBoxList ccbl; /// <summary> /// Initializes the designer /// </summary> /// <param name="component"></param> public override void Initialize(IComponent component) { // Make sure that this designer is attached to // a CategorizedCheckBoxList if(component is CategorizedCheckBoxList) { base.Initialize (component); ccbl = (CategorizedCheckBoxList)component; } }
このクラスでinternalというアクセス修飾子を使用していることに注意してください。このCategorizedCheckBoxLisControlDesignerクラスは大幅にカスタマイズされているので、CategorizedCheckBoxListクラスでのみ使用されると考えてよいでしょう。ControlDesignerをinternalとして宣言すると、同じ.NETアセンブリ内のファイルからしか利用できなくなります。
最初に、Visual Studioで設定されたプロパティにアクセスするためのCategorizedCheckBoxList変数を生成します。次に、Initializeメソッドをオーバーライドします。このデザイナを使用するコンポーネントがCategorizedCheckBoxListである場合は、そのコンポーネントをロードして、CategorizedCheckBoxListプロテクト変数を初期化します。
GetDesignTimeHtmlメソッドを実行すると、CategorizedCheckBoxListをサンプルデータと一緒に表示するためのHTMLが次のように生成されます。なお、太字のコードはCategorizedCheckBoxListから取得されるプロパティを示しています。
/// <summary> /// Writes the HTML used be VS.net to display /// the control at design-time. /// </summary> /// <returns></returns> public override string GetDesignTimeHtml() { try { // Start building the HTML StringBuilder Sb = new StringBuilder(); // Table Sb.Append("<table"); Sb.Append(" cellspacing=\""); Sb.Append(ccbl.CellSpacing.ToString()); Sb.Append("\""); Sb.Append(" cellpadding=\""); Sb.Append(ccbl.CellPadding.ToString()); Sb.Append("\""); Sb.Append(" border=\"0\">"); // Category Row Sb.Append("<tr><td colspan=\"4\""); Sb.Append(" class=\""); Sb.Append(ccbl.CategoryCssClass); Sb.Append("\""); Sb.Append(">Fruit</td></tr>"); // Item Row Sb.Append("<tr valign=\"top\">"); Sb.Append("<td><input type=\"checkbox\" " + "name=\"1\" value=\"1\" class=\""); Sb.Append(ccbl.CheckBoxCssClass); Sb.Append("\"></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\">Apples</td>"); Sb.Append("<td><input type=\"checkbox\" " + "name=\"1\" value=\"2\" class=\""); Sb.Append(ccbl.CheckBoxCssClass); Sb.Append("\"></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\">Oranges</td>"); Sb.Append("</tr>"); // Item Row Sb.Append("<tr valign=\"top\">"); Sb.Append("<td><input type=\"checkbox\" " + "name=\"1\" value=\"3\" class=\""); Sb.Append(ccbl.CheckBoxCssClass); Sb.Append("\"></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\">Tangerines</td>"); Sb.Append("<td></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\"></td>"); Sb.Append("</tr>"); // Category Row Sb.Append("<tr><td colspan=\"4\""); Sb.Append(" class=\""); Sb.Append(ccbl.CategoryCssClass); Sb.Append("\""); Sb.Append(">Vegetables</td></tr>"); // Item Row Sb.Append("<tr valign=\"top\">"); Sb.Append("<td><input type=\"checkbox\" " + "name=\"2\" value=\"1\" class=\""); Sb.Append(ccbl.CheckBoxCssClass); Sb.Append("\"></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\">Broccoli</td>"); Sb.Append("<td><input type=\"checkbox\" " + "name=\"2\" value=\"2\" class=\""); Sb.Append(ccbl.CheckBoxCssClass); Sb.Append("\"></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\">Green Beans</td>"); Sb.Append("</tr>"); // Item Row Sb.Append("<tr valign=\"top\">"); Sb.Append("<td><input type=\"checkbox\" " + "name=\"2\" value=\"3\" class=\""); Sb.Append(ccbl.CheckBoxCssClass); Sb.Append("\"></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\">Potatoes</td>"); Sb.Append("<td><input type=\"checkbox\" " + "name=\"2\" value=\"4\" class=\""); Sb.Append(ccbl.CheckBoxCssClass); Sb.Append("\"></td>"); Sb.Append("<td class=\""); Sb.Append(ccbl.TextCssClass); Sb.Append("\">Tomatoes</td>"); Sb.Append("</tr>"); // End the table Sb.Append("</table>"); return Sb.ToString(); } catch(Exception ex) { // Display the error in VS.net, in Design view return String .Concat("<h3>Error</h3>Stack Trace:<br>", ex.StackTrace); } }
ここまでくれば、後はこのControlDesignerをCategorizedCheckBoxListコントロールから利用できるように指定するだけです。そのために、もう一度コード属性を使います。
/// <summary> /// The CategorizedCheckBoxList is like a CheckBoxList, /// but with the ability to categorize the display of items. /// </summary> [Designer("UsefulControls.CategorizedCheckBoxListControlDesigner"), DefaultProperty("DataTable"), ToolboxData("<{0}:ccbl runat=server></{0}:ccbl>")] public class CategorizedCheckBoxList : System.Web.UI.WebControls.WebControl { /// <summary> /// Initializes a new instance of /// the CategorizedCheckBoxList class. /// </summary> public CategorizedCheckBoxList() { // Init the class }
以上でCategorizedCheckBoxListコントロールのコーディングは終わりです。ここから先は、このコントロールの使い方を見ていきましょう。
Webカスタムコントロールの利用
カスタムコントロールの使い方としては、ツールボックスからドラッグアンドドロップしてページに配置するのが最も簡単な方法です。作成したコントロールをツールボックスに追加するには、まずツールボックス上で右クリックして[Add/Remove Items]を選択します。

するとダイアログボックスが表示されるので、そこでローカルPCの.NET Framework Componentsフォルダを選択し、Web Control LibraryプロジェクトのDLLを指定します。ただし、DLLの場所は、プロジェクトをデバッグ構成でビルドした場合はプロジェクトフォルダの下の「bin\Debug」フォルダに、リリース構成でビルドした場合は「bin\Release」になっています。
DLLを追加すると、CategorizedCheckBoxListというコンポーネントが表示されるので選択します。
[OK]をクリックするとツールボックス上にCategorizedCheckBoxListが表示されるはずです。

ASPXページでCategorizedCheckBoxListコントロールを利用するために、このコントロールをツールボックスからVisual Studio .NETのデザインビュー内のページにドラッグアンドドロップします。コントロールをページに配置すると、表示される列数やCSSプロパティを設定できるようになります。
このコントロールのデータソースとなるDataTableは、分離コードで指定することをお勧めします。これは、用意できていないうちにCategorizedCheckBoxListからアクセスされるのを防ぐためです。一方、チェックボックスのカテゴリ、値、およびラベルを示す列の名前は、分離コードファイルとASPXページのどちらで指定してもかまいません。
それでは、この記事のダウンロードサンプルに収録されている「Default.aspx」ページの内容を見ていきましょう。Page_Loadメソッドは次のようになっています。
private void Page_Load(object sender, System.EventArgs e) { // If the page has not posted-back, // or if it has but the "Show List" checkbox is checked, // get the data for our CategorizedCheckBoxList. if(!IsPostBack || chkShowList.Checked == true) { // Get the data GetMdbData(); } else { // Hide the CategorizedCheckBoxList CategorizedCheckBoxList1.Visible = false; // Hide the "Show List" checkbox chkShowList.Visible = false; // Hide the submit button, too btnTestValues.Visible = false; } }
ここでは、コントロールが表示される場合にのみコントロール用データの取得を行っています(CheckBoxListコントロールのインスタンスを動的に生成する場合は、コントロールの表示と非表示に関係なく、データ取得を毎回行う必要があります)。
次に、GetMdbDataメソッドを呼び出し、Accessデータベースからデータを選択します。GetMdbDataメソッドのコードは次のとおりです。
protected void GetMdbData() { // Create a connetion OleDbConnection Conn = new OleDbConnection(); Conn.ConnectionString = String.Concat( "Provider=Microsoft.Jet.OleDb.4.0;data source=", Server.MapPath("SampleData.mdb")); // Build a data adapter that selects all of the columns // and rows in a saved query called qryCarModelCarMaker, // in the Access database OleDbDataAdapter Adp = new OleDbDataAdapter( "SELECT * FROM qryCarModelCarMaker", Conn); // Create an instance of our Cars typed dataset Cars TypedSampleData = new Cars(); // Use the adapter to fill the CarTable Adp.Fill(TypedSampleData, "CarTable"); // Specify the data properties for // our CategorizedCheckBoxList control CategorizedCheckBoxList1.DataTable = TypedSampleData.CarTable; CategorizedCheckBoxList1.DataTextColumn = "Model"; CategorizedCheckBoxList1.DataValueColumn = "CarModelPK"; CategorizedCheckBoxList1.DataCategoryColumn = "Make"; // Clean-up Conn.Dispose(); Adp.Dispose(); }
ここではダイナミックデータベースクエリの結果を格納した型付きのDataSetを使用していますが、CategorizedCheckBoxListコントロールは、DataTableと、および必要なデータを格納している列の名前だけを受け付けます。サンプルプロジェクトの他のページでは、型付きのデータとそうでないデータの両方をXMLファイルから読み込んでいます。
いくつかのアイテムを選択したうえで[Test Checkbox Values]ボタンをクリックすると、選択したチェックボックスの値が表示されます。このサンプルで表示されるのは、選択された車種のプライマリキーの値です。では、選択したアイテムの値の取得方法を見てみましょう。
private void btnTestValues_Click(object sender, System.EventArgs e) { // Were any checkboxes checked? if(CategorizedCheckBoxList1.Selections.Count > 0) { // Yes. Let's use a string builder to tell // the user what we find. StringBuilder Sb = new StringBuilder(); Sb.Append("The following values were selected:"); // Use an HTML un-ordered list to display the values Sb.Append("<ul>"); // Loop through the selections foreach(string check in CategorizedCheckBoxList1.Selections) { // Add this item to our HTML list Sb.Append("<li>"); Sb.Append(check); Sb.Append("</li>"); } // End the list Sb.Append("</ul>"); // Set the text of our label Label1.Text = Sb.ToString(); } else { // Use our label to tell the user // that nothing was picked. Label1.Text = "No checkboxes were selected."; } }
このメソッドを実行すると、選択したチェックボックスの値のリストが次のように表示されます。

以上でCategorizedCheckBoxListコントロールの基本的な説明はほぼ終わりですが、デフォルト選択の指定方法の説明をまだしていませんでした。このコントロールでは、ArrayListに基づくSelectionsというパブリックプロパティを参照することで、事前選択オプションを簡単に指定できます。
// If the page has not posted-back, // or if it has but the "Show List" checkbox is checked, // get the data for our CategorizedCheckBoxList. if(!IsPostBack || chkShowList.Checked == true) { // Get the data GetMdbData(); // Select all of the Audi models CategorizedCheckBoxList1.Selections.Add("37"); CategorizedCheckBoxList1.Selections.Add("38"); CategorizedCheckBoxList1.Selections.Add("39"); CategorizedCheckBoxList1.Selections.Add("40"); CategorizedCheckBoxList1.Selections.Add("41"); }
なお、このArrayListには必ず文字列値を指定するということを忘れないでください。
まとめ
.NET Frameworkには、さまざまなWebコントロールを含んだツールボックスが用意されています。カスタムコントロールを自分で開発しようとする前に、必ず標準のコントロールの利用を検討してみるべきです。標準のWebコントロールは、ここで紹介した例よりもかなり複雑なものです。上位レベルおよび下位レベルのブラウザのサポートや、豊富なイベントモデルが用意されています。しかも、こうしたWebコントロールについては、ASP.NETのリリースごとにMicrosoftによるアップグレードが期待ができます。
しかし、あなたの要求を満たすコントロールが見つからないときには、どんどん自作してください。ユーザーコントロールに比べれば複雑ですが、Webカスタムコントロールは手に負えないほど難解なものではありませんし、数多くのメリットをもたらしてくれます。あらゆるコントロールの描画に利用されるHTMLについても同じことが言えます。想い描いたアイデアをHTMLで表現する方法が理解できれば、Webカスタムコントロールを構築することができます。


