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

質問
-
・環境
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が表示される)
・補足
そもそも画面に表示しきれないレイアウトはどうなの?という問題は認識していますが、画面解像度その他含めてどうしてもそういう対処が必要のため、回避策を求めています。
よろしくお願いします。
回答
-
再現方法の手順が不足しているけど、たぶん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
すべての返信
-
再現方法の手順が不足しているけど、たぶん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