none
フォームに表示しきれない時に、勝手にフォーカスが当てられスクロールバー位置が変更されるのを防止したい RRS feed

  • 質問

  • ・環境

    OS:Windows 7

    VS:2012

    VB.Net:4.5

    ・問題点

    DataGridViewにて、Columnを3つ用意しColumn3のみを表示するようにフォームの幅を縮めスクロールバーを移動させる。

    Column3を入力後、描画はColumn3を表示しておいて欲しいが、スクロールバーが勝手に移動し、Column1からの描画になる。

    そのため、編集するたびに画面が移動して、入力がしづらい状態になっている。

    セル確定するたびにLeaveイベント等は発生しないが、GotFocusは発生しているためセルの確定時に何らかの処理がフォーカスを

    DataGridViewに当てようとし、当てられた結果、グリッドの左上から表示させようとして、スクロールバーが可変していると思われます。

    ・やりたい事

    Column3を編集したのだから、表示はColumn3のままを維持して欲しい

    出来ればDataGridViewが自分自身にフォーカスを設定しているのであれば、それをキャンセルしたい。

    ・現象再現方法

    ①DataGridViewをフォーム上に作成する(Columnは3つ程用意する)。

    ②フォームを縮め、DataGridViewを縮め、オートスクロールバーを表示させる(Column1のみ表示)

    ③Column3を表示出来るようにスクロールバーを移動させる

    ④Column3のセルを編集状態にし、解除する(確定またはColumn2をクリック等)

    ⑤DataGridViewが左寄せで表示される(Column3からColumn1が表示される)

    ・補足

    そもそも画面に表示しきれないレイアウトはどうなの?という問題は認識していますが、画面解像度その他含めてどうしてもそういう対処が必要のため、回避策を求めています。

    よろしくお願いします。

    2014年7月5日 2:03

回答

  • 再現方法の手順が不足しているけど、たぶんFormのAutoScroll=Trueとしているんだろうと推測してみる

    FormやPanelなどは、他のコントロールを内側に配置できてスクロールバーを表示できるScrollableControlを継承しており、内部のコントロールにフォーカスがセットされるとそのコントロールが表示できるように自動でスクロールさせています。
    また、フォーカスのセットはすでにセットされていても再度セットできるので、Leaveイベント無しでもGotFocusイベントは発生します。

    DataGridViewを継承してWndProcをオーバーライドしてWM_SETFOCUSを無視してもいいけど、他のコントロールからフォーカスが移ってきたときの移動もされなくなります。
    そこで他の方法として、Scrollableコントロールが自動でスクロールさせるときの移動量を書き換えてやることで移動させなかったりできます。

    Public Class Form1
    
        Protected Overrides Sub OnLoad(e As EventArgs)
            MyBase.OnLoad(e)
    
            Me.AutoScroll = True 'このウィンドウ(ScrollableControl)がスクロールバーを表示して内部をスクロールできるようにする
    
            Me.Controls.Clear()
            Dim dgv As New DataGridView
            dgv.Width = 400
            dgv.Height = 300
            For i As Integer = 1 To 3
                Dim clm As New DataGridViewTextBoxColumn
                clm.HeaderText = "Column" + i.ToString()
                clm.Width = 100
                dgv.Columns.Add(clm)
            Next
    
            dgv.Rows.Add(100)
            For c As Integer = 0 To dgv.Columns.Count - 1
                For r As Integer = 0 To dgv.Rows.Count - 1
                    dgv(c, r).Value = String.Format("C={0},R={1}", c, r)
                Next
            Next
    
            dgv.CurrentCell = dgv(2, 50)
            Me.Controls.Add(dgv)
    
            'DataGridViewより小さくしてこのウィンドウにスクロールバーを表示させる
            Me.Width = 150
            Me.Height = 200
    
            dgv.Focus() 'ためしにDataGridViewにフォーカスを設定してカレントセルが表示される位置にスクロールするか試す
        End Sub
    
    
        Protected Overrides Function ScrollToControl(activeControl As Control) As Point
            'ScrollableControlの子要素にフォーカスがセットされた時に
            'そのコントロールを表示するのに必要なスクロール量が計算される
            'つまり標準ではDataGridViewであればDataGridViewが表示されるように計算される
            'どうせなら、DataGridViewのCurrentCellが表示されるようにスクロール量を補正してみる
    
            Dim p As Point
            p = MyBase.ScrollToControl(activeControl) '対象のコントロールの左上を表示するための移動量
    
            If (TypeOf activeControl Is DataGridView) Then
                Dim dgv As DataGridView
                dgv = CType(activeControl, DataGridView)
    
                'カレントセルのDataGridViewの左上に対しての相対座標でのセルの位置を取得
                Dim rect As Rectangle = dgv.GetCellDisplayRectangle(dgv.CurrentCell.ColumnIndex, dgv.CurrentCell.RowIndex, True)
    
                Dim rectDGV As Rectangle = New Rectangle(0, 0, dgv.Width, dgv.Height)
                If (rect.Width > 0 And (rectDGV.Contains(rect) Or rectDGV.IntersectsWith(rect))) Then '必要ないだろうがカレントセルが表示範囲にあるかどうか調べる
                    p.Offset(-rect.Left, -rect.Top) 'カレントセルの左上が表示されるように移動量を補正。センターに配置したければWindowの大きさの半分をさらに補正してください
                End If
    
                'p=Point.Empty 'スクロールさせたくなければ移動量をゼロにしてしまえばいい
            End If
    
            Return p
        End Function
    End Class
    


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク mogja 2014年7月6日 2:06
    2014年7月5日 12:28

すべての返信

  • 再現方法の手順が不足しているけど、たぶんFormのAutoScroll=Trueとしているんだろうと推測してみる

    FormやPanelなどは、他のコントロールを内側に配置できてスクロールバーを表示できるScrollableControlを継承しており、内部のコントロールにフォーカスがセットされるとそのコントロールが表示できるように自動でスクロールさせています。
    また、フォーカスのセットはすでにセットされていても再度セットできるので、Leaveイベント無しでもGotFocusイベントは発生します。

    DataGridViewを継承してWndProcをオーバーライドしてWM_SETFOCUSを無視してもいいけど、他のコントロールからフォーカスが移ってきたときの移動もされなくなります。
    そこで他の方法として、Scrollableコントロールが自動でスクロールさせるときの移動量を書き換えてやることで移動させなかったりできます。

    Public Class Form1
    
        Protected Overrides Sub OnLoad(e As EventArgs)
            MyBase.OnLoad(e)
    
            Me.AutoScroll = True 'このウィンドウ(ScrollableControl)がスクロールバーを表示して内部をスクロールできるようにする
    
            Me.Controls.Clear()
            Dim dgv As New DataGridView
            dgv.Width = 400
            dgv.Height = 300
            For i As Integer = 1 To 3
                Dim clm As New DataGridViewTextBoxColumn
                clm.HeaderText = "Column" + i.ToString()
                clm.Width = 100
                dgv.Columns.Add(clm)
            Next
    
            dgv.Rows.Add(100)
            For c As Integer = 0 To dgv.Columns.Count - 1
                For r As Integer = 0 To dgv.Rows.Count - 1
                    dgv(c, r).Value = String.Format("C={0},R={1}", c, r)
                Next
            Next
    
            dgv.CurrentCell = dgv(2, 50)
            Me.Controls.Add(dgv)
    
            'DataGridViewより小さくしてこのウィンドウにスクロールバーを表示させる
            Me.Width = 150
            Me.Height = 200
    
            dgv.Focus() 'ためしにDataGridViewにフォーカスを設定してカレントセルが表示される位置にスクロールするか試す
        End Sub
    
    
        Protected Overrides Function ScrollToControl(activeControl As Control) As Point
            'ScrollableControlの子要素にフォーカスがセットされた時に
            'そのコントロールを表示するのに必要なスクロール量が計算される
            'つまり標準ではDataGridViewであればDataGridViewが表示されるように計算される
            'どうせなら、DataGridViewのCurrentCellが表示されるようにスクロール量を補正してみる
    
            Dim p As Point
            p = MyBase.ScrollToControl(activeControl) '対象のコントロールの左上を表示するための移動量
    
            If (TypeOf activeControl Is DataGridView) Then
                Dim dgv As DataGridView
                dgv = CType(activeControl, DataGridView)
    
                'カレントセルのDataGridViewの左上に対しての相対座標でのセルの位置を取得
                Dim rect As Rectangle = dgv.GetCellDisplayRectangle(dgv.CurrentCell.ColumnIndex, dgv.CurrentCell.RowIndex, True)
    
                Dim rectDGV As Rectangle = New Rectangle(0, 0, dgv.Width, dgv.Height)
                If (rect.Width > 0 And (rectDGV.Contains(rect) Or rectDGV.IntersectsWith(rect))) Then '必要ないだろうがカレントセルが表示範囲にあるかどうか調べる
                    p.Offset(-rect.Left, -rect.Top) 'カレントセルの左上が表示されるように移動量を補正。センターに配置したければWindowの大きさの半分をさらに補正してください
                End If
    
                'p=Point.Empty 'スクロールさせたくなければ移動量をゼロにしてしまえばいい
            End If
    
            Return p
        End Function
    End Class
    


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク mogja 2014年7月6日 2:06
    2014年7月5日 12:28
  • サンプルコード有難うございます。

    フォーム側のAutoScrollは確かにtrueとなっております。

    サンプルを実行してみました所、期待する動作をすることが出来ました。

    ただ、p = Point.Emptyの箇所を有効化し、試してみたところ左上に戻るといった動作をしてしまいました。

    サンプルを変更して動きをもう少し見てみます。有難う御座いました。

    2014年7月6日 2:26