Wednesday, November 15, 2023

Swapping two rows in a data table

File this under "I can't believe this was so hard".

I have a requirement to allow the user to swap two rows in an Infragistics XamDataGrid. The grid is bound to a data table so I have to swap two data rows in the data table. This is generic code so I cannot make any assumptions about the data. I thought this would be trivial until I had to do this on a grid with child layouts. The child rows were getting disconnected from the parent.

There is no Move method on a data row or data table. You have to delete and insert. But as soon as you remove a row from a data table it changes to an unattached state and all its item data is lost. The same happens to all of its child records. So before removing the row, you have to store off all its data and all its child data.

It gets complicated. Here's my solution.

        Protected Function MoveDataRow(OldRow As DataRow, Delta As Integer) As DataRow
            Dim dt As DataTable= OldRow.Table
            Dim newRow As DataRow = dt.NewRow()
            Dim oldRowState As DataRowState = OldRow.RowState
            Dim ChildRows As New Dictionary(Of DataRelation, List(Of cRow))()
            Dim InsertAt As Integer = dt.Rows.IndexOf(OldRow) + Delta

            For Each dataRelation As DataRelation In dt.DataSet.Relations
                If dataRelation.ParentTable.Equals(dt) Then
                    ChildRows.Add(dataRelation, New List(Of cRow))
                    For Each childDataRow As DataRow In OldRow.GetChildRows(dataRelation)
                        ChildRows(dataRelation).Add(New cRow(childDataRow))
                        dataRelation.ChildTable.Rows.Remove(childDataRow)
                    Next
                End If
            Next

            newRow.ItemArray = OldRow.ItemArray
            dt.Rows.Remove(OldRow)
            dt.Rows.InsertAt(newRow, InsertAt)

            newRow.AcceptChanges()
            Select Case oldRowState
                Case DataRowState.Added : newRow.SetAdded()
                Case DataRowState.Modified : newRow.SetModified()
            End Select

            For Each kvp As KeyValuePair(Of DataRelation, List(Of cRow)) In ChildRows
                For Each row As cRow In kvp.Value
                    Dim newChildRow As DataRow = kvp.Key.ChildTable.NewRow()
                    newChildRow.ItemArray = row.ItemArray
                    kvp.Key.ChildTable.Rows.Add(newChildRow)
                    newChildRow.AcceptChanges()
                    Select Case row.RowState
                        Case DataRowState.Added : newChildRow.SetAdded()
                        Case DataRowState.Modified : newChildRow.SetModified()
                    End Select
                Next
            Next

            Return newRow
        End Function

        Private Class cRow
            Public Sub New(row As DataRow)
                Me.ItemArray = row.ItemArray
                Me.RowState = row.RowState
            End Sub
            Public ItemArray As Object()
            Public RowState As DataRowState
        End Class

You probably notice some repetition of code and that this only handles one level of child row. It's ripe for recursion. Please, feel free to do that for me 😀

If you have a better algorithm, please let me know. Surely it doesn't have to be this complicated!