by WebSurfer
2011年5月24日 22:40
下の画像のように、複数連続していた半角スペースが、非同期ポストバックで再描画されると 1 文字になってしまうという話です。
Label に表示する文字列の中の連続する半角スペースをブラウザ上でそのまま表示したいので、Label コントロールの CssClass に white-space: pre; と設定していたとします。さらに、その Label コントロール を UpdatePanel に配置していたとします。
初期画面では、半角スペースは設定したとおり連続してブラウザに表示されます(white-space: pre; の設定がないと、複数連続した半角スペースを Label に設定しても、ブラウザに表示された時は一個になってしまいます)。
ところが、非同期ポストバックをかけて UpdatePanel 内を部分更新すると、初期画面と同様に複数連続した半角スペースを Label に設定しても、ブラウザに表示された時は一個になってしまいます。もちろん CssClass の white-space: pre; の設定は有効な状態でです。
なお、これはブラウザに依存する問題で、IE6 と IE7 で発生します(それ以前のバージョンは未検証)。IE8, IE9, Firefox, Chrome, Safari は期待通り UpdatePanel 内を部分更新しても問題ありません。
何故でしょう?
最初は ASP.NET AJAX に関係する問題と思っていたのですが、そうではなかったです。ヒントはブラウザに依存するというところです。
原因は AJAX の部分レンダリングの際、JavaScript によって innerHTML を書き換えるとき、JavaScript が複数スペースを一個にしてしまうところにありました。
書き換えられた後は一個しかスペースがないので、いくら white-space: pre; としても複数のスペースは表示されないというわけでした。
という訳で、ASP.NET AJAX とは関係ありませんでした。IE6, IE7 で JavaScript を使って innerHTML を書き換えてやるだけで、この問題は再現できます。例えば、以下のコードのように。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Rewrite innerHTML with multiple white space</title>
<script type="text/javascript">
//<![CDATA[
var str = "<h1>半角スペース> <5 文字??</h1>";
function RewriteInnerHTML(id, innerHtml){
document.getElementById(id).innerHTML = innerHtml;
}
//]]>
</script>
</head>
<body>
<div id="div1" style="white-space: pre;">
<h1>半角スペース> <5 文字</h1>
</div>
<input type="button"
value="innerHTML 書き換え"
id="button1"
onclick="RewriteInnerHTML('div1', str);" />
</body>
</html>
上記のコードを IE6 で実行し、[innerHTML 書き換え]ボタンをクリックした時のものがこの記事の一番上に示した画像です。
9d82bcbf-06f2-4129-9439-66a516b157e8|1|4.0
Tags: AJAX
AJAX
by WebSurfer
2011年5月22日 17:52
応答のエンコーディングを UTF-16 にする場合、MSDN ライブラリの globalization 要素 (ASP.NET 設定スキーマ) に、requestEncoding 属性と responseEncoding 属性は同じにする必要があると書いてあるので、以下のように両方 UTF-16 に設定してしまうことがあるかもしれません。
<configuration>
<system.web>
<globalization
requestEncoding="utf-16"
responseEncoding="utf-16"/>
</system.web>
</configuration>
でも、そうすると、サーバー側でポストデータが正しく取得できず、IsPostPack プロパティが true にならなかったり、Button.Click などのイベントが発生しないという問題が起こります。
原因は、以下のマイクロソフトサポートオンラインのページに詳しく書いてありますが、IE が submit するデータのエンコーディングです。
INFO: Internet Explorer Always POSTs Unicode Data as UTF-8
IE は、ページのエンコーディングが UTF-16 の場合、submit するデータは UTF-8 でエンコーディングするそうです。
データのエンコーディングが UTF-8 なのに、requestEncoding="utf-16" と設定されているため、サーバー側が UTF-16 として処置するので、データが正しく取得できないということのようです。
IE 以外のブラウザ(Firefox 3.6.17 と Chrome 11.0.696.68)でも試してみましたが、結果は同じでした。
ちなみに Shift_JIS の場合は、ページのエンコーディングが Shift_JIS ならブラウザが submit するデータのエンコーディングも Shift_JIS になるので、UTF-16 の時のような問題ないそうです(というより、requestEncoding と responseEncoding の両方を Shift_JIS としておかないとダメ)。
解決策は、globalization 要素の requestEncoding="utf-16" を削除(デフォルトで UTF-8 になる)することです。
または、特定のページだげ応答のエンコーディングを UTF-16 にしたいのであれば、そのページのページディレクティブで ResponseEncoding="UTF-16" と設定し、その他はすべてデフォルトのまま(globalization 要素は指定しない)とすればよさそうです。
by WebSurfer
2011年4月30日 12:52
2017/8/16 注記追加
Windows 10 IE11 では Quirks モード(IE5 相当)にしても expression 関数が働かないようで、テーブルのヘッダ・列は固定されませんのでご注意ください。この記事はもう意味がないかもしれませんが、せっかく書いたので残し��おきます。
以前の記事 table のヘッダ、列を固定 では ListView を使ったときの例を書きましたが、ここでは GridView を使ったときの例を紹介します。 (2013/2/7 追記:このページに紹介したのは IE の互換モード(Quirks モード)専用ですが、GridView のヘッダ、列を固定(その 2)のページに IE7+ (標準モードの), Firefox, Chrome, Safari, Opera コンパチのものを書きましたので、そちらも見てください)
GridView も html にレンダリングされると table, tr, th, td などの要素になりますが、それらは自動的に(勝手に)生成され、かつ、直接スタイルを適用できないので結構面倒です。
今回は先の ListView を使った場合の例よりヘッダーを少し複雑にしました(一部のセルの rowspan を 2 にしてみました)ので、ますます面倒になっています。(笑)
という訳で、実際にはあまり役に立たないと思いますが、せっかく苦労して作ったので書いておくことにしました。
上の画像を出力したコードは下記の通りです。実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。
<%@ 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">
DataTable CreateDataTable()
{
DataTable dt = new DataTable();
DataRow dr;
dt.Columns.Add(new DataColumn("Code", typeof(Int32)));
dt.Columns.Add(new DataColumn("Name", typeof(string)));
dt.Columns.Add(new DataColumn("Price", typeof(Int32)));
dt.Columns.Add(new DataColumn("Qty", typeof(Int32)));
dt.Columns.Add(new DataColumn("Amount", typeof(Int32)));
dt.Columns.Add(new DataColumn("Remarks", typeof(string)));
for (int i = 0; i < 50; i++)
{
dr = dt.NewRow();
dr["Code"] = i;
dr["Name"] = "Item " + i.ToString();
dr["Price"] = 123000 * (i + 1);
dr["Qty"] = i + 1;
dr["Amount"] = 123000 * (i + 1) * (i + 1);
dr["Remarks"] = "Remarks " + i.ToString();
dt.Rows.Add(dr);
}
return dt;
}
void Page_Load(Object sender, EventArgs e)
{
if (!IsPostBack)
{
GridView1.DataSource = CreateDataTable();
GridView1.DataBind();
}
}
// RowDataBound で細工すると PostBack で表示が崩れるので
// 注意(ViewState との関係が崩れるらしい)
protected void GridView1_RowCreated(object sender,
GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Header)
{
System.Collections.Generic.List<TableCell> cells =
new System.Collections.Generic.List<TableCell>();
foreach (TableCell cell in e.Row.Cells)
{
cells.Add(cell);
}
GridViewRow row1 =
new GridViewRow(
-1,
-1,
DataControlRowType.Header,
DataControlRowState.Normal);
cells[0].RowSpan = 2;
cells[1].RowSpan = 2;
cells[5].RowSpan = 2;
TableHeaderCell headerCell = new TableHeaderCell();
headerCell.ColumnSpan = 3;
headerCell.Controls.Add(new LiteralControl("注文詳細"));
row1.Cells.Add(cells[0]);
row1.Cells.Add(cells[1]);
row1.Cells.Add(headerCell);
row1.Cells.Add(cells[5]);
GridViewRow row2 =
new GridViewRow(
-1,
-1,
DataControlRowType.Header,
DataControlRowState.Normal);
for (int i = 2; i < 5; i++)
{
row2.Cells.Add(cells[i]);
}
row1.CssClass = "FreezingHeader1";
row2.CssClass = "FreezingHeader2";
GridView1.Controls[0].Controls.Clear();
GridView1.Controls[0].Controls.Add(row1);
GridView1.Controls[0].Controls.Add(row2);
}
}
// position:relative を適用することにより border の
// 幅が変わってしまう。以下はその調整。
protected void GridView1_PreRender(object sender, EventArgs e)
{
// ヘッダは 3 行できるのでそれの識別用
int count = 0;
// GridView.Rows はデータ行の GridViewRow のみ
// GridView1.Controls[0].Controls はヘッダ、
// フッターも含む
foreach (GridViewRow row in GridView1.Controls[0].Controls)
{
if (row.RowType == DataControlRowType.Header)
{
if (count == 0)
{
row.Cells[0].Style["border-width"] = "2 1 1 2";
row.Cells[1].Style["border-width"] = "2 1 1 1";
// これがないとヘッダの "備考" のセルの下半分
// が切れてしまう。
// "コード" と "商品名" セルには FreezingCol
// に position:relative が含まれるので不用
row.Cells[3].Style["position"] = "relative";
row.Cells[3].Style["border-width"] = "2 2 1 1";
}
// 何故か自動でできてしまうヘッダの 3 行目を消去
if (count == 2)
{
row.Style["display"] = "none";
}
}
else if (row.RowType == DataControlRowType.DataRow)
{
if (row.RowIndex == GridView1.Rows.Count - 1)
{
row.Cells[0].Style["border-width"] = "1 1 2 2";
row.Cells[1].Style["border-width"] = "1 1 2 1";
}
else
{
row.Cells[0].Style["border-width"] = "1 1 1 2";
row.Cells[1].Style["border-width"] = "1 1 1 1";
}
}
else if (row.RowType == DataControlRowType.Footer)
{
// 今回、フッターはないので何もしない。
}
count++;
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<%--Quirks モードに設定--%>
<meta http-equiv="X-UA-Compatible" content="IE=5" />
<style type="text/css">
.FreezingHeader1
{
z-index: 10;
position: relative;
top: expression(this.offsetParent.scrollTop);
background-color: #0000cc;
}
.FreezingHeader2
{
z-index: 5;
position: relative;
top: expression(this.offsetParent.scrollTop);
background-color: #0000cc;
}
.FreezingCol
{
z-index: 1;
left: expression(
document.getElementById("freezingDiv").scrollLeft);
position: relative;
background-color: white;
}
#freezingDiv
{
overflow: auto;
width: 350px;
height: 200px;
}
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">
<div id="freezingDiv">
<asp:GridView ID="GridView1"
runat="server"
CssClass="style1"
Width="450px"
AutoGenerateColumns="False"
OnRowCreated="GridView1_RowCreated"
OnPreRender="GridView1_PreRender">
<Columns>
<asp:BoundField DataField="Code"
HeaderText="コード" >
<HeaderStyle CssClass="FreezingCol" />
<ItemStyle CssClass="FreezingCol" />
</asp:BoundField>
<asp:BoundField DataField="Name"
HeaderText="商品名" >
<HeaderStyle CssClass="FreezingCol" />
<ItemStyle CssClass="FreezingCol" />
</asp:BoundField>
<asp:BoundField DataField="Price"
HeaderText="単価" >
</asp:BoundField>
<asp:BoundField DataField="Qty"
HeaderText="数量" >
</asp:BoundField>
<asp:BoundField DataField="Amount"
HeaderText="合価" >
</asp:BoundField>
<asp:BoundField DataField="Remarks"
HeaderText="備考" >
</asp:BoundField>
</Columns>
</asp:GridView>
</div>
<asp:Button ID="Button1"
runat="server"
Text="PostBack" />
</form>
</body>
</html>