none
DataGridView セルに合わせて横スクロールする RRS feed

  • 質問

  • みなさん。こんにちは。
    いつも参考にさせて頂いていいます。

    質問ですが、
    DataGridViewで、文字列を検索し、ヒットした場合、そのセルに移動する処理を行っています。
    方法は、指定文字列をもつセルがあれば、CurrentCellをそのセルに設定しています。

    この時、縦のスクロールは自動で行われ、該当行が表示されるのですが、
    横スクロールは行われず、該当セルの列位置が画面表示領域以外の場合、手作業で横スクロールしないと
    表示がされません。
    横スクロールも自動で行われ、該当セルを表示させるには、どの様にしたら良いか、ご存知の方、ご教授お願いします。

    宜しくお願いします。

    2010年11月8日 3:01

回答

  • 正常にスクロールされる場合とされない場合があるんですよね?

    以前、この件を調べて、バグっぽい箇所を見つけました。
    その際、詳細な資料を作成したんですが、フィードバックはしてませんでした。
    当時作成した資料を探したのですが、どこかへいっちゃったようです。
    確か、表示中の最後の列幅と、残りの非表示列幅が関係していたと思います。

    しんなべさんは VB ですよね?(2回目の確認です(^^;)
    下記コードは VB2008 で動作確認しました。
    フォームに DataGridView と Button を1つずつ配置して実行し、End キーを押して最終列に移動してみてください(追記:じゃなくて、ボタンを押してみてください)。

    もしこれで回避できる場合は、このコードでは CurrentCell を一度 Nothing に変更しますので、しんなべさんのアプリケーションに組み込まれた際にこれによる弊害が生じないか確認してください。
    見当はずれでしたらすみません。

    Imports System.Reflection

    Public Class Form1
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            DataGridView1.Columns.Clear()
            DataGridView1.Columns.Add("col1", "col1")
            DataGridView1.Columns.Add("col2", "col2")
            DataGridView1.Columns.Add("col3", "col3")
            DataGridView1.Columns.Add("col4", "col4")
            DataGridView1.Width = 360
            DataGridView1.Columns(2).Width = 300
            DataGridView1.RowCount = 2
        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            DataGridView1.CurrentCell = DataGridView1(3, 0)
        End Sub

        Private Sub DataGridView1_CurrentCellChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) _
            Handles DataGridView1.CurrentCellChanged
            Dim grid = DirectCast(sender, DataGridView)
            If grid.CurrentCell Is Nothing Then Return
            Me.BeginInvoke(DirectCast(AddressOf DataGridViewScrollFix, Action(Of DataGridView)), grid)
        End Sub

        Private _inDataGridViewScrollFix As Boolean

        Private Sub DataGridViewScrollFix(ByVal grid As DataGridView)
            If _inDataGridViewScrollFix Then
                _inDataGridViewScrollFix = False
                Return
            End If

            Dim currentCell = grid.CurrentCell
            If currentCell Is Nothing OrElse currentCell.Displayed Then Return

            grid.CurrentCell = Nothing

            'DataGridView の private な ScrollColumns メソッドの
            '先頭にある処理が災いしている。
            'DataGridView.displayedBandsInfo.LastTotallyDisplayedScrollingCol = -1
            'というコードに相当する処理をリフレクションで無理矢理実行することで、
            'その処理を迂回する。
            Dim type = GetType(DataGridView)
            Dim fi = type.GetField("displayedBandsInfo", BindingFlags.Instance Or BindingFlags.NonPublic)
            If fi IsNot Nothing Then
                Dim displayedBandsInfo = fi.GetValue(grid)
                If displayedBandsInfo IsNot Nothing Then
                    type = displayedBandsInfo.GetType()
                    Dim pi = type.GetProperty("LastTotallyDisplayedScrollingCol", _
                        BindingFlags.Instance Or BindingFlags.Public)
                    If pi IsNot Nothing Then
                        pi.SetValue(displayedBandsInfo, -1, Nothing)
                    End If
                End If
            End If

            _inDataGridViewScrollFix = True
            grid.CurrentCell = currentCell
        End Sub
    End Class

    • 編集済み TH01 2010年11月10日 8:27 少しコードを修正
    • 回答としてマーク しんなべ 2010年11月12日 8:33
    2010年11月8日 5:31
  • > 確か、表示中の最後の列幅と、残りの非表示列幅が関係していたと思います。

    少し違いました。
    しんなべさんも同じ状況かは不明ですが、1年前に調べたときの資料が見つかりましたので、不具合の概要を書かせてもらいます。(実際はもっといろいろな条件があって複雑です。)

    セルの表示の為に列のスクロールが必要な場合、DataGridView の private なメソッドである ScrollColumnIntoView が「スクロールアウトする列数」を決定し、同じく private な ScrollColumns メソッドがそれを受け取って実際にスクロールを行います。(どちらも DataGridViewMethods.cs で実装されています。)

    ScrollColumns が受け取るのは「スクロールアウトする列数」なのに、その引数値をチェックする際には「スクロールして表示されるようになる列数」かのように扱われてしまっています。
    (開発中に仕様変更があったのに、チェックロジックだけ旧仕様のままになってたとかを想像します。)

    具体的には、前の私の返信の例ですと、col1 と col2 と col3 の3列をスクロールアウトすれば、col4 を完全に表示することができますので、ScrollColumnIntoView は ScrollColumns に対して「3列スクロールして」と頼みます。(col1 と col2 のスクロールアウトだけでは col4 は一部しか表示できないので、col3 もスクロールアウトの対象に含められます。)

    しかし ScrollColumns では、完全に見えている col2 より右側には、移動先である col4 を含めて2列しかありませんので、「3列も移動できない」としてスクロール処理を全く行いません。

    前の私の返信のコードでは、そのチェックロジックのみを迂回します。
    チェックロジックを迂回してしまっても、その後の処理では問題なく処理できるコードになっていますので、弊害はないはずです。

    • 回答としてマーク しんなべ 2010年11月12日 8:34
    2010年11月10日 8:30

すべての返信

  • こちらでVS2010を使ってテストした限り、きちんと横スクロールも行われ、CurrentCellが表示されました。もしVS2010をお使いでしたら、他の何かが影響していると思われますのでご確認ください。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年11月8日 5:01
    モデレータ
  • 同じく、VS2008にてVC#にて検証しましたが、問題なく表示されました。

    なお、CurrentCellに、今から設定しようと思っているセルと同じセルが既に指定されている場合は、

    CurrentCellを設定してもカーソル及びスクロールは移動しない模様です。

     

     

    • 編集済み honefai 2010年11月11日 7:05
    2010年11月8日 5:12
  • 正常にスクロールされる場合とされない場合があるんですよね?

    以前、この件を調べて、バグっぽい箇所を見つけました。
    その際、詳細な資料を作成したんですが、フィードバックはしてませんでした。
    当時作成した資料を探したのですが、どこかへいっちゃったようです。
    確か、表示中の最後の列幅と、残りの非表示列幅が関係していたと思います。

    しんなべさんは VB ですよね?(2回目の確認です(^^;)
    下記コードは VB2008 で動作確認しました。
    フォームに DataGridView と Button を1つずつ配置して実行し、End キーを押して最終列に移動してみてください(追記:じゃなくて、ボタンを押してみてください)。

    もしこれで回避できる場合は、このコードでは CurrentCell を一度 Nothing に変更しますので、しんなべさんのアプリケーションに組み込まれた際にこれによる弊害が生じないか確認してください。
    見当はずれでしたらすみません。

    Imports System.Reflection

    Public Class Form1
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            DataGridView1.Columns.Clear()
            DataGridView1.Columns.Add("col1", "col1")
            DataGridView1.Columns.Add("col2", "col2")
            DataGridView1.Columns.Add("col3", "col3")
            DataGridView1.Columns.Add("col4", "col4")
            DataGridView1.Width = 360
            DataGridView1.Columns(2).Width = 300
            DataGridView1.RowCount = 2
        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            DataGridView1.CurrentCell = DataGridView1(3, 0)
        End Sub

        Private Sub DataGridView1_CurrentCellChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) _
            Handles DataGridView1.CurrentCellChanged
            Dim grid = DirectCast(sender, DataGridView)
            If grid.CurrentCell Is Nothing Then Return
            Me.BeginInvoke(DirectCast(AddressOf DataGridViewScrollFix, Action(Of DataGridView)), grid)
        End Sub

        Private _inDataGridViewScrollFix As Boolean

        Private Sub DataGridViewScrollFix(ByVal grid As DataGridView)
            If _inDataGridViewScrollFix Then
                _inDataGridViewScrollFix = False
                Return
            End If

            Dim currentCell = grid.CurrentCell
            If currentCell Is Nothing OrElse currentCell.Displayed Then Return

            grid.CurrentCell = Nothing

            'DataGridView の private な ScrollColumns メソッドの
            '先頭にある処理が災いしている。
            'DataGridView.displayedBandsInfo.LastTotallyDisplayedScrollingCol = -1
            'というコードに相当する処理をリフレクションで無理矢理実行することで、
            'その処理を迂回する。
            Dim type = GetType(DataGridView)
            Dim fi = type.GetField("displayedBandsInfo", BindingFlags.Instance Or BindingFlags.NonPublic)
            If fi IsNot Nothing Then
                Dim displayedBandsInfo = fi.GetValue(grid)
                If displayedBandsInfo IsNot Nothing Then
                    type = displayedBandsInfo.GetType()
                    Dim pi = type.GetProperty("LastTotallyDisplayedScrollingCol", _
                        BindingFlags.Instance Or BindingFlags.Public)
                    If pi IsNot Nothing Then
                        pi.SetValue(displayedBandsInfo, -1, Nothing)
                    End If
                End If
            End If

            _inDataGridViewScrollFix = True
            grid.CurrentCell = currentCell
        End Sub
    End Class

    • 編集済み TH01 2010年11月10日 8:27 少しコードを修正
    • 回答としてマーク しんなべ 2010年11月12日 8:33
    2010年11月8日 5:31
  • > 確か、表示中の最後の列幅と、残りの非表示列幅が関係していたと思います。

    少し違いました。
    しんなべさんも同じ状況かは不明ですが、1年前に調べたときの資料が見つかりましたので、不具合の概要を書かせてもらいます。(実際はもっといろいろな条件があって複雑です。)

    セルの表示の為に列のスクロールが必要な場合、DataGridView の private なメソッドである ScrollColumnIntoView が「スクロールアウトする列数」を決定し、同じく private な ScrollColumns メソッドがそれを受け取って実際にスクロールを行います。(どちらも DataGridViewMethods.cs で実装されています。)

    ScrollColumns が受け取るのは「スクロールアウトする列数」なのに、その引数値をチェックする際には「スクロールして表示されるようになる列数」かのように扱われてしまっています。
    (開発中に仕様変更があったのに、チェックロジックだけ旧仕様のままになってたとかを想像します。)

    具体的には、前の私の返信の例ですと、col1 と col2 と col3 の3列をスクロールアウトすれば、col4 を完全に表示することができますので、ScrollColumnIntoView は ScrollColumns に対して「3列スクロールして」と頼みます。(col1 と col2 のスクロールアウトだけでは col4 は一部しか表示できないので、col3 もスクロールアウトの対象に含められます。)

    しかし ScrollColumns では、完全に見えている col2 より右側には、移動先である col4 を含めて2列しかありませんので、「3列も移動できない」としてスクロール処理を全く行いません。

    前の私の返信のコードでは、そのチェックロジックのみを迂回します。
    チェックロジックを迂回してしまっても、その後の処理では問題なく処理できるコードになっていますので、弊害はないはずです。

    • 回答としてマーク しんなべ 2010年11月12日 8:34
    2010年11月10日 8:30
  • みなさん。こんにちは。いつも参考にさせて頂いています。

    trapemiyaさん。honefaiさん。TH01さん。
    いつもご回答、有難うございます。

    返事が遅れてしまい、失礼しました。

    TH01さん。
    ご教授頂いたコードを使用し、無事スクロール出来るようになりました。
    本当に助かりました。
    有難うございます。

    TH01さんが調べられた内容を見てお聞きしたい事が有るのですが、
    DataGridView の中身を調べられた様ですが、Framework クラスライブラリの
    中もデバッグ、ステップ実行は出来るのでしょうか?
    宜しければ、教えて下さい。

    宜しくお願いします。

    2010年11月12日 8:33
  • すべてできるわけではありませんが、できます。
    検索すると以下のサイトが見つかりました。
    (私はどこかのブログを見て設定したのですが、そこを見つけることはできませんでした。)

    シンボル サーバーを Visual Studio .NET デバッガで使用する方法
    http://support.microsoft.com/kb/319037/ja

    Visual Studio 2008で見る.NET Frameworkのソースコード
    http://www.atmarkit.co.jp/fdotnet/insiderseye/20080222sourcecode/sourcecode.html

    2010年11月12日 10:35
  • TH01さん。

    教えて頂いたURLを参考に、環境を作ってみようと思います。

    ご教授、ご返事有難うございます。

    宜しくお願いします。

     

     

    2010年11月15日 2:43