locked
Restore Selection in Datagrid RRS feed

  • Question

  • I have a WPF DataGrid that I'm changing from SelectionUnit="FullRow" to SelectionUnit="CellOrRowHeader".

    After performing an operation on the DataGrid I'm using .Items.Refresh after updating the data which clears the selection the user had. I'm trying to restore the users selection.

    Collecting the selected cells and getting a reference to the .CurrentCell before the refresh and simply adding those cells to the .SelectedCells AFTER the refresh, then setting focus back to the datagrid works fine EXCEPT that the focus changes in some way and the .CurrentCell doesn't get set.

    I suspect I need to set focus to the .CurrentCell (true?) but haven't found a way to do so. The DataGridCell object has a .Focus() but I've not been able to figure out how to get assign a DataGridCell object.

    So, I guess two questions: 1. Is there a way other than .Items.Refresh (which does the entire DataGrid) to update the cells I'm modifying that doesn't change the selection? 2. How the heck do I get a reference to the DataGridCell object from the DataGridCellInfo objects (in the .SelectedCells collection) I'm working with so I can try the .Focus()?

    Thanks,
    Ken

    Tuesday, March 3, 2020 2:31 PM

Answers

  • Hi,

    vb.net code:

    Imports System.Windows.Controls.Primitives

     Dim rowIndex As Integer
        Dim columnIndex As Integer
    
        Private Sub Button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim dr As DataGridRow = CType(myDataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex), DataGridRow)
            Dim presenter As DataGridCellsPresenter = MyGetVisualChild(Of DataGridCellsPresenter)(dr)
            Dim cell As DataGridCell = CType(presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex), DataGridCell)
            cell.Focus()
        End Sub
    
        Public Shared Function MyGetVisualChild(Of T As Visual)(ByVal parent As Visual) As T
            Dim child As T = Nothing
            Dim numVisuals As Integer = VisualTreeHelper.GetChildrenCount(parent)
    
            For i As Integer = 0 To numVisuals - 1
                Dim v As Visual = CType(VisualTreeHelper.GetChild(parent, i), Visual)
                child = TryCast(v, T)
    
                If child Is Nothing Then
                    child = MyGetVisualChild(Of T)(v)
                End If
                If child IsNot Nothing Then
                    Exit For
                End If
            Next
    
            Return child
        End Function
    
        Private Sub MyDataGrid_SelectedCellsChanged(ByVal sender As Object, ByVal e As SelectedCellsChangedEventArgs)
            Dim _cells = myDataGrid.SelectedCells
    
            If _cells.Any() Then
                rowIndex = myDataGrid.Items.IndexOf(_cells.First().Item)
                columnIndex = _cells.First().Column.DisplayIndex
            End If
        End Sub

    Best Regards,

    Alex


    "Windows Presentation Foundation" forum will be migrating to a new home on Microsoft Q&A (Preview)!
    We invite you to post new questions in the "Developing Universal Windows apps" forum’s new home on Microsoft Q&A (Preview)!
    For more information, please refer to the sticky post.

    • Marked as answer by KenKrugh Thursday, March 5, 2020 2:10 PM
    Wednesday, March 4, 2020 6:23 AM
  • So, the form in this app has a large DataGrid, a TextBox and a couple of other controls that are bound to the selected row/cell in the DataGrid, as well as a MediaBox that updates with whatever is selected in the DataGrid.

    The aforementioned TextBox is a larger, easier place to edit the text that appears in the DataGrid. While binding and unbinding this TextBox to the selected DataGrid cell (so that the TextBox always reflects what's selected in the DataGrid) I ended having to wait a quarter of a second at times while editing completed in the DataGrid.

    That same concept worked to fix this Null problem. I moved Alex's fabulous reselect code into the timer so that a quarter of a second is waited between the DataGrid data refresh and the reselect.

    I'd be interested to know if this type of thing (waiting with a timer) is considered "bad practice" from those who know this stuff WAY better than I (which is most) and alternative ways of possibly handing things.

    Once again, thanks a TON Alex for your help.

    All the best,
    Ken

    • Marked as answer by KenKrugh Thursday, March 5, 2020 2:23 PM
    Thursday, March 5, 2020 2:23 PM

All replies

  • Hi,

    You can test my code to regain focus after losing focus:

      int rowIndex;
            int columnIndex;
            private void Button_Click(object sender, RoutedEventArgs e)
            {         
                DataGridRow dr = (DataGridRow)myDataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex);
                DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(dr);
                DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex); 
                cell.Focus();           
            }
            public static T GetVisualChild<T>(Visual parent) where T : Visual
            {
                T child = default(T);
                int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
                for (int i = 0; i < numVisuals; i++)
                {
                    Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                    child = v as T;
                    if (child == null)
                    {
                        child = GetVisualChild<T>(v);
                    }
                    if (child != null)
                    {
                        break;
                    }
                }
                return child;
            }
    
      
            private void MyDataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
            {    
                var _cells = myDataGrid.SelectedCells;
                if (_cells.Any())
                {
                    rowIndex = myDataGrid.Items.IndexOf(_cells.First().Item);
                    columnIndex = _cells.First().Column.DisplayIndex;
                }
            }

    Best Regards,

    Alex


    "Windows Presentation Foundation" forum will be migrating to a new home on Microsoft Q&A (Preview)!
    We invite you to post new questions in the "Developing Universal Windows apps" forum’s new home on Microsoft Q&A (Preview)!
    For more information, please refer to the sticky post.

    Wednesday, March 4, 2020 2:10 AM
  • Hey Alex,

    Thanks so much for the code. Converting it to VB seems to have worked pretty well but the line with ItemContainerGenerator.ContainerFromIndex is returning nothing. I saw the ContainerFromIndex  while trying to solve this but couldn't figure out how to use it.

    This is a portion of your code converted, then plugged into mine.

    Dim rowIndex = Me.DGridFileList.Items.IndexOf(TheSelRow)
    Dim dr As DataGridRow = CType(Me.DGridFileList.ItemContainerGenerator.ContainerFromIndex(rowIndex), DataGridRow)
    Dim presenter As Primitives.DataGridCellsPresenter = GetVisualChild(Of Primitives.DataGridCellsPresenter)(dr)
    Dim cell As DataGridCell = CType(presenter.ItemContainerGenerator.ContainerFromIndex(CurrCel.Column.DisplayIndex), DataGridCell)
    cell.Focus()
    

    As I mentioned, the line setting the dr variable is returning nothing. I'm not sure what it might mean but the Intellisense doesn't list ItemContainerGenerator off the datagrid object when I'm typing. The code you see here doesn't produce an error, but it is also passing back nothing.

    Might I have a reference of some sort missing from by project?

    Thanks again,
    Ken

    Wednesday, March 4, 2020 3:41 AM
  • Hi,

    vb.net code:

    Imports System.Windows.Controls.Primitives

     Dim rowIndex As Integer
        Dim columnIndex As Integer
    
        Private Sub Button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim dr As DataGridRow = CType(myDataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex), DataGridRow)
            Dim presenter As DataGridCellsPresenter = MyGetVisualChild(Of DataGridCellsPresenter)(dr)
            Dim cell As DataGridCell = CType(presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex), DataGridCell)
            cell.Focus()
        End Sub
    
        Public Shared Function MyGetVisualChild(Of T As Visual)(ByVal parent As Visual) As T
            Dim child As T = Nothing
            Dim numVisuals As Integer = VisualTreeHelper.GetChildrenCount(parent)
    
            For i As Integer = 0 To numVisuals - 1
                Dim v As Visual = CType(VisualTreeHelper.GetChild(parent, i), Visual)
                child = TryCast(v, T)
    
                If child Is Nothing Then
                    child = MyGetVisualChild(Of T)(v)
                End If
                If child IsNot Nothing Then
                    Exit For
                End If
            Next
    
            Return child
        End Function
    
        Private Sub MyDataGrid_SelectedCellsChanged(ByVal sender As Object, ByVal e As SelectedCellsChangedEventArgs)
            Dim _cells = myDataGrid.SelectedCells
    
            If _cells.Any() Then
                rowIndex = myDataGrid.Items.IndexOf(_cells.First().Item)
                columnIndex = _cells.First().Column.DisplayIndex
            End If
        End Sub

    Best Regards,

    Alex


    "Windows Presentation Foundation" forum will be migrating to a new home on Microsoft Q&A (Preview)!
    We invite you to post new questions in the "Developing Universal Windows apps" forum’s new home on Microsoft Q&A (Preview)!
    For more information, please refer to the sticky post.

    • Marked as answer by KenKrugh Thursday, March 5, 2020 2:10 PM
    Wednesday, March 4, 2020 6:23 AM
  • Your brilliant code worked perfectly Alex, and I suspect it was also working the other night and I needn't have troubled you for all that VB code.

    The problem was/is the Null return value for dr variable, which seems to be a timing thing of some sort, which I've run into one other time with this app.

    Just before the call to your routine I stuck in a msgbox to verify I had the column and row index correct for the selected cell. After clicking OK on the msgbox, everything worked perfectly. Take the msgbox out and the dr var gets assigned Null.

    I don't think the datagrid is finished refreshing before the ItemContainerGenerator.ContainerFromIndex line of code.

    I tried setting the affected column's NotifyOnTargetUpdated=true and using the TargetUpdated event to do the reselect, but everything became slower than grim death.

    Any suggestions anyone has are more than welcome.

    Thanks!
    Ken

    Thursday, March 5, 2020 5:24 AM
  • So, the form in this app has a large DataGrid, a TextBox and a couple of other controls that are bound to the selected row/cell in the DataGrid, as well as a MediaBox that updates with whatever is selected in the DataGrid.

    The aforementioned TextBox is a larger, easier place to edit the text that appears in the DataGrid. While binding and unbinding this TextBox to the selected DataGrid cell (so that the TextBox always reflects what's selected in the DataGrid) I ended having to wait a quarter of a second at times while editing completed in the DataGrid.

    That same concept worked to fix this Null problem. I moved Alex's fabulous reselect code into the timer so that a quarter of a second is waited between the DataGrid data refresh and the reselect.

    I'd be interested to know if this type of thing (waiting with a timer) is considered "bad practice" from those who know this stuff WAY better than I (which is most) and alternative ways of possibly handing things.

    Once again, thanks a TON Alex for your help.

    All the best,
    Ken

    • Marked as answer by KenKrugh Thursday, March 5, 2020 2:23 PM
    Thursday, March 5, 2020 2:23 PM