WebSurfer's Home

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

GridView でクリックされたセル位置の取得

by WebSurfer 2010年8月7日 15:04

ユーザーがブラウザ上で GridView のセルをクリックすると、ポストバックされて、サーバ側のプログラムでクリックされたセルの位置を取得する方法です。

それだけでは面白くない(?)ので、データソースの列が動的に変化するとし、ヘッダを含めて LinkButton または Button で表示し、ページングを有効にするというシナリオです。

以下の画像のような感じです。これはページャーの 3 をクリックして 3 ページに移動し、ヘッダーを含めて上から 2 行目の 10000 をクリックした結果です。

GridView でクリックされたセルの位置の取得

アプリは以下のようにして作成します。こんなシナリオは普通ないので、役に立たないと言われるかもしれませんが(苦笑)

ページング

ページングのためのコードを自力で書くのはかなり大変です。ページングの機能を持つデータソースコントロールとデータバインドコントロールを組み合わせて使うのが現実的と思います。

ここでは、データソースコントロールに ObjectDataSource を使用し、DataTable を作成するメソッドと GridView(データバインドコントロール)の間を仲介してやることにします。

GridView の列の設定

データソースの列が動的に変化するという前提があります。

テキストで表示するなら、AutoGenerateColumns を true にすれば自動的に必要な列を生成してくれますが、LinkButton または Button を使って表示するという要件があるのでそうはいきません。

AutoGenerateColumns="False" としておき、Page.Load のイベントハンドラで DataTable から列情報を取得し、その情報をもとに ButtonField を初期化して、GridView に設定してやるという操作が必要です。

各行へのデータバインドは、ButtonField.DataTextField プロパティに DataColumn.Caption を設定しておけば、ObjectDataSource と GridView が自動で行ってくれます。

セル位置の設定・取得

クリックされたセルの列・行位置は、GridView.RowCommand イベントハンドラで、引数の GridViewCommandEventArgs オブジェクトの CommandName, CommandArgument プロパティから取得します。

列番号の取得には CommandName を利用します。そのためには、事前に CommandName に列番号を設定しておく必要があります。上記の「GridView の列の設定」の際に DataColumn.Ordinal プロパティから DataTable 内での列位置を取得し、ButtonField.CommandName プロパティに設定します。

行番号の取得には CommandArgument を使用します。CommandArgument にはデフォルトで行番号が設定されるので、事前の設定は不要です。

取得できる行番号は、ページングした場合、例えば1ページ目の n 行と、2ページ目の n 行とで同じになりますので注意してください。

ヘッダー行の変更

ここまでの処置では、まだヘッダーが普通のテキストのままです。これを LinkButton または Button で表示するようにします。

それには、GridView.RowCreated イベントのハンドラで、ヘッダーの Cell の内容を LinkButton または Button に書き換えてやります。

サンプルコード


<%@ 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">
  protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.Header)
    {
      TableCellCollection cells = e.Row.Cells;
      foreach (TableCell cell in cells)
      {
        string text = cell.Text;
        cell.Controls.Clear();
        //LinkButton button = new LinkButton();  // ハイパーリンク表示
        Button button = new Button();          // ボタン表示                
        button.Text = text;
        button.CommandArgument = "-1";
        button.CommandName = cells.GetCellIndex(cell).ToString();
        cell.Controls.Add(button);
      }
    }
  }

  protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
  {
    // ページャーがクリックされた場合
    if (e.CommandName == "Page")
    {
      Label1.Text = String.Empty;
    }
    else
    {
      Label1.Text = "Page: " + GridView1.PageIndex.ToString() + 
        ", Command Argument: " + e.CommandArgument.ToString() + 
        ", Command Name: " + e.CommandName;
    }
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!Page.IsPostBack)
    {
      DataView view = (DataView)ObjectDataSource1.Select();
      DataTable table = view.Table;
      foreach (DataColumn column in table.Columns)
      {
        ButtonField field = new ButtonField();
        //field.ButtonType = ButtonType.Link;     // ハイパーリンク表示
        field.ButtonType = ButtonType.Button;   // ボタン表示                
        field.HeaderText = column.Caption;
        field.DataTextField = column.Caption;
        field.CommandName = column.Ordinal.ToString();
        GridView1.Columns.Add(field);
      }
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h3>方法2: ButtonField を利用</h3>
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server" 
      SelectMethod="CreateDataTable2" 
      TypeName="PagingTest">
    </asp:ObjectDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AllowPaging="True" 
      DataSourceID="ObjectDataSource1" 
      onrowcreated="GridView1_RowCreated" 
      onrowcommand="GridView1_RowCommand"
      PageSize="5" 
      AutoGenerateColumns="False" 
      EnableViewState="True">
    </asp:GridView>
    <asp:Label ID="Label1" runat="server"></asp:Label>
  </div>
  </form>
</body>
</html>

ついでに、ObjectDataSource の TypeName, SelectMethod が参照しているクラス/メソッドもアップしておきます。


using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

public class PagingTest
{
  public static DataTable CreateDataTable2()
  {
    DataTable wktbl = new DataTable();
    DataRow wkrow;
        
    wktbl.Columns.Add("y/x");
    wktbl.Columns.Add("x1.min/.max");
    wktbl.Columns.Add("x2.min/.max");
    wktbl.Columns.Add("x3.min/.max");
    wktbl.Columns.Add("x4.min/.max");

    for (int i = 0; i < 20; i++)
    {
      wkrow = wktbl.NewRow();
      wkrow["y/x"] = "y1.min/.max";
      wkrow["x1.min/.max"] = "10000";
      wkrow["x2.min/.max"] = "20000";
      wkrow["x3.min/.max"] = "30000";
      wkrow["x4.min/.max"] = "40000";
      wktbl.Rows.Add(wkrow);
    }

    return wktbl;
  }
}

Tags: ,

ASP.NET

ダウンロードは別ウィンドウで

by WebSurfer 2010年8月6日 20:52

ファイルをダウンロードする際、ダウンロード後にも何かの処置を継続し、その結果を表示したい場合はどうすればいいのでしょう?

例えば、Button をクリックするとポストバックがかかり、その Click イベントのハンドラでファイルをダウンロードし、その後そのページを継続して表示するが、ダウンロード後は表示したくない部分がある場合を考えて見ます。

ファイルダウンロード後ページの一部を隠す

ダウンロード後は表示したくない部分を Panel に入れて、Click イベントで Panel.Visible プロパティを false に設定するという手段を考えると思います。

しかしながら、ファイルのダウンロードとその処置を 1 ページで行うとうまくいきません。

何故なら、ボタンがクリックされてポストバックがかかり、サーバーから送られてくるのは HTTP 応答ヘッダとダウンロードされるファイルのバイト列だけだからです。

ボタンクリック後もポスト前の画面が表示されているのは、ブラウザにポスト前の画面が保持されているからで(ここのところ確証はないのですが)、ポストバック後サーバーから送られてきたものが表示されているわけではありません。前の画面だから、Panel の中身は表示されたままです。

Response.End(), Response.Flush() などをしなかったら、Download も処置の継続もうまくのではと思って、いろいろ試してみましたが、1 ページで処置するのは無理でした。

というわけで、ファイルをダウンロードする別のページを作って、それを呼び出すのがよさそうです。例えば、以下のような感じです。

<%@ 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 Button1_Click(object sender, EventArgs e)
  {
     Panel1.Visible = false;
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>ファイルダウンロード</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>ファイルダウンロード後に以下の部分を隠す</h1>
    <asp:Panel ID="Panel1" runat="server">
      <div style="background-color: Silver">
        <h2>隠す部分</h2>
        <asp:Button ID="Button1" 
          runat="server" 
          Text="Download" 
          onclick="Button1_Click" 
          OnClientClick="window.open('Download.aspx', null);" />
     </div>
   </asp:Panel>    
  </div>
  </form>
</body>
</html>

ダウンロードを行う別ページは、何をどのようにダウンロードするかによって千差万別ですが、DB からデータを取得して CSV ファイルにしてダウンロードする場合の例を、ついでにアップしておきます。

この例では、デリミタに使っている改行やコンマがフィールドに含まれているとうまくいませんので注意してください。

<%@ Page Language="C#" ContentType="application/octet-stream" 
ResponseEncoding="Shift_JIS" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<!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)
  {        
    string connString = 
      ConfigurationManager.ConnectionStrings["Pubs"].ConnectionString;
    string query = "SELECT * FROM employee";
    SqlConnection sqlConn = new SqlConnection(connString);
    SqlCommand sqlCmd = new SqlCommand(query, sqlConn);
    string csvString = String.Empty;
    try
    {
      sqlConn.Open();
      SqlDataReader reader = sqlCmd.ExecuteReader();
      while (reader.Read())
      {
        for (int i = 0; i < reader.FieldCount; i++)
        {
          if (i == reader.FieldCount - 1)
          {
            csvString += reader[i].ToString() + "\r\n";
          }
          else
          {
            csvString += reader[i].ToString() + ",";
          }
        }
      }
    }
    finally
    {
      sqlConn.Close();
    }

    Encoding encode = Encoding.GetEncoding("shift_jis");
    Response.AppendHeader("Content-Disposition", "attachment; filename=test.csv");
    Response.Write("Employee ID,First Name,Middle Initial,Last Name,Job ID,Job Level,Pub ID,Hire Date\r\n");
    Response.BinaryWrite(encode.GetBytes(csvString));
    Response.End();
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>無題のページ</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>    
  </div>
  </form>
</body>
</html>

なお、IE8 の場合、空白のウィンドウが一旦開いてしまい、「ファイルのダウンロード」ダイアログのボタンをクリックするまで閉じないという気に入らない点があります。

今のところ回避策が見つかっていません。回避策をご存知の方はアドバイスいただけるとうれしいです。

Tags: ,

Upload Download

Web Custom Control の例外処置

by WebSurfer 2010年8月6日 17:26

Web カスタムコントロールでは、Visual Studio でプロパティの初期値を設定する際、入力値をチェックして条件に合わなければ例外をスローするようにできます。以下の画像のような感じです。

Web カスタムコントロールの例外処置

具体的には、例えば以下のように、値の設定時に入力値をチェックして条件に合わなければ例外をスローするようにコーディングしたとします。

namespace CustomControlThrowExceptionTest
{
  public class MyControl : WebControl
  {
    public decimal MaxValue
    {
      get
      {
        object obj = ViewState["MaxValue"];
        return (obj != null) ? (decimal)obj : Decimal.MaxValue;
      }
      set
      {
        if (value < MinValue)
        {
          throw new Exception("MaxValueException");
        }
        ViewState["MaxValue"] = value;
      }
    }

    public decimal MinValue
    {
      get
      {
         object obj = ViewState["MinValue"];
         return (obj != null) ? (decimal)obj : Decimal.MinValue;
      }
      set
      {
        if (MaxValue < value)
        {
          throw new Exception("MinValueException");
        }
        ViewState["MinValue"] = value;
      }
    }
    ・・・中略・・・
  }
}

上記のプロパティを含む Web カスタムコントロールを作って、それをページに配置します。

そして、Visual Studio のプロパティウィンドウで、例えば、まず MaxValue を 100 に設定し、次に MinValue に 200 を入力すると、上の画像のようなダイアログが現れて設定の誤りを知らせてくれます。

コンパイルして Bin フォルダに配置しても、App_Code フォルダにソースのままファイルを置いても同じ動作をします(条件をチェックして例外をスローします)。

Web ユーザーコントロールの場合は、チェックは実行時でないとできません(間違った初期値が設定できてしまいます)。

知ってました? こんなことは常識と言われるかもしれませんが、それを知った当時、Web ユーザーコントロールしか知らない未熟者だった自分には驚きでした。

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