SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

ListItemの属性をレンダリングできるWebカスタムコントロールの作成

CheckBoxListコントロールを拡張して項目に属性を追加する

項目属性もレンダリングできるようにリストコントロールを拡張する

 ここには、実は2つのバグがあります。1つは、リストコントロールが項目属性をレンダリングしないこと、もう1つは、ListItemクラスが値をビューステートに保存しないこと(したがって、ポストバックで値が失われること)です。本稿で取り上げるのは、そのうち最初のバグ――それも、CheckBoxListにおける最初のバグ――の克服法だけです(2番目のバグについては、いずれ将来の記事で解決方法を考えます)。しかし、RadioButtonListもよく似たようなものですから、ここに示すロジック/コードは、RadioButtonListにも簡単に適用できるでしょう。DropDownListやListBoxで項目属性を設定する必要があるときは、DropDownListでの問題修正に必要なコードを取り上げたVictor Garcia Apreaニュースグループ投稿がありますから、そちらを参考にしてください。

 以降で見ていく解決方法は、リストコントロールクラスを拡張するという方法です。つまり、項目のレンダリングコードを書き換えて、属性のレンダリングを行わせるようにします(Victorがニュースグループ投稿で採用しているアプローチもこれです)。他に、Microsoftサポート技術情報の記事Q309338では、リスト上の項目ごとに属性を設定する必要があるときはリストコントロールなど使わず、HTMLコントロールを使用するという方法を提案しています。

 今回ご紹介する私のアプローチは、かなり無骨で力技に近いものです。実際のCheckBoxListクラスにはcontrolToRepeatという名前のCheckBox型パブリックメンバ変数があり、これが、CheckBoxリスト中の各項目のレンダリングに使われます。RenderItem()メソッドでは、このcontrolToRepeat変数のIDプロパティが、これからレンダリングする反復項目のカレントインデックスに設定されます。また、TextCheckedの両プロパティが対応ListItemTextプロパティとSelectedプロパティに基づいて設定され、TextAlignEnabledの両プロパティがCheckBoxListのTextAlignプロパティとEnabledプロパティに基づいて設定されます。私たちがなすべきことは、このメソッドに踏み込み、現在のListItemの各属性をcontrolToRepeatに割り当ててやることです。

 CheckBoxListのRenderItem()メソッドは、残念ながらオーバーライド不可のプライベートメソッドであるため、派生クラスでメソッドをオーバーライドするという簡単な方法はとれません。派生クラスでIRepeatInfoUserインターフェイスを実装し、IRepeatInfoUserに含まれるあらゆるメンバのコードを用意することが必要です(救いは、RenderItem()以外はすべて1行だけの簡単なメソッドであることです)。つまり、私たちのRenderItem()インスタンスでは、基本クラスのcontrolToRepeatプライベートインスタンスにアクセスできません。そのため、拡張クラスにcontrolToRepeatの独自コピーを持たせなければなりません。さらに、それに関連してもう1つ、CheckBoxListのメソッドのうち、controlToRepeatプライベートメンバを使用するすべてのメソッドのオーバーライド、という面倒な作業も発生します。これは、基本クラスではなく拡張クラスのcontrolToRepeatインスタンスを使用させるために欠かせない作業です。もっとも、この作業自体は、Reflectorから得られるコードリストのカットアンドペーストで間に合います(無骨で力技に近いアプローチだと申し上げたでしょう?)。

 ここにコード全体を掲載することは控えます。全体を見たい方は、本稿のサンプルコードをダウンロードしてください。肝心なのはRenderItem()メソッドの実装ですから、その部分だけを示しておきましょう。太字で示す部分が、元のRenderItem()実装にはなく、私が付け加えたコードです。

public void RenderItem(System.Web.UI.WebControls.ListItemType itemType,
                       int repeatIndex, RepeatInfo repeatInfo, 
                       HtmlTextWriter writer)
{
    controlToRepeat.ID = repeatIndex.ToString(
                         NumberFormatInfo.InvariantInfo);
    controlToRepeat.Text = this.Items[repeatIndex].Text;
    controlToRepeat.TextAlign = this.TextAlign;
    controlToRepeat.Checked = this.Items[repeatIndex].Selected;
    controlToRepeat.Enabled = this.Enabled;    
    
    ''controlToRepeat.Attributes.Clear();''
    ''foreach (string key in Items[repeatIndex].Attributes.Keys)''
        ''controlToRepeat.Attributes.Add(''
            ''key, Items[repeatIndex].Attributes[key]);''
        
    controlToRepeat.RenderControl(writer);
}

 ご覧のとおり、私が追加したコードではcontrolToRepeatAttributesコレクションの内容をクリアし、そこに現在のListItemの属性(あれば)を入れています。

ListItemレベル属性の使用例

 ListItemレベルの属性をあれこれ調べる気になったのは、妻の苦労がきっかけでした。妻は、データベースに直結した数個のチェックボックスの他に、[None]オプションを含んでいるCheckBoxListを作成しようとしていました。クライアント側の動作としては、[None]チェックボックスがクリックされたら、いま選択されているすべてのチェックボックスをクリアすること、同様に、[None]チェックボックスがオンの状態で別のチェックボックスがオンにされたときは、[None]チェックボックスを自動的にクリアすることを望んでいました。妻にとってどうということのない作業のはずでしたが、クライアント側のonclick属性を追加しようとしてもうまくいきませんでした。もちろん、CheckBoxListが項目属性をレンダリングしてくれないことが原因です。

 妻はオンラインでいろいろと調べ、これが既知のバグであることを知ると、私のところへやってきて、「4Guysの記事用にちょうどいいネタがあるわよ」と言ったのでした。この拡張されたCheckBoxListコントロールを使用すれば、CheckBoxList中の項目に属性を追加できます。ここで、いま述べたクライアント側動作がどのようなコードで実現されるかを、サンプルとして紹介しておきます。

HTML部分
<%@ Register TagPrefix="cc1" Namespace="skmExtendedControls" 
             Assembly="skmExtendedControls" %>

<form runat="server">
  Select the Peripherals You'd Like to Include in Your Purchase:<br />
  <cc1:skmCheckBoxList id="addOns" runat="server" />
</form>
ソースコード部分
Private Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _
                                                   Handles MyBase.Load
   If Not Page.IsPostBack Then
      'Populate CheckBoxList from database
      'Also be sure to add on "None" option
   End If

    'Add none client-side script for CheckBoxList
    AddNoneScriptToCheckBoxList(addOns, 0)
End Sub

Private Sub AddNoneScriptToCheckBoxList(ByVal cbl As CheckBoxList, _
                                            ByVal noneIndex As Integer)
    'Now, Add client-side actions
    Dim i As Integer
    For i = 0 To cbl.Items.Count - 1
        If i = noneIndex Then
            cbl.Items(i).Attributes("onclick") = _
                String.Format("skm_Uncheck('{0}', {1}, {2}, true);", _
                             cbl.ClientID, noneIndex, cbl.Items.Count)
        Else
            cbl.Items(i).Attributes("onclick") = _
                String.Format("skm_Uncheck('{0}', {1}, {2}, false);", _
                              cbl.ClientID, noneIndex, cbl.Items.Count)
        End If
    Next

    'Finally, add the skm_Uncheck client-side function
    Const SKM_UNCHECK_KEY As String = "skm_Uncheck"
    If Not Page.IsClientScriptBlockRegistered(SKM_UNCHECK_KEY) Then
        Page.RegisterClientScriptBlock(SKM_UNCHECK_KEY, _
"<script language=""JavaScript"">" & vbCrLf & _
"function skm_Uncheck(cbID, offset, total, uncheckAllButThisOne) {" & _
_vbCrLf & _
"  if (uncheckAllButThisOne)" & vbCrLf & _
"    for (var i = 0; i < total; i++) { " & vbCrLf & _
"      var cb = document.getElementById(cbID + '_' + i);" & vbCrLf & _
"      if (cb && offset != i) cb.checked = false;" & vbCrLf & _
"    }" & vbCrLf & _
"  else {" & vbCrLf & _
"    var cb = document.getElementById(cbID + '_' + offset);" & _
vbCrLf & "    if (cb) cb.checked = false;" & vbCrLf & _
"  }" & vbCrLf & _
"} </script>")
    End If
End Sub

 初めてのページ訪問では、Page_LoadイベントハンドラによってCheckBoxList中の項目に値が設定されます。一般的には、ここで何らかのデータベース呼び出しが使われます。CheckBoxListに[None]オプションを追加することを忘れないでください。これは、Itemsコレクションに新しいListItemInsert()することで実行できます。リストコントロールにプログラムでListItemを追加する方法については、『Adding a Default ListItem in a Databound Listbox in ASP.NET』を参照してください。

 次に、AddNoneScriptToCheckBoxList()が呼び出され、CheckBoxListインスタンスと、CheckBoxList内での[None]チェックボックスのオフセット(位置)が渡されます(この例では、[None]チェックボックスをリストの先頭に置いているので、オフセットは0です)。この呼び出しはIf Not Page.IsPostBack条件の外にあることに注意してください。これは、CheckBoxListの項目に対する属性設定がビューステートに対してシリアル化されていないためです。つまり、ポストバックごとに属性が失われます。したがって、ポストバックのたびにクライアント側スクリプトを追加し直さなければなりません。

 必要なクライアント側スクリプトをCheckBoxListの各項目に追加する作業は、AddNoneScriptToCheckBoxList()メソッドで行います。最初のループでは、CheckBoxList中のListItemを反復処理しながら、クライアント側のonclick属性にskm_UncheckというJavaScript関数の呼び出しを設定しています。skm_Uncheck関数は、もっと下にあるRegisterClientScriptBlock()呼び出しでページに挿入されます。skm_Uncheck関数は、引数として4つの値を受け取ります(CheckBoxListのID、チェックボックスのオフセット、CheckBoxList内のチェックボックスの総数、さらにオン状態のチェックボックスをクリアするのか、他のすべてのチェックボックスをクリアするのかを示す値)。ASP.NETページのサーバ側ソースコード部分からクライアント側スクリプトを作成する作業の詳細については、『Working with Client-Side Script』をお読みください。

終わりに

 ASP.NET 1.xのリストコントロールが項目属性をレンダリングしないのは、ASP.NETの組み込みコントロールに内在する問題であり、バグです。リストコントロールの項目属性をいじりたければ、HTMLコントロールを使うのが最も簡単な迂回策ですが、本稿で見たとおり、独自のWebコントロールクラスを作成して、属性のレンダリングを行うこともできます。

 本稿では、項目属性もレンダリングされるようにCheckBoxList Webコントロールを拡張する方法を見てきました。これで一応の問題は解決されるものの、まだ、項目属性がビューステートに保存されないという不便さが残ります。たぶん、将来の記事ではこの問題に再度触れることになるでしょう。ビューステート問題は不便ではありますが、項目のAttributesコレクションへの書き込みを、初回ページロード時だけでなく毎回行うことで迂回できます。

 この拡張版CheckBoxListコントロールを使って、CheckBoxList中の各チェックボックスでクライアント側イベントを利用するデモを作成してみました。CheckBoxList中の[None]チェックボックスをクリックしてオンにすると、自動的に他のすべてのCheckBoxListチェックボックスがクリアされるようになっています。ライブデモをぜひご覧ください。

 それでは、ハッピープログラミング!

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Scott Mitchell(Scott Mitchell)

http://www.4guysfromrolla.com/ScottMitchell.shtml

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/230 2006/04/11 19:04

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー