WebSurfer's Home

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

table のヘッダ、列を固定

by WebSurfer 2011年1月23日 15:17
2017/8/16 注記追加
Windows 10 IE11 では Quirks モード(IE5 相当)にしても expression 関数が働かないようで、テーブルのヘッダ・列は固定されませんのでご注意ください。この記事はもう意味がないかもしれませんが、せっかく書いたので残しておきます。

MSDN フォーラムなどで、IE の互換モード(正確には Quirks モードという IE5 以前のレンダリングエンジン)で動く table のヘッダ(tr 要素)を固定する "Freezing" という名前の css に関する質問を時々見かけます。

これは IE 独自拡張の expression 関数を使ったものですが、ListView でヘッダーを 2 行にした場合にも適用できるかどうか試してみました。結果は下の画像のように期待通り表示されました。

table のヘッダ(tr 要素)を固定

ただし、ヘッダーだけでなく列も固定すると、何故か固定した部分の border の幅が広くなってしまい、それを解決する方法が見つかっていないのが悔しいところですが。(汗)

上の画像を作ったコードは以下の通りです。Microsoft が提供している Northwind サンプルデータベースの Products テーブルを使用しています。

<%@ 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">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title></title>
  <%--Quirks モードに設定--%>
  <meta http-equiv="X-UA-Compatible" content="IE=5" />
  <style type="text/css">
    .FreezingHeader
    {
      z-index: 10;
      position: relative;
      top: expression(this.offsetParent.scrollTop);
      background-color: #0000cc; /* ヘッダ部分の border の色 */
    }

    .FreezingCol
    {
      z-index: 1;
      left: expression(document.getElementById("freezingDiv").scrollLeft);
      position: relative;
      background-color: white;
    }

    #freezingDiv
    {
      overflow: auto;
      width: 400px;
      height: 300px;
    }

    table.style1
    {
      border-style: none; /* 指定するとスクロールでずれる */
      text-align: center;
      border-collapse: collapse;            
    }
       
    table.style1 th
    {
      border-style: solid;
      border-width: 2px;
      border-color: #0000cc;
      background-color: #6699FF;
      color: #FFFFFF;
      padding: 5px;            
    }
       
    table.style1 td
    {
      border-style: solid;
      border-width: 2px;
      border-color: #0000cc;
      padding: 5px;
    }

  </style>
</head>
<body>
  <form id="form1" runat="server">
  <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:Northwind %>" 
    SelectCommand=
        "SELECT [ProductID], [ProductName], [QuantityPerUnit], [UnitPrice], [UnitsInStock] 
        FROM [Products]">
  </asp:SqlDataSource>
  <div id="freezingDiv">
    <asp:ListView ID="ListView1" 
      runat="server" 
      DataKeyNames="ProductID" 
      DataSourceID="SqlDataSource1" 
      EnableModelValidation="True">
      <ItemTemplate>
        <tr>
          <td class="FreezingCol">
            <asp:Label ID="ProductIDLabel" 
              runat="server" 
              Text='<%# Eval("ProductID") %>' />
          </td>
          <td class="FreezingCol">
            <asp:Label ID="ProductNameLabel" 
              runat="server" 
              Text='<%# Eval("ProductName") %>' />
          </td>
          <td>
            <asp:Label ID="QuantityPerUnitLabel" 
              runat="server" 
              Text='<%# Eval("QuantityPerUnit") %>' />
          </td>
          <td>
            <asp:Label ID="UnitPriceLabel" 
              runat="server" 
              Text='<%# Eval("UnitPrice") %>' />
          </td>
          <td>
            <asp:Label ID="UnitsInStockLabel" 
              runat="server" 
              Text='<%# Eval("UnitsInStock") %>' />
          </td>
        </tr>
      </ItemTemplate>
      <LayoutTemplate>
        <table ID="itemPlaceholderContainer" 
          runat="server" 
          class="style1">
          <tr runat="server" class="FreezingHeader">
            <th runat="server" colspan="2" class="FreezingCol">
              ID and Name</th>
            <th runat="server" colspan="3">
              Details of Products</th>
          </tr>
          <tr runat="server" class="FreezingHeader">
            <th runat="server" class="FreezingCol">
              ProductID</th>
            <th runat="server" class="FreezingCol">
              ProductName</th>
            <th runat="server">
              QuantityPerUnit</th>
            <th runat="server">
              UnitPrice</th>
            <th runat="server">
              UnitsInStock</th>
          </tr>
          <tr ID="itemPlaceholder" runat="server">
            </tr>
        </table> 
      </LayoutTemplate>
    </asp:ListView>
  </div>
  </form>
</body>
</html>

個人的には IE 専用のハック的な方法と思っていますので、これを実際に使うことはなさそうですが、こういったこともできるということでご参考まで。

------------ 2011/4/29 追記 ------------

上の画像のように固定した部分の border の幅が広くなってしまう問題は、以下のように、class="FreezingCol" を付与した th, td 要素に inline スタイルで border の幅を指定してやることで解決できます。

・・・前略・・・
<ItemTemplate>
  <tr>
    <td class="FreezingCol" style="border-width: 1 1 1 2;">
      <asp:Label ID="ProductIDLabel" 
        runat="server" 
        Text='<%# Eval("ProductID") %>' />
    </td>
    <td class="FreezingCol" style="border-width: 1 1 1 1;">
      <asp:Label ID="ProductNameLabel" 
        runat="server" 
        Text='<%# Eval("ProductName") %>' />
    </td>
    ・・・中略・・・
</ItemTemplate>
<LayoutTemplate>
  <table ID="itemPlaceholderContainer" 
    runat="server" 
    class="style1">
    <tr runat="server" class="FreezingHeader">
      <th runat="server" colspan="2" class="FreezingCol" style="border-width: 2 1 1 2;">
        ID and Name</th>
      <th runat="server" colspan="3">
         Details of Products</th>
    </tr>
    <tr runat="server" class="FreezingHeader">
      <th runat="server" class="FreezingCol" style="border-width: 1 1 1 2;">
        ProductID</th>
      <th runat="server" class="FreezingCol" style="border-width: 1 1 1 1;">
        ProductName</th>
・・・後略・・・

Tags: , ,

ASP.NET

二度押し防止

by WebSurfer 2010年12月12日 12:20

UpdatePanel を利用した非同期ポストバックを行う際に、最初の部分ページ更新が完了するまで 2 回目以降のリクエストをキャンセルする(いわゆる、ボタンの二度押し防止)方法の紹介です。

二度押し防止

その前に、ちょっと前置きが長くなりますが、それを実現するために利用する PageRequestManager クライアントオブジェクトについて説明します。

ASP.NET AJAX Extensions の ScriptManager と UpdatePanel コントロールを使用して部分ページ更新を行う場合、自力でクライアントスクリプトを書く必要はありません。

ただし、以下ような操作を行う場合は、クライアント側で適切なタイミングで発生するイベントを利用して、クライアントスクリプトで処置する必要があります。

  1. 複数の非同期ポストバックの処理方法を制御する。
  2. 部分ページ更新のリクエストをキャンセルする。
  3. 部分ページ更新のプログレスを表示する。
  4. 部分ページ更新時にデータを受け渡す。
  5. クライアントスクリプトでエラーを処理する。

開発者が XMLHttpRequest オブジェクトを直接管理している場合は、リクエストとレスポンスを完全に制御して、上記のような操作ができるらしいです。

しかしながら、自分には「XMLHttpRequest オブジェクトを直接管理」などということは無理です。だから ASP.NET AJAX Extensions を利用しているわけですし。

従って、XMLHttpRequest オブジェクトを管理している ASP.NET AJAX Extensions がイベントモデルを提供してくれない限り、自分には何もできません。

ScriptManager と UpdatePanel コントロールを使用しての非同期ポストバックは、内部で XMLHttpRequest オブジェクトを呼び出す PageRequestManager クライアントオブジェクトが関係しています。

PageRequestManager が提供している以下のイベントを利用して、開発者が部分ページ更新のリクエストとレスポンスの処理をカスタマイズできます。

  • initializeRequest
  • beginRequest
  • pageLoading
  • pageLoaded
  • endRequest

詳しくは MSDN ライブラリの ASP.NET PageRequestManager クラスの概要 および PageRequestManager のイベントの処理 を参照してください。

前置きが長くなりましたが、ここでは、上記 1 項の「複数の非同期ポストバックの処理方法を制御する」でボタンの二度押しを防止する方法と、上記 2 項の「部分ページ更新のリクエストをキャンセルする」で未完了のリクエストをユーザーがキャンセルできる方法の例を示したコードを紹介します。

<%@ 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 Button_Click(object sender, EventArgs e)
  {
    System.Threading.Thread.Sleep(5000);
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>二度押し防止</title>
  <style type="text/css">
    #UpdatePanel1, #UpdatePanel2, #UpdatePanel3 { 
      width: 200px; 
      height: 250px; 
      border: gray 1px solid;
      position: relative;
      float: left; 
      margin-left: 10px; 
      margin-top: 10px;
    }
    #UpdateProgress1 {
      width: 400px; 
      background-color: #FFC080;
      border: gray 1px solid;
      bottom: 0%; 
      left: 0px; 
      position: absolute;
    }
    .labalposition {
      bottom: 0%;
      left: 0px; 
      position: absolute;
    }
  </style>
  <script type="text/javascript">
    <!--
    var manager;
    var initiatingButtonId;

    function pageLoad(sender, args) {
      if (args.get_isPartialLoad() === false) {
        manager = Sys.WebForms.PageRequestManager.getInstance();
        manager.add_initializeRequest(OnInitializeRequest);
        manager.add_endRequest(OnEndRequest);
      }
    }

    function OnInitializeRequest(sender, args) {
      if (manager.get_isInAsyncPostBack()) {
        var buttonId = args.get_postBackElement().id.toLowerCase();
        if (buttonId == "button1" && 
          initiatingButtonId == "button1") {
          $get("Label1").innerHTML = "このパネルの更新中";
        }
        else if (buttonId == "button1" && 
          initiatingButtonId == "button2") {
          $get("Label1").innerHTML = "Panel B 更新中";
        }
        else if (buttonId == "button2" && 
          initiatingButtonId == "button1") {
          $get("Label2").innerHTML = "Panel A 更新中";
        }
        else if (buttonId == "button2" && 
          initiatingButtonId == "button2") {
          $get("Label2").innerHTML = "このパネルの更新中";
        }

        args.set_cancel(true);
      }
      else {
        initiatingButtonId = 
          args.get_postBackElement().id.toLowerCase();
      }
    }

    function OnEndRequest(sender, args) {
      $get("Label1").innerHTML = "";
      $get("Label2").innerHTML = "";
    }

    function AbortPostBack() {
      if (manager.get_isInAsyncPostBack()) {
        manager.abortPostBack();
      }
    }
    //-->
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server">
  </asp:ScriptManager>
  <asp:UpdatePanel ID="UpdatePanel1" 
    UpdateMode="Conditional" 
    runat="server">
    <ContentTemplate>
      Panel A
      <hr />            
      <%=DateTime.Now.ToString() %> <br />
      <asp:Button ID="Button1" 
        runat="server" 
        Text="Refresh Panel" 
        OnClick="Button_Click" />    
      <br />
      前の非同期ポストバック処理が終了しないと、
      次の非同期ポストバックはできません。
      ボタンをクリックしてもキャンセルされます。
      <br />
      <asp:Label ID="Label1" 
        runat="server" 
        CssClass="labalposition" />
        </ContentTemplate>
  </asp:UpdatePanel>

  <asp:UpdatePanel ID="UpdatePanel2" 
    UpdateMode="Conditional" 
    runat="server">
    <ContentTemplate>
      Panel B
      <hr />
      <%=DateTime.Now.ToString() %> <br />
      <asp:Button ID="Button2" 
        runat="server" 
        Text="Refresh Panel" 
        OnClick="Button_Click"/>
      <br />
      こちらも、左のパネルと同様、
      前の非同期ポストバック処理が終了しないと、
      ボタンをクリックしてもポストバックはキャンセルされます。
      <asp:Label ID="Label2" 
        runat="server" 
        CssClass="labalposition" />    
    </ContentTemplate>
  </asp:UpdatePanel>    
  <asp:UpdateProgress ID="UpdateProgress1" 
    runat="server">
    <ProgressTemplate>
      非同期ポストバックで更新中です・・・  
      <input type="button" 
        value="stop" 
        onclick="AbortPostBack()" />            
    </ProgressTemplate>
  </asp:UpdateProgress>
  </form>
</body>
</html>

通常のポストバックにおける二度押し防止では、制御対象のボタンがクリックされた時に document.readyState が complete になっていない場合を通信中と判断し、通信処理を行わないように制御する手段が取られることがあります。しかしながら、UpdatePanel を利用した非同期ポストバックでは document.readyState は即 complete となるため、その方法は使えませんので注意してください。

他に、上記 4 項の「部分ページ更新時にデータを受け渡す」例が、先の記事 ModalPopup で編集・更新操作 に書いてありますので、興味があればそちらも見てください。pageLoaded イベントと ScriptManager の RegisterDataItem メソッドを使っています。

------------ 2010/4/14 追記 ------------

この記事で紹介したコードを実際に動かして試せるよう 実験室 にアップしました。興味のある方は試してみてください。

Tags: ,

AJAX

多言語対応カスタムコンロトール

by WebSurfer 2010年12月6日 21:33

リソースファイルを用いて多言語対応させた Web カスタムコントロールの話です。

多言語対応カスタムコンロトール

Culture, UICulture の値として "auto" を設定した場合には、ASP.NET がリクエスト情報に含まれる Accept-Language ヘッダの内容から判断して、自動的にカルチャを特定してくれます。

上の画像は、IE8 で言語をフランス語(フランス) fr-FR に設定してページを要求した結果、サーバー側でカルチャが fr-FR と判断され、カスタムコントロールのリソースファイル Resource.fr-FR.resx に定義されたフランス語のリソース(今回の例では、文字列 Adresse e-mail, Enregister)が表示されたものです。

この例に使ったカスタムコントロールの構成は以下の通りです。リソースの基になるのが Resource.resx で、これはソリューションエクスプローラ上で手動で追加します。その下の Resource.Designer.cs は「厳密に型指定されたリソースクラス」というもので、Resource.resx を追加すると自動生成されます。

カスタムコントロールの構成

Resource.resx にはデフォルトのリソースを定義します。今回の例では、下の画像に示すように LabelText, ButtonText という名前の 2 つの文字列です。要求されたカルチャのリソースファイルがない場合(この例では en-US, fr-FR, ja-JP 以外が指定された場合)、Resource.resx に定義されたリソースが使われます。

デフォルトのリソース

Resource.Designer.cs には Resource という名前のクラスが定義され、その中にカルチャを設定する Culture 静的プロパティ、ローカライズされたリソースを取得する静的プロパティ(この例では Resource.resx で設定した LabelText, ButtonText と同じ名前のプロパティ)が定義されます。

ローカライズされたリソースは個別のリソースファイルを追加して定義します。例えば、フランス語(フランス)の場合は Resource.fr-FR.resx という名前のリソースファイルを追加し、以下の画像のように Resource.resx で設定した LabelText, ButtonText と同じ名前でフランス語の文字列を設定します。

フランス語(フランス)の場合の Resource.fr-FR.resx

今回の例に用いた MyServerControl.cs の内容は以下の通りです。以下のコードで、Resource というのが Resource.Designer.cs で定義される「厳密に型指定されたリソースクラス」です。

Resource.Culture 静的プロパティで現在のカルチャを設定し、設定されたカルチャのリソースファイルからローカライズされた文字列を、Resource.LabelText, Resource.ButtonText 静的プロパティを用いて取得しています。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Threading;
using System.Globalization;

namespace MultiLanguageServerControl
{
  public class MyServerControl : WebControl
  {
    public virtual string LabelText
    {
      get
      {
        string s = (string)ViewState["LabelText"];
        if (s == null)
        {
          Resource.Culture = Thread.CurrentThread.CurrentUICulture;
          s = Resource.LabelText;
        }
        return s;
      }
      set
      {
        ViewState["LabelText"] = value;
      }
    }

    public virtual string ButtonText
    {
      get
      {
        string s = (string)ViewState["ButtonText"];
        if (s == null)
        {
          Resource.Culture = 
              Thread.CurrentThread.CurrentUICulture;
          s = Resource.ButtonText;
        }
        return s;
      }
      set
      {
        ViewState["ButtonText"] = value;
      }
    }

    protected override HtmlTextWriterTag TagKey
    {
      get
      {
        return HtmlTextWriterTag.Table;
      }
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
      StringBuilder sb = new StringBuilder();
      sb.AppendLine("<tr>");
      sb.AppendFormat("<td><span id=\"Label1\">{0}</span></td>", 
        LabelText);
      sb.Append("<td><input type=\"text\" id=\"TextBox1\" /></td>");
      sb.AppendFormat(
        "<td><input type=\"submit\" value=\"{0}\" id=\"Button1\" /></td>", 
        ButtonText);
      sb.AppendLine("</tr>");
      writer.Write(sb.ToString());
    }
  }
}

このソリューションをビルドすると、以下の��うな dll が生成されます。この MultiLanguageServerControl.dll と en-US, fr-FR, ja-JP フォルダを中身ごと Web アプリケーションの Bin フォルダにコピーします。

ソリューションをビルドした結果

aspx ページでは以下のように Culture, UICulture を "auto" に設定し、ブラウザの言語を希望の言語(今回の例では、ja-JP または en-US または fr-FR)に設定してページを要求すれば、当該リソースファイルからローカライズされた文字列が取得されます。例えば fr-FR とすれば、一番上の画像のように表示されます。(設定を忘れるとどうなるかは、この記事の下の方の「2016/6/17 追記」を見てください)

<%@ Page Language="C#" Culture="auto" UICulture="auto" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Import Namespace="System.Threading" %>
<%@ Register Assembly="MultiLanguageServerControl" 
  Namespace="MultiLanguageServerControl" 
  TagPrefix="cc2" %>

<!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_Load(object sender, EventArgs e)
  {
    CultureInfo ci = Thread.CurrentThread.CurrentUICulture;
    Label1.Text = "現在の CultureInfo: " + ci.ToString();
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Label ID="Label1" runat="server" />
    <br /><br />
    <cc2:MyServerControl runat="server" />
  </div>
  </form>
</body>
</html>

aspx ページ本体の多言語化の話は別途・・・というより、@IT の記事 リソース・ファイル活用で国際化対応ページを構築するには? を見たほうが早いかも。

-------- 2016/6/17 追記 --------

Culture, UICulture を "auto" に設定すると、ASP.NET は、ブラウザから送信されてくる要求ヘッダに含まれる Accept-Language の設定を調べて、その要求を処理するスレッドのカルチャを Accept-Language に設定されているカルチャに書き換えるようです。

そして、実行時に、リソースマネージャが要求を処理しているスレッドのカルチャ情報を参照してローカライズされたリソースを検索し、UI に表示されるテキストを取得するという仕組みになっています。

Culture, UICulture を "auto" に設定するのを忘れるとブラウザの言語設定は無視されます。デフォルトではシステムのロケールに該当するカルチャがスレッドに設定されますので、例えば日本語 OS で xxx.ja-JP.resx というリソースがあれば、常にそれから UI に表示されるテキストを取得します。

Web サイトが日本語専用でサーバーも日本にあれば忘れても問題ないかもしれませんが、ホスティングサービス(Azure も含む)でサーバーが外国にある場合は Culture, UICulture を "auto" に設定するのを忘れると問題が出ると思います。

ロケール、カルチャ、Culture と UICulture の違いなどについては、記事「カルチャの基本とカルチャ情報」が参考になりましたので、忘れないようにリンクを張っておきます。

Tags: , ,

Web Custom Control

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar