开发者

Is there a way to prevent a user selecting another row when DataGrid is in edit mode?

开发者 https://www.devze.com 2023-01-26 07:13 出处:网络
I want, that if a cell/row goes to edit mode, then, if the user attempts to select a different row, it should try to commit th开发者_开发技巧at row, if the row is not committed sucessfully, it should

I want, that if a cell/row goes to edit mode, then, if the user attempts to select a different row, it should try to commit th开发者_开发技巧at row, if the row is not committed sucessfully, it should decline the selection request and the editing row should remain selected and in edit mode.

Do you have experience with a good helper for it? any good workaround?

NOTE: I've been struggling with this issue for a long time and already got some experience, so please post only working examples, not random thoughts.


The code bellow is includes extension from here (code normalized to fit StackOverflow screen witdh, sorry).

Imports System.ComponentModel
Imports System.Windows.Threading
Namespace Components


  Public NotInheritable Class DataGridSelectionChangingBehavior

    Public Shared Function GetEnableSelectionChanging(
      ByVal element As DataGrid) As Boolean
      If element Is Nothing Then Throw New ArgumentNullException("element")
      Return element.GetValue(EnableSelectionChangingProperty)
    End Function
    Public Shared Sub SetEnableSelectionChanging(
        ByVal element As DataGrid, ByVal value As Boolean)
      If element Is Nothing Then Throw New ArgumentNullException("element")
      element.SetValue(EnableSelectionChangingProperty, value)
    End Sub
    Public Shared ReadOnly EnableSelectionChangingProperty As _
         DependencyProperty =
      DependencyProperty.RegisterAttached("EnableSelectionChanging",
        GetType(Boolean),
        GetType(DataGridSelectionChangingBehavior),
        New FrameworkPropertyMetadata(False,
          New PropertyChangedCallback(
            AddressOf EnableSelectionChanging_PropertyChanged)))

    Public Shared Sub AddSelectionChangingHandler(
      ByVal element As DataGrid, handler As SelectionChangingEventHandler)
      If element IsNot Nothing Then _
        element.AddHandler(
          DataGridSelectionChangingBehavior.SelectionChangingEvent, handler)
    End Sub
    Public Shared Sub RemoveSelectionChangingHandler(
      ByVal element As DataGrid, handler As SelectionChangingEventHandler)
      If element IsNot Nothing Then _
        element.RemoveHandler(
          DataGridSelectionChangingBehavior.SelectionChangingEvent, handler)
    End Sub
    Public Shared ReadOnly SelectionChangingEvent As RoutedEvent =
      EventManager.RegisterRoutedEvent("SelectionChanging",
        RoutingStrategy.Bubble,
        GetType(SelectionChangingEventHandler),
        GetType(DataGridSelectionChangingBehavior))

    Private Shared Sub EnableSelectionChanging_PropertyChanged(
      ByVal sender As Object, ByVal e As DependencyPropertyChangedEventArgs)
      Dim dataGrid = DirectCast(sender, DataGrid)
      If CBool(e.NewValue) Then
        AddHandler dataGrid.PreparingCellForEdit,
          AddressOf DataGrid_PreparingCellForEdit
        AddHandler dataGrid.SelectionChanged,
          AddressOf DataGrid_SelectionChanged
      Else
        RemoveHandler dataGrid.PreparingCellForEdit,
          AddressOf DataGrid_PreparingCellForEdit
        RemoveHandler dataGrid.SelectionChanged,
          AddressOf DataGrid_SelectionChanged
        RecentColumn.Remove(dataGrid)
      End If
    End Sub

    Private Shared Sub DataGrid_SelectionChanged(
      ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
      If e.RemovedItems.Count = 0 Then Exit Sub
      Dim dataGrid = DirectCast(sender, DataGrid)
      Dim removed = e.RemovedItems(0)
      Dim row = dataGrid.GetContainerFromItem(Of DataGridRow)(removed)
      Dim scea As New SelectionChangingEventArgs(row,
        DataGridSelectionChangingBehavior.SelectionChangingEvent, dataGrid)
      dataGrid.RaiseEvent(scea)

      If scea.Cancel Then
        RemoveHandler dataGrid.SelectionChanged,
          AddressOf DataGrid_SelectionChanged
        Dim operation = dataGrid.Dispatcher.BeginInvoke(
          Sub()
            dataGrid.SelectedItem = removed
            Dim column As DataGridColumn = Nothing
            If RecentColumn.TryGetValue(dataGrid, column) Then
              Dim cellsPanel =
                row.GetVisualDescendant(Of DataGridCellsPanel)().
                  Children.Cast(Of DataGridCell)()
              Dim recentCell =
                If(cellsPanel.SingleOrDefault(
                   Function(cell) cell.Column Is column),
                     cellsPanel.FirstOrDefault)
              If recentCell IsNot Nothing Then
                recentCell.IsEditing = True
                Keyboard.Focus(recentCell)
              End If
            End If
          End Sub,
          DispatcherPriority.ContextIdle)

        AddHandler operation.Completed,
          Sub(s, ea) AddHandler dataGrid.SelectionChanged,
            AddressOf DataGrid_SelectionChanged
      End If
    End Sub

    Private Shared m_RecentColumn As Dictionary(Of DataGrid, DataGridColumn)
    Public Shared ReadOnly Property RecentColumn() As Dictionary(Of DataGrid, 
                                                            DataGridColumn)
      Get
        If m_RecentColumn Is Nothing Then m_RecentColumn =
          New Dictionary(Of DataGrid, DataGridColumn)()
        Return m_RecentColumn
      End Get
    End Property

    Private Shared Sub DataGrid_PreparingCellForEdit(
      ByVal sender As Object, e As DataGridPreparingCellForEditEventArgs)
      Dim dataGrid = DirectCast(sender, DataGrid)
      RecentColumn(dataGrid) = e.Column
    End Sub

  End Class

  Public Delegate Sub SelectionChangingEventHandler(
    ByVal sender As Object, ByVal e As SelectionChangingEventArgs)

  Public Class SelectionChangingEventArgs : Inherits RoutedEventArgs

    Public Sub New(ByVal row As DataGridRow, routedEvent As RoutedEvent)
      MyBase.New(routedEvent)
      m_CurrentRow = row
    End Sub

    Public Sub New(ByVal row As DataGridRow,
                   ByVal routedEvent As RoutedEvent,
                   ByVal source As Object)
      MyBase.New(routedEvent, source)
      m_CurrentRow = row
    End Sub

    Private m_CurrentRow As DataGridRow
    Public ReadOnly Property CurrentRow() As DataGridRow
      Get
        Return m_CurrentRow
      End Get
    End Property

    Public ReadOnly Property Item() As Object
      Get
        If CurrentRow IsNot Nothing Then Return CurrentRow.Item
        Return Nothing
      End Get
    End Property

    Public Property Cancel As Boolean

  End Class

End Namespace

Usage:

<DataGrid Name="dg"
src:DataGridSelectionChangingBehavior.EnableSelectionChanging="True" 
src:DataGridSelectionChangingBehavior.SelectionChanging="dg_SelectionChanging">

Code behind (pseudu):

Private Sub dg_SelectionChanging(ByVal sender As Object, 
    ByVal e As SelectionChangingEventArgs)
  If e.CurrentRow.IsEditing Then
    Dim item = TryCast(e.CurrentRow.Item, MyEntityType)

    If item IsNot Nothing AndAlso item.IsValid Then
      Dim dataGrid = DirectCast(sender, DataGrid)
      Dim committed = dataGrid.CommitEdit(DataGridEditingUnit.Row, True)
      If committed Then Exit Sub
    End If
    e.Cancel = True
  End If
End Sub

Note: visual studio might not show the event in the intellisense and might even generate a design-time error, but it should compile and work perfect.

FYI: code formatted modified to fit SO screen.

0

精彩评论

暂无评论...
验证码 换一张
取 消