次の方法で共有


コンテンツ ページのコントロール ID の名前付け (VB)

作成者: Scott Mitchell

PDF のダウンロード

ContentPlaceHolder コントロールが名前付けコンテナーとして機能することで、プログラムによるコントロールの操作が困難になる方法を示します (FindControl を使用)。 この問題と回避策を確認します。 また、結果の ClientID 値にプログラムでアクセスする方法についても説明します。

はじめに

すべての ASP.NET サーバー コントロールには、コントロールを一意に識別する ID プロパティが含まれており、これは分離コード クラスでプログラムによってコントロールにアクセスする手段です。 同様に、HTML ドキュメント内の要素には、要素を一意に識別する id 属性を含めることができます。これらの id 値は、特定の HTML 要素をプログラムで参照するためにクライアント側スクリプトでよく使用されます。 これを考慮すると、ASP.NET サーバー コントロールが HTML にレンダリングされるときに、その ID 値がレンダリングされた HTML 要素の id 値として使用されると想定できます。 特定の状況では、1 つの ID 値を持つ単一のコントロールがレンダリングされたマークアップに複数回表示される可能性があるため、これは必ずしも当てはまるわけではありません。 ProductNameID 値を持つ Label Web コントロールの TemplateField を含む GridView コントロールについて考えてみましょう。 実行時に GridView がデータ ソースにバインドされると、この Label は GridView 行ごとに 1 回繰り返されます。 レンダリングされる各 Label には、一意の id 値が必要です。

このようなシナリオを処理するために、ASP.NET では、特定のコントロールを名前付けコンテナーとして示すことができます。 名前付けコンテナーは、新しい ID 名前空間として機能します。 名前付けコンテナー内に表示されるすべてのサーバー コントロールのレンダリング id 値には、名前付けコンテナー コントロールのプレフィックスとして ID が付けられます。 たとえば、GridView クラスと GridViewRow クラスはどちらも名前付けコンテナーです。 その結果、IDProductName を持つ GridView TemplateField で定義された Label コントロールには、レンダリングされた idGridViewID_GridViewRowID_ProductName が付けられます。 GridViewRowID は GridView 行ごとに一意であるため、結果として得られる id の値は一意です。

Note

INamingContainerインターフェイス は、特定の ASP.NET サーバー コントロールが名前付けコンテナーとして機能する必要があることを示すために使用されます。 INamingContainer インターフェイスは、サーバー コントロールが実装する必要があるメソッドをスペル アウトしません。それらは代わりにマーカーとして使用されます。 レンダリングされたマークアップを生成する際に、コントロールがこのインターフェイスを実装している場合、ASP.NET エンジンはその子孫のレンダリングされた id 属性値の前に自動的に ID 値を付けます。 このプロセスについては、手順 2 で詳しく説明します。

名前付けコンテナーは、レンダリングされる id 属性値を変更するだけでなく、ASP.NET ページの分離コード クラスからコントロールをプログラムで参照する方法にも影響します。 この FindControl("controlID") メソッドは、通常プログラムで Web コントロールを参照するために使用されます。 ただし、FindControl は名前付けコンテナーには浸透しません。 そのため、この Page.FindControl メソッドを直接使用して GridView またはその他の名前付けコンテナー内のコントロールを参照することはできません。

お気づきかもしれませんが、マスター ページと ContentPlaceHolder はどちらも名前付けコンテナーとして実装されています。 このチュートリアルでは、マスター ページが HTML 要素 id の値に与える影響と、FindControl を使ってコンテンツ ページ内の Web コントロールをプログラムで参照する方法について説明します。

手順 1: 新しい ASP.NET ページを追加する

このチュートリアルで説明する概念を示すために、Web サイトに新しい ASP.NET ページを追加しましょう。 ルート フォルダーに "IDIssues.aspx" という名前を付けた新しいコンテンツ ページを作成し、それを Site.master マスター ページにバインドします。

Add the Content Page IDIssues.aspx to the Root Folder

図 01: ルート フォルダーにコンテンツ ページ IDIssues.aspx を追加する

Visual Studio では、マスター ページの 4 つの ContentPlaceHolders ごとにコンテンツ コントロールが自動的に作成されます。 「複数の ContentPlaceHolders と既定のコンテンツ」のチュートリアルで説明したように、コンテンツ コントロールが存在しない場合は、マスター ページの既定の ContentPlaceHolder コンテンツが代わりに出力されます。 ContentPlaceHolder QuickLoginUI および LeftColumnContent にはこのページに適した既定のマークアップが含まれているため、対応するコンテンツ コントロールを IDIssues.aspx から削除します。 この時点で、コンテンツ ページの宣言型マークアップは次のようになります。

<%@ Page Language="VB" MasterPageFile="~/Site.master" AutoEventWireup="false" CodeFile="IDIssues.aspx.vb" Inherits="IDIssues" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server">
</asp:Content>

マスター ページでタイトル、メタ タグ、その他の HTML ヘッダーを指定する」のチュートリアルでは、ページのタイトルが明示的に設定されていない場合に自動で構成するカスタム ベース ページ クラス (BasePage) を作成しました。 IDIssues.aspx ページがこの機能を使用するには、ページの分離コード クラスが (System.Web.UI.Page ではなく) BasePage クラスから派生している必要があります。 分離コード クラスの定義を次のように変更します。

Partial Class IDIssues
 Inherits BasePage

End Class

最後に、この新しいレッスンのエントリを含むように Web.sitemap ファイルを更新します。 <siteMapNode> 要素を追加し、その title 属性と url 属性をそれぞれ "Control ID Naming Issues" (コントロール ID の名前付けの問題) と ~/IDIssues.aspx に設定します。 この追加を行った後は、Web.sitemap ファイルのマークアップは次のようになります。

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
 <siteMapNode url="~/Default.aspx" title="Home">
 <siteMapNode url="~/About.aspx" title="About the Author" />
 <siteMapNode url="~/MultipleContentPlaceHolders.aspx" title="Using Multiple ContentPlaceHolder Controls" />
 <siteMapNode url="~/Admin/Default.aspx" title="Rebasing URLs" />
 <siteMapNode url="~/IDIssues.aspx" title="Control ID Naming Issues" />
 </siteMapNode>
</siteMap>

図 2 に示すように、Web.sitemap の新しいサイト マップ エントリは、左側の列の [LESSONS] (レッスン) セクションにすぐに反映されます。

The Lessons Section Now Includes a Link to

図 02: レッスン セクションに "Control ID Naming Issues" (コントロール ID の名前付けの問題) へのリンクが含まれた

手順 2: レンダリングされた ID の変更を調べる

ASP.NET エンジンがサーバー コントロールのレンダリングされた id 値に加える変更について理解を深めるために、いくつかの Web コントロールを IDIssues.aspx ページに追加し、ブラウザーに送信されたレンダリングされたマークアップを表示してみましょう。 具体的には、"Please enter your age." (年齢を入力してください) というテキストの後に TextBox Web コントロールを入力します。 ページの下に Button Web コントロールと Label Web コントロールを追加します。 TextBox のID および Columns プロパティをそれぞれ Age と 3 に設定します。 Button の Text および ID プロパティを [送信] と SubmitButton に設定します。 Label の Text プロパティをクリアし、IDResults に設定します。

この時点で、コンテンツ コントロールの宣言型マークアップは次のようになります。

<p>
 Please enter your age:
 <asp:TextBox ID="Age" Columns="3" runat="server"></asp:TextBox>
</p>
<p>
 <asp:Button ID="SubmitButton" runat="server" Text="Submit" />
</p>
<p>
 <asp:Label ID="Results" runat="server"></asp:Label>
</p>

図 3 は、Visual Studio のデザイナーで表示したときのページを示しています。

The Page Includes Three Web Controls: a TextBox, Button, and Label

図 03: Page には、TextBox、Button、Label の 3 つの Web コントロールが含まれる (クリックしてフルサイズの画像を表示)

ブラウザーからページにアクセスし、HTML ソースを表示します。 次のマークアップが示すように、TextBox、Button、および Label Web コントロールの HTML 要素の id 値は、Web コントロールの ID 値とページ内の名前付けコンテナーの ID 値の組み合わせです。

<p>
 Please enter your age:
 <input name="ctl00$MainContent$Age" type="text" size="3" id="ctl00_MainContent_Age" />
</p>
<p>

 <input type="submit" name="ctl00$MainContent$SubmitButton" value="Submit" id="ctl00_MainContent_SubmitButton" />
</p>
<p>
 <span id="ctl00_MainContent_Results"></span>
</p>

このチュートリアルで前述したように、マスター ページとその ContentPlaceHolders の両方が名前付けコンテナーとして機能します。 したがって、両方とも、入れ子になったコントロールのレンダリングされた ID 値を提供します。 TextBox の id 属性 ctl00_MainContent_Age を例に挙げます。 TextBox コントロールの ID 値が Age であることを思い出してください。 このプレフィックスには、ContentPlaceHolder コントロールの IDMainContent が付いています。 さらに、この値にはマスター ページの IDctl00 がプレフィックスとして付けられます。 正味の影響は、マスター ページの ID 値、ContentPlaceHolder コントロール、および TextBox 自体で構成される id 属性です。

この動作を図 4 に示します。 Age TextBox のレンダリング id を決定するには、TextBox コントロールの IDAge から始めます。 次に、コントロール階層を上に進めます。 各名前付けコンテナー (桃色のノード) で、現在のレンダリング id に名前付けコンテナー id をプレフィックスとして付けます。

The Rendered id Attributes are Based On the ID Values of the Naming Containers

図 04: レンダリングされる id 属性は、名前付けコンテナーの ID 値に基づく

Note

ここで説明したように、レンダリングされる id 属性の ctl00 部分はマスター ページの ID 値を構成しますが、この ID 値がどのように発生したか疑問に思うかもしれません。 マスター ページまたはコンテンツ ページのどこにも指定しませんでした。 ASP.NET ページのほとんどのサーバー コントロールは、ページの宣言型マークアップを通じて明示的に追加されます。 MainContent ContentPlaceHolder コントロールは、Site.master のマークアップで明示的に指定されました。Age TextBox は IDIssues.aspx のマークアップで定義されました。 これらの種類のコントロールの ID 値は、プロパティ ウィンドウまたは宣言構文から指定できます。 マスター ページ自体などの他のコントロールは、宣言型マークアップでは定義されません。 したがって、それらの ID 値はユーザーに対して自動的に生成される必要があります。 ASP.NET エンジンは、ID が明示的に設定されていないコントロールの実行時に ID 値を設定します。 名前付けパターン ctlXX を使用します。XX は連続して増加する整数値です。

マスター ページ自体は名前付けコンテナーとして機能するため、マスター ページで定義されている Web コントロールに対しても、レンダリングされた id 属性値が変更されています。 たとえば、「マスター ページを使用したサイト全体のレイアウトの作成」チュートリアルのマスター ページに追加した DisplayDate Label には、次のマークアップがレンダリングされます。

<span id="ctl00_DateDisplay">current date</span>

この id 属性には、マスター ページのID 値 (ctl00) と Label Web コントロールの ID 値 (DateDisplay) の両方が含まれていることに注意してください。

手順 3: FindControl 経由でプログラムで Web コントロールを参照する

すべての ASP.NET サーバー コントロールには、コントロールの子孫で controlID という名前のコントロールを検索する FindControl("controlID") メソッドが含まれています。 このようなコントロールが見つかった場合は、返されます。一致するコントロールが見つからない場合は、FindControlNothing を返します。

FindControl は、コントロールにアクセスする必要があるが、コントロールへの直接参照がないシナリオで役立ちます。 たとえば、GridView などのデータ Web コントロールを操作する場合、GridView のフィールド内のコントロールは宣言構文で 1 回定義されますが、実行時には GridView 行ごとにコントロールのインスタンスが作成されます。 したがって、実行時に生成されたコントロールは存在しますが、分離コード クラスから直接参照を使用することはできません。 その結果、GridView のフィールド内の特定のコントロールをプログラムで操作するために FindControl を使用する必要があります。 (FindControl を使用してデータ Web コントロールのテンプレート内のコントロールにアクセスする方法の詳細については、「データに基づくカスタム書式設定」を参照してください)。この同じシナリオは、Web フォームに Web コントロールを動的に追加する場合にも発生し、これについては「動的データ入力ユーザー インターフェイスの作成」で説明されています。

FindControl メソッドを使用してコンテンツ ページ内のコントロールを検索する方法を説明するには、SubmitButtonClick イベントのイベント ハンドラーを作成します。 イベント ハンドラーで、次のコードを追加します。このコードは、FindControl メソッドを使用して Age TextBox と Results Label をプログラムで参照し、ユーザーの入力に基づいて Results でメッセージを表示します。

Note

もちろん、この例の Label コントロールと TextBox コントロールを参照するために FindControl を使用する必要はありません。 これらのコントロールは、ID プロパティ値を使用して直接参照できます。 ここでは、コンテンツ ページから FindControl を使用する場合の動作を説明するために FindControl を使用します。

Protected Sub SubmitButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SubmitButton.Click
 Dim ResultsLabel As Label = CType(FindControl("Results"), Label)
 Dim AgeTextBox As TextBox = CType(Page.FindControl("Age"), TextBox)

 ResultsLabel.Text = String.Format("You are {0} years old!", AgeTextBox.Text)
End Sub

FindControl メソッドの呼び出しに使用される構文は、SubmitButton_Click の最初の 2 行で若干異なりますが、意味的には同等です。 すべての ASP.NET サーバー コントロールに FindControl メソッドが含まれることを思い出してください。 これには、すべての ASP.NET 分離コード クラスの派生元となる Page クラスが含まれます。 したがって、分離コード クラスまたはカスタム 基底クラスの FindControl メソッドをオーバーライドしていない場合、FindControl("controlID") の呼び出しはPage.FindControl("controlID") の呼び出しと同じです。

このコードを入力したら、ブラウザーから IDIssues.aspx ページにアクセスし、年齢を入力して、[送信] ボタンをクリックします。 [送信] ボタをクリックすると、NullReferenceException が発生します (図 5 を参照)。

A NullReferenceException is Raised

図 05: NullReferenceException の発生 (クリックしてフルサイズの画像を表示)

SubmitButton_Click イベント ハンドラーにブレークポイントを 設定すると、FindControl への両方の呼び出しが Nothing を返すことがわかります。 Age TextBox の Text プロパティにアクセスしようとすると NullReferenceException が発生します。

問題は、Control.FindControl が同じ名前付けコンテナー内にあるコントロールの子孫のみを検索することです。 マスター ページは新しい名前付けコンテナーを構成するため、Page.FindControl("controlID") への呼び出しはマスター ページ オブジェクト ctl00 には浸透しません。 (図 4 に戻って、マスター ページ オブジェクト ctl00 の親として Page オブジェクトを示すコントロール階層を表示します)。そのため、Results Label と Age TextBox が見つからず、LabelResultsLabelAgeTextBox には Nothingの値が割り当てられます。

この問題には 2 つの回避策があります。適切なコントロールに対して、一度に 1 つの名前付けコンテナーをドリルダウンできます。または、名前付けコンテナーに浸透する独自の FindControl メソッドを作成できます。 これらの各オプションを確認してみましょう。

適切な名前付けコンテナーの詳細調査

Results Label または Age TextBox を参照するために FindControl を使用するには、同じ名前付けコンテナー内の先祖コントロールから FindControl を呼び出す必要があります。 図 4 に示すように、MainContent ContentPlaceHolder コントロールは、同じ名前付けコンテナーにある唯一の Results または Age の先祖です。 つまり、次のコード スニペットに示すように、MainContent コントロールから FindControl メソッドを呼び出すと、Results または Age コントロールへの参照が正しく返されます。

Dim ResultsLabel As Label = CType(MainContent.FindControl("Results"), Label)
Dim AgeTextBox As TextBox = CType(MainContent.FindControl("Age"), TextBox)

ただし、ContentPlaceHolder はマスター ページで定義されているため、上記の構文を使用してコンテンツ ページの分離コード クラスから MainContent ContentPlaceHolder を操作することはできません。 代わりに、MainContent への参照を取得するために FindControl を使用する必要があります。 次の変更を加えて、SubmitButton_Click イベント ハンドラー内のコードを置き換えます。

Protected Sub SubmitButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SubmitButton.Click
 Dim MainContent As ContentPlaceHolder = CType(FindControl("MainContent"), ContentPlaceHolder)

 Dim ResultsLabel As Label = CType(MainContent.FindControl("Results"), Label)
 Dim AgeTextBox As TextBox = CType(MainContent.FindControl("Age"), TextBox)

 ResultsLabel.Text = String.Format("You are {0} years old!", AgeTextBox.Text)
End Sub

ブラウザーからページにアクセスし、年齢を入力し、[送信] ボタンをクリックすると、NullReferenceException が発生します。 SubmitButton_Click イベント ハンドラーにブレークポイントを設定すると、MainContent オブジェクトの FindControl メソッドを呼び出そうとしたときにこの例外が発生することがわかります。 FindControl メソッドが "MainContent" という名前のオブジェクトを見つけることができないため、MainContent オブジェクトは Nothing と等しくなります。 根本的な理由は、Results Label コントロールと Age TextBox コントロールの場合と同じです。FindControl はコントロール階層の先頭から検索を開始し、名前付けコンテナーには侵入しませんが、MainContent ContentPlaceHolder は名前付けコンテナーであるマスター ページ内にあります。

MainContent への参照を取得するために FindControl を使用する前に、まずマスター ページ コントロールへの参照が必要です。 マスター ページへの参照を取得したら、そこから FindControl を通して MainContent ContentPlaceHolder への参照を取得し、そこから Results Label と Age TextBox への参照を取得します (ここでも FindControl を使用します)。 しかし、マスター ページへの参照を取得するにはどうすればよいでしょうか? レンダリングされたマークアップの id 属性を 調べることで、マスター ページの ID 値が ctl00 であることが分かります。 そのため、Page.FindControl("ctl00") を使用してマスター ページへの参照を取得し、そのオブジェクトを使用して MainContent への参照を取得することもできます。 次のスニペットは、このロジックを示しています。

'Get a reference to the master page
Dim ctl00 As MasterPage = CType(FindControl("ctl00"), MasterPage)

'Get a reference to the ContentPlaceHolder
Dim MainContent As ContentPlaceHolder = CType(ctl00.FindControl("MainContent"), ContentPlaceHolder)

'Reference the Label and TextBox controls
Dim ResultsLabel As Label = CType(MainContent.FindControl("Results"), Label)
Dim AgeTextBox As TextBox = CType(MainContent.FindControl("Age"), TextBox)

このコードは確かに機能しますが、マスター ページの自動生成 ID が常に ctl00 になることを前提としています。 自動生成された値を前提とすることは決して良い考えではありません。

幸い、マスター ページへの参照には、Page クラスの Master プロパティを使用してアクセスできます。 したがって、MainContent ContentPlaceHolder にアクセスするためにマスター ページの参照を取得する際に FindControl("ctl00") を使用しなくても、代わりに Page.Master.FindControl("MainContent") を使用できます。 SubmitButton_Click イベント ハンドラーを次のコードで更新します。

Protected Sub SubmitButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SubmitButton.Click
 'Get a reference to the ContentPlaceHolder
 Dim MainContent As ContentPlaceHolder = CType(Page.Master.FindControl("MainContent"), ContentPlaceHolder)

 'Reference the Label and TextBox controls
 Dim ResultsLabel As Label = CType(MainContent.FindControl("Results"), Label)
 Dim AgeTextBox As TextBox = CType(MainContent.FindControl("Age"), TextBox)

 ResultsLabel.Text = String.Format("You are {0} years old!", AgeTextBox.Text)
End Sub

今回は、ブラウザーからページにアクセスし、年齢を入力し、[送信] ボタンをクリックすると、期待どおりに Results Label にメッセージが表示されます。

The User's Age is Displayed in the Label

図 06: ユーザーの年齢が Label に表示される (クリックしてフルサイズの画像を表示)

名前付けコンテナーを使用した反復検索

前のコード例でマスター ページから MainContent ContentPlaceHolder コントロールを参照した後、Results Label コントロールと Age TextBox コントロールを MainContent から参照した理由は、Control.FindControl メソッドがコントロールの名前付けコンテナー内でのみ検索を行うためです。 2 つの異なる名前付けコンテナー内の 2 つのコントロールの ID 値は同じである可能性があるため、ほとんどのシナリオで名前付けコンテナー内に FindControl を留めるのは理にかなっています。 TemplateFields の 1 つのうち ProductName という名前が付けられた Label Web コントロールを定義する GridView の場合を考えてみましょう。 実行時にデータが GridView にバインドされると、GridView 行ごとに ProductName Label が作成されます。 FindControl がすべての名前付けコンテナーを検索して Page.FindControl("ProductName") を呼び出した場合、FindControl はどの Label インスタンスを返すはずですか? 最初の GridView 行の ProductName Label ですか? 最後の行にあるものですか?

つまり、ほとんどの場合、Control.FindControlControl の名前付けコンテナーだけを検索させることは理にかなっています。 ただし、他にも、今回の場合のように、すべての名前付けコンテナーで一意の IDがあり、コントロールにアクセスするためにコントロール階層内の各名前付けコンテナーを細心の注意を払って参照するのを避けたい場合もあります。 すべての名前付けコンテナーを反復的に検索する FindControl バリアントを持つことも理にかなっています。 残念ながら、.NET Framework にはこのようなメソッドは含まれていません。

良いニュースは、すべての名前付けコンテナーを反復的に検索する独自の FindControl メソッドを作成できることです。 実際、拡張メソッドを使用すると、FindControlRecursive メソッドを Control クラスに取り付けて、既存の FindControl メソッドに付随させることができます。

Note

拡張メソッドは、C# 3.0 および Visual Basic 9 の新機能です。これは、.NET Framework バージョン 3.5 および Visual Studio 2008 に付属する言語です。 つまり、拡張メソッドを使用すると、開発者は特殊な構文を使用して既存のクラス型の新しいメソッドを作成できます。 この便利な機能の詳細については、拡張メソッドを使用した基本型機能の拡張に関する記事を参照してください。

拡張メソッドを作成するには、"PageExtensionMethods.vb" という名前の App_Code フォルダーに新しいファイルを追加します。 controlID という名前の String パラメーターを入力として受け取る FindControlRecursive という名前の拡張メソッドを追加します。 拡張メソッドが正しく機能するためには、クラスを Module としてマークし、拡張メソッドの前に <Extension()> 属性を付ける必要があります。 さらに、すべての拡張メソッドは、拡張メソッドが適用される型のオブジェクトを最初のパラメーターとして受け入れる必要があります。

次のコードを PageExtensionMethods.vb ファイルに追加して、この Module および FindControlRecursive 拡張メソッドを定義します。

Imports System.Runtime.CompilerServices

Public Module PageExtensionMethods
 <Extension()> _
  Public Function FindControlRecursive(ByVal ctrl As Control, ByVal controlID As String) As Control
 If String.Compare(ctrl.ID, controlID, True) = 0 Then
 ' We found the control!
 Return ctrl
 Else
 ' Recurse through ctrl's Controls collections
 For Each child As Control In ctrl.Controls
 Dim lookFor As Control = FindControlRecursive(child, controlID)

 If lookFor IsNot Nothing Then
 Return lookFor  ' We found the control
 End If
 Next

 ' If we reach here, control was not found
 Return Nothing
 End If
 End Function
End Module

このコードを配置したら、IDIssues.aspx ページの分離コード クラスに 戻り、現在の FindControl メソッド呼び出しをコメント アウトします。 Page.FindControlRecursive("controlID") 呼び出して置き換えます。 拡張メソッドのすばらしいところは、IntelliSense ドロップダウン リスト内に直接表示される点です。 図 7 に示すように、Page を入力 してからピリオドを押すと、FindControlRecursive メソッドは他の Control クラス メソッドと共に IntelliSense ドロップダウンに含まれます。

Extension Methods are Included in the IntelliSense Drop-Downs

図 07: 拡張メソッドが IntelliSense ドロップダウンに含まれる (クリックしてフルサイズの画像を表示)

SubmitButton_Click イベント ハンドラーに次のコードを入力し、ページにアクセスして年齢を入力し、[送信] ボタンをクリックしてテストします。 図 6 に示すように、結果として得られる出力は、"You are years old!" (あなたは <年齢> 歳です) というメッセージになります。

Protected Sub SubmitButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SubmitButton.Click
 Dim ResultsLabel As Label = CType(Page.FindControlRecursive("Results"), Label)
 Dim AgeTextBox As TextBox = CType(Page.FindControlRecursive("Age"), TextBox)

 ResultsLabel.Text = String.Format("You are {0} years old!", AgeTextBox.Text)
End Sub

Note

拡張メソッドは C# 3.0 および Visual Basic 9 では新たに使用されるため、Visual Studio 2005 を使用している場合は拡張メソッドを使用できません。 代わりに、ヘルパー クラスで FindControlRecursive メソッドを実装する必要があります。 Rick Strahl は、ブログ記事「ASP.NET Maser Pages and FindControl」でこのような例を示しています。

手順 4: クライアント側スクリプトで正しい id 属性値を使用する

このチュートリアルの概要で説明したように、Web コントロールのレンダリング id 属性は、多くの場合、特定の HTML 要素をプログラムで参照するためにクライアント側スクリプトで使用されます。 たとえば、次の JavaScript は、その id によって HTML 要素を参照し、その値をモーダル メッセージ ボックスに表示します。

var elem = document.getElementById("Age");
if (elem != null)
    alert("You entered " + elem.value + " into the Age text box.");

名前付けコンテナーを含まない ASP.NET ページでは、レンダリングされる HTML 要素の id 属性は Web コントロールの ID プロパティ値と同じであることを思い出してください。 このため、id 属性値を JavaScript コードにハード コーディングする必要があります。 つまり、クライアント側スクリプトを使用して Age TextBox Web コントロールにアクセスする必要があることがわかっている場合は、document.getElementById("Age") の呼び出しを使用してアクセスします。

このアプローチの問題は、マスター ページ (または他の名前付けコンテナー コントロール) を使用する場合、レンダリングされる HTML id は Web コントロールの ID プロパティと同義ではないということです。 最初に行いたいことは、ブラウザーを使用してページにアクセスし、ソースを表示して実際の id 属性を決定することかもしれません。 レンダリングされた id 値がわかったら、それを getElementById への呼び出しに貼り付けて、クライアント側スクリプトを使用して操作する必要がある HTML 要素にアクセスできます。 この方法は、ページのコントロール階層に対する変更や名前付けコントロールの ID プロパティの変更の内容によっては、結果として id 属性が変更され、JavaScript コードが破損するため、理想的ではありません。

良いニュースは、レンダリングされる id 属性値は、Web コントロールの ClientID プロパティ を介してサーバー側コードでアクセス可能であるということです。 このプロパティを使用して、クライアント側スクリプトで使用される id 属性値を決定する必要があります。 たとえば、JavaScript 関数をページに追加して呼び出し時に Age TextBox の値をモーダル メッセージ ボックスに表示するには、次のコードを Page_Load イベント ハンドラーに追加します。

ClientScript.RegisterClientScriptBlock(Me.GetType(), "ShowAgeTextBoxScript", _
 "function ShowAge() " & vbCrLf & _
 "{" & vbCrLf & _
 " var elem = document.getElementById('" & AgeTextBox.ClientID & "');" & vbCrLf & _
 " if (elem != null)" & vbCrLf & _
 " alert('You entered ' + elem.value + ' into the Age text box.');" & vbCrLf & _
 "}", True)

上記のコードは、Age TextBox の ClientID プロパティの値を getElementById の JavaScript 呼び出しに挿入します。 ブラウザーからこのページにアクセスし、HTML ソースを表示すると、次の JavaScript コードが表示されます。

<script type="text/javascript">
//<![CDATA[
function ShowAge()
{
 var elem = document.getElementById('ctl00_MainContent_Age');
 if (elem != null)
 alert('You entered ' + elem.value + ' into the Age text box.');
}//]]>
</script>

getElementById の呼び出しの中で正しい id 属性値 ctl00_MainContent_Age がどのように表示されるかに注意してください。 この値は実行時に計算されるため、ページ コントロール階層に対する後の変更に関係なく機能します。

Note

この JavaScript の例は、サーバー コントロールによってレンダリングされる HTML 要素を正しく参照する JavaScript 関数を追加する方法を示すだけのものです。 この関数を使用するには、ドキュメントが読み込まれるか、特定のユーザー アクションが発生したときに関数を呼び出すために、追加の JavaScript を作成する必要があります。 これらのトピックおよび関連トピックの詳細については、「クライアント側スクリプトの操作」を参照してください。

まとめ

特定の ASP.NET サーバー コントロールは、名前付けコンテナーとして機能します。これは、その子孫コントロールのレンダリングされた id 属性値と、FindControl メソッドが詳細に調べるコントロールの範囲に影響します。 マスター ページに関しては、マスター ページ自体とその ContentPlaceHolder コントロールの両方が名前付けコンテナーです。 したがって、FindControl を使用したコンテンツ ページ 内のコントロールをプログラムで参照するには、もう少し多くの作業を行う必要があります。 このチュートリアルでは、ContentPlaceHolder コントロールを詳しく調べてその FindControl メソッドを呼び出す手法と、すべての名前付けコンテナーを反復的に検索する独自の FindControl の実装を展開する手法という 2 つの手法を確認しました。

Web コントロールの参照に関しては、コンテナーの名前付けに関するサーバー側の問題に加えて、クライアント側の問題もあります。 名前付けコンテナーがなければ、Web コントロールの ID プロパティ値とレンダリングされる id 属性値は同じになります。 ただし、名前付けコンテナーが追加されると、レンダリングされる id 属性には、Web コントロールの ID 値とそのコントロール階層の先祖の名前付けコンテナーの両方が含まれます。 これらの名前付けの問題は、Web コントロールの ClientID プロパティを使用してクライアント側スクリプトでレンダリングされる id 属性値を決定する限り、問題ではありません。

プログラミングに満足!

もっと読む

この記事で説明したトピックの詳細については、次のリソースを参照してください。

作成者について

複数の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell 氏は、1998 年から Microsoft Web のテクノロジに取り組んでいます。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の著書は、「Sams Teach Yourself ASP.NET 3.5 in 24 Hours」です。 Mitchell 氏には、mitchell@4GuysFromRolla.com で、または彼のブログ (http://ScottOnWriting.NET) 経由で連絡できます。

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Zack Jones と Suchi Barnerjee でした。 今後の MSDN の記事を確認することに関心がありますか? ご希望なら、mitchell@4GuysFromRolla.com でメッセージをお送りください。.