- PR -

ユーザコントロール間で更新を反映させるには?[ASP.NET2.0]

1
投稿者投稿内容
BT
ベテラン
会議室デビュー日: 2006/09/24
投稿数: 81
お住まい・勤務地: Tokyo
投稿日時: 2007-03-02 15:14
お世話になります。

(少し前に質問して中途半端になっているモバイルはちょっと中断して)
現在、いわゆるショッピングカートといわれるものを作ってみようとしています。
カート情報を格納するDataTableを用意し、DataListで作った商品リストで商品を選択した際に発生するSelectedIndexChangedイベントハンドラの中で、そのDataTableに対して選択した商品を追加していきます。また、ページ間の受け渡しのため、追加後にDataTableをSession変数に入れるようにしました。

カートの中身については、GridViewを用意してそれに先程のSession変数をBindして、何が入っているのかをリスト表示することにし、マスターページに置いて、ページを移動しても常に片隅に表示されるようにします。

と、ここまで考えて、作成を始めましたが、少し躓いています。

柔軟性を考えて、「商品リスト」、「カートの中身」はそれぞれ別のユーザコントロールとして作成し、ウェブフォーム上にそれらを配置することにしましたが、「商品リスト」で商品を選択しても、すぐにはカートにそれが反映されず、次のクリックで(つまり1回遅れて)表示がなされます。

こういうのは大抵DataBindのタイミングだろうと思うので、商品を選択してセッションに入れた後、最後にカートのGridViewのDataBindが行われればいいのではと思いました。
しかし、実際これをどこでどうやればいいのか悩んでしまいました。ユーザコントロールでなく同じページに両者があれば、DataListのSelectedIndexChangedの中でもう片方のGridViewのDataBindを呼べばよいのでしょうが、どちらもユーザコントロールの場合、その変更をもう一方に伝えるにはどのようにすればよいのでしょうか?

ちなみに、「商品リスト」だけを親のウェブフォームに直接置いてみたところ、変更が即座に反映され意図した動作になりました。

すみませんがよろしくお願いします。
しょくぱん
常連さん
会議室デビュー日: 2006/05/31
投稿数: 31
投稿日時: 2007-03-02 16:33
「商品リスト」で選択された内容を即時に「カートの中身」に反映させたいということですよね?

「カートの中身」ではDataBindをどのタイミングで行なっていますか?
Page_Loadのif (!IsPostBack)文の外で行なえば、
ポストバックのたびにDataBindされるのでは?
しょくぱん
常連さん
会議室デビュー日: 2006/05/31
投稿数: 31
投稿日時: 2007-03-02 16:49
って今やってみたらできませんでした・・・。
ごめんなさい・・・。
BT
ベテラン
会議室デビュー日: 2006/09/24
投稿数: 81
お住まい・勤務地: Tokyo
投稿日時: 2007-03-02 17:15
しょくばんさん、どうもありがとうございます。
実際にやってみていただいたようで、どうもすみません。

やりたいことは、まさにおっしゃる通りで、選択した商品を即座にカートの中身に反映させたいということです。

カートのコードはまだ試験段階というのもありますが、実に単純で以下のとおりです。
ポストバックに関わらず毎回バインドしています。

public partial class CartControl : System.Web.UI.UserControl
{
  protected void Page_Load(object sender, EventArgs e)
  {
    DataTable cart = (DataTable)Session["Cart"];

    GridView1.DataSource = new DataView(cart);
    GridView1.DataBind();
  }
}

DataBindのタイミングがユーザコントロールの場合どうなるかが問題ですが、
通常のウェブフォームの場合、

Page_Load→変更系イベント→アクション系イベント

という順番だと思いますので、ユーザコントロールも

親フォームのPage_Load→ユーザコントロールのPage_Load→変更系・・・・

のように同じタイミングで発生するとすると、親ウェブページから見るとPage_Loadが最初に来てカートのBindが終わった後に、商品リストにあるSelectedIndexChangedが呼ばれることになるので、その中で追加している商品は次回の表示の際にならないと表示されないのだと推測しています。

とすると、商品リスト側のSelectedIndexChangedの後にカートのDataBindをするにはどうするか?カート側に何かしらメソッドを実装して、それを商品リストのSelectedIndexChangedの中から呼び出すのだろうか?とかおぼろげに考えていますが、実際どうしたらよいのかちょっと思いつかない状態です。

もうちょっと調べてみようと思います。
ありがとうございました。
しょくぱん
常連さん
会議室デビュー日: 2006/05/31
投稿数: 31
投稿日時: 2007-03-02 17:57
こんなの考えてみました。

ユーザーコントロール1(ID=UseCtrl1)と2(ID=UseCtrl2)が1つのWebFormにのっています。
ユーザーコントロール1にはボタンがひとつ、
ユーザーコントロール2にはラベル(ID=Label1)がひとつと
リンクボタン(ID=btnDummy、Visible=true、Text=空文字)がのっています。
このリンクボタンはイベントを起こすためのダミーボタンで
Textが空文字なので画面上は見えません。

んでユーザーコントロール1のボタン押下時のイベントに

// セッションに値をセット
Session["uc_test"] = "test";

StringBuilder sb = new StringBuilder();

sb.Append("<script language=\\\\"javascript\\\\">");
sb.Append("var btn = window.document.getElementById('UseCtrl2_btnDummy');");
sb.Append("btn.click();");
sb.Append("</script>");

// JavaScriptを書き出す
Page.RegisterStartupScript("client", sb.ToString());

ユーザーコントロール2のリンクボタン押下時のイベントに

if(Session["uc_test"] != null) Label1.Text = (string)Session["uc_test"];

と書くとユーザーコントロール1でセットした文字が2で表示できるんですが・・・。
あまりスマートじゃない気がいたします・・・。
BT
ベテラン
会議室デビュー日: 2006/09/24
投稿数: 81
お住まい・勤務地: Tokyo
投稿日時: 2007-03-02 21:17
しょくぱんさんどうもありがとうございます。

うーんなるほど。JavaScriptと組み合わせてこういうことができるとは。
自分はどうしてもASP.NETの範疇でやってしまおうという発想になってしまう傾向があるので、こういう考えはなかなか浮かばないです。勉強になります。

ただ、あまり複雑になってしまうようだと、ユーザコントロールで部品化をして、コーディングを少なくしてしまおうという目的から外れてしまうので、ユーザコントロール自体を止めたほうが良いのかもしれません。

あの後ちょっと調べてみたのですが、

http://www.microsoft.com/japan/msdn/net/aspnet/usercontrols.aspx

ここの最後のあたりにユーザコントロール内で発生したイベントを親のウェブフォームで拾うというのを見つけました。しかし、これまでイベントデリゲートあたりの理解は避けて通っていたので読んでもさっぱりです。^_^;まあコードをそのまま写しても動きそうではありますが。

ただ、この解説は親フォームでイベントを拾うもので、ここではさらにそれを受けて子のユーザコントロールであるカートに対してDataBindをするようにしないといけない(?)ので、まだまだ道は遠そうです。
それに、せっかく部品化したのに、ユーザコントロール同士が密になってしまうとそれもまた目的に反しますし。

どちらかはユーザコントロールにしないのが一番簡単かもしれません。カートはマスターページに入れてるし商品リストはあまり触りたくないのでカートかな。
どっとねっとふぁん
ぬし
会議室デビュー日: 2005/02/23
投稿数: 935
投稿日時: 2007-03-03 23:44
参考になるかなぁ。。。

http://fredrik.nsquared2.com/viewpost.aspx?PostID=377
BT
ベテラン
会議室デビュー日: 2006/09/24
投稿数: 81
お住まい・勤務地: Tokyo
投稿日時: 2007-03-04 01:57
できました!
どっとねっとふぁんさんに教えていただいたリンク先を参考にやってみたところ簡単にできました。

なるほど。商品リスト側のSelectedIndexChangedが発生したらPage.FindControlでカートを見つけてそれをDataBind()してやればいいんですね。いわれてみるとそうか!という感じです。ただ、何もしないとユーザコントロールからユーザコントロールには直接アクセスができないので、リンク先のように呼び出し元のユーザコントロール側に以下の記述を追加してやると、それができるようになるということのようです。

<%@ Reference VirtualPath="~/CartControl.ascx" %>

これだと、商品リスト側だけを少し弄ればよく、関係も一方向ですので、ユーザコントロール間の独立性は保ったまま、期待する動作を得られます。素晴らしい。
商品リストのコードビハインドで変更した箇所は以下の最後の二行だけです。

protected void DataList1_SelectedIndexChanged(object sender, EventArgs e)
{
 DataTable cart = (DataTable)Session["Cart"];
 DataRow dr = cart.NewRow();
 //ここで選択した商品をDataTableに追加
 cart.Rows.Add(dr);

 //変更したDataTableをSessionに戻す
 Session["Cart"] = cart;

 //カートコントロールを探し出してDataBind
 CartControl cartctl = (CartControl)Page.FindControl("CartControl1");
 cartctl.DataBind();
}

上のコードは試験のため同一フォーム上に二つのコントロールを置いてやってみましたが、後でカートをマスターページに移動してやってみたいと思います。マスターページへのアクセス方法は@ITに記事があったと思いますのでたぶん問題ないでしょう。

あと、上のコードには出てきませんが、Sessionがnullの場合にSelectedIndexChangedの中でDataTableを作成するようにすると初回のクリックのみカートに表示されないため、親フォームのPage_Load内でDataTableの作成をしてSessionに追加するようにしていますが、ここまで来たら親フォームで全くコードを書かないようにしたいので、そのあたりをやってみることにします。

どうもありがとうございました。
1

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