views:

56

answers:

2

I need to make the DataGrid, so if I am in edit mode, and the current row is not valid, the user should not be able to select a different row.

In fact, I think that the DataGrid is supposed to avoid selection of another row once the CollectionChangingEventArgs of the CollectionView is flagged for cancellation, the DataGrid should stick to the CollectionView.

Now that I have to refresh, it's alright and works fine as long as the CollectionView is refreshable (i.e. is not in edit mode), once it's in edit mode, the Refresh method throws an InvalidOperationException telling that "'Refresh' is not allowed during an AddNew or EditItem transaction.".

I tried the following but it doesn't help, create a WPF application with the follwing content and see for yourself:

<Window 
  x:Class="MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
  <Window.Resources>
    <CollectionViewSource x:Key="ItemsCollection"/>
  </Window.Resources>
  <DataGrid 
    Name="dataGrid" 
    AutoGenerateColumns="False"
    IsSynchronizedWithCurrentItem="True" 
    ItemsSource="{Binding Source={StaticResource ItemsCollection}}">
    <DataGrid.Columns>
      <DataGridTextColumn 
        Binding="{Binding Title, UpdateSourceTrigger=PropertyChanged}"/>
      <DataGridTextColumn Binding="{Binding Value}"/>
      <DataGridCheckBoxColumn Binding="{Binding IsValid, Mode=OneWay}"/>
    </DataGrid.Columns>
  </DataGrid>
</Window>

Imports System.ComponentModel
Imports System.Collections.ObjectModel
Class MainWindow

  WithEvents collectionView As CollectionView

  Private Sub Window_Loaded(ByVal sender As Object,
     ByVal e As RoutedEventArgs) Handles MyBase.Loaded
    Dim ItemsCollection = DirectCast(Resources("ItemsCollection"), 
      CollectionViewSource)
    ItemsCollection.Source =
      New ObservableCollection(Of Item) From
      {
        New Item("item 1", 1),
        New Item("item 2", 2),
        New Item("item 3", 3),
        New Item("item 4", 4)
      }

    collectionView = ItemsCollection.View
  End Sub

  Private Sub ItemsCollectionView_CurrentChanging(ByVal sender As Object,
     ByVal e As CurrentChangingEventArgs) Handles collectionView.CurrentChanging
    If Not e.IsCancelable Then Exit Sub
    If isInEditMode Then
      dataGrid.SelectedIndex = collectionView.CurrentPosition
      collectionView.Refresh()
    End If
  End Sub

  Private isInEditMode As Boolean
  Private Sub dataGrid_BeginningEdit(ByVal sender As Object,
     ByVal e As DataGridBeginningEditEventArgs) Handles dataGrid.BeginningEdit
    isInEditMode = True
  End Sub

  Private Sub dataGrid_CellEditEnding(ByVal sender As Object,
     ByVal e As DataGridCellEditEndingEventArgs) Handles dataGrid.CellEditEnding
    If Not DirectCast(e.Row.Item, Item).IsValid Then
      e.Cancel = True
    Else
      isInEditMode = False
    End If
  End Sub
End Class

Public Class Item : Implements INotifyPropertyChanged
  Public Sub New()
  End Sub
  Public Sub New(ByVal title As String, ByVal value As Integer)
    m_Title = title
    m_Value = value
  End Sub

  Private m_Title As String
  Public Property Title() As String
    Get
      Return m_Title
    End Get
    Set(ByVal value As String)
      If value = m_Title Then Exit Property
      m_Title = value
      OnPropertyChanged("Title")
      OnPropertyChanged("IsValid")
    End Set
  End Property

  Private m_Value As Integer
  Public Property Value() As Integer
    Get
      Return m_Value
    End Get
    Set(ByVal value As Integer)
      m_Value = value
      OnPropertyChanged("Value")
    End Set
  End Property

  Public ReadOnly Property IsValid As Boolean
    Get
      Return Not String.IsNullOrWhiteSpace(Title)
    End Get
  End Property

  Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
  End Sub

  Public Event PropertyChanged(ByVal sender As Object,
                               ByVal e As PropertyChangedEventArgs) _
                             Implements INotifyPropertyChanged.PropertyChanged
End Class
A: 

Had the same problem myself and solved it by using the following. It is in C# but hopefully you can translate it :-) Before collectionView.Refresh() you should call

CommitTransactions(CollectionViewSource.GetDefaultView(collectionView) as BindingListCollectionView);

protected void CommitTransactions(IEditableCollectionView itemsView)
{
    if (itemsView.IsAddingNew == true)
    {
        itemsView.CommitNew();
    }
    if (itemsView.IsEditingItem == true)
    {
        itemsView.CommitEdit();
    }
    dataGrid.CommitEdit();
}
Meleak
The problem is I don't want to commit edit, I just want the DataGrid to remain in edit mode and avoid selection of another row.
Shimmy
A: 

If you use a Validation rule than you won't be able to select another row in the DataGrid until it is valid again.

From
http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx
"This rule iterates over all of the items within the binding group (i.e., the DataGrid row) probing them for errors. In this case, the IDataErrorInfo.Error property is used for object level validation."

<DataGrid Name="dataGrid" 
          AutoGenerateColumns="False" 
          IsSynchronizedWithCurrentItem="True"  
          CellEditEnding="dataGrid_CellEditEnding"
          ItemsSource="{Binding Source={StaticResource ItemsCollection}}">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Title, UpdateSourceTrigger=PropertyChanged}"/>
        <DataGridTextColumn Binding="{Binding Value}"/>
        <DataGridCheckBoxColumn Binding="{Binding IsValid, Mode=OneWay}"/>
    </DataGrid.Columns>
    <DataGrid.RowValidationRules>
        <local:IsValidValidationRule ValidationStep="UpdatedValue"/>
    </DataGrid.RowValidationRules>
</DataGrid>

And the IsValidValidationRule, again sorry for the C# :)

public class IsValidValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        BindingGroup group = (BindingGroup)value;

        StringBuilder error = null;
        foreach (var item in group.Items)
        {
            // aggregate errors
            IDataErrorInfo info = item as IDataErrorInfo;
            if (info != null)
            {
                if (!string.IsNullOrEmpty(info.Error))
                {
                    if (error == null)
                        error = new StringBuilder();
                    error.Append((error.Length != 0 ? ", " : "") + info.Error);
                }
            }
        }

        if (error != null)
            return new ValidationResult(false, error.ToString());

        return ValidationResult.ValidResult;
    }
}
Meleak
I don't work with ValidationRules, I work with DataAnnotations, anyway I tried to add `If String.IsNullOrWhiteSpace(value) Then Throw New InvalidOperationException("Title must not be empty.")` as the first line of the Title setter, the DataGrid catches the error and invalidates the UI, but it still allows me switching to another row. Is it because I have to set the RowValidationRules property? If so how do I set it, since I don't use ValidationRules at all (I use IDataErrorInfo).
Shimmy
I think you still have to set the RowValidationRules if you want the behavior of not being able to change from the Erroneous row. I updated my example with IDataErrorInfo.
Meleak