複合コントロールの作成
今回のアプリケーションでは、ユーザーがテキストを入力するテキストボックスと、それに対応する送信ボタン、およびユーザーにフィードバックを表示するラベルを用意します。今回作成するカスタムコントロールは、次のような構造のXMLファイルを参照することで、ユーザーから送信されたテキストに不適切な言葉(不適ワード)が含まれているかどうかをチェックします。
<?xml version="1.0"?> <words> <word>word root 1</word> <word>word root 2</word> </words>
サンプルを期待どおりに動作させるには、このファイルの内容をあらかじめ変更しておく必要があります。本稿で実装するバージョンでは、送信されたテキストに不適ワードが含まれていないかどうかだけを報告します。必要に応じて、この機能を拡張してください。
以降では、Textboxコントロール、Labelコントロール、Buttonコントロールという3つのASP.NETサーバーコントロールを組み合わせた複合コントロール(Composite)について説明します。ユーザーが子Buttonコントロールをクリックすると、Compositeは送信されたテキストを分析し、XMLファイル(カスタムプロパティで指定。デフォルトは「bad_words.xml」)内の不適ワードがテキスト中に含まれていないかをチェックし、カスタムイベントを発生させます。Compositeは子LabelコントロールのTextプロパティもトップレベルプロパティとして公開しています。
複合コントロールの子コントロールはカプセル化されていることに注意してください。既定では、子コントロールを親の外部から見ることはできません。ページ開発者が親のControlsコレクションを使って子コントロールにアクセスしようとしても、リテラルコントロール(サーバー側の処理を必要としない基本的なHTML要素を出力するのに使用)が介在するため、特定の子コントロールのインデックスを取得するのは困難です。
複合コントロールでは、子コントロールをプロパティとして公開するかどうかを選択できるほか、子コントロールのどのプロパティ/イベントをトップレベルのプロパティ/イベントとして公開するかを選択できます。複合コントロールが子コントロールのプロパティを公開する場合は、次の例に示すように、単純に処理を子コントロールに委譲します。
' Delegate to label, which is an instance of ' System.Web.UI.WebControls.Label Public Property Text() As String Get EnsureChildControls() Return label.Text End Get Set EnsureChildControls() label.Text = value End Set End Property
それでは、複合コントロールをインスタンス化するWebフォームのコードから見ていきましょう。
<%@ Page Language="vb" debug="true" trace="true" %> <%@ Register TagPrefix="Custom" Namespace="CustomControls" Assembly = "CustomControls" %> <html> <script language="VB" runat=server> Private Sub CheckText(sender As Object, e As CheckEventArgs) If e.Match = false Then Composite.Text = "<h2>Clean your dirty mind out!</h2>" Else Composite.Text = "Text validated OK." End If End Sub </script> <body> <h1>Anti-Swear Composite Control Example</h1><br> <form runat=server> <Custom:Composite id = "Composite" OnCheck = "CheckText" filename = "bad_words.xml" runat = server/> </form> </body> </html>
このコードでは、名前空間とアセンブリ名を指定して複合コントロールを登録しています。後で、複合コントロールを.dllにコンパイルし、アプリケーションの「bin」ディレクトリに保存します。このディレクトリは、ASP.NETが最初に検索する場所です。前述の通り、再利用性を高めるために複合コントロールのdllをGACに配置することもできます。これについては、また別の記事で説明します。
Webフォームのユーザーインターフェイス内では、次の情報を指定してカスタムコントロールをインスタンス化しています。
- 複合コントロールの
OnCheckイベント発生時に実行されるローカルサブルーチン。このサブルーチンでは、パブリックプロパティを通じて複合コントロールのラベルのテキスト値を書き換えます。このテキストの内容は、OnCheckイベントで設定される別のパブリックプロパティの値によって異なります。 - 不適ワードが定義されているXMLファイルの名前。
複合コントロールが呼び出すCheckTextサブルーチンの内容については、前述のコードを参照してください。
次は、複合コントロールの実装を見ていきましょう。今回のサンプルでは、2つのクラスをそれぞれ「composite.vb」と「checkevent.vb」という別々のVBソースファイルとして実装します。
Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Xml Imports System.Collections Namespace CustomControls Public Class Composite Inherits Control Implements INamingContainer Private _filename As String = "bad_words.xml" Private label As Label Public box1 As TextBox Public Property filename() As String Get Return _filename End Get Set _filename = value End Set End Property 'takes as input the submitted text and returns 'the sanitisied version if naughty words found, 'or a copy of the original string if not Public Function CheckString(InputString as String) as string Dim alWordList As new ArrayList dim xmlDocPath as string = mappathsecure("bad_words.xml") dim xmlReader as XmlTextreader = _ new xmlTextReader(xmlDocPath) dim element as string dim output as string dim asterisks as string = "*************************" 'load the naughty word roots into an arraylist while (xmlReader.Read()) if xmlReader.NodeType=xmlNodeType.Text then alWordList.Add(xmlReader.Value) end if end while xmlReader.Close() 'check the string, replacing naughty roots with the 'appropriate number of asterisks. For Each element in alWordList InputString=InputString.Replace(element, _ asterisks.substring(1, (element.length))) Next Return InputString End Function Public Property Text() As String Get 'This method first checks the current value of the 'ChildControlsCreated property. 'If this value is false, 'the CreateChildControls method is called. EnsureChildControls() Return label.Text End Get Set EnsureChildControls() label.Text = value End Set End Property Public Event Check As CheckEventHandler Protected Overridable Sub OnCheck(ce As CheckEventArgs) RaiseEvent Check(Me, ce) End Sub 'create the child controls of the composite control Protected Overrides Sub CreateChildControls() Controls.Add(New LiteralControl("<h3>Enter some text: ")) 'the text box Dim box1 As New Textbox() box1.Text = "" Controls.Add(box1) Controls.Add(New LiteralControl("</h3>")) 'the button Dim button1 As New Button() button1.Text = "Submit" Controls.Add(New LiteralControl("<br>")) Controls.Add(button1) 'dynamically add an event handler 'to the newly created button object AddHandler button1.Click, AddressOf Me.ButtonClicked Controls.Add(New LiteralControl("<br><br>")) label = New Label() label.Height = Unit.Pixel(50) label.Width = Unit.Pixel(500) label.Text = "" Controls.Add(label) End Sub Protected Overrides Sub OnPreRender(e As EventArgs) CType(Controls(1), TextBox).Text = "" End Sub Private Sub ButtonClicked(sender As [Object], e As EventArgs) OnCheck(New CheckEventArgs(CType(Controls(1), _ TextBox).Text,CheckString(CType(Controls(1), _ TextBox).Text))) End Sub End Class End Namespace
このコードの概要を順番に説明していきます。
- 使用するクラスに必要な名前空間をインポートし、このクラスが存在する名前空間を定義します。
- 複合クラス
Compositeを定義します。基本コントロールクラスを継承し、INamingContainerインターフェイスを実装することを指定します。これによって、ポストバックイベントを子Buttonコントロールにルーティングできます。 Compositeは、OnInitや自身のコンストラクタではなく、CreateChildControls()メソッド内で子コントロールを作成します。CreateChildControls()は、コード内で子コントロールを使用する場面でChildControlsCreatedを通じて呼び出されます。Compositeは、子ButtonコントロールのClickイベントを公開しません。代わりにClickイベントを処理し、カスタムイベントCheckを発生させます。複合コントロールに子コントロール上のイベントを処理させる場合は、CreateChildControlsの中でイベントハンドラを関連付ける必要があります。Compositeは、次のパブリックプロパティを公開します。Text――基本の値は、子LabelコントロールのTextプロパティです。Filename――不適ワードリストの読み込み元となるXMLファイル名を取得または設定できます。- メインのパブリック関数は
CheckStringです。この関数は、指定のXMLファイルから不適ワードを配列リストに読み込み、配列リスト内の1つ1つのワードを、受け取ったテキスト文字列の中で検索します。不適ワードが見つかった場合は、適当な数のアスタリスクに置き換えます。 OnPreRenderでは、子TextBoxコントロールのテキストをクリアします。ButtonClickedが実行されるタイミングは、子Buttonコントロールがクリックされて、onCheckが適切な引数と共に呼び出されたときです(新しいCheckEventArgsオブジェクトには、チェック済みテキストと未チェックのテキストがコンストラクタパラメータとして渡されます)。ButtonClickedが呼び出されると、OnCheckが親.aspxページ内のコードで処理されるイベントを発生させます。このイベントが子コントロールからコンテナへと「バブルアップ」され、コンテナオブジェクトのトップレベルイベントとして公開されます。
' CheckEvent.vb ' Contains the code for the custom event data class CheckEventArgs. ' Also defines the event handler for the Check event. Imports System Namespace CustomControls Public Class CheckEventArgs Inherits EventArgs Private _match As Boolean = False Public Sub New(string1 As String, string2 as String) If string1=string2 Then _match = True End If End Sub Public ReadOnly Property Match() As Boolean Get Return _match End Get End Property End Class Public Delegate Sub CheckEventHandler(sender As Object, _ ce As CheckEventArgs) End Namespace
基本は「composite.vb」と同じです。CheckEventArgsのコンストラクタは2つの文字列をパラメータとして受け取り、ブール値matchの値を適切に設定します。ここではイベントハンドラCheckEventHandlerも定義します。
最後に、今回のようにIDEの助けを借りずに作業を進めてきた場合は、次のようにしてコマンドラインからコードをコンパイルできます(Webアプリケーションのルート内に「bin」サブディレクトリを作成しておき、ルートから下記のコマンドを実行します)。
vbc /t:library /out:./bin/CustomControls.dll /r:System.dll/r:System.web.dll /r:System.drawing.dll /r:System.Data.dll
/r:System.xml.dll *.vb
ここで説明した内容が、実用的な小規模アプリケーションで複合コントロールを使用する際の参考になれば幸いです。ここで紹介した例は、自分の環境に合わせて変更していただいてかまいません。今後記事で取り上げてほしい内容や、私自身または私の会社が興味を持ちそうなプロジェクトの可能性の問い合わせなど、ご意見ご要望がありましたらぜひお寄せください。
参考資料
- .NET Framework SDKドキュメンテーション
- 『ASP.NET: Tips, Tutorials, and Code』 Stephen Walther・Doug Seven・Donny Mack・Chris Payne・Billy Anders・Adam Nathan・Dan Wahlin 著、Scott Mitchell 編集、Sams、2001年8月
- 『Professional ASP.NET 1.0』 Richard Anderson・Brian Francis・Alex Homer・Ron Howard・David Sussman・Karli Watson 著、Wrox Pr Inc、2002年2月
- 各種オンラインリソース

/r:System.web.dll /r:System.drawing.dll /r:System.Data.dll