WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

DataGridView に ComboBox を表示

by WebSurfer 23. January 2014 15:26

DataGridView の特定の列を ComboBox を使って表示する方法を備忘録として書いておきます。

DataGridView に ComboBox を表示

Northwind サンプルデータベースの Products テーブルを例に使い、上の画像のように Category 列と Supplier 列を ComboBox を使って表示します。

Products テーブルを DataGridView に表示して更新操作ができるようにするだけなら、自分でコードは一行も書かなくても、Visual Studio のウィザードを使ってドラッグ&ドロップ操作だけで Windows Form アプリを作成できます。

ただし、Products テーブルの中の CategoryID, SupplierID は ID(数字)で表示されるので、そのままではユーザーにとっては分かりにくいです。

ID に代えて、名前を Categories, Suppliers テーブルから取得して表示した方がユーザーにとっては分かりやすいはずです。

さらにユーザーがデーターベースを編集・更新する場合は、ComboBox を使用してドロップダウン形式で一覧を表示し、その中からユーザーが選択できるようにするとさらにユーザーフレンドリーになると思います。

今回の例では、CategoryID, SupplierID 列に ComboBox を使い、各行の CategoryID, SupplierID に該当する名前(CategoryName, CompanyName)を Categories, Suppliers テーブルから取得して ComboBox に表示するようにします。

Category 列の方は Visual Studio のウィザードを利用して、自力では一行もコードを書かずに ComboBox を実装してみます。

Supplier 列の方は自力でコードを書いて DataTable を作成し、それを ComboBox の DataSource に設定することにします。

以下にその手順を書きます。

  1. Visual Studio の[ファイル(F)]⇒[新しいプロジェクト(P)...]メニューから[Windows フォームアプリケーション]テンプレートを使って空のプロジェクトを作成する。
  2. ソリューションエクスプローラーでプロジェクトのルートを右クリック。[追加(D)]⇒[新しい項目(W)...]⇒[データセット]で適当な名前(例えば NorthwindDataSet.xsd)をつけて空の xsd ファイルを作成。(注:下の画像はステップ 12 まで進んだ時の完成済みのデータセットで、このステップの時点では何も表示されていません)

NorthwindDataSet.xsd ファイル

  1. ツールボックスから TableAdapter をドラッグ&ドロップすると TableAdapter 構成ウィザードが立ち上がる。
  2. クエリビルダで以下の SELECT クエリを作り、これをベースに DataTable + TableAdapter を作る。

    SELECT ProductID, ProductName, CategoryID, SupplierID,
    UnitPrice, Discontinued FROM Products

    DataGridView に表示するとともに編集・更新操作を行うのはこれなので、TableAdapter 内に作成する更新系のメソッド等の生成オプションはフルにチェックを入れておく。
  3. TableAdapter 構成ウィザードでの操作が完了すると、ProductsDataTable と ProductsTableAdapter が自動生成されているはず。(上の NorthwindDataSet.xsd ファイルの画像で、左側の箱)
  4. Form1.cs を開きそのデザイン画面を表示する。データソースウィンドウを開くとその中に Products という項目があるはずなので、それを Form にドラッグ&ドロップする。
  5. 上のステップまでで、Products テーブルの表示、編集、更新ができるアプリが完成するはず。とりあえずこの段階で DataGridView への表示、レコードの INSERT, DELETE, UPDATE が問題なくできることを確認しておく。
  6. 上記が確認できたら、最初に Category の方を ComboBox で表示するための設定を行う。
  7. 自力でコードを書くのは必要最小限にするため、TableAdapter 構成ウィザードを利用して DataTable + TableAdapter を作り、これを利用する。
  8. NorthwindDataSet.xsd ファイルを開き、ツールボックスからもう一つ TableAdapter をドラッグ&ドロップする。
  9. TableAdapter 構成ウィザードが立ち上がるので、クエリビルダで以下の SELECT クエリを作り、これをベースに DataTable + TableAdapter を作成する。

    SELECT CategoryID, CategoryName FROM Categories

    こちらは、CategoryID に該当する CategoryName を ComboBox を使って表示するためのデーターソースとして利用するだけなので、TableAdapter に更新系のメソッドは不要。
  10. TableAdapter 構成ウィザードでの操作が完了すると、CategoriesDataTable と CategoriesTableAdapter が自動生成されているはず。(上の NorthwindDataSet.xsd ファイルの画像で、右側の箱)
  11. DataGridView の列の編集画面で、CatrgoryID 列の ColumnType を DataGridViewComboBoxColumn に変更する。

列の編集画面その1

  1. 上の画面で、DataSource を[他のデータソース]⇒[プロジェクトデータソース]⇒[NorthwindDataSet]⇒[Catrgories]に設定すると、自動的に categoriesBindingSource が生成され、DataSource に設定される。
  2. 次に、DisplayMember を CategoryName に、ValueMember を CategoryID に設定する。

列の編集画面その2

  1. ここまでの操作で、Category の方は ComboBox で表示され、編集する際は ComboBox をドロップダウンしてその中から選択して更新をかけることが可能になるはず。
  2. 次に、Supplier の方を ComboBox に表示するための設定を行う。こちらは TableAdapter 構成ウィザードを使用しないで、自力でコードを書いて DataTable を作り、それをデーターソースに利用してみる。
  3. Category の場合と同様に、DataGridView の列の編集画面で、SupplierID 列の ColumnType を DataGridViewComboBoxColumn に変更する。
  4. その先は自力でコードを書いて DataTable を作成し、それを DataGridViewComboBoxColumn の DataSource に設定する。
  5. Form1.cs のコード画面を開く。
  6. ADO.NET のライブラリを使い、Suppliers テーブルから SupplierID, CompanyName を取得し DataTable を作成するコードを追加する。具体例は以下のサンプルコードの CreateSuppliersTable を参照。
  7. Form1_Load メソッド(自動生成されているはず)の中に、上記 18 の手順で作成した DataGridViewComboBoxColumn の DataSource プロパティを CreateSuppliersTable メソッドが返す DataTable に、DisplayMember プロパティを "CompanyName" に、ValueMember プロパティを "SupplierID" に設定する。具体例は以下のサンプルコードを参照。
  8. 以上の操作で、一番上の画像のアプリが完成するはずです。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;  // 要追加

namespace DataGridViewComboBox
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }

    private void productsBindingNavigatorSaveItem_Click(
        object sender, EventArgs e)
    {
      this.Validate();
      this.productsBindingSource.EndEdit();
      this.tableAdapterManager.UpdateAll(
          this.northwindDataSet);
    }

    private void Form1_Load(object sender, EventArgs e)
    {
      this.categoriesTableAdapter.Fill(
          this.northwindDataSet1.Categories);
      this.productsTableAdapter.Fill(
          this.northwindDataSet.Products);
      // ここまでのコードは Visual Studio のウィザードで
      // 自動生成される。


      // ここから下のコードを自力で書いて追加する。

      // DataSource に設定するのは Supplier の ComboBox に
      // 表示するデータを含む DataTable。下のヘルパーメソッ
      // ド CreateSuppliersTable で作成。
      this.dataGridViewTextBoxColumn4.DataSource = 
          CreateSuppliersTable();
      this.dataGridViewTextBoxColumn4.DisplayMember = 
          "CompanyName";
      this.dataGridViewTextBoxColumn4.ValueMember = 
          "SupplierID";
    }

    // Suppliers テーブルから SupplierID, CompanyName を抽出
    // して DataTable を作るためのヘルパーメソッド。
    private DataTable CreateSuppliersTable()
    {
      DataTable table = new DataTable("Suppliers");
      table.Columns.Add(
            new DataColumn("SupplierID", typeof(int)));
      table.Columns.Add(
            new DataColumn("CompanyName", typeof(string)));

      string connString = DataGridViewComboBox.Properties.
          Settings.Default.NORTHWNDConnectionString;
      string query = 
          "SELECT SupplierID, CompanyName FROM Suppliers " + 
          "ORDER BY SupplierID";

      using (SqlConnection conn = new SqlConnection(connString))
      {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand(query, conn))
        {
          using (SqlDataReader reader = cmd.ExecuteReader())
          {
            if (reader != null)
            {
              while (reader.Read())
              {
                DataRow row = table.NewRow();
                row["SupplierID"] = reader.GetInt32(0);
                row["CompanyName"] = reader.GetString(1);
                table.Rows.Add(row);
              }
            }
          }
        }
      }
      return table;
    }

  }
}

Tags: , ,

.NET Framework

Object 型と列挙型引数のオーバーロード

by WebSurfer 10. August 2013 12:48

SqlParameterCollection.Add メソッド には以下のオーバーロードがあります。

  • Add (Object)
  • Add (SqlParameter)
  • Add (String, SqlDbType)
  • Add (String, Object)
  • Add (String, SqlDbType, Int32)
  • Add (String, SqlDbType, Int32, String)

このうち、Add(String, Object) は Add(String, SqlDbType) との曖昧さの問題で廃止され、代わりに AddWithValue メソッド の使用が推奨されています。

何が曖昧かと言うと、MSDN ライブラリの SqlParameterCollection.Add メソッド (String, Object) の説明に書いてありますように、第 2 引数に 0(整数ゼロ)を渡すと、Add (String, Object) ではなくて、Add (String, SqlDbType) オーバーロードが呼び出されることです。

第 2 引数に 0 を渡せばもちろん、(int)0 でも Add (String, SqlDbType) オーバーロードが呼び出されます。

Add (String, Object) オーバーロードを呼び出すには、第 2 引数に Convert.ToInt32(0) または (object)0 を渡さなければなりません。

何故でしょう? 以下のような理由だと思います。(多少推測があります)

まず、SqlDbType が列挙型というところが要注意です。MSDN ライブラリ Enumeration Types (C# Programming Guide) によると、0(整数ゼロ)は列挙型のデフォルト値とのことで、特別な意味を持つようです。

それゆえ、0 や (int)0 は SqlDbType 列挙型のデフォルト値と判断されるらしく、Add (String, Object) ではなくて、Add (string, SqlDbType) オーバーロードが呼ばれます。

Convert.ToInt32(0) は(Object 型から派生した)int 型、(object)0 は Object 型と判断されて、Add (String, Object) オーバーロードが呼ばれます。

ちなみに、もし Add (String, Int32) というオーバーロードがあったとすると、第 2 引数は 0 でも (int)0 でも Convert.ToInt32(0) でも、Add (String, Int32) オーバーロードが呼ばれるはずです。

検証するため以下のサンプルを作ってみましたが、サンプルコード内のコメントにあるように期待通りの結果となりました。

namespace MyConsoleApplication
{
    enum Days { Sat, Sun, Mon, Tue, Wed, Thu, Fri };

    class TestClass
    {
        public void Add1(object obj)
        {
            Console.WriteLine("Add1(object)");
        }

        public void Add1(Days d)
        {
            Console.WriteLine("Add1(Days)");
        }

        public void Add2(object obj)
        {
            Console.WriteLine("Add2(object)");
        }

        public void Add2(Days d)
        {
            Console.WriteLine("Add2(Days)");
        }

        public void Add2(int i)
        {
            Console.WriteLine("Add2(int)");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TestClass tc = new TestClass();

            tc.Add1(0);
            tc.Add1(1);
            tc.Add1((int)0);
            tc.Add1(Convert.ToInt32(0));
            tc.Add1((object)0);

            Console.WriteLine("--------------------");

            tc.Add2(0);
            tc.Add2(1);
            tc.Add2((int)0);
            tc.Add2(Convert.ToInt32(0));
            tc.Add2((object)0);

            // 結果は:

            // Add1(Days)
            // Add1(object)
            // Add1(Days)
            // Add1(object)
            // Add1(object)
            // --------------------
            // Add2(int)
            // Add2(int)
            // Add2(int)
            // Add2(int)
            // Add2(object)
        }        
    }
}

Tags: ,

.NET Framework

WebBrowser で HttpOnly 属性の Cookie 取得

by WebSurfer 13. August 2012 15:35

.NET Framework の WebBrowser を利用した Windows アプリで、ドキュメントに関連付けられている HTTP Cookie を取得するには HtmlDocument.Cookie プロパティ が利用できます。

ただし、HttpOnly 属性を持つ HTTP Cookie は例外です。その理由は、The Code Project のページ Retrieve HttpOnly Session Cookie in WebBrowser に述べられていますが、IE6 以降でクロスサイトスクリプティング対応のため HttpOnly 属性が追加され、その属性を持つ HTTP Cookie にはクライアントスクリプトからアクセスできなくなっているからだそうです。

HttpOnly 属性を持つ HTTP Cookie には、例えば、ASP.NET のセッションクッキー、匿名ユーザー識別用クッキーが該当します(下の Fiddler による応答ヘッダーの画像で、名前が ASP.NET_SessionId と .ASPXANONYMOUS のもの)。

HTTP Cookies(Fiddler による応答ヘッダー)

上の画像の HTTP Cookie の内、HtmlDocument.Cookie プロパティで取得できるのは RandomNumber と DateTimeCookie のみです。この例では、HtmlDocument.Cookie プロパティから取得できる文字列は以下のようになります。

DateTimeCookie=2012/08/13 14:22:48; RandomNumber=1123940529

すべての HTTP Cookie を取得するには、WININET.dll の InternetGetCookieEx メソッド を利用できます(mshtml.dll ではない点に注意)。以下のような感じです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WebBrowserHttpOnlyCookie
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();

      textBox1.Text = 
          "http://msdntestnew/171-SendCookies.aspx";
    }

    private void button1_Click(object sender, EventArgs e)
    {
      webBrowser1.Navigate(textBox1.Text);
    }

    [DllImport("wininet.dll", CharSet = CharSet.Auto, 
      SetLastError = true)]
    static extern bool InternetGetCookieEx(
        string pchURL, 
        string pchCookieName, 
        StringBuilder pchCookieData, 
        ref uint pcchCookieData, 
        int dwFlags, 
        IntPtr lpReserved);

    const int INTERNET_COOKIE_HTTPONLY = 0x00002000;

    public static string GetCookies(string uri)
    {
      uint datasize = 1024;           
      StringBuilder cookieData = 
        new StringBuilder((int)datasize);
      bool result = InternetGetCookieEx(
                         uri,
                         null,
                         cookieData,
                         ref datasize,
                         INTERNET_COOKIE_HTTPONLY,
                         IntPtr.Zero);

      if (result && cookieData.Length > 0)
      {
        return cookieData.ToString();
      }
      else
      {
        return null;
      }
    }

    private void webBrowser1_DocumentCompleted(
      object sender, 
      WebBrowserDocumentCompletedEventArgs e)
    {
      label1.Text = webBrowser1.Document.Cookie;
      label2.Text = GetCookies(textBox1.Text);
    }
  }
}

Tags: , ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  January 2020  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar