DataGridViewで入力できるコンボボックスを1セルだけ作る[C#]

DataGridViewに入力できるコンボボックスを1セルだけ作成する。
入力されたアイテムがコンボボックス内のリストになければ、コンボボックス内のリストの先頭に追加する。

これを実現するために、DataGridViewのEditingControlShowingイベントとCellValidatingイベントでいろいろする必要がある。

EditingControlShowingイベントは、編集用のコンボボックスが作成され表示される直前にコールされる。
DataGridViewの場合はDataGridViewComboBoxEditingControlが作成されるが、このコントロールのDropDownStyleをComboBoxStyle.DropDownに変更する。これでコンボボックスへの入力は可能となるが、入力した内容がリストに追加されない。
リストへの追加はCellValidatingイベントで行う。

CellValidatingイベントはセルがフォーカスを失う時にコールされる。
このとき、入力された文字列がコンボボックス内のリストにない場合は、リストに追加する必要がある。

さらに、最終セルの場合(例えば3×3のリストで右下のセル)は入力後のEnterキーでCellValidatingイベントが発生しない。これはカレントのセルが遷移しないため。これを解決するためにEnterキーのイベントでカレントセルを一度クリアし、再度設定してCellValidatingイベントを発生させる。

以下、サンプルソース。

// フォームのロードでDataGridViewを初期化
private void Form1_Load(object sender, EventArgs e)
{
    // 列を追加
    dataGridView1.Columns.Add("aaaa", "aaaa");
    dataGridView1.Columns.Add("bbbb", "bbbb");
    dataGridView1.Columns.Add("cccc", "cccc");

    // 行を追加
    dataGridView1.Rows.Add();
    dataGridView1.Rows.Add();
    dataGridView1.Rows.Add();

    // DataGridView全体を編集可能にする
    dataGridView1.ReadOnly = false;
    // 3x3の一番右下のセルを入力できるコンボボックスにする。
    dataGridView1[2, 2] = new DataGridViewComboBoxCell();
    dataGridView1[2, 2].ReadOnly = false; // 編集可能に設定

    // コンボボックスにアイテムを追加する
    ((DataGridViewComboBoxCell)dataGridView1[2, 2]).Items.Add("あああ");
    ((DataGridViewComboBoxCell)dataGridView1[2, 2]).Items.Add("いいい");
    ((DataGridViewComboBoxCell)dataGridView1[2, 2]).Items.Add("ううう");
    ((DataGridViewComboBoxCell)dataGridView1[2, 2]).Items.Add("えええ");
    ((DataGridViewComboBoxCell)dataGridView1[2, 2]).Items.Add("おおお");
}

// DataGridViewのEditingControlShowingイベントを処理する
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    DataGridView dgv = (DataGridView)sender;

    // コントロールがDataGridViewComboBoxEditingControlの場合
    if (e.Control is DataGridViewComboBoxEditingControl)
    {
        // 該当位置のセルの場合
        if ((dgv.CurrentCell.RowIndex == 2) && (dgv.CurrentCell.ColumnIndex == 2))
        {
            // DropDownStyleをドロップダウンに変更
            ((DataGridViewComboBoxEditingControl)e.Control).DropDownStyle = ComboBoxStyle.DropDown;
            // Enrerキーイベントを捕まえるためのイベントを登録
            ((DataGridViewComboBoxEditingControl)e.Control).PreviewKeyDown += new PreviewKeyDownEventHandler(Form1_PreviewKeyDown);
        }
    }
}

// DataGridViewComboBoxEditingControlのキーイベントを処理する
void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
    // Enterキーの場合
    if (e.KeyCode == Keys.Enter)
    {
        // カレントセルをバックアップ
        DataGridViewCell curCell = ((DataGridViewComboBoxEditingControl)sender).EditingControlDataGridView.CurrentCell;
        // カレントセルをクリア
        ((DataGridViewComboBoxEditingControl)sender).EditingControlDataGridView.CurrentCell = null;
        // カレントセルを再設定(これでCellValidatingイベントが発行される)
        ((DataGridViewComboBoxEditingControl)sender).EditingControlDataGridView.CurrentCell = curCell;
    }
}

// CellValidatingイベントを処理する
private void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
    DataGridView dgv = (DataGridView)sender;

    // 該当位置のセルの場合
    if ((dgv.CurrentCell.RowIndex == 2) && (dgv.CurrentCell.ColumnIndex == 2))
    {
        // コンボボックス内のリストを更新
        UpdateCmbList(dgv.CurrentCell.ColumnIndex, dgv.CurrentCell.RowIndex, e.FormattedValue, ref this.dataGridView1);
    }
}

// コンボボックス内のリストを更新する
private void UpdateCmbList(int colIndex, int rowIndex, object value, ref DataGridView dgv)
{
    bool delete = false;
    bool add = false;

    // 値を文字列に変換
    string formattedValue = string.Empty;
    if (value != null)
    {
        formattedValue = value.ToString();
    }
    // リストの項目数が選択項目より多い場合は先頭の項目を削除
    if (((DataGridViewComboBoxCell)dgv.Rows[rowIndex].Cells[colIndex]).Items.Count > 5)
    {
        // 先頭の項目が今回入力した文字列でない場合は削除を予約
        // ここで削除すると、セルのValueをまだ変更していないので例外が発生する。
        // セルのValueを変更後にリストから削除する。
        if (((DataGridViewComboBoxCell)dgv.Rows[rowIndex].Cells[colIndex]).Items[0].ToString() != formattedValue)
        {
            delete = true;
        }
    }
    // 値がリストに存在するか確認
    if (((DataGridViewComboBoxCell)dgv.Rows[rowIndex].Cells[colIndex]).Items.Contains(formattedValue) != true)
    {
        // 存在しない場合は先頭に追加
        ((DataGridViewComboBoxCell)dgv.Rows[rowIndex].Cells[colIndex]).Items.Insert(0, formattedValue);
        add = true;
    }
    // 一度コミットする(リストへの追加を有効にするため)
    dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
    // セルのValueを更新
    ((DataGridViewComboBoxCell)dgv.Rows[rowIndex].Cells[colIndex]).Value = formattedValue;
    // リストから削除
    if (delete == true)
    {
        int index = 0;
        if (add == true)
        {
            index = 1;
        }
        ((DataGridViewComboBoxCell)dgv.Rows[rowIndex].Cells[colIndex]).Items.RemoveAt(index);
    }
    // もう一度コミットする
    dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
}

コメントする

ASP.NETでHiddenFieldの値が取得できない[ASP.NET]

ASP.NETでHiddenFieldに格納したはずの値がPostbackで取得できなくて3時間くらいはまりました。
JavascriptでHiddenFieldに値を格納する場合は、”value”の先頭文字は小文字です。

コメントする

データベース・スキーマ・ユーザを作成する[SQL Server]

SQL Serverのデータベース構築時に手順を誤ると「ログインは別のユーザー名でアカウントがあります」などとエラーが出て先に進めなくなることが、あります。次の手順で作成すれば怒られずにすむようです。

1) saでログインする。
2) データベースを作成する。
3) ログインを作成する。(ユーザーではありません)
  ・このとき、既定のデータベースに先ほど作成したデータベースを指定する。
4) 作成したログインのユーザマッピングを変更する。
  ・既定のスキーマを入力する。ここに入力したスキーマがあとで作成される。
  ・データベース ロール メンバーシップでdb_ownerにチェックを入れる。
この操作により、データベースにスキーマとユーザが作成される。

テーブルを作成する場合は、作成したログインでログインしたあとに作成する。

コメントする

WebServiceでオブジェクトを渡すとエラーになる[ASP.NET,C#]

WebService経由で自作クラスのオブジェクトをやりとりできますが、メンバに入れるとエラーになるものもあります。
例えば、Dictionary。Dictionaryでもジェネリック型だけを使用する場合は問題ないようですが、Dictionary<int,>のに自作クラスを指定するとエラーになるようです。
Listは問題ないようですが、Web参照側(クライアント側)ではListではなく配列になります。
また、ジェネリック型でも”Type”はエラーとなるようです。

コメントする

セッション変数に何も格納しないとそのセッションは破棄される[ASP.NET]

セッション変数になにも格納しないと、そのセッションは維持されず、次回のリクエストで新しいセッションIDが返される。
セッションを継続するためには、セッション変数になにかを格納する必要がある。

コメントする

C#からSQL Serverを使う場合はtimestampではなくdatetimeを使う[C#,SQL Server]

SQL Server側でフィールドの型をtimestampとした場合、C#側でDataColumnを参照するとデータ型がnullになる。
SQL Server側をdatetimeにした場合は、C#側ではDateTime型として認識できる。

コメントする

例外をthrowしたとき、finallyは実行されるのか?[C#]

実行されます。
try内でthrowしても、catch内でthrowしても、finallyは実行されます。

public void ParentMethod()
{
    int result;

    try
    {
        result = ZeroDivide(); // (1) ここでメソッドをコール

        System.Diagnostics.Debug.WriteLine(result); // ここは処理されない
    }
    catch(Exception e)
    {
        System.Diagnostics.Debug.WriteLine(e); // (5) 例外をcatch
    }
}

public int ZeroDivide()
{
    int result = 0;

    try
    {
        int a = 2;
        int b = 0;
        a = 2 / b; // (2) ゼロで除算なので例外発生

        result = 1; // ここは処理されない
    }
    catch (Exception e)
    {
        throw e;  // (3) ここでcatchされる
    }
    finally
    {
        result = 2; // (4) ここが実行されて
    }

    return result; // ここは処理されない
}

コメントする

相対パスをフルパスにする[Win32API]

_fullpath()を使う。それだけ。

コメントする

Visual Studio 2005で「このバージョンのサーバーはサポートされていません。 Microsoft SQL Server 2005 Beta 2 かそれ以降が必要です。」というエラーが出る[MSVS2005]

Visual Studio 2005からSQL Server 2008に接続しようとしたとき、
「このバージョンのサーバーはサポートされていません。 Microsoft SQL Server 2005 Beta 2 かそれ以降が必要です。」
というダイアログが表示されることがあります。
これは、Microsoft SQL Server 2008 サポート用 Microsoft Visual Studio 2005 Service Pack 1 更新プログラムを入れると直ります。

コメントする

既定のスキーマを設定しているのにテーブルにアクセスできない[SQL Server]

SQL Server 2005からスキーマという概念が導入されました。
例えばテーブルを示す完全修飾名は、サーバ名.データベース名.スキーマ名.テーブル名となり、テーブル名の前にスキーマ名が入ります。
スキーマを作成・指定しなければ既定のスキーマはdboとなります。

スキーマを変更したテーブルにODBC経由でアクセスするためには、スキーマ名.テーブル名でアクセスしなければなりませんが、データベースのユーザに既定のスキーマを指定すれば、テーブル名だけでアクセスが可能になります。(サーバ名.データベース名はODBCのデータソースで解決している)

ですが、既定のスキーマを指定したにもかかわらず、既定のスキーマに属するテーブルにアクセスできないことがあります。
MSDNによると、

ユーザーが固定サーバー ロール sysadmin のメンバである場合、DEFAULT_SCHEMA の値は無視されます。固定サーバー ロール sysadmin のすべてのメンバには、dbo の既定のスキーマが割り当てられます。

つまり、ログインにsysadminのサーバーロールをつけると、既定のスキーマではなくdboが使われるということで、既定のスキーマは無視されるということです。
CREATE TABLEなどはできなくなりますが、sysadminは外しておくのがよいようです。

コメントする

« 新しい投稿 · 過去の投稿 »
フォロー

Get every new post delivered to your Inbox.