はじめに
ASP.NET 1.xには、リストコントロールとして使えるWebコントロールが4つあります。
- DropDownList
- CheckBoxList
- RadioButtonList
- ListBox
いずれもSystem.Web.UI.WebControls.ListControlクラスから派生したコントロールで、任意の数のListItemインスタンスを表示できます。その限りでは、4つとも基本的に同じものと言えますが、各コントロールの本体とその関連ListItem群のレンダリング方法はそれぞれ異なっています。たとえば、CheckBoxListの場合は本体がHTMLの<table>としてレンダリングされ、表のセル内にはCheckBox Webコントロールが配置されますが、DropDownListの場合は本体がHTMLの<select>要素、各ListItemが<option>要素としてレンダリングされます。
ただ、どのコントロールにも共通する難点として、「項目属性がレンダリングされない」という事実があります。たとえば、CheckBoxListを表示し、リスト中のいくつかのCheckBoxを特定のCSSクラスで表示したいとしましょう。あるいは、RadioButtonListコントロール中の特定のRadioButtonが選択されたときに、何らかのクライアント側JavaScriptが実行されるようにしたいとします。一般的には、CheckBoxまたはRadioButton WebコントロールのAttributesコレクションを使って設定できる機能ですが、残念なことに、リストコントロールのレンダリングでは、項目属性がレンダリングされません。
本稿では、まず、これらのリストコントロールがいったいどのようにレンダリングされるのかを理解した上で、リストコントロールのListItemインスタンスの属性もレンダリングされるようにリストコントロールクラスを拡張する方法を見ていきます。最後にちょっとしたデモがありますから、これも見てください。このデモでは、CheckBoxListの中に[None]というチェックボックスオプションを用意しており、このチェックボックスをオンにすると、クライアント側スクリプトが実行されて、リスト内の他のチェックボックスが自動的にオフになります。この機能の実装方法については、続きをお読みください。
リストコントロールのレンダリング
ASP.NET 1.xでは、RadioButtonListおよびCheckBoxListのレンダリング方法は、ListBoxおよびDropDownListのレンダリング方法とは少し異なります。RadioButtonListとCheckBoxListは、ListItemのレンダリングにSystem.Web.UI.WebControls.RepeatInfoクラスを使用します。RepeatInfoクラスは、HTMLの<table>内に項目群を垂直方向または水平方向にレンダリングします。ただし、RepeatInfoクラスは外側のHTML <table>のレンダリングだけを担当するという点に注意してください。内部要素のレンダリング処理は、外から与えられるオブジェクト(IRepeatInfoUserインターフェイスを実装しているもの)によって行われます。次の図は、RepeatInfoクラスによるデータレンダリングの流れを示しています。

ご覧のとおり、RepeatInfoクラスは、RenderRepeater()メソッドに渡されるIRepeatInfoUser実装オブジェクトにレンダリングの多くの部分を代行させます。IRepeatInfoUserインターフェイスには、HasHeader、HasFooter、RepeatedItemCountなどの諸プロパティと、実際に項目のレンダリングを行うRenderItem()メソッドが定義されています。
CheckBoxListとRadioButtonListは、このIRepeatInfoUserインターフェイスを自身で実装しています。したがって、WebコントロールがRepeatInfoクラスのRenderRepeater()メソッドに引き渡すものは、自身のインスタンスに他なりません。つまり、CheckBoxListとRadioButtonListはどちらもIRepeatInfoUserインターフェイスの必要なプロパティとメソッドを実装しているということです。CheckBoxListとRadioButtonListは、RenderRepeater()メソッドを呼び出すときに自身のコピーを引き渡します。そのため、リストコントロールの個々の項目のレンダリングの際には、リストコントロール自身のRenderItem()メソッドが呼び出され、それによって適切な項目がレンダリングされます。つまり、CheckBoxListにはCheckBoxのインスタンス、RadioButtonListにはRadioButtonのインスタンスが追加されます。
IRepeatInfoUserインターフェイスを実装しているものとしては、RadioButtonListとCheckBoxListの他にあと1つ、DataListがあります。このコントロールも、内部的にRepeatInfoクラスを使ってレンダリングを行います。前記2つがCheckBoxまたはRadioButtonをレンダリングするのに対し、DataListはDataListItemをレンダリングします。また、上の図では、
RepeatInfoクラスが「適切な<table>セルをレンダリングする」(Render appropriate <table> cell)とあります。「適切な」という言葉を使ったのは、RepeatInfoクラスを使用するWebコントロールでは、1行当たりの項目数などのフォーマットオプションを、RepeatDirection、RepeatLayout、RepeatColumnsといったプロパティで指定できるためです。このプロパティをどう設定するかで、RepeatInfoクラスが当該項目のために表内に新しい行をレンダリングするかどうかが決まります。 DropDownListとListBoxのレンダリングには、RepeatInfoクラスが使用されません。それぞれの項目は<option>要素として、RenderContents()メソッドの内部で直接レンダリングされます。具体的には、Itemsプロパティが列挙され、コレクション中のListItemごとに、<option>タグが適切なテキスト属性・値属性とともにレンダリングされます。DropDownListとListBoxとの違いは、ListBoxではレンダリングされる<select>タグにmultiple属性が加えられることです。これは、エンドユーザーがリストから複数の項目を選択できることを意味します。
リストコントロールのListItemには、なぜ属性を適用できないか
リストコントロール中の項目に属性を与えようとして、ひどくがっかりした経験をお持ちの方は多いでしょう。たとえば、「Red」、「Green」、「Blue」の3項目を含むDropDownListを作成したいとします。どうせなら各項目の背景色をそれぞれ赤、緑、青にしたいと思い、次のようなコードを書いてみました。
'Assuming items in DropDownList are: 'Red, Green, and Blue (in that order) DropDownListID.Items(0).Attributes("style") = "background-color:red;" DropDownListID.Items(1).Attributes("style") = "background-color:green;" DropDownListID.Items(2).Attributes("style") = "background-color:blue;"
しかし、このページをブラウザに表示してみると、どのリスト項目の背景も白色になっているはずです。ソースを表示してみると、<option>要素にstyle属性が付加されていないことがわかります。宣言構文によって属性を指定した場合も同様で、属性は失われます。何が起こったのでしょう。
残念ながら、リストコントロールでは項目属性をレンダリングできません。これは明らかにバグであり、ニュースグループ上でもさまざまに取り上げられています。リストコントロールのソースコードをReflectorで覗いてみると、「Attributes」と見れば、それをすべてオミットするように作られていることがわかります。この理由の一部は、おそらく、リストコントロールの各インスタンスが、レンダリング前はListItemクラスで表現されていることにあるのでしょう。AttributesコレクションはSystem.Web.UI.WebControls.WebControlクラスで定義されており、自動的にビューステートに保存されます。したがって、その値はポストバックが行われても存続します。一方、ListItemクラスはWebControlから派生したクラスではなく、Attributesプロパティを持っていますが、その値はビューステートに保存されません。
