Можно ли сохранить выбор нескольких строк при сортировке DataGridView? - PullRequest
1 голос
/ 20 марта 2020

Я успешно сохранил выделение одной строки при сортировке столбцов в DataGridView, но на этот раз я намерен отслеживать выбор нескольких строк при сортировке столбцов в DataGridView. Я видел один пост на эту топи c со ссылкой на DataGrid, но ответ был бесполезным.

Сначала я попытался просто сделать копию предыдущей выбранной коллекции строк, а затем сделать копию текущей выбранной коллекции строк. Однако это не сработало, потому что при сортировке столбца я заметил, что событие SelectionChanged срабатывает дважды до того, как событие Sorted срабатывает один раз.

Поэтому я разработал класс, который хранит три последовательных копии, и после сортировки следует просто повторно выбрать самую раннюю из 3 копий. Sub UpdateSelection вызывается для события SelectionChanged, а sub SelectPrevious вызывается для события 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

Ответы [ 2 ]

3 голосов
/ 20 марта 2020

Этот код работал для меня и должен работать независимо от источника данных:

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

Стоит отметить, что методы Sort класса DataGridView 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, и он будет просто работать.

0 голосов
/ 20 марта 2020

Или вы можете просто иметь логическое значение в базовом дататабле и столбце флажка - пусть пользователь помечает флажки в столбце, или, возможно, выбирает строки и нажимает кнопку, чтобы «отметить выбранные строки», а затем дает им больше кнопок «выполнить удаление отмеченных строк» ​​и т. д. c

Я обычно предпочитаю этот подход, если у меня есть несколько вариантов смешанного режима работы, так как множественные выборы - непостоянная / легко теряемая вещь, и пользователи обычно не понимаю, как комбинировать нажатие клавиши shift / ctrl, чтобы легко выбрать несколько смежных диапазонов. Проще просто дать им систему, в которой они могут выбрать несколько строк, и кнопку, чтобы пометить эти строки как интересные для дальнейших действий, а затем только для дальнейших действий. выполнять по отмеченным рядам.

Если вы думаете, что ваши пользователи могут не понимать, и просто выделите несколько строк и нажмите кнопку действия, возможно, вы сможете предварительно отметить все выделенные строки, если нет отмеченных строк при нажатии кнопки действия

В конечном счете, способ, которым мы думаем, что пользователь поймет нашу программу и использует ее интерфейс, сильно отличается от того, как они это делают. Я потратил недели на создание красивого и полезного пользовательского интерфейса для программы, в том числе средства массовой загрузки из файла Excel, и был довольно напуган, увидев, что они полностью игнорируют пользовательский интерфейс и даже для загрузки одного пользователя в систему ударит up Excel, введите его данные в одну строку и сохраните как электронную таблицу, затем импортируйте одного пользователя; это обошло все автозаполнение, поиск и другие предложения, сделанные пользовательским интерфейсом, но оно преподало важный урок: никогда не стоит недооценивать разницу между тем, как вы предполагаете, что программа будет использоваться по сравнению с тем, как она будет фактически использоваться

...