none
DataGridViewのDropDownListに矢印キーで移動 RRS feed

  • 質問

  • おはようございます。

    VB2005+SQLServerExpressを使用しております。

    DataGridViewのComboboxにおいて、直接入力がしたいと思い、EditingControlShowingイベントにてDropDownStyleを変更して実現しております。

    こちらでデータを入力した場合、前方一致の形で該当するデータまで移動をしてくれ、リストが表示されるのですが、矢印キーでそのリストに移動することができません。

    ヘルプなどを何度も呼んでいるのですが、どうしてもそのような項目が見当たりません。
    何か実現する方法があるのでしょうか。

    どうか、アドバイスお願いします。

    2011年4月11日 18:52

回答

  • ようやく時間が取れたの検証してみました。

    TABキーおよびENTERキーはまずComboBoxが受け取ります。つまり、DataGridViewComboBoxEditingControlが受け取ることになります。この時、TABキーとENTERキーではDataGridViewComboBoxEditingControlにおける動作が異なります。TABキーの場合ですと項目の選択(SelectedIndexなどのセット)をするところまで行ってくれるようですが、ENTERキーの場合はそれを行わず、そのままDropDownListを閉じてしまうようです。ですから、DataGridViewComboBoxEditingControlにおいて、ENTERキーでもSelectedIndexをセットするようにすれば解決しそうです。

    >ところで、前回の返信の中にあった「鉛筆マーク」の確認はどのような意味があるのでしょうか。

    編集中という意味です。DataRowのBeginEditメソッドが実行された状態です。バインド系のコントロールにはこのメソッドが必要です。バインドを行うコントロールではデータ項目をいくつか変更し、<del>その変更結果をまとめてデータソースに返す必要があるからです。例えば3つの列がある場合、1つの列を変更する度にデータソースに書き戻していたのでは効率が悪いですし</del>、エラーチェックでも不具合が生じます。3つの列を入力し終わった時点でエラーチェックをして欲しいからです。そうしなければ、例えば3列目はnullを禁止にしていても、1列目を入力し終わった時点でDataRowのエラーチェックが走ってしまい、3列目はnullじゃダメとしかられても、「おいおいちょっと待ってよ、まだ全部入力してないんだからね」っていうことです。
    編集中を終わるにはEndEditメソッドを実行し、<del>この時点でデータソースのDataRowへまとめて書き戻されます。</del>
    BeginEditメソッド、EndEditメソッドはバインド系のコントロールが自動的に呼び出します。
    つまり、鉛筆マークになっていない状態ではいくらComboBoxをいじっても、データソースへその値が書き戻されないということを言いたかったのです。

    さて、話を元に戻して、TI-cb400さんのコードに少し手を加えてみました。標準のDataGridViewComboBoxEditingControlを使わずに、それを継承したExDataGridViewComboBoxEditingControlでENTERキーを受け取ったらSelectedIndexをセットするようにしてます。このクラスを使うために、DataGridViewComboBoxColumnを継承したExDataGridViewComboBoxColumnクラスも定義しています。一度コンパイルするとこのExDataGridViewComboBoxColumnがデザイナで選択できるようになりますので、デザイナで選択して下さい。
    また、以下のコードにおいて、
    cb.EditingControlDataGridView.NotifyCurrentCellDirty(True)
    という記述がありますが、これは強制的に鉛筆モードにするための記述です。理由は上で説明した通りです。

    Imports System.Data.SqlClient
    
    Public Class DataGridViewComboBox3
    
      Private DT As DataTable
    
      Private Sub SetCombobox()
        DT = New DataTable
    
        Using Cn As New SqlConnection(My.MySettings.Default.TESTConnectionString)
          Using Cmd As New SqlCommand
    
            With Cmd
              .Connection = Cn
              .CommandType = CommandType.StoredProcedure
              .CommandText = "SelectTEST"
              '.Parameters.AddWithValue("@firstkana", "")
              '.Parameters.AddWithValue("@lastkana", "")
            End With
    
            Using DA As New SqlDataAdapter
              DA.SelectCommand = Cmd
    
              DA.Fill(DT)
            End Using
    
            Dim clm As DataGridViewComboBoxColumn = Me.CDataGridView1.Columns("clmEmployee")
    
            With clm
              .DataSource = DT
              '.DisplayMember = "ptname"
              '.ValueMember = "pt_ptcode"
              .DisplayMember = "TEST1"
              .ValueMember = "ID"
            End With
    
          End Using
        End Using
      End Sub
    
      Private Sub SetDateFormat()
    
        '日付の書式設定
        'Dim Wareki As New clsDGVWarekiTextBoxCell
        'Dim clm As DataGridViewColumn = Me.CDataGridView1.Columns("clmDate")
    
        'clm.CellTemplate = Wareki
    
        'Headerの文字位置設定
        Me.CDataGridView1.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
    
        For ColumnIndex As Integer = 0 To Me.CDataGridView1.ColumnCount - 1
          Me.CDataGridView1.Columns(ColumnIndex).SortMode = DataGridViewColumnSortMode.NotSortable
        Next
      End Sub
    
      Private Sub frmTest_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        SetCombobox()
        SetDateFormat()
      End Sub
    
    End Class
    
    Public Class CDataGridView
      Inherits System.Windows.Forms.DataGridView
    
    
      Private Sub CDataGridView_CellEnter(ByVal sender As Object, _
          ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles Me.CellEnter
        Dim dgv As CDataGridView = CType(sender, CDataGridView)
    
        '編集コントロールがコンボボックスの場合
        If TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewComboBoxColumn Then
          SendKeys.Send("{F4}")
          '編集コントロールがテキストボックスの場合
        ElseIf TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewTextBoxColumn Then
          SendKeys.Send("{F2}")
        End If
      End Sub
    
      '**********************************************************************************************
      'Enterキーで移動
      '**********************************************************************************************
      <System.Security.Permissions.UIPermission( _
        System.Security.Permissions.SecurityAction.LinkDemand, _
        Window:=System.Security.Permissions.UIPermissionWindow.AllWindows)> _
      Protected Overrides Function ProcessDialogKey( _
       ByVal keyData As Keys) As Boolean
    
        System.Diagnostics.Debug.WriteLine("ProcessDialogKey")
    
        If (keyData And Keys.KeyCode) = Keys.Enter Then
          Return Me.ProcessTabKey(keyData)
        ElseIf (keyData And Keys.KeyCode) = Keys.Delete Then
          If MessageBox.Show("選択している行を削除します。" & vbCr & "よろしいですか" _
            , "削除確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) = DialogResult.OK Then
            DeleteRows()
          End If
        End If
    
        Return MyBase.ProcessDialogKey(keyData)
      End Function
    
      <System.Security.Permissions.SecurityPermission( _
         System.Security.Permissions.SecurityAction.LinkDemand, Flags:= _
        System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)> _
      Protected Overrides Function ProcessDataGridViewKey(ByVal e As System.Windows.Forms.KeyEventArgs) As Boolean
        System.Diagnostics.Debug.WriteLine("ProcessDataGridViewKey")
    
        If e.KeyCode = Keys.Enter Then
          Return Me.ProcessRightKey(e.KeyData)
        End If
    
        Return MyBase.ProcessDataGridViewKey(e)
      End Function
    
      '**********************************************************************************************
      '選択されている行を削除
      '**********************************************************************************************
      Private Sub DeleteRows()
        Dim r As DataGridViewRow
    
        For Each r In Me.SelectedRows
          Me.Rows.Remove(r)
        Next
      End Sub
    
      '**********************************************************************************************
      'Comboboxに直接入力できるようにDropDownStyleをDropDownに変更
      '**********************************************************************************************
      Private Sub CDataGridView_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles Me.EditingControlShowing
        If TypeOf e.Control Is DataGridViewComboBoxEditingControl Then
          Dim cb As DataGridViewComboBoxEditingControl = CType(e.Control, DataGridViewComboBoxEditingControl)
    
          'cb.DropDownStyle = ComboBoxStyle.DropDown  //既定のスタイルなのでセットしなくても良い
    
          '必ず編集モードにする。
          cb.EditingControlDataGridView.NotifyCurrentCellDirty(True)
        End If
      End Sub
    
    
    End Class
    
    Public Class ExDataGridViewComboBoxColumn
      Inherits System.Windows.Forms.DataGridViewComboBoxColumn
    
      Public Sub New()
        MyBase.New()
        Me.CellTemplate = New ExDataGridViewComboBoxCell
      End Sub
    
    End Class
    
    Public Class ExDataGridViewComboBoxCell
      Inherits System.Windows.Forms.DataGridViewComboBoxCell
    
      Public Overrides ReadOnly Property EditType() As System.Type
        Get
          Return GetType(ExDataGridViewComboBoxEditingControl)
        End Get
      End Property
    
    End Class
    
    Public Class ExDataGridViewComboBoxEditingControl
      Inherits System.Windows.Forms.DataGridViewComboBoxEditingControl
    
      Protected Overrides Function IsInputKey(ByVal keyData As Keys) As Boolean
        Select Case keyData
          Case Keys.Enter
            Me.SelectedIndex = Me.FindStringExact(Me.Text)
            Return False
          Case Else
            Return MyBase.IsInputKey(keyData)
        End Select
      End Function
    
    End Class

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    • 編集済み trapemiyaModerator 2011年4月30日 12:37 編集するとコードが崩れるので再掲載
    • 回答としてマーク TI-cb400 2011年5月2日 9:18
    2011年4月30日 8:21
    モデレータ

すべての返信

  • おはようございます。

    追加情報です。

    ComboboxのDropDownStyleは「DropDown」にしてあります。

    現状の動作として
    ・Comboboxにフォーカスが移動すると、DropDownListが表示される
    ・Listが表示された最初の状態では、矢印キーでListを移動でき、Enterキーでデータが確定される
    となっています。

    しかし、ここでComboboxに文字を入力すると、その文字を含む先頭行までListが移動してくれます。
    この状態でも、矢印キーでデータは移動できているのですが、選択されているであろう行が青く反転されません。

    また、該当の行が見つかりEnterキーを押してもその段階では選択したデータが表示されているのですが、
    フォーカスが移動すると別のデータになってしまいます。

    ただ、上記の操作をしてマウスで該当のデータをクリックすると、フォーカスを移動しても選択したデータのままと
    なっています。

    SelectedValueが変更されていないためと考えられるのですが、DisplayMemberを変更して、SelectedValueが
    一緒に変更にならない理由がわかりません。

    どうか、アドバイスお願いいたします。

    2011年4月12日 18:51
  • おはようございます。

    情報があまりにも不足していることに気がつきましたので、追加の記載です。

    DataGridViewのComboboxはDataTableをDataSourceとしてセットしてあります。

    いろいろと考えていたのですが、流れとしては以下の形になるかと考えます。
    ・Comboboxに文字入力
    ・候補の先頭にComboboxのデータが移動
    ・データを選択

    データの選択のところで、ComboboxのDisplayMemberであるデータはCombobox上に
    表示されているのですが、ValueMemberがセットされていないようでした。

    そこで、
    ・Comboboxに現在表示されているデータを取得
    ・上記の値を元にDataTableから該当の行を検索
    ・検索により取得した行からComboboxのValueMemberにセットしている値を取得
    ・ComboboxのSelectedValueに上記の値をセット

    で実現可能と思われるのですが、流れとしてはいかがでしょうか。

    それとも、もっと簡単に実現可能な方法がありますでしょうか。

    2011年4月13日 23:56
  • こんにちは。

    DataGridViewのComboboxを更新したときの表示されているテキストを取得し、DataTable内を検索するキーとするため、
    以下のように記述をして見ました。

    しかし、この状態ではテキストが取得できません。

    ヘルプやインターネットでいろいろと調べてみているのですが、

    Private Sub DataGridView_CellValidating(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles dgvAzukarikin.CellValidating
            Dim dgv As DataGridView = CType(sender, DataGridView)

            '
            If TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewComboBoxColumn Then
                Dim str As String = dgv.CurrentCell.FormattedValue
                Dim foundRow() As DataRow

                foundRow = retDT.Select("name = '" & str & "'")

                Me.dgvAzukarikin.Item(e.ColumnIndex, e.RowIndex).Value = foundRow("name")
            End If
        End Sub

    DataGridViewのComboboxの表示されているデータを取得するにはどのようにすればよいのでしょうか。

    どうか、アドバイスお願いします。

    2011年4月16日 8:56
  • ComboBoxを取得するには、DataGridViewのEditingControlShowingイベントを利用します。その引数であるDataGridViewEditingControlShowingEventArgsのControlプロパティをキャストすることにより、当該のComboBoxを取得することができます。ComboBoxが取得できれば、そのTextプロパティで表示されているデータが取得できるはずです。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月16日 13:20
    モデレータ
  • ご回答ありがとうございます。

    以下のように記述をして見ました。

    Private EditingControlCombobox As DataGridViewComboBoxEditingControl


     Private Sub dgv_CellValidating(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles dgv.CellValidating
            Dim dgv As DataGridView = CType(sender, DataGridView)

            '
            If TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewComboBoxColumn Then
                Dim SearchName As String = ""
                Dim foundRow() As DataRow

                SearchName = EditingControlCombobox.Text

                If SearchName <> "" Then
                    foundRow = DT.Select("name = '" & SearchName & "'")   →  ここ

                    EditingControlCombobox.SelectedValue = foundRow(0)

                End If
            End If
        End Sub


        Private Sub dgv_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles dgv.EditingControlShowing
            If TypeOf e.Control Is DataGridViewComboBoxEditingControl Then
                Dim dgv As DataGridView = CType(sender, DataGridView)

                EditingControlCombobox = CType(e.Control, DataGridViewComboBoxEditingControl)

            End If
        End Sub

    DataGridViewのComboboxにはDataTableがバインドされており、常時データが300件程度あるため、comboboxに直接データを入力し
    頭だしをしたうえで選択をしたいと考えております。

    また、以下のコードでセルに何も入力しない場合は、1行前に同じ行のデータを転記するようにしております。

    Private Sub dgvAzukarikin_CellLeave(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvAzukarikin.CellLeave
            If e.RowIndex >= 1 Then

                Me.dgv.CurrentCell.Value = Me.dgv(e.ColumnIndex, e.RowIndex - 1).Value

            End If
    End Sub

    上記の内容が原因ではないと思うのですが、新しい行に移動して、Comboboxから選択をすると、1行前のComboboxのデータが表示されてしまいます。

    EditingControlShowingで取得したComboboxがそのままになっていることが原因かと思い、DataGridViewのCellLeaveイベントをComboboxを取得するために
    用意した変数をNothingにしたところ、今度はデータ自体が取得できなくなってしまいました。

    原因を調べているところですが、まだわかっていません。

    よろしければアドバイスをお願いします。

    2011年4月17日 9:13
  • DataGridView全体でDataGridViewComboBoxEditingControlのインスタンスは一つしかありません。ComboBox列をクリックしたタイミングでそのComboBoxのインスタンスが場所を変えて表示されます。EditingControlShowingは実際にComboBoxが表示される前に発生しますので、一つ前のComboBoxの状態を取得することになります。
    DataGridViewComboBoxEditingControlにKeyPressイベントハンドラを設定し、そこで頭出しをされてみてはいかがでしょうか?

    (参考)
    DataGridViewでセルが編集中の時にキーイベントを捕捉する
    http://dobon.net/vb/dotnet/datagridview/textboxevent.html

    (追記)
    TextChangedイベント辺りがいいかもしれませんね。 


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月17日 12:18
    モデレータ
  • ご回答ありがとうございます。

    理解が悪くて大変申し訳ないのですが、DataGridViewにKeyPressイベントは見つかったのですが、TextChangedイベントがみつからないのですが、
    こちらはどこにあるのでしょうか。

    また、EditingControlShowingは1つ前のComboboxの状態を取得するとのことですが、なぜ、上記の参考URLでは現在編集中の内容を取得する
    ことになるのでしょうか。

    今、開発環境が手元にないので、実際にコードのテストはもう少し後になります。

    よろしくお願いします。

    2011年4月18日 22:01
  • 理解が悪くて大変申し訳ないのですが、DataGridViewにKeyPressイベントは見つかったのですが、TextChangedイベントがみつからないのですが、
    こちらはどこにあるのでしょうか。

    DataGridViewではなくComboBoxのTextChangedイベントのことを言っています。繰り返しになりますが、DataGridViewでComboBoxを操作している時は、DataGridViewの上にComboBoxが現れて、それを操作しているイメージになります。

    また、EditingControlShowingは1つ前のComboboxの状態を取得するとのことですが、なぜ、上記の参考URLでは現在編集中の内容を取得する
    ことになるのでしょうか。

    こちらも繰り返しになりますが、EditingControlShowingイベントで得られるComboBoxは常に1つのインスタンスです。上記の参考URLではそのComboBoxに対してKeyPressイベントハンドラを設定しているだけで、ComboBoxの値を取得しているわけではありません。この後、ComboBoxに値を入力することによりKeyPressイベントが発生し、そのイベントハンドラで入力された値を取得しています。つまり、ComboBoxから値を取得するのはComboBoxが表示され、ユーザーが入力した後になります。一方、EditingControlShowingイベントはComboBoxが表示される前に発生します。ComboBoxのインスタンスは1つで使いまわしていますから、そのインスタンスから値を取得すると、1つ前のユーザーが入力した値になるわけです。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月19日 0:48
    モデレータ
  • ご回答が遅くなりました。

    DataGridViewのComboboxのTextを取得し、ComboboxのValueMemberの値を取得するところまでできました。

    しかし、そのあたいをComboboxに指定したいのですが、セルを移動するとComboboxが空白になってしまいます。
    (取得したEditingControlのSelectedValueにセットすればできそうなのですが)

    以下、コードです。

    Private Sub dgv_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles dgvAzukarikin.EditingControlShowing
            If TypeOf e.Control Is DataGridViewComboBoxEditingControl Then
                '    Dim dgv As DataGridView = CType(sender, DataGridView)

                Me.dataGridViewCombobox = CType(e.Control, DataGridViewComboBoxEditingControl)

                'TextChangedイベントハンドラを追加
                AddHandler Me.dataGridViewCombobox.TextChanged, AddressOf dataGridViewCombobox_TextChanged
            End If
        End Sub

        Private Sub dataGridViewCombobox_TextChanged(ByVal sender As Object, ByVal e As EventArgs)
            'Dim cb As DataGridViewComboBoxEditingControl = CType(sender, DataGridViewComboBoxEditingControl)
            dataGridViewCombobox = CType(sender, DataGridViewComboBoxEditingControl)

            _Name = dataGridViewCombobox.Text

            If InStr(_Name, " ") <> 0 AndAlso _Name <> "" Then
                Dim foundRow() As DataRow

                foundRow = DT.Select("name = '" & _Name & "'")

                dataGridViewCombobox.SelectedValue = foundRow(0)           → ここ

            End If
           
        End Sub

    上記のfoundRowに表示中のデータを含むDataTableの行を取得していることまではデバッグで確認しております。

    矢印の部分で値をセットできると思ったのですが、デバッグで確認するとエラーにはならないのですが、矢印の部分のdataGridViewComboboxが
    Nothingになっています。

    値のセットの仕方が悪いのでしょうか。

    Comboboxのヘルプなどを読み直しているところですが、どうにも理由がわかりません。

    2011年4月23日 1:09
  •             dataGridViewCombobox.SelectedValue = foundRow(0)           → ここ

    SelectedValueにDataRowをセットすることは通常ありえませんので、foundRow(0)(0)のような形になるはずです。
    また、「dataGridViewComboboxがNothingになっています。」と書かれていますが、上記の理由により、dataGridComboboxのSelectedValueがNothingということではないでしょうか?

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月23日 13:32
    モデレータ
  • ご回答ありがとうございます。

    以前作成したコードを何の考えもなしにコピーしていたので、まったく気づいていませんでした。
    本当に基本的なことを失念しておりました。

    ご教授いただいたおかげでようやく、形になってきました。

    しかし、最後に1点疑問があります。

    現在、DataGridViewはカスタムコントロール(この説明でよいかわからないのですが)として使用しています。
    具体的にはEnterキーを押すことでTabキー同様次のセルへ移動できるようにあらかじめしております。

    現在のDataGridViewにおいて、Tabキーで該当のComboboxから次のセルへ移動すると、データがComboboxに
    表示されるのですが、Enterキーを押した場合では、最初のときと同様、Comboboxが空白になってしまいます。

    はじめは、Enterキーを押したときにTabキーを押したときと同じ動作をさせればよいと思ったのですが、もともと使用している
    DataGridViewにはそのコードを記述してあり、なぜ、このような動作になってしまうか、皆目見当がつきません。

    改めて、DataGridViewを配置しているフォームにも、同じコードを書いて試してみようと思うのですが、同じことを2度やるのは
    どう考えてもおかしいので、悩んでおります。

    何か、原因があるものなのでしょうか。
    もう少し、調べてみたいと思います。

    2011年4月24日 3:25
  • おはようございます。

    経過報告です。(まだ、思い通りには動いていません。)

    Enterキーを押した際に、TextChangedイベントよりも先に、DataGridViewのEnterキーを押すことによりTabキーと同じ動作をするコードが
    先に走っているかとおもい、デバッグにて確認をしたところ、先にTextChangedイベントが動作し、DataGridViewComboboxに値をセットしてから
    DataGridViewのEnterキーを押した際のイベントに移動しておりました。

    以下、DataGridViewのカスタムコントロールの該当部分のコードです。

     '**********************************************************************************************
        'Enterキーで移動
        '**********************************************************************************************
        <System.Security.Permissions.UIPermission( _
            System.Security.Permissions.SecurityAction.LinkDemand, _
            Window:=System.Security.Permissions.UIPermissionWindow.AllWindows)> _
    Protected Overrides Function ProcessDialogKey( _
         ByVal keyData As Keys) As Boolean

            If (keyData And Keys.KeyCode) = Keys.Enter Then

                Return Me.ProcessTabKey(keyData)
            ElseIf (keyData And Keys.KeyCode) = Keys.Delete Then
                If MessageBox.Show("選択している行を削除します。" & vbCr & "よろしいですか" _
                    , "削除確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) = DialogResult.OK Then
                    DeleteRows()
                End If
            End If

            Return MyBase.ProcessDialogKey(keyData)
        End Function

    また、余談ではありますが、Enterキーを押した際にTabキーと同じ動作をするはずなので、右隣のセルに移動するはずなのですが、なぜか
    下のセルに移動してしまうことがあります。
    こちらも、原因がよくわかっていません。

    Tabキーを用いて、移動をすれば目的の動作は実現できてはいるのですが、記述したコードどおりにしか、プログラムは動かないといわれるように
    どうにも、現状はすっきりとしないので、何とか解決をしたいと思っております。

    又、経過を報告しますので、何かお気づきの方、ぜひともアドバイスお願いします。

    2011年4月25日 18:01
  • なかなかわかりずらい問題のようですが、Enterキーでの移動に関する処理に問題がありそうということですね?
    その処理にProcessDialogKeyメソッドを使用されていますが、これはDataGridViewがEditモードの場合に使用できます。Editモードでない時はProcessDataGridViewKeyメソッドを使用することになりますが、この辺りの使い分けは大丈夫でしょうか? 例えばReadOnlyのカラムの場合はEditできませんので、ProcessDataGridViewKeyメソッドを使う必要があるのではないかと思います。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月26日 5:12
    モデレータ
  • ご回答ありがとうございます。

    使い分けの件に関してはまったく知りませんでした。

    また、DataGridViewのEnterきーの挙動を知るためにヘルプを見たところ、どうもいろいろな挙動が混ざってしまっているような印象です。

    まず、編集モードの件ですが、常にEditモードになっているはずです。
    というのは、使用しているカスタムコントロールのDataGridViewに以下のコードを記述しております。

    Private Sub CDataGridView_CellEnter(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles Me.CellEnter
            Dim dgv As CDataGridView = CType(sender, CDataGridView)

            '編集コントロールがコンボボックスの場合
            If TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewComboBoxColumn Then
                SendKeys.Send("{F4}")

                '編集コントロールがテキストボックスの場合
            ElseIf TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewTextBoxColumn Then
                SendKeys.Send("{F2}")
            End If
        End Sub

    しかし、いろいろとテストをしてみると、時々編集モードにならない場合(セル全体が青く反転し、テキストボックスなどの
    先頭にカーソルが来ていない状態)があり、そのような場合にEnterキーを押すと下のセルに移動するようです。
    (ヘルプを見ましたが、これがDataGridViewにおける、Enterキーの標準的な動作なのですね)

    ProcessDataGridViewKeyメソッドをためして、また報告をしたいと思います。

    2011年4月26日 18:35
  • 経過報告です。

    ProcessDataGridViewKeyメソッドを試してみましたが、結果は変わりません。

    DataGridViewComboboxを編集モードにて入力を行い、リストから候補を選択し、Tabキーで移動をする場合はデータが残り、
    Enterキーで移動をする場合は、表示が消えてしまいます。

    正直、現段階ではお手上げです。

    2011年4月27日 9:46
  • Enterキーで移動した際に表示が消えてしまう場合、DataGridViewの行ヘッダはえんぴつマークが出ているのでしょうか? また、Enterキーによる横移動を止めた時には表示が消えずに正しく入力されるのでしょうか? 私の方でも簡単なテストコードを書いて試していますが、問題を把握しきれていないようです。可能であれば、再現する最低限のコードを目指してテスト的なものを作られてみてはいかがでしょうか? そして、再現できればそのコードを示していただくと、いろいろな方の具体的なアドバイスがいただけるのではないかと思います。もちろん私も力の及ぶ限り拝見させていただきます。また、再現する最低限のコードを構築する過程で、何かに気付かれる可能性もあると思います。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月27日 15:04
    モデレータ
  • ご回答ありがとうございました。

    鉛筆マークの件ですが、Comboboxに直接入力をしている間は黒い三角マークになり、直接入力をせず、矢印キーでリストから選択する場合は
    鉛筆マークのままになっています。

    また、テストで同じようなものを作成してみたのですが、結果は同じでした。

    以下に、テストで作成したコードです。

    デザイナ

    <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
    Partial Class frmTest
        Inherits System.Windows.Forms.Form

        'フォームがコンポーネントの一覧をクリーンアップするために dispose をオーバーライドします。
        <System.Diagnostics.DebuggerNonUserCode()> _
        Protected Overrides Sub Dispose(ByVal disposing As Boolean)
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
            MyBase.Dispose(disposing)
        End Sub

        'Windows フォーム デザイナで必要です。
        Private components As System.ComponentModel.IContainer

        'メモ: 以下のプロシージャは Windows フォーム デザイナで必要です。
        'Windows フォーム デザイナを使用して変更できます。 
        'コード エディタを使って変更しないでください。
        <System.Diagnostics.DebuggerStepThrough()> _
        Private Sub InitializeComponent()
            Me.CDataGridView1 = New HpSystem.CDataGridView
            Me.clmDate = New System.Windows.Forms.DataGridViewTextBoxColumn
            Me.clmEmployee = New System.Windows.Forms.DataGridViewComboBoxColumn
            Me.Column1 = New System.Windows.Forms.DataGridViewTextBoxColumn
            CType(Me.CDataGridView1, System.ComponentModel.ISupportInitialize).BeginInit()
            Me.SuspendLayout()
            '
            'CDataGridView1
            '
            Me.CDataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize
            Me.CDataGridView1.Columns.AddRange(New System.Windows.Forms.DataGridViewColumn() {Me.clmDate, Me.clmEmployee, Me.Column1})
            Me.CDataGridView1.Location = New System.Drawing.Point(12, 46)
            Me.CDataGridView1.Name = "CDataGridView1"
            Me.CDataGridView1.RowTemplate.Height = 21
            Me.CDataGridView1.Size = New System.Drawing.Size(562, 339)
            Me.CDataGridView1.TabIndex = 0
            '
            'clmDate
            '
            Me.clmDate.HeaderText = "日付"
            Me.clmDate.Name = "clmDate"
            '
            'clmEmployee
            '
            Me.clmEmployee.HeaderText = "社員"
            Me.clmEmployee.Name = "clmEmployee"
            '
            'Column1
            '
            Me.Column1.HeaderText = "Column1"
            Me.Column1.Name = "Column1"
            '
            'frmTest
            '
            Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 12.0!)
            Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
            Me.ClientSize = New System.Drawing.Size(586, 445)
            Me.Controls.Add(Me.CDataGridView1)
            Me.Name = "frmTest"
            Me.Text = "frmTest"
            CType(Me.CDataGridView1, System.ComponentModel.ISupportInitialize).EndInit()
            Me.ResumeLayout(False)

        End Sub
        Friend WithEvents CDataGridView1 As HpSystem.CDataGridView
        Friend WithEvents clmDate As System.Windows.Forms.DataGridViewTextBoxColumn
        Friend WithEvents clmEmployee As System.Windows.Forms.DataGridViewComboBoxColumn
        Friend WithEvents Column1 As System.Windows.Forms.DataGridViewTextBoxColumn
    End Class

    フォームのコード

    Imports System.Data.SqlClient

    Public Class frmTest
        Private DT As DataTable

        Private Sub SetCombobox()
            DT = New DataTable

            Using Cn As New SqlConnection(My.MySettings.Default.Con)
                Using Cmd As New SqlCommand

                    With Cmd
                        .Connection = Cn
                        .CommandType = CommandType.StoredProcedure
                        .CommandText = "s_s_ptsearch"
                        .Parameters.AddWithValue("@firstkana", "")
                        .Parameters.AddWithValue("@lastkana", "")
                    End With

                    Using DA As New SqlDataAdapter
                        DA.SelectCommand = Cmd

                        DA.Fill(DT)
                    End Using

                    Dim clm As DataGridViewComboBoxColumn = Me.CDataGridView1.Columns("clmEmployee")

                    With clm
                        .DataSource = DT
                        .DisplayMember = "ptname"
                        .ValueMember = "pt_ptcode"
                    End With

                End Using
            End Using
        End Sub

        Private Sub SetDateFormat()

            '日付の書式設定
            Dim Wareki As New clsDGVWarekiTextBoxCell
            Dim clm As DataGridViewColumn = Me.CDataGridView1.Columns("clmDate")

            clm.CellTemplate = Wareki

            'Headerの文字位置設定
            Me.CDataGridView1.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter

            For ColumnIndex As Integer = 0 To Me.CDataGridView1.ColumnCount - 1
                Me.CDataGridView1.Columns(ColumnIndex).SortMode = DataGridViewColumnSortMode.NotSortable
            Next
        End Sub

        Private Sub frmTest_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            SetCombobox()
            SetDateFormat()
        End Sub
    End Class

    2011年4月27日 23:35
  • 問題はCDataGridViewの実装だと思うのです。CDataGridViewではなく、問題が再現する最小限のDataGridViewの新たに作成したカスタムコントロールのコードをご提示できますでしょうか? 再現するコードがあれば、こちらでもいろいろと試行することができます。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月28日 16:27
    モデレータ
  • ご回答ありがとうございます。

    以下がカスタムコントロールのコードです。

    Public Class CDataGridView
        Inherits System.Windows.Forms.DataGridView

      
        Private Sub CDataGridView_CellEnter(ByVal sender As Object, _
                ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles Me.CellEnter
            Dim dgv As CDataGridView = CType(sender, CDataGridView)

            '編集コントロールがコンボボックスの場合
            If TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewComboBoxColumn Then
                SendKeys.Send("{F4}")

                '編集コントロールがテキストボックスの場合
            ElseIf TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewTextBoxColumn Then
                SendKeys.Send("{F2}")
            End If
        End Sub

        '**********************************************************************************************
        'Enterキーで移動
        '**********************************************************************************************
        <System.Security.Permissions.UIPermission( _
            System.Security.Permissions.SecurityAction.LinkDemand, _
            Window:=System.Security.Permissions.UIPermissionWindow.AllWindows)> _
    Protected Overrides Function ProcessDialogKey( _
         ByVal keyData As Keys) As Boolean

            If (keyData And Keys.KeyCode) = Keys.Enter Then

                Return Me.ProcessTabKey(keyData)
            ElseIf (keyData And Keys.KeyCode) = Keys.Delete Then
                If MessageBox.Show("選択している行を削除します。" & vbCr & "よろしいですか" _
                    , "削除確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) = DialogResult.OK Then
                    DeleteRows()
                End If
            End If

            Return MyBase.ProcessDialogKey(keyData)
        End Function

        <System.Security.Permissions.SecurityPermission( _
             System.Security.Permissions.SecurityAction.LinkDemand, Flags:= _
            System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)> _
        Protected Overrides Function ProcessDataGridViewKey(ByVal e As System.Windows.Forms.KeyEventArgs) As Boolean
            If e.KeyCode = Keys.Enter Then
                Return Me.ProcessRightKey(e.KeyData)
            End If

            Return MyBase.ProcessDataGridViewKey(e)
        End Function

        '**********************************************************************************************
        '選択されている行を削除
        '**********************************************************************************************
        Private Sub DeleteRows()
            Dim r As DataGridViewRow

            For Each r In Me.SelectedRows
                Me.Rows.Remove(r)
            Next
        End Sub

        Private Sub CDataGridView_CellValidating(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles Me.CellValidating

        End Sub

        '**********************************************************************************************
        'Comboboxに直接入力できるようにDropDownStyleをDropDownに変更
        '**********************************************************************************************
        Private Sub CDataGridView_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles Me.EditingControlShowing
            If TypeOf e.Control Is DataGridViewComboBoxEditingControl Then
                Dim cb As DataGridViewComboBoxEditingControl = CType(e.Control, DataGridViewComboBoxEditingControl)

                cb.DropDownStyle = ComboBoxStyle.DropDown
            End If
        End Sub

    End Class

    ところで、前回の返信の中にあった「鉛筆マーク」の確認はどのような意味があるのでしょうか。
    Googleでも検索をしてみましたが、すぐにはよく理解ができませんでした。

    2011年4月28日 20:46
  • ようやく時間が取れたの検証してみました。

    TABキーおよびENTERキーはまずComboBoxが受け取ります。つまり、DataGridViewComboBoxEditingControlが受け取ることになります。この時、TABキーとENTERキーではDataGridViewComboBoxEditingControlにおける動作が異なります。TABキーの場合ですと項目の選択(SelectedIndexなどのセット)をするところまで行ってくれるようですが、ENTERキーの場合はそれを行わず、そのままDropDownListを閉じてしまうようです。ですから、DataGridViewComboBoxEditingControlにおいて、ENTERキーでもSelectedIndexをセットするようにすれば解決しそうです。

    >ところで、前回の返信の中にあった「鉛筆マーク」の確認はどのような意味があるのでしょうか。

    編集中という意味です。DataRowのBeginEditメソッドが実行された状態です。バインド系のコントロールにはこのメソッドが必要です。バインドを行うコントロールではデータ項目をいくつか変更し、<del>その変更結果をまとめてデータソースに返す必要があるからです。例えば3つの列がある場合、1つの列を変更する度にデータソースに書き戻していたのでは効率が悪いですし</del>、エラーチェックでも不具合が生じます。3つの列を入力し終わった時点でエラーチェックをして欲しいからです。そうしなければ、例えば3列目はnullを禁止にしていても、1列目を入力し終わった時点でDataRowのエラーチェックが走ってしまい、3列目はnullじゃダメとしかられても、「おいおいちょっと待ってよ、まだ全部入力してないんだからね」っていうことです。
    編集中を終わるにはEndEditメソッドを実行し、<del>この時点でデータソースのDataRowへまとめて書き戻されます。</del>
    BeginEditメソッド、EndEditメソッドはバインド系のコントロールが自動的に呼び出します。
    つまり、鉛筆マークになっていない状態ではいくらComboBoxをいじっても、データソースへその値が書き戻されないということを言いたかったのです。

    さて、話を元に戻して、TI-cb400さんのコードに少し手を加えてみました。標準のDataGridViewComboBoxEditingControlを使わずに、それを継承したExDataGridViewComboBoxEditingControlでENTERキーを受け取ったらSelectedIndexをセットするようにしてます。このクラスを使うために、DataGridViewComboBoxColumnを継承したExDataGridViewComboBoxColumnクラスも定義しています。一度コンパイルするとこのExDataGridViewComboBoxColumnがデザイナで選択できるようになりますので、デザイナで選択して下さい。
    また、以下のコードにおいて、
    cb.EditingControlDataGridView.NotifyCurrentCellDirty(True)
    という記述がありますが、これは強制的に鉛筆モードにするための記述です。理由は上で説明した通りです。

    Imports System.Data.SqlClient
    
    Public Class DataGridViewComboBox3
    
      Private DT As DataTable
    
      Private Sub SetCombobox()
        DT = New DataTable
    
        Using Cn As New SqlConnection(My.MySettings.Default.TESTConnectionString)
          Using Cmd As New SqlCommand
    
            With Cmd
              .Connection = Cn
              .CommandType = CommandType.StoredProcedure
              .CommandText = "SelectTEST"
              '.Parameters.AddWithValue("@firstkana", "")
              '.Parameters.AddWithValue("@lastkana", "")
            End With
    
            Using DA As New SqlDataAdapter
              DA.SelectCommand = Cmd
    
              DA.Fill(DT)
            End Using
    
            Dim clm As DataGridViewComboBoxColumn = Me.CDataGridView1.Columns("clmEmployee")
    
            With clm
              .DataSource = DT
              '.DisplayMember = "ptname"
              '.ValueMember = "pt_ptcode"
              .DisplayMember = "TEST1"
              .ValueMember = "ID"
            End With
    
          End Using
        End Using
      End Sub
    
      Private Sub SetDateFormat()
    
        '日付の書式設定
        'Dim Wareki As New clsDGVWarekiTextBoxCell
        'Dim clm As DataGridViewColumn = Me.CDataGridView1.Columns("clmDate")
    
        'clm.CellTemplate = Wareki
    
        'Headerの文字位置設定
        Me.CDataGridView1.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
    
        For ColumnIndex As Integer = 0 To Me.CDataGridView1.ColumnCount - 1
          Me.CDataGridView1.Columns(ColumnIndex).SortMode = DataGridViewColumnSortMode.NotSortable
        Next
      End Sub
    
      Private Sub frmTest_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        SetCombobox()
        SetDateFormat()
      End Sub
    
    End Class
    
    Public Class CDataGridView
      Inherits System.Windows.Forms.DataGridView
    
    
      Private Sub CDataGridView_CellEnter(ByVal sender As Object, _
          ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles Me.CellEnter
        Dim dgv As CDataGridView = CType(sender, CDataGridView)
    
        '編集コントロールがコンボボックスの場合
        If TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewComboBoxColumn Then
          SendKeys.Send("{F4}")
          '編集コントロールがテキストボックスの場合
        ElseIf TypeOf dgv.Columns(e.ColumnIndex) Is DataGridViewTextBoxColumn Then
          SendKeys.Send("{F2}")
        End If
      End Sub
    
      '**********************************************************************************************
      'Enterキーで移動
      '**********************************************************************************************
      <System.Security.Permissions.UIPermission( _
        System.Security.Permissions.SecurityAction.LinkDemand, _
        Window:=System.Security.Permissions.UIPermissionWindow.AllWindows)> _
      Protected Overrides Function ProcessDialogKey( _
       ByVal keyData As Keys) As Boolean
    
        System.Diagnostics.Debug.WriteLine("ProcessDialogKey")
    
        If (keyData And Keys.KeyCode) = Keys.Enter Then
          Return Me.ProcessTabKey(keyData)
        ElseIf (keyData And Keys.KeyCode) = Keys.Delete Then
          If MessageBox.Show("選択している行を削除します。" & vbCr & "よろしいですか" _
            , "削除確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) = DialogResult.OK Then
            DeleteRows()
          End If
        End If
    
        Return MyBase.ProcessDialogKey(keyData)
      End Function
    
      <System.Security.Permissions.SecurityPermission( _
         System.Security.Permissions.SecurityAction.LinkDemand, Flags:= _
        System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)> _
      Protected Overrides Function ProcessDataGridViewKey(ByVal e As System.Windows.Forms.KeyEventArgs) As Boolean
        System.Diagnostics.Debug.WriteLine("ProcessDataGridViewKey")
    
        If e.KeyCode = Keys.Enter Then
          Return Me.ProcessRightKey(e.KeyData)
        End If
    
        Return MyBase.ProcessDataGridViewKey(e)
      End Function
    
      '**********************************************************************************************
      '選択されている行を削除
      '**********************************************************************************************
      Private Sub DeleteRows()
        Dim r As DataGridViewRow
    
        For Each r In Me.SelectedRows
          Me.Rows.Remove(r)
        Next
      End Sub
    
      '**********************************************************************************************
      'Comboboxに直接入力できるようにDropDownStyleをDropDownに変更
      '**********************************************************************************************
      Private Sub CDataGridView_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles Me.EditingControlShowing
        If TypeOf e.Control Is DataGridViewComboBoxEditingControl Then
          Dim cb As DataGridViewComboBoxEditingControl = CType(e.Control, DataGridViewComboBoxEditingControl)
    
          'cb.DropDownStyle = ComboBoxStyle.DropDown  //既定のスタイルなのでセットしなくても良い
    
          '必ず編集モードにする。
          cb.EditingControlDataGridView.NotifyCurrentCellDirty(True)
        End If
      End Sub
    
    
    End Class
    
    Public Class ExDataGridViewComboBoxColumn
      Inherits System.Windows.Forms.DataGridViewComboBoxColumn
    
      Public Sub New()
        MyBase.New()
        Me.CellTemplate = New ExDataGridViewComboBoxCell
      End Sub
    
    End Class
    
    Public Class ExDataGridViewComboBoxCell
      Inherits System.Windows.Forms.DataGridViewComboBoxCell
    
      Public Overrides ReadOnly Property EditType() As System.Type
        Get
          Return GetType(ExDataGridViewComboBoxEditingControl)
        End Get
      End Property
    
    End Class
    
    Public Class ExDataGridViewComboBoxEditingControl
      Inherits System.Windows.Forms.DataGridViewComboBoxEditingControl
    
      Protected Overrides Function IsInputKey(ByVal keyData As Keys) As Boolean
        Select Case keyData
          Case Keys.Enter
            Me.SelectedIndex = Me.FindStringExact(Me.Text)
            Return False
          Case Else
            Return MyBase.IsInputKey(keyData)
        End Select
      End Function
    
    End Class

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    • 編集済み trapemiyaModerator 2011年4月30日 12:37 編集するとコードが崩れるので再掲載
    • 回答としてマーク TI-cb400 2011年5月2日 9:18
    2011年4月30日 8:21
    モデレータ
  • 編集中という意味です。DataRowのBeginEditメソッドが実行された状態です。バインド系のコントロールにはこのメソッドが必要です。バインドを行うコントロールではデータ項目をいくつか変更し、その変更結果をまとめてデータソースに返す必要があるからです。例えば3つの列がある場合、1つの列を変更する度にデータソースに書き戻していたのでは効率が悪いですし、エラーチェックでも不具合が生じます。3つの列を入力し終わった時点でエラーチェックをして欲しいからです。そうしなければ、例えば3列目はnullを禁止にしていても、1列目を入力し終わった時点でDataRowのエラーチェックが走ってしまい、3列目はnullじゃダメとしかられても、「おいおいちょっと待ってよ、まだ全部入力してないんだからね」っていうことです。
    編集中を終わるにはEndEditメソッドを実行し、この時点でデータソースのDataRowへまとめて書き戻されます。

    すみません。上記は誤りです。まとめてデータソースへ書き出されるわけではありません。つまり、DataGridViewの列に値を入力し終わったタイミングでDataTableへ書き出されます。編集中は制約などが一時的に無効になります。詳しくは以下を参考にして下さい。

    DataRow.BeginEdit メソッド
    http://msdn.microsoft.com/ja-jp/library/system.data.datarow.beginedit.aspx

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年4月30日 12:29
    モデレータ
  • ご回答ありがとうございました。

    ご提示いただいた内容で、やりたいことが実現できました。

    本当にありがとうございました。

    2011年5月2日 9:25