【问题标题】:Is it possible to retain the selection of multiple rows when sorting DataGridView?对DataGridView进行排序时是否可以保留多行的选择?
【发布时间】:2020-07-01 13:17:01
【问题描述】:

在对DataGridView 中的列进行排序时,我成功地保留了单行的选择,但这次我打算在对DataGridView 中的列进行排序时跟踪多行的选择。我看到一篇关于这个主题的帖子提到了DataGrid,但答案没有帮助。

首先,我尝试非常简单地复制先前选定的行集合,然后复制当前选定的行集合。但是这不起作用,因为当您对列进行排序时,我注意到SelectionChanged 事件在Sorted 事件触发一次之前触发两次

因此我设计了一个存储三个连续副本的类,在排序后,它应该重新选择三个副本中最早的一个。 UpdateSelection sub 在 SelectionChanged 事件上调用,SelectPrevious sub 在 Sorted 事件上调用。


然而

问题是这样的:下面的代码在选择项目时似乎可以工作。每次选择一个项目时,Debug.Print 结果都会正确后退。 但是我排序后,所有这些数组副本在第一个 SelectionChanged 事件中被清除。我真的不明白怎么做。

除非我弄错了,因为每个数组都是一个副本,它应该不受影响,对吗?即使它清除了m_CurrentRows,它也不应该清除m_PreviousRows0, 1, 2。它应该一次退一步,就像选择行时一样。

我在寻找什么

我正在寻找一种不完全删除所有先前选择数组的方法 - 这本身就令人费解。

或者在调用Sort,但在Sorted 触发之前存储选择的方法。这并不明显,也无法预测用户何时会点击列标题。似乎试图跟踪选择每次选择或取消选择任何东西都不起作用,所以如果有办法拦截它(如下所示)那会更好,但我需要知道怎么做。


NB - 带有扩展的模块 - 如果我错过了任何让我知道,我会包括在内。另外,在检查我使用的单元格值 2 时,请确保数据集至少有 3 列。

    Class clsDataGridViewSelectedRowTracker
        Private ReadOnly m_DataGridView As DataGridView

        Private ReadOnly m_CurrentRows As List(Of DataGridViewRow)

        Private m_PreviousRows0() As DataGridViewRow
        Private m_PreviousRows1() As DataGridViewRow
        Private m_PreviousRows2() As DataGridViewRow


        ''' <summary>
        ''' Create new instance of DataGridView Selected Row Tracker
        ''' </summary>
        ''' <param name="dataGridView">Instance of DataGridView - SelectionMode must be FullRowSelect</param>
        Friend Sub New(ByRef dataGridView As DataGridView)
            m_DataGridView = dataGridView

            m_CurrentRows = New List(Of DataGridViewRow)

            m_PreviousRows0 = {}
            m_PreviousRows1 = {}
            m_PreviousRows2 = {}

            If Not m_DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect Then
                m_DataGridView.SelectionMode=DataGridViewSelectionMode.FullRowSelect
            End If

        End Sub

        ''' <summary>
        ''' Updates selection tracker with current and previous selection values
        ''' </summary>
        Friend Sub UpdateSelection()

            'Debugging the current issue - displays all values each time an item is selected
            If m_CurrentRows.Count > 0 AndAlso m_PreviousRows2.Length > 0 Then
                Debug.Print("{0}   -   {1}   -   {2}   -   {3}", "C: " & m_CurrentRows(0).Value.Cell(2), "0: " & m_PreviousRows0(0).Value.Cell(2), "1: " & m_PreviousRows1(0).Value.Cell(2), "2: " & m_PreviousRows2(0).Value.Cell(2))
            ElseIf m_CurrentRows.Count > 0 AndAlso m_PreviousRows1.Count > 0 Then
                Debug.Print("{0}   -   {1}   -   {2}   -   {3}", "C: " & m_CurrentRows(0).Value.Cell(2), "0: " & m_PreviousRows0(0).Value.Cell(2), "1: " & m_PreviousRows1(0).Value.Cell(2), "2: ")
            ElseIf m_CurrentRows.Count > 0 AndAlso m_PreviousRows0.Count > 0 Then
                Debug.Print("{0}   -   {1}   -   {2}   -   {3}", "C: " & m_CurrentRows(0).Value.Cell(2), "0: " & m_PreviousRows0(0).Value.Cell(2), "1: ", "2: ")
            ElseIf m_CurrentRows.Count > 0 Then
                Debug.Print("{0}   -   {1}   -   {2}   -   {3}", "C: " & m_CurrentRows(0).Value.Cell(2), "0: ", "1: ", "2: ")
            End If

            'Back up current rows and previous 2 instances
            If m_PreviousRows1 IsNot Nothing AndAlso m_PreviousRows1.Length > 0 Then
                ReDim m_PreviousRows2(m_PreviousRows1.Length - 1)
                Call m_PreviousRows1.CopyTo(m_PreviousRows2, 0)
            End If

            If m_PreviousRows0 IsNot Nothing AndAlso m_PreviousRows0.Length > 0 Then
                ReDim m_PreviousRows1(m_PreviousRows0.Length - 1)
                Call m_PreviousRows0.CopyTo(m_PreviousRows1, 0)
            End If

            If m_CurrentRows.Count > 0 Then
                ReDim m_PreviousRows0(m_CurrentRows.Count - 1)
                Call m_CurrentRows.CopyTo(m_PreviousRows0, 0)
            End If

            'Get currently selected rows, if any
            Dim m_selectedRows As DataGridViewSelectedRowCollection = m_DataGridView.SelectedRows

            'Clear list of current rows
            Call m_CurrentRows.Clear()

            'Add each selected item to list of currently selected rows
            For Each EachSelectedRow As DataGridViewRow In m_selectedRows
                Call m_CurrentRows.Add(EachSelectedRow)
            Next

        End Sub

        ''' <summary>
        ''' Attempts to select the previously selected rows
        ''' </summary>
        Friend Sub SelectPrevious()
            'Ensure Grid exists and contains rows
            If m_DataGridView IsNot Nothing AndAlso m_DataGridView.RowCount > 0 Then

                'Visible
                Dim m_VisibleRow As DataGridViewRow = Nothing

                'Compare each row value against previous row values
                For Each EachDataGridViewRow As DataGridViewRow In m_DataGridView.Rows
                    'Use the level two instance of previous rows after sorting
                    For Each EachPreviousRow As DataGridViewRow In m_PreviousRows2

                        If EachPreviousRow.Value.Row.Equivalent(EachDataGridViewRow.Value.Row) Then
                            'Select the row
                            EachDataGridViewRow.Selected = True

                            'Only store visible row for the first selected row
                            If m_VisibleRow Is Nothing Then m_VisibleRow = EachDataGridViewRow
                        End If

                    Next 'Each Previous Selected Row
                Next 'Each Row

                'Ensure first selected row is always visible
                If m_VisibleRow IsNot Nothing AndAlso Not m_VisibleRow.Displayed Then

                    If (m_VisibleRow.Index - m_DataGridView.DisplayedRowCount(True) \ 2) > 0 Then
                        'Place row in centre of DataGridView
                        m_DataGridView.FirstDisplayedScrollingRowIndex = m_VisibleRow.Index - m_DataGridView.DisplayedRowCount(True) \ 2
                    Else
                        'Place row at top of DataGridView
                        m_DataGridView.FirstDisplayedScrollingRowIndex = m_VisibleRow.Index
                    End If

                End If

            End If
        End Sub

    End Class




    Module Extensions

        ''' <summary>
        ''' Determines whether the specified string is equivalent to current string (Not case sensitive)
        ''' </summary>
        ''' <param name="str1">The string to compare with the following string</param>
        ''' <param name="str2">The second string to compare</param>
        ''' <returns></returns>
        <DebuggerStepThrough()>
        <Extension()>
        Friend Function Equivalent(ByVal str1 As String, str2 As String) As Boolean
            Return str1.ToUpper.Equals(str2.ToUpper)
        End Function

        ''' <summary>
        ''' Quick extension to speed up proceedings
        ''' </summary>
        ''' <param name="dgvr"></param>
        ''' <param name="cellindex"></param>
        ''' <returns></returns>
        <Extension>
        Friend Function CellValueString(ByRef dgvr As DataGridViewRow, ByVal cellindex As Integer) As String
            If dgvr Is Nothing Then Return String.Empty
            If dgvr.Cells Is Nothing Then Return String.Empty
            If cellindex >= dgvr.Cells.Count Then Return String.Empty
            If dgvr.Cells(cellindex).Value Is Nothing Then Return String.Empty
            Return dgvr.Cells(cellindex).Value.ToString
        End Function

    End Module

【问题讨论】:

  • 如果网格绑定到BindingSource,那么行索引将对应于BindingSource 中的项目索引。您可以在排序之前获取这些索引处的项目,然后在排序之后获取这些项目的新索引,然后选择这些索引处的行。
  • dgv.DataSource = 数据表。我不确定如何在“排序之前”获取索引,因为我不确定在Sorted 之前会触发什么以便获取此信息并在Sorted 中使用它。我也不确定如何从数据表中获取项目的索引。
  • 然后使用BindingSource“调用排序后存储选择的方法”。为什么在拨打Sort之后还要这样做?为什么以前不这样做?

标签: vb.net datagridview columnsorting


【解决方案1】:

这段代码对我有用,无论数据源如何都应该可以工作:

Private Sub SortGrid(direction As ListSortDirection)
    Dim selectedItems = DataGridView1.SelectedRows.
                                      Cast(Of DataGridViewRow)().
                                      Select(Function(dgvr) dgvr.DataBoundItem).
                                      ToArray()

    DataGridView1.Sort(DataGridView1.Columns(0), direction)

    For Each row As DataGridViewRow In DataGridView1.Rows
        row.Selected = selectedItems.Contains(row.DataBoundItem)
    Next
End Sub

值得注意的是DataGridView 类的Sort 方法是Overridable,因此您可以创建自己的自定义类来继承DataGridView 并添加该功能:

Imports System.ComponentModel

Public Class DataGridViewEx
    Inherits DataGridView

    Public Overrides Sub Sort(comparer As IComparer)
        Dim selectedItems = GetSelectedItems()

        MyBase.Sort(comparer)

        ReselectRows(selectedItems)
    End Sub

    Public Overrides Sub Sort(dataGridViewColumn As DataGridViewColumn, direction As ListSortDirection)
        Dim selectedItems = GetSelectedItems()

        MyBase.Sort(dataGridViewColumn, direction)

        ReselectRows(selectedItems)
    End Sub

    Private Function GetSelectedItems() As Object()
        Return If(DataSource Is Nothing,
                  Nothing,
                  SelectedRows.Cast(Of DataGridViewRow)().
                               Select(Function(dgvr) dgvr.DataBoundItem).
                               ToArray())
    End Function

    Private Sub ReselectRows(selectedItems As Object())
        If selectedItems IsNot Nothing Then
            For Each row As DataGridViewRow In Rows
                row.Selected = selectedItems.Contains(row.DataBoundItem)
            Next
        End If
    End Sub

End Class

使用该控件而不是常规的DataGridView,它将正常工作。

【讨论】:

  • 您的Sub SortGrid 仅在从其他控件调用Sort 时有效,而不是在单击将Sort 作为事件引发的列标题时。另一方面,自定义类就像一种享受,谢谢!我还是不太明白为什么自定义控件中的Overrides Sub Sort事件仍然保留了所有选中项,而原控件的Sort事件却没有。
  • 如果你说你不明白为什么标准 DataGridView 不保留选择,这可能是因为排序行为不仅重新排列现有行,而是破坏现有行并创建新行。据推测,这比其他情况下需要的所有交换更有效。也许不重新选择等效行是一种疏忽,或者他们有特定的理由不实施它,但至少自己做起来并不难。不过,在绑定 BindingSource 上设置 Sort 时尚未对此进行测试。
【解决方案2】:

或者,您可以在基础数据表和复选框列中有一个布尔值 - 让用户勾选列中的框,或者选择行并单击一个按钮以“勾选选定的行”,然后给他们更多按钮“执行删除勾选行”等

如果我正在进行某种混合工作模式的多选,我通常更喜欢这种方法,因为多选是一个反复无常/容易丢失的事情,用户通常不明白如何结合 shift/ctrl click 轻松选择多个连续的范围.. 更容易给他们一个系统,他们可以在其中选择一些多行和一个按钮来将这些行标记为对进一步操作感兴趣,然后进一步的操作只在标记的行上执行。

如果您认为您的用户可能不理解,并且只是突出显示某些行并单击操作按钮,那么如果单击操作按钮时没有勾选的行,您可以预先勾选所有突出显示的行

最终,我们认为用户理解我们的程序并使用其界面的方式与他们的做法大不相同。我花了数周时间为一个程序创建了一个漂亮且有用的 UI,包括一个从 excel 文件中大量上传的工具,并且相当震惊地看到他们完全忽略了 UI,甚至将单个用户加载到系统中,都会命中打开excel,将他的详细信息输入一行并保存为电子表格,然后导入一个用户;这绕过了 UI 提出的所有自动完成、查找和其他建议,但它教会了一个重要的教训,即永远不要低估您设想程序的使用方式与实际使用方式之间的差异

【讨论】:

  • 不幸的是,我是最终用户,而且我确实有一个按钮,允许在另一个应用程序中处理所选项目,但我需要能够对我的按不同的列列出以查找我需要选择的更多项。我只想在排序时保留任何选定的对象。
  • 我不确定我是否理解此评论与答案的关系?
  • 因为您没有提供答案,所以您提供了令人不满意的冗长解决方法,包括勾选框和额外笨拙的按钮,我不太喜欢娱乐。你的理由是让最终用户的事情变得更简单,但由于我既是程序员又是最终用户,这不是我需要担心的事情。
猜你喜欢
  • 2013-11-24
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
  • 2018-04-20
  • 2011-09-30
  • 2010-10-15
  • 1970-01-01
相关资源
最近更新 更多