コンテンツ ページのコントロール ID の名前付け (VB)
ContentPlaceHolder コントロールが名前付けコンテナーとして機能することで、プログラムによるコントロールの操作が困難になる方法を示します (FindControl を使用)。 この問題と回避策を確認します。 また、結果の ClientID 値にプログラムでアクセスする方法についても説明します。
はじめに
すべての ASP.NET サーバー コントロールには、コントロールを一意に識別する ID
プロパティが含まれており、これは分離コード クラスでプログラムによってコントロールにアクセスする手段です。 同様に、HTML ドキュメント内の要素には、要素を一意に識別する id
属性を含めることができます。これらの id
値は、特定の HTML 要素をプログラムで参照するためにクライアント側スクリプトでよく使用されます。 これを考慮すると、ASP.NET サーバー コントロールが HTML にレンダリングされるときに、その ID
値がレンダリングされた HTML 要素の id
値として使用されると想定できます。 特定の状況では、1 つの ID
値を持つ単一のコントロールがレンダリングされたマークアップに複数回表示される可能性があるため、これは必ずしも当てはまるわけではありません。 ProductName
が ID
値を持つ Label Web コントロールの TemplateField を含む GridView コントロールについて考えてみましょう。 実行時に GridView がデータ ソースにバインドされると、この Label は GridView 行ごとに 1 回繰り返されます。 レンダリングされる各 Label には、一意の id
値が必要です。
このようなシナリオを処理するために、ASP.NET では、特定のコントロールを名前付けコンテナーとして示すことができます。 名前付けコンテナーは、新しい ID
名前空間として機能します。 名前付けコンテナー内に表示されるすべてのサーバー コントロールのレンダリング id
値には、名前付けコンテナー コントロールのプレフィックスとして ID
が付けられます。 たとえば、GridView
クラスと GridViewRow
クラスはどちらも名前付けコンテナーです。 その結果、ID
ProductName
を使用して GridView TemplateField で定義された Label コントロールには、GridViewID_GridViewRowID_ProductName
のレンダリングされたid
値が与えられます。 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
マスター ページにバインドします。
図 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] (レッスン) セクションにすぐに反映されます。
図 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
プロパティをクリアし、ID
を Results
に設定します。
この時点で、コンテンツ コントロールの宣言型マークアップは次のようになります。
<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 のデザイナーで表示したときのページを示しています。
図 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 コントロールの ID
値 MainContent
が付いています。 さらに、この値にはマスター ページの ID
値 ctl00
がプレフィックスとして付けられます。 正味の影響は、マスター ページの ID
値、ContentPlaceHolder コントロール、および TextBox 自体で構成される id
属性値です。
この動作を図 4 に示します。 Age
TextBox のレンダリング id
を決定するには、TextBox コントロールの ID
値 Age
から始めます。 次に、コントロール階層を上に進めます。 各名前付けコンテナー (桃色のノード) で、現在のレンダリング id
に名前付けコンテナー id
をプレフィックスとして付けます。
図 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")
メソッドが含まれています。 このようなコントロールが見つかった場合は、返されます。一致するコントロールが見つからない場合は、FindControl
は Nothing
を返します。
FindControl
は、コントロールにアクセスする必要があるが、コントロールへの直接参照がないシナリオで役立ちます。 たとえば、GridView などのデータ Web コントロールを操作する場合、GridView のフィールド内のコントロールは宣言構文で 1 回定義されますが、実行時には GridView 行ごとにコントロールのインスタンスが作成されます。 したがって、実行時に生成されたコントロールは存在しますが、分離コード クラスから直接参照を使用することはできません。 その結果、GridView のフィールド内の特定のコントロールをプログラムで操作するために FindControl
を使用する必要があります。 (FindControl
を使用してデータ Web コントロールのテンプレート内のコントロールにアクセスする方法の詳細については、「データに基づくカスタム書式設定」を参照してください)。この同じシナリオは、Web フォームに Web コントロールを動的に追加する場合にも発生し、これについては「動的データ入力ユーザー インターフェイスの作成」で説明されています。
FindControl
メソッドを使用してコンテンツ ページ内のコントロールを検索する方法を説明するには、SubmitButton
の Click
イベントのイベント ハンドラーを作成します。 イベント ハンドラーで、次のコードを追加します。このコードは、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 を参照)。
図 05: NullReferenceException
の発生 (クリックしてフルサイズの画像を表示)
SubmitButton_Click
イベント ハンドラーにブレークポイントを 設定すると、FindControl
への両方の呼び出しが Nothing
を返すことがわかります。 Age
TextBox の Text
プロパティにアクセスしようとすると NullReferenceException
が発生します。
問題は、Control.FindControl
が同じ名前付けコンテナー内にあるコントロールの子孫のみを検索することです。 マスター ページは新しい名前付けコンテナーを構成するため、Page.FindControl("controlID")
への呼び出しはマスター ページ オブジェクト ctl00
には浸透しません。 (図 4 に戻って、マスター ページ オブジェクト ctl00
の親として Page
オブジェクトを示すコントロール階層を表示します)。そのため、Results
Label と Age
TextBox が見つからず、LabelResultsLabel
と AgeTextBox
には 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 にメッセージが表示されます。
図 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.FindControl
に Control の名前付けコンテナーだけを検索させることは理にかなっています。 ただし、他にも、今回の場合のように、すべての名前付けコンテナーで一意の 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 ドロップダウンに含まれます。
図 07: 拡張メソッドが IntelliSense ドロップダウンに含まれる (クリックしてフルサイズの画像を表示)
SubmitButton_Click
イベント ハンドラーに次のコードを入力し、ページにアクセスして年齢を入力し、[送信] ボタンを選択してテストします。 図 6 に示すように、結果として得られる出力は、"You are
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.NET Master Pages and
FindControl
- 動的データ入力ユーザー インターフェイスの作成
- 方法: ASP.NET マスター ページ コンテンツの参照
- マスター ページ: ヒント、テクニック、トラップ
- クライアント側スクリプトの操作
作成者について
複数の 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 までご一報ください。