- PR -

[VS2005 ASP.NET C♯]GridViewの動的作成テンプレートのポストバック時の値取得について

投稿者投稿内容
dotnetmemo
常連さん
会議室デビュー日: 2006/04/29
投稿数: 24
投稿日時: 2007-02-19 08:41
こんにちは
Accessさんのリプライでほぼ解決しそうですが、参考までにコメントさせていただきます。

問題点としては2つ
・Postback時もPage_LoadのタイミングでGridViewの構造をCreateしている
・DataBinding(テンプレート)のタイミングでコントロールIDを設定している
です。

1つ目は、ViewStateの復元の前に前回のGridViewコントロールと同じ構造をCreateしておく必要があります。これはPage_Loadよりも前です。以下が参考になります。

DataGrid Web サーバー コントロールについてよく寄せられる質問
http://www.microsoft.com/japan/msdn/vs/WebApplication/vbtchTopQuestionsAboutASPNETDataGridServerControl.aspx

2番目のIDの設定は、DataBindingでIDを設定するということは、DataBindが行われない場合または前とIDが異なり正しくViewStateの情報を戻せないためです。

参考までに修正したソースを掲載しておきます。

public class inputGridViewTemplate : ITemplate
{
int count;

public inputGridViewTemplate(int _count)
{
count = _count;
}

public void InstantiateIn(Control container)
{
TextBox test = new TextBox();
test.DataBinding += new EventHandler(test_DataBinding);
container.Controls.Add(test);
}
public void test_DataBinding(object sender, EventArgs e)
{
System.Web.UI.WebControls.TextBox test = (System.Web.UI.WebControls.TextBox)sender;
GridViewRow container = (GridViewRow)test.NamingContainer;
test.Text = DataBinder.Eval(container.DataItem, "COL" + count).ToString();
}
}


public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
int count = 4;

ViewState["GridColumnCount"] = count;
CreateGridView(count);

// データソース設定
GridView1.DataSource = GetData(count);
// 入力情報のバインド実行
GridView1.DataBind();
}
}

protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
CreateGridView((int)ViewState["GridColumnCount"]);

}
private void CreateGridView(int columnCount)
{
// カウント数分テンプレートフィールドを作成
for (int i = 0; i < columnCount; i++)
{
TemplateField tFieldTest = new TemplateField();
tFieldTest.ItemTemplate = new inputGridViewTemplate(i);
GridView1.Columns.Add(tFieldTest);
}
}

private DataTable GetData(int count)
{
DataTable dt = new DataTable();
for (int i = 0; i < count; i++)
{
dt.Columns.Add("COL" + i.ToString());

}

for (int j = 0; j < 2; j++)
{
DataRow row = dt.NewRow();
for (int j2 = 0; j2 < count; j2++)
{
row[j2] = j.ToString() + "X" + j2.ToString();
}
dt.Rows.Add(row);
}
return dt;
}
}

のぉりぃ
会議室デビュー日: 2007/02/15
投稿数: 14
投稿日時: 2007-02-19 14:25
>dotnetmemoさん、Accessさん、べるさん
返答ありがとうございます。
おかげさまで、どうすればよいかはわかりました。
テキストボックスのプロパティもText以外はInstantiateInで行うようにしました。

しかし、Initでテンプレートフィールドを追加しているのにもかかわらず、ボタンクリックのイベントハンドラの中でFindControlをしたところ、Controlが見つかりません。
「オブジェクトがインスタンスに設定されていません」のエラーがでます。
コントロールはきちんと追加されてるはずなのですが。。。
InstantiateIn処理をきちんと通っているので。

もう少しでできそうなんですが、なかなかうまくいきません。
うまくいきましたら、また報告させていただきます。
のぉりぃ
会議室デビュー日: 2007/02/15
投稿数: 14
投稿日時: 2007-02-19 23:33
うまくいきました。

OnInitでTemplateFiled追加。これだけでできました。
返答をしていただいたみなさん、ありがとうございます。

>Accessさん、dotnetmemo
わざわざサンンプル、修正ソースを作ってもらってご迷惑をお掛けしました。
私の理解力があればこんなことにはならなかったのですが。。。

今回はとても勉強になりました。
基礎知識を勉強する必要があることにも気付きました。

みなさん、貴重な返答を本当にありがとうございました。
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-02-20 01:23
解決したようで何よりですが、ひとつ。

Init で行う必要があるという意見が多数を占めてますが、Load で大丈夫です。
でないと「動的作成コントロールは全て Load イベント直前までに作れ」という話になってしまいます。

(1)PreInit イベント
(2)Init イベント
(3)InitComplete イベント
(4)LoadViewState メソッド呼び出し
(5)LoadPostData メソッド呼び出し
(6)PreLoad イベント
(7)Load イベント
(8)動的作成コントロールの LoadViewState メソッド呼び出し
(9)LoadPostData メソッド呼び出し
(10)LoadComplete イベント
(11)PreRender イベント
(12)PreRenderComplete イベント
(13)SaveViewSate メソッド呼び出し
(14)SaveStateComplete イベント
(15)Unload イベント

イベントとメソッド呼び出しを混在させてますが、ASP.NET 2.0 の場合は、ページライフサイクルの流れは概ねこんな感じです(ASP.NET 1.x の場合はもっとイベントは少ない)。
注目して欲しいのは、LoadPostData が Load の前後で呼ばれている事です。ASP.NET がこのように LoadPostData の呼び出しを二回行っているのは、Load イベントでコントロールが動的に作成される可能性がある事を認識しているからです。
つまり、一回目の LoadPostData で Post データをセットすべきコントロールがない場合は、それを別のコレクションに保持しておき、二回目の LoadPostData で再セットしようとします。

で、値が変わらないと言っている場合は、
(1)二回目の LoadPostData の前に値を確認しようとしている。
(2)二回目の LoadPostData の後にデータバインドして、値を確認している。
のどちらかのはずです。

dotnetmemo さんが掲載してくれたコードを更に改修すると、
コード:

public class inputGridViewTemplate : ITemplate {
int count;

public inputGridViewTemplate(int _count) {
count = _count;
}

public void InstantiateIn(Control container) {
TextBox test = new TextBox();
test.DataBinding += new EventHandler(test_DataBinding);
container.Controls.Add(test);
}
public void test_DataBinding(object sender, EventArgs e) {
System.Web.UI.WebControls.TextBox test = (System.Web.UI.WebControls.TextBox)sender;
GridViewRow container = (GridViewRow)test.NamingContainer;
test.Text = DataBinder.Eval(container.DataItem, "COL" + count).ToString();
test.ID = "TextBox" + count.ToString();
}
}

public partial class _Default : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
int count = 4;
CreateGridView(count);
GridView1.DataSource = GetData(count);
GridView1.DataBind();
}

private void CreateGridView(int columnCount) {
for (int i = 0; i < columnCount; i++) {
TemplateField tFieldTest = new TemplateField();
tFieldTest.ItemTemplate = new inputGridViewTemplate(i);
GridView1.Columns.Add(tFieldTest);
}
}

private DataTable GetData(int count) {
DataTable dt = new DataTable();
for (int i = 0; i < count; i++) {
dt.Columns.Add("COL" + i.ToString());

}

for (int j = 0; j < 1; j++) {
DataRow row = dt.NewRow();
for (int j2 = 0; j2 < count; j2++) {
row[j2] = j.ToString() + "X" + j2.ToString();
}
dt.Rows.Add(row);
}
return dt;
}

protected void Button1_Click(object sender, EventArgs e) {
// (0,0) の TextBox に "aiueo" と入力したら例外にならない。
// つまりこの時点で TextBox に入力した値がとれる。

TextBox t = (TextBox)GridView1.Rows[0].FindControl("TextBox0");
if (t.Text != "aiueo") {
throw new ApplicationException("ガーン!");
}
}
}


ボタンを一つ置いてクリックしたら、クリックイベントで問題なくユーザーが入力した値が取得できています。
どこで誤っていたのかはのぉりぃさんしか知りませんが、上記と比べてどこが間違っていたかわかるでしょうか?


最後に非常に余計なお世話だと思いますが、いろいろごちゃごちゃする前にまず問題を単純化すれば、難しい問題というのはそうはありません。
TextBox を作成するタイミングが悪いのか?
TextBox の値を確認するタイミングが悪いのか?
データバインドするタイミングが悪いのか?
テンプレートの作り方がわるいのか?
ビューステート?ポストデータ?ポストバック?
etc…。

まずは単純なコードで何が原因なのかを突き止めましょう。そうしたら解法はスグです。


[ メッセージ編集済み 編集者: 囚人 編集日時 2007-02-20 01:26 ]
Access
ぬし
会議室デビュー日: 2002/04/08
投稿数: 829
投稿日時: 2007-02-20 06:37
囚人さん貴重な情報ありがとうございます。

LoadPostDataが2回実行されるとは知りませんでした。

予断ですが、以下の部分をDataSourceIDを使用するようにして
データのバインドをASP.NETに委ねると
TextBoxが参照できなくなるようです。

GridView1.DataSource = GetData(count);
GridView1.DataBind();



GridView1.DataSourceID = "SqlDataSource1";



[ メッセージ編集済み 編集者: Access 編集日時 2007-02-20 07:54 ]
どっとねっとふぁん
ぬし
会議室デビュー日: 2005/02/23
投稿数: 935
投稿日時: 2007-02-20 10:35
> 予断ですが、以下の部分をDataSourceIDを使用するようにして
> データのバインドをASP.NETに委ねると
> TextBoxが参照できなくなるようです。

この場合、データバインドのタイミングが変わってきます。
Pageのイベントでいうと、PreRender イベントとPreRenderComplete イベントの間に
データバインドが自動で呼ばれるようになります。
データが設定されるのが遅くなるため、うまく動かなくなるんでしょうね。
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-02-20 12:30
引用:

予断ですが、以下の部分をDataSourceIDを使用するようにして
データのバインドをASP.NETに委ねると
TextBoxが参照できなくなるようです。

GridView1.DataSource = GetData(count);
GridView1.DataBind();



GridView1.DataSourceID = "SqlDataSource1";


引用:

この場合、データバインドのタイミングが変わってきます。
Pageのイベントでいうと、PreRender イベントとPreRenderComplete イベントの間に
データバインドが自動で呼ばれるようになります。
データが設定されるのが遅くなるため、うまく動かなくなるんでしょうね。


それは貴重な情報ですね。ありがとうございます。
DataSourceID を使用すると、データバインドのタイミングが隠蔽されて若干解り難いですね。
PreRender と PreRenderComplete の間のタイミングでデータバインドするのは、個人的には ASP.NET の不具合に近いと思いましたが、どうなんでしょう。

_________________
囚人@わんくま同盟
囚人のジレンマな日々

[ メッセージ編集済み 編集者: 囚人 編集日時 2007-02-20 12:30 ]
Access
ぬし
会議室デビュー日: 2002/04/08
投稿数: 829
投稿日時: 2007-02-20 13:26
>この場合、データバインドのタイミングが変わってきます。
>Pageのイベントでいうと、PreRender イベントとPreRenderComplete イベントの間に
>データバインドが自動で呼ばれるようになります。
>データが設定されるのが遅くなるため、うまく動かなくなるんでしょうね。
ちなみに、DataSourceIDを使用しても
Page_PreInitでTemplateFieldを追加すると
正常に動作します。
ASP.NETにバグでもあるのですかね・・・

スキルアップ/キャリアアップ(JOB@IT)