WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

GridView, ListView に合計表示

by WebSurfer 2010年11月7日 19:04
ListView に合計を表示

GirdView や ListView で、ある列の合計金額を計算して、フッターなどに表示したいというケースが時々あります。備忘録として、その例を書いておきます。

GridView は「行」(GridViewRow クラス)で構成されているのに対して、ListView は「項目」(ListViewItem クラス)で構成されているという違いがありますが、基本的な方法は行/項目にデータがバインドされるときのイベントを利用して、値を取得して合計していくという操作は同じだと思います。

GridView, ListView どちらの場合も、データソースコントロールが取得してバインドするデータが DataTable の場合(デフォルト)は、DataItem プロパティを使って DataRowView を取得できますので、それから各行/項目の値を取得するのがよさそうです。

合計した結果を書き込むところが、ちょっと違います。

GridView では、フッターでも RowDataBound イベントが発生します。そのイベントハンドラでデータ行かフッター行かが判定でき、フッター行の場合に合計をフッターに書き込むことができます。

ShowFooter="True" として、その中の TableCell の Text プロパティに書き込む例は以下の通りです。

decimal total = 0m;
    
protected void GridView1_RowDataBound(object sender, 
  GridViewRowEventArgs e)
{        
  if (e.Row.RowType == DataControlRowType.DataRow)
  {
    DataRowView drv = (DataRowView)e.Row.DataItem;
    total = total + (decimal)drv["Freight"];
  }
  else if (e.Row.RowType == DataControlRowType.Footer)
  {
    e.Row.Cells[1].Text = "Freight Total";
    e.Row.Cells[2].Text = String.Format("${0:N2}", total);
  }        
}

ListView では、合計の取得は ItemDataBound イベントハンドラで可能ですが、GridView の時のようにフッター行に合計結果を書き込むことはできません。

LayoutTemplate にフッターの行を追加して Label を配置し、その Text プロパティに書き込むことになります。そのタイミングは、ListView.DataBound イベントがよさそうです。

上の画像を出力した ListView のコードを以下にアップしておきます。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  decimal total = 0m;
    
  protected void ListView1_ItemDataBound(object sender, 
      ListViewItemEventArgs e)
  {
    if (e.Item.ItemType == ListViewItemType.DataItem)
    {
      ListViewDataItem lvdi = (ListViewDataItem)e.Item;
      DataRowView drv = (DataRowView)lvdi.DataItem;
      total = total + (decimal)drv["Freight"];
    }
  }

  protected void ListView1_DataBound(object sender, EventArgs e)
  {
    Label label = (Label)ListView1.FindControl("totalLabel");
    label.Text = String.Format("${0:N2}", total);
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Show Total in ListView</title>
  <style type="text/css">
    table.style1
    {
      border-style: solid;
      border-width: 2px;
      border-color: Black;
      text-align: center;
      border-collapse: collapse;
    }
       
    table.style1 th
    {
      border-style: solid;
      border-width: 2px 1px 2px 1px;
      border-color: Black;
      background-color: #6699FF;
      color: #FFFFFF;
    }
        
    table.style1 td
    {
      border-style: solid;
      border-width: 1px;
      border-color: Black;        
    }
        
    .footer
    {
      background-color: #CCFFFF;
    }  
  </style>

</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h3>Alfreds Futterkiste</h3>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand="SELECT [OrderID], [OrderDate], [Freight] 
        FROM [Orders] 
        WHERE [CustomerID]='ALFKI'">            
    </asp:SqlDataSource>
    <asp:ListView ID="ListView1" 
      runat="server" 
      DataKeyNames="OrderID" 
      DataSourceID="SqlDataSource1" 
      EnableModelValidation="True" 
      OnItemDataBound="ListView1_ItemDataBound" 
      OnDataBound="ListView1_DataBound">
      <ItemTemplate>
        <tr>
          <td>
            <asp:Label ID="OrderIDLabel" 
              runat="server" 
              Text='<%# Eval("OrderID") %>' />
          </td>
          <td>
            <asp:Label ID="OrderDateLabel" 
              runat="server" 
              Text='<%# Eval("OrderDate", "{0:yyyy/MM/dd}") %>' />
          </td>
          <td style="text-align: right;">
            <asp:Label ID="FreightLabel" 
              runat="server" 
              Text='<%# Eval("Freight", "${0:N2}") %>' />
          </td>
        </tr>
      </ItemTemplate>
      <LayoutTemplate>
        <table ID="itemPlaceholderContainer" 
          runat="server" 
          class="style1">
          <tr runat="server">
            <th runat="server">
              OrderID</th>
            <th runat="server">
              OrderDate</th>
            <th runat="server">
              Freight</th>
          </tr>
          <tr ID="itemPlaceholder" runat="server">
          </tr>
          <tr class="footer">
            <td></td>
            <td>Freight Total</td>
            <td style="text-align: right;">
              <asp:Label ID="totalLabel" runat="server" /></td>
          </tr>
        </table>                        
      </LayoutTemplate>
    </asp:ListView>
  </div>
  </form>
</body>
</html>

Tags: ,

ASP.NET

UpdatePanel へのトリガ追加

by WebSurfer 2010年11月6日 19:08

2010/11/8 追記

MSDN ライブラリの AsyncPostBackTrigger クラス の説明によると、"AsyncPostBackTrigger コントロールのプログラムによる追加はサポートされていません。" とのことです。というわけで、以下の方法はボツです。(汗) 別の方法で書き換えたものを UpdatePanel へのトリガ追加(改版) にポストしましたのでそちらを見てください。

AsyncPostBackTrigger を動的に設定

ASP.NET AJAX Extensions の UpdatePanel コントロールのトリガに AsyncPostBackTrigger クラスの ControlID として Button などのコントロールを動的に設定する話です。

大体のケースでは静的に設定できると思いますが、UniqueID を設定しなければならない場合は話が別です。

UniqueID を設定する必要があるのは、トリガとなる Button などのコントロールが名前付けコンテナに含まれ、ID とは異なる一意の UniqueID がサーバー側で設定される場合です。

名前付けコンテナとは、例えば、Repeater, DataList, DetailsView, FormView, GridView などやマスタページの ContentPlaceHolder です。

そのような場合に、AsyncPostBackTrigger クラスの ControlID プロパティに(UniqueID ではなく)ID を設定すると、「UpdatePanel 'UpdatePanel1' のトリガ用の ID 'Button1' のコントロールが見つかりませんでした。」というようなエラーが出るはずです。

というわけで本来は UniqueID を設定しなければなりませんが、UniqueID はサーバー側で生成されますので、サーバー側で動的に設定した方が簡単そうです。

ただし、タイミングが問題です。自分が試した限りですが、Page_Load ではうまくいかず、Page_Init でないとダメでした。(Page_Load で設定すると UpdateMode="Conditional" ではスクリプトエラー、デフォルトの Always にすればスクリプトエラーは出ないものの偶数回目のクリックで全画面が更新されてしまうというように、期待した動きにはなりません)

しかしながら、目的の(トリガとする)コントロールの参照が Page_Init では取得できない場合が困ります。例えば DetailesView の Template 内に配置したコントロールを FindControl メソッドで取得しようとしても、Page_Init では取得できません。

で、どうするかと言えば、ちょっとハック的なやり方ですが、Page_Init ではダミーのコントロールを設定し、Page_Load で目的のコントロールを設定するとうまくいきました。

DetailsView の Template に配置した Button を、AsyncPostBackTrigger クラスの ControlID プロパティに設定するサンプルを以下にアップしておきます。

<%@ 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 Page_Init(object sender, EventArgs e)
  {
    // ここでは LinkButton1 取得できない。以下は常に null。
    // それはよしとして、不可解なのは、以下の一文を入れて
    // おくと LinkButton1 クリックで、DetailsView が消えて
    // しまうこと。何故???
    //LinkButton lb =
    //  (LinkButton)DetailsView1.FindControl("LinkButton1");

    // この時点でダミーでもいいから以下のように Trigger を追
    // 加しておかないとうまくいかない。これなしでページロード
    // でトリガを追加すると、LinkButton1 クリックでスクリプト
    // エラーとなる。
    AsyncPostBackTrigger trigger = new AsyncPostBackTrigger();
    trigger.ControlID = DummyButton.ID;
    UpdatePanel2.Triggers.Add(trigger);
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    LinkButton lb = 
      (LinkButton)DetailsView1.FindControl("LinkButton1");
    if (lb != null)
    {
      AsyncPostBackTrigger trigger = new AsyncPostBackTrigger();

      // DetailsView に配置したボタンは UniqueID を持つ。
      // それを ControlID に設定しないとエラーになる。
      trigger.ControlID = lb.UniqueID;
      UpdatePanel2.Triggers.Add(trigger);
    }        
  }    

  protected void LinkButton1_Click(object sender, EventArgs e)
  {
    Label1.Text = DateTime.Now.ToString();
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>AsyncPostBackTrigger を動的に設定</title>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server">
  </asp:ScriptManager>

  <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:MyDB %>" 
    SelectCommand="SELECT [id], [name], [price] FROM [table]" >
  </asp:SqlDataSource>

  <asp:Button ID="DummyButton" 
    runat="server" 
    style="display: none;" />
  <h2>AsyncPostBackTrigger を動的に設定</h2>
  <h3>DetailsView</h3>
  <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
      <asp:DetailsView ID="DetailsView1" 
        runat="server" 
        AutoGenerateRows="False" 
        DataKeyNames="id" 
        DataSourceID="SqlDataSource1" 
        EnableModelValidation="True" 
        AllowPaging="True" 
        Width="250px">
        <Fields>
          <asp:BoundField DataField="id" 
            HeaderText="id" 
            InsertVisible="False" 
            ReadOnly="True" 
            SortExpression="id" />
          <asp:BoundField DataField="name" 
            HeaderText="name" 
            SortExpression="name" />
          <asp:BoundField DataField="price" 
            HeaderText="price" 
            SortExpression="price" />
          <asp:TemplateField ShowHeader="False">
            <ItemTemplate>
              <asp:LinkButton ID="LinkButton1" 
                runat="server" 
                CausesValidation="False" 
                Text="Show Current Time" 
                OnClick="LinkButton1_Click">
              </asp:LinkButton>
            </ItemTemplate>
          </asp:TemplateField>
        </Fields>
      </asp:DetailsView>            
    </ContentTemplate>
  </asp:UpdatePanel>
  <hr />
  <h3>Time</h3>
  <asp:UpdatePanel ID="UpdatePanel2" 
    runat="server" 
    UpdateMode="Conditional">
    <ContentTemplate>
      <asp:Label ID="Label1" 
        runat="server" 
        Text="[time]" />
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>

Tags: , ,

AJAX

ACT の TabContainer

by WebSurfer 2010年11月5日 21:04
ACT の TabContainer と Calendar の問題

AJAX Control Toolkit の TabContainer や Calendar を使用する際、head 要素内にコードブロック(即ち、<% ... %>)を定義すると例外がスローされるという問題と回避策の紹介です。なお、この問題は .NET 4 でも同様に発生します。

Calendar や TabContainer は AjaxControlToolkit.ScriptObjectBuilder クラスの RegisterCssReferences メソッドを起動し、head タグ内に HTML link 要素(CSS ファイルの定義)を追加しようとします。その時に head タグ内にコードブロックがあると、画像に示したように例外をスローします。

この問題を再現する具体的なコード例を下に載せておきます。この例のように JavaScript を定義する際に ClientID を取得するため、コードブロックを埋め込むケースはよくあるのではないでしょうか。

回避策は書くまでもないですが、コードブロックを head タグの外に移動するだけです。

なお、'<%=TextBox1.ClientID%>' の代わりに、実際の ClientID をプログラマがハードコーディングするのは避けたほうがよさそうです。何故なら、コントロールを配置する場所によって Client ID は変化しますし、コードを変更しなくても将来の ASP.NET の仕様の変更で変わるかもしれませんので。

<%@ Page Language="C#" %>
<%@ Register Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit" 
    TagPrefix="asp" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
    <script type="text/javascript">   
    <!--
        function ShowText() {
            var txtbx = $get('<%=TextBox1.ClientID%>');
            txtbx.value = 'これはテストです。';
        }
    //-->
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ToolkitScriptManager ID="ToolkitScriptManager1" 
            runat="server">
        </asp:ToolkitScriptManager>
        <asp:TextBox ID="TextBox1" 
            runat="server" 
            Text="Test" />
        <asp:Button ID="Button1" 
            runat="server" 
            Text="Button" 
            OnClientClick="ShowText(); return false;" />
        <asp:TabContainer ID="TabContainer1" 
            runat="server" 
            ActiveTabIndex="0" 
            Height="160px" 
            Width="360px">
            <asp:TabPanel runat="server" 
                HeaderText="TabPanel1" 
                ID="TabPanel1">
                <ContentTemplate>ABCDE</ContentTemplate>
            </asp:TabPanel>
            <asp:TabPanel ID="TabPanel2" 
                runat="server" 
                HeaderText="TabPanel2">
                <ContentTemplate>FGHIJ</ContentTemplate>
            </asp:TabPanel>
        </asp:TabContainer>
    </div>
    </form>
</body>
</html>

Tags: , ,

AJAX

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar