• 追加された行はこの色です。
  • 削除された行はこの色です。
#title(Microsoft ASP.NET AJAXを使う3)

#navi(.NET プログラミング研究)

#contents

*Microsoft ASP.NET AJAXを使う3 [#f1a19caf]

前号を発行してからかなり日が開いてしまいましたが、その間に、Visual Studio 2008が発売されました。.NET Framework 3.5からはASP.NET AJAXが組み込まれていますので、別途インストールする必要がありません。また、今まで紹介したASP.NET AJAXの使い方はそのまま使えるはずです。

**UpdateProgressコントロールを使って、現在の状況を表示する [#updateprogress]

今まで紹介してきたAjaxのサンプルで、ASP.NET AJAXを使わなかったものは、ゆうパックの料金を調べている最中に、「読み込み中...」と表示されていました。ASP.NET AJAXでこのような現在の状況を表示するには、UpdateProgressコントロールを使います。

UpdateProgressコントロールの使い方は、非常に簡単です。前々号の「[[UpdatePanelを使う>../80#updatepanel]]」のサンプルを改良して進行状況を表示するには、ページにUpdateProgressコントロールを追加し、その中(HTMLでは<ProgressTemplate>の中)に「読み込み中...」と記入するだけです。

&ref(updateprogress1.png);

全体のコードを以下に示します。すぐに処理が終了してしまうと「読み込み中...」が一瞬で消えてしまいますので、ここではあえて時間がかかるように、DropDownListのSelectedIndexChangedイベントハンドラでThread.Sleepメソッドを呼び出しています。また、UpdateProgressのDisplayAfterプロパティを0にして、すぐに「読み込み中...」が表示されるようにしています。デフォルトは500となっており、500ミリ秒後に「読み込み中...」が表示されます。

#code(vbnet){{
<%@ Page Language="VB" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<script runat="server">
 
    Protected Sub PackageSizeList_SelectedIndexChanged( _
            ByVal sender As Object, ByVal e As EventArgs)
        '3秒停止する
        System.Threading.Thread.Sleep(3000)
        
        Dim ddl As DropDownList = CType(sender, DropDownList)
        If ddl.SelectedValue <> "0" Then
            Dim fee As Integer = _
                Me.GetYoupackFee(Integer.Parse(ddl.SelectedValue))
            ResultLabel.Text = String.Format("料金は {0}円", fee)
        Else
            ResultLabel.Text = ""
        End If
    End Sub
 
    Public Function GetYoupackFee(ByVal packSize As Integer) As Integer
        If packSize <= 60 Then
            Return 600
        ElseIf packSize <= 80 Then
            Return 800
        ElseIf packSize <= 100 Then
            Return 1000
        ElseIf packSize <= 120 Then
            Return 1200
        ElseIf packSize <= 140 Then
            Return 1400
        ElseIf packSize <= 160 Then
            Return 1600
        ElseIf packSize <= 170 Then
            Return 1700
        Else
            Return -1
        End If
    End Function
    
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>ゆうパック送料検索</title>
</head>
<body>
    <h1>ゆうパック送料検索</h1>
    <p>同一都道府県内への配達、重量30kgまで</p>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <asp:Label
            ID="Label1"
            runat="server"
            Text="荷物の大きさ(縦・横・高さの合計): "
            AssociatedControlID="PackageSizeList">
        </asp:Label>
        <asp:DropDownList
            ID="PackageSizeList"
            runat="server"
            AutoPostBack="True"
            OnSelectedIndexChanged="PackageSizeList_SelectedIndexChanged">
            <asp:ListItem Value="0">(選択してください)</asp:ListItem>
            <asp:ListItem Value="60">60cmまで</asp:ListItem>
            <asp:ListItem Value="80">80cmまで</asp:ListItem>
            <asp:ListItem Value="100">100cmまで</asp:ListItem>
            <asp:ListItem Value="120">120cmまで</asp:ListItem>
            <asp:ListItem Value="140">140cmまで</asp:ListItem>
            <asp:ListItem Value="160">160cmまで</asp:ListItem>
            <asp:ListItem Value="170">170cmまで</asp:ListItem>
        </asp:DropDownList>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Label ID="ResultLabel" runat="server"></asp:Label>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="PackageSizeList"
                    EventName="SelectedIndexChanged" />
            </Triggers>
        </asp:UpdatePanel>
        <asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="0">
            <ProgressTemplate>
                読み込み中...
            </ProgressTemplate>
        </asp:UpdateProgress>
    </div>
    </form>
</body>
</html>
}}

#code(csharp){{
<%@ Page Language="C#" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<script runat="server">
    
    protected void PackageSizeList_SelectedIndexChanged(
        object sender, EventArgs e)
    {
        //3秒停止する
        System.Threading.Thread.Sleep(3000);

        DropDownList ddl = (DropDownList)sender;
        if (ddl.SelectedValue != "0")
        {
            int fee = this.GetYoupackFee(int.Parse(ddl.SelectedValue));
            ResultLabel.Text = string.Format("料金は {0}円", fee);
        }
        else
        {
            ResultLabel.Text = "";
        }
    }
 
    public int GetYoupackFee(int packSize)
    {
        if (packSize <= 60)
            return 600;
        else if (packSize <= 80)
            return 800;
        else if (packSize <= 100)
            return 1000;
        else if (packSize <= 120)
            return 1200;
        else if (packSize <= 140)
            return 1400;
        else if (packSize <= 160)
            return 1600;
        else if (packSize <= 170)
            return 1700;
        else
            return -1;
    }
 
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>ゆうパック送料検索</title>
</head>
<body>
    <h1>ゆうパック送料検索</h1>
    <p>同一都道府県内への配達、重量30kgまで</p>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Label
                    ID="Label1"
                    runat="server"
                    Text="荷物の大きさ(縦・横・高さの合計): "
                    AssociatedControlID="PackageSizeList">
                </asp:Label>
                <asp:DropDownList
                    ID="PackageSizeList"
                    runat="server"
                    AutoPostBack="True"
                    OnSelectedIndexChanged="PackageSizeList_SelectedIndexChanged">
                    <asp:ListItem Value="0">(選択してください)</asp:ListItem>
                    <asp:ListItem Value="60">60cmまで</asp:ListItem>
                    <asp:ListItem Value="80">80cmまで</asp:ListItem>
                    <asp:ListItem Value="100">100cmまで</asp:ListItem>
                    <asp:ListItem Value="120">120cmまで</asp:ListItem>
                    <asp:ListItem Value="140">140cmまで</asp:ListItem>
                    <asp:ListItem Value="160">160cmまで</asp:ListItem>
                    <asp:ListItem Value="170">170cmまで</asp:ListItem>
                </asp:DropDownList>
                <br />
                <asp:Label ID="ResultLabel" runat="server"></asp:Label>
            </ContentTemplate>
        </asp:UpdatePanel>
        <asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="0">
            <ProgressTemplate>
                読み込み中...
            </ProgressTemplate>
        </asp:UpdateProgress>
    </div>
    </form>
</body>
</html>
}}

***AssociatedUpdatePanelIDプロパティ [#f6edf27f]

ページ内にUpdatePanelが複数存在する場合は、基本的にはどのUpdatePanelが更新されてもUpdateProgressに現在の状況が表示されます。あるUpdatePanelが更新されたときだけUpdateProgressに状況を表示するには、UpdateProgressのAssociatedUpdatePanelIDプロパティを使用して、UpdatePanelと関連付けます。例えば上記の例では、AssociatedUpdatePanelIDプロパティを"UpdatePanel1"としてUpdatePanel1と関連付けることができます。

***DynamicLayoutプロパティについて [#of37b4f9]

UpdateProgressが置かれている部分は、HTMLでは<div>で囲まれます。UpdateProgressの中身が表示されていない時(はじめの状態)は、この<div>のdisplayスタイルがnoneですが、UpdateProgressの中身が表示される時は、JavaScriptでblockに変更されます。具体的には、上記の例では、UpdateProgress1の部分のHTMLは次のようになります。

#code(html){{
<div id="UpdateProgress1" style="display:none;">
    読み込み中...
</div>
}}

ただしこのようになるのは、UpdateProgressのDynamicLayoutプロパティがTrueの場合です。これをFalseにすると、UpdateProgressの中身が表示されていない時の<div>のスタイルはdisplayがblockでvisibilityがhiddenとなり、UpdateProgressの中身が表示される時は、visibilityがvisibleに変更されます。

#code(html){{
<div id="UpdateProgress1" style="visibility:hidden;display:block;">
    読み込み中...
</div>
}}

つまり、DynamicLayoutプロパティがTrueの場合は、UpdateProgressが表示されていない時はその場所を占有するものが何もない状態で、UpdateProgressが表示されるとその行が新に挿入されるようにして表示されます。よってこの場合は、UpdateProgressが表示されると、UpdateProgress以降のコンテンツは新に挿入された分だけ下に押し出されます。

一方DynamicLayoutプロパティがFalseの場合は、UpdateProgressが表示されていない時でもその部分にUpdateProgressで表示する内容分のスペースが空いた状態になります。よってこの場合は、UpdateProgressが表示されても、UpdateProgress以降のコンテンツが動かされません。

**自分で現在の状況を表示する [#tc148b93]

.NET Framework 3.5からは問題ないようですが、ASP.NET AJAX 1.0では、前々号の「[[トリガーをUpdatePanelの外に出す>../80#triggers]]」のサンプルのようにトリガーをUpdatePanelの外に出した場合は、UpdateProgressを追加しただけでは何も表示されません。AssociatedUpdatePanelIDプロパティを設定してもダメです。

同様に、前号の「[[UpdatePanelに関連付けられていないコントロールで非同期ポストバックを行う>../81#registerasyncpostbackcontrol]]」のようにRegisterAsyncPostBackControlメソッドを使った場合もうまくいきません。これは、.NET Framework 3.5でもうまくいきません。

このようなケースで現在の状況を表示するには、JavaScriptを自分で書く必要があります。

いまさら「JavaScriptを自分で書け」と言われても...と思われるかもしれませんが、メリットも多くあります。例えば、UpdateProgressコントロールを使わずに現在の状況を表示することができますので、より柔軟な表示が可能になります。

***Microsoft AJAX Library [#e43c1fa1]

UpdateProgressコントロールを使わずに、自分で現在の状況を表示する場合、問題になるのは、いつ現在の状況を表示して、いつ消せばよいのかという点です。

ASP.NET AJAXにはそれを解決する方法が用意されています。それには、「Microsoft AJAX Library」というECMAScript(JavaScript)を基にしたクライアントプログラミングのフレームワークを使用します。「Microsoft AJAX Library」はASP.NET AJAX Extensionsをインストールしていればインストールされますので、通常は問題ないでしょう。(Microsoft AJAX Libraryを単独でインストールすることもできます。)

Microsoft AJAX Libraryにある[[Sys.WebForms.PageRequestManagerクラス>http://msdn2.microsoft.com/ja-jp/library/bb311028.aspx]]の[[beginRequestイベント>http://msdn2.microsoft.com/ja-jp/library/bb397432.aspx]]で、いつ非同期ポストバックが開始されたかが分かりますので、このタイミングで現在の状況を表示させます。同じように、非同期ポストバックの終了は[[endRequestイベント>http://msdn2.microsoft.com/ja-jp/library/bb383810.aspx]]で分かりますので、ここで現在の状況を非表示にします。

このPageRequestManagerは、ScriptManagerコントロールのSupportsPartialRenderingプロパティがTrueの時に使用できます。デフォルトでTrueですので、通常は気にする必要はありません。また、ページにUpdatePanelコントロールが1つ以上あることも必要です。

***具体例 [#q58cdc31]

それでは具体例を示します。ここではUpdateProgressコントロールを使わずに、結果を表示するLabelコントロールに"読み込み中..."と表示されるように先ほどのサンプルを改造します。

先ほどのサンプルとの違いは、UpdateProgressコントロールを削除した点と、<script>を追加した点です。

変更箇所は<html>内だけですので、以下に<html>内だけを示します。

#code(html){{
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>ゆうパック送料検索</title>
</head>
<body>
    <h1>ゆうパック送料検索</h1>
    <p>同一都道府県内への配達、重量30kgまで</p>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        
<script type="text/javascript">
var postBackElement, msgElement;

//PageRequestManagerを作成
var prm = Sys.WebForms.PageRequestManager.getInstance();
//イベントハンドラを追加
prm.add_initializeRequest(InitializeRequest);

//非同期ポストバックの開始
function InitializeRequest(sender, args)
{
    //トリガーのIDを取得する
    postBackElement = args.get_postBackElement();
    //トリガーがPackageSizeListかどうか調べる
    if (postBackElement.id == 'PackageSizeList')
    {
        //現在の状況を表示する
        msgElement = $get('ResultLabel');
        msgElement.innerHTML = "読み込み中...";
    }
}
</script>

        <asp:Label
            ID="Label1"
            runat="server"
            Text="荷物の大きさ(縦・横・高さの合計): "
            AssociatedControlID="PackageSizeList">
        </asp:Label>
        <asp:DropDownList
            ID="PackageSizeList"
            runat="server"
            AutoPostBack="True"
            OnSelectedIndexChanged="PackageSizeList_SelectedIndexChanged">
            <asp:ListItem Value="0">(選択してください)</asp:ListItem>
            <asp:ListItem Value="60">60cmまで</asp:ListItem>
            <asp:ListItem Value="80">80cmまで</asp:ListItem>
            <asp:ListItem Value="100">100cmまで</asp:ListItem>
            <asp:ListItem Value="120">120cmまで</asp:ListItem>
            <asp:ListItem Value="140">140cmまで</asp:ListItem>
            <asp:ListItem Value="160">160cmまで</asp:ListItem>
            <asp:ListItem Value="170">170cmまで</asp:ListItem>
        </asp:DropDownList>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Label ID="ResultLabel" runat="server"></asp:Label>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="PackageSizeList"
                    EventName="SelectedIndexChanged" />
            </Triggers>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>
}}

***JavaScriptの説明 [#jc332edb]

JavaScriptのコードの説明をします。

まず、[[PageRequestManager.getInstanceメソッド>http://msdn2.microsoft.com/ja-jp/library/bb397699.aspx]]により、PageRequestManagerのインスタンスを作成します。そして、非同期ポストバックが開始された時に発生するbeginRequestイベントのハンドラを追加します。

beginRequestイベントハンドラ(InitializeRequestメソッド)では、まずトリガー(ポストバックを起こした要素)のIDを調べ、"PackageSizeList"かどうかを確認しています。もしそうであれば、結果を表示するLabelである"ResultLabel"というIDを持つ要素を取得し、その内容を"読み込み中..."に変更しています。

なお"[[$get>http://msdn2.microsoft.com/ja-jp/library/bb397717.aspx]]"というのは、[[Sys.UI.DomElement.getElementByIdメソッド>http://msdn2.microsoft.com/ja-jp/library/bb397706.aspx]]のショートカットで、IDから要素を取得するためのメソッドです。

JavaScriptを記述する位置ですが、必ずScriptManagerの後にしてください。ScriptManagerの前ではPageRequestManagerのインスタンスが作成されておらず、エラーになります。

この例では非同期ポストバックが完了したときに何もしませんでした。もし非同期ポストバックが完了したときに、beginRequestで表示した現在の状況を消す必要がある場合は、endRequestイベントを使用します。endRequestイベントの使い方は、beginRequestイベントと同じです。

**JavaScriptを外部ファイルに記述する [#x7d6ab80]

上記の例ではページ内にJavaScriptを記述しましたが、外部ファイルにJavaScriptを記述してみましょう。単純にJavaScriptの部分を"progress.js"といったファイルに記述し、

#code(html){{
<script type="text/javascript" src="progress.js"></script>
}}

とすることもできますが、ここではScriptManagerのScriptsプロパティを使用する方法を紹介します。

***JavaScriptの説明 [#n28e0297]

まずJavaScriptを外部ファイルに記述します。ここでは以下のようなコードを"progress.js"という名前で保存したとします。

#code(javascript){{
var postBackElement, msgElement;

//アプリケーションのloadイベントハンドラを追加
Sys.Application.add_load(ApplicationLoad)

//アプリケーションのloadイベントハンドラ
function ApplicationLoad(sender, args)
{
    //PageRequestManagerを作成
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //イベントハンドラを追加
    prm.add_initializeRequest(InitializeRequest);
}

//非同期ポストバックの開始
function InitializeRequest(sender, args)
{
    //トリガーのIDを取得する
    postBackElement = args.get_postBackElement();
    //トリガーがPackageSizeListかどうか調べる
    if (postBackElement.id == 'PackageSizeList')
    {
        //現在の状況を表示する
        msgElement = $get('ResultLabel');
        msgElement.innerHTML = "読み込み中...";
    }
}

//スクリプトの読み込みが完了したことをScriptManagerに知らせる
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
}}

前のコードと比較すると、違いは2箇所あります。

一つ目は、[[Sys.Application.loadイベント>http://msdn2.microsoft.com/ja-jp/library/bb383829.aspx]]のハンドラ内でPageRequestManagerの作成を行っている点です。loadイベントはアプリケーション内のすべてのオブジェクトが作成、初期化された後に発生しますので、この時には確実にPageRequestManagerのインスタンスが作成されています。

上記の例のようにApplication.add_loadを使う以外に、"pageLoad"という名前の関数を作成する方法があります。"pageLoad"はMicrosoft AJAX Libraryで予約された名前であり、"pageLoad"という名前の関数は、Application.loadイベントハンドラになるということになっています。pageLoadを使用した例を以下に示します。

#code(javascript){{
//アプリケーションのloadイベントハンドラ
function pageLoad(sender, args)
{
    //PageRequestManagerを作成
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //イベントハンドラを追加
    prm.add_initializeRequest(InitializeRequest);
}
}}

2つ目の違いは、最後の行で[[Sys.Application.notifyScriptLoadedメソッド>http://msdn2.microsoft.com/ja-jp/library/bb310952.aspx]]を呼び出している点です。ScriptManagerのScriptsプロパティに登録されている外部JavaScriptでは、必ず最後でnotifyScriptLoadedを呼び出す必要があります。

アセンブリに埋め込まれたスクリプトファイルでは、通常は、notifyScriptLoadedを呼び出してはいけません。これは、スクリプトを表すScriptReferenceオブジェクトの[[NotifyScriptLoadedプロパティ>http://msdn2.microsoft.com/ja-jp/library/bb346403.aspx]]がデフォルトでTrueであり、自動的にnotifyScriptLoadedが呼び出されるためです。スクリプトからnotifyScriptLoadedを呼び出したい場合は、NotifyScriptLoadedプロパティをFalseにします。

***ScriptManager.Scriptsプロパティに追加する [#f76e81e1]

このように作成したJavaScriptファイルをScriptManagerのScriptsプロパティに追加するには、デザイナを使用した場合は、次のようにします。

+デザイナでScriptManagerを選択する。
+プロパティウィンドウの"Scripts"の右に表示される"..."ボタンをクリックして"ScriptReferenceコレクションエディタ"を表示する。
+"メンバ"の下の"追加"ボタンをクリックして、ScriptReferenceを追加する。
+"ScriptReference プロパティ"の"Path"に"progress.js"と入力する。
+"OK"ボタンをクリックする。

&ref(updateprogress2.png);

このようにして出来上がったコードは次のようになります。<html>内以外は初めの例と同じですので、<html>内だけを示します。

#code(html){{
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>ゆうパック送料検索</title>
</head>
<body>
    <h1>ゆうパック送料検索</h1>
    <p>同一都道府県内への配達、重量30kgまで</p>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="progress.js" />
            </Scripts>
        </asp:ScriptManager>
        <asp:Label
            ID="Label1"
            runat="server"
            Text="荷物の大きさ(縦・横・高さの合計): "
            AssociatedControlID="PackageSizeList">
        </asp:Label>
        <asp:DropDownList
            ID="PackageSizeList"
            runat="server"
            AutoPostBack="True"
            OnSelectedIndexChanged="PackageSizeList_SelectedIndexChanged">
            <asp:ListItem Value="0">(選択してください)</asp:ListItem>
            <asp:ListItem Value="60">60cmまで</asp:ListItem>
            <asp:ListItem Value="80">80cmまで</asp:ListItem>
            <asp:ListItem Value="100">100cmまで</asp:ListItem>
            <asp:ListItem Value="120">120cmまで</asp:ListItem>
            <asp:ListItem Value="140">140cmまで</asp:ListItem>
            <asp:ListItem Value="160">160cmまで</asp:ListItem>
            <asp:ListItem Value="170">170cmまで</asp:ListItem>
        </asp:DropDownList>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Label ID="ResultLabel" runat="server"></asp:Label>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="PackageSizeList"
                    EventName="SelectedIndexChanged" />
            </Triggers>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>
}}

***参考 [#gbf37d2b]

-[[PageRequestManager のイベントの処理>http://msdn2.microsoft.com/ja-jp/library/bb398976.aspx]]
-[[ASP.NET AJAX Client Life-Cycle Events>http://asp.net/AJAX/Documentation/Live/overview/AJAXClientEvents.aspx]]

**参考 [#yb828d4b]

-[[UpdateProgress Control Overview>http://asp.net/ajax/documentation/live/overview/UpdateProgressOverview.aspx]]
-[[UpdateProgress Control Tutorials>http://asp.net/AJAX/Documentation/Live/tutorials/UpdateProgressTutorials.aspx]]

**コメント [#r5f16099]
#comment

//これより下は編集しないでください
#pageinfo([[:Category/.NET]] [[:Category/ASP.NET]],2008-03-17 (月) 00:59:24,DOBON!,2008-06-30 (月) 00:24:15,DOBON!)


[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]