WPF DataGrid validation/binding mode bug

匿名 (未验证) 提交于 2019-12-03 08:48:34

问题:

I created a new project, very simple, to test only the Microsoft WPF DataGrid behavior. Nothing else involved, I only use the standard DataGrid:

<Window x:Class="MainWindow"          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"           mc:Ignorable="d"           d:DesignHeight="300" d:DesignWidth="300">      <DataGrid ItemsSource="{Binding Employees, Mode=TwoWay}"               x:Name="tlv"               AutoGenerateColumns="False"               SelectionMode="Extended"               CanUserAddRows="true"               SelectionUnit="CellOrRowHeader">         <DataGrid.Columns>             <DataGridTextColumn Header="First Name" Binding="{Binding FirstName, Mode=TwoWay}"/>             <DataGridTextColumn Header="Last Name" Binding="{Binding LastName, Mode=TwoWay}"/>             <DataGridTextColumn Header="Salary" Binding="{Binding Salary, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}"/>         </DataGrid.Columns>     </DataGrid> </Window> 

Code behind:

Imports System.Collections.ObjectModel Imports DataGridTest.Data  Class MainWindow Sub New()      ' This call is required by the designer.     InitializeComponent()      ' Add any initialization after the InitializeComponent() call.     Me.DataContext = Me     Employees = New ObservableCollection(Of Employee)(EmployeeRepository.GetFlatListData())     BindableSelectedItems = New ObservableCollection(Of Object) End Sub   Private _employees As ObservableCollection(Of Employee)  Public Property Employees() As ObservableCollection(Of Employee)     Get         Return _employees     End Get     Set(ByVal value As ObservableCollection(Of Employee))         _employees = value     End Set End Property   Private _bindableSelectedItems As ObservableCollection(Of Object) Public Property BindableSelectedItems() As ObservableCollection(Of Object)     Get         Return _bindableSelectedItems     End Get     Set(value As ObservableCollection(Of Object))         'Set the new value of BindableSelectedItems         _bindableSelectedItems = value     End Set End Property   Private _selectedEmployeeForSelectedItemsSimulation As Employee Public Property SelectedEmployeeForSelectedItemsSimulation() As Employee     Get         Return _selectedEmployeeForSelectedItemsSimulation     End Get     Set(value As Employee)         'Set the new value of SelectedEmployeeForSelectedItemsSimulation         _selectedEmployeeForSelectedItemsSimulation = value          If _selectedEmployeeForSelectedItemsSimulation IsNot Nothing AndAlso BindableSelectedItems IsNot Nothing Then             BindableSelectedItems.Clear()             BindableSelectedItems.Add(value)         End If     End Set End Property End Class 

The Employee class, which implements IDataErrorInfo:

Imports Microsoft.Practices.Prism.ViewModel Imports System.Collections.ObjectModel Imports System.ComponentModel  Namespace Data Public Class Employee     Inherits NotificationObject     Implements IDataErrorInfo      Public Sub New()      End Sub      Public Sub New(ByVal fName As String, ByVal lName As String, ByVal salary As Double)         FirstName = fName         LastName = lName         Me.Salary = salary     End Sub       Private _firstName As String     Public Property FirstName() As String         Get             Return _firstName         End Get         Set(value As String)             'Set the new value of FirstName             _firstName = value              'Warn any Observers that the FirstName have changed.             RaisePropertyChanged(Function() Me.FirstName)         End Set     End Property       Private _lastName As String     Public Property LastName() As String         Get             Return _lastName         End Get         Set(value As String)             'Set the new value of LastName             _lastName = value              'Warn any Observers that the LastName have changed.             RaisePropertyChanged(Function() Me.LastName)         End Set     End Property       Private _isSelected As Boolean     Public Property IsSelected() As Boolean         Get             Return _isSelected         End Get         Set(value As Boolean)             'Set the new value of IsSelected             _isSelected = value              'Warn any Observers that the IsSelected have changed.             RaisePropertyChanged(Function() Me.IsSelected)         End Set     End Property      Private _salary As Double     Public Property Salary() As Double         Get             Return _salary         End Get         Set(value As Double)             'Set the new value of Salary             _salary = value              'Warn any Observers that the Salary have changed.             RaisePropertyChanged(Function() Me.Salary)         End Set     End Property        Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error         Get             Return String.Empty         End Get     End Property      Default Public ReadOnly Property Item(columnName As String) As String Implements IDataErrorInfo.Item         Get              If Me.Salary <= 0 Then                 Return "The salary must be positive."             End If              Return String.Empty         End Get     End Property End Class End Namespace 

So far, so good. Then, here is what happens when I try to update the salary value after having added a new row:

The value resets to 0!

  1. Why is it doing this? Something wrong with the way I am using DataGrids?
  2. Is there a workaround?

[EDIT] ... and WORKAROUND

I finally found what looks like a workaround, even if I still don't know the reason of what I now consider as a bug in the Microsoft DataGrid.

If a binding mode is specified in the datagrid column with a Validation, the bug occurs; if instead I do not specify the binding mode, everything is fine.

<DataGridTextColumn Header="Salary" Binding="{Binding Salary, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}"/> 

What I do not quite understand though, is that as far as I know the default binding mode is TwoWay... Well, at least I solved my problem.

I opened a bug report on Microsoft Connect, here

回答1:

just to let you know, I got news from Microsoft, they say this is not important and they won't even try to solve the bug.

Have a look here

So, this is a bug that has been around for a while, and won't be fixed any time soon.



回答2:

Assuming the DataGrid is bound I've previously created a solution using the CellEditEnding event (though not using reflection just a class implementing INotifyPropertyChanged). For it to work the UpdateSourceTrigger needs to be set Explicit for the column bindings

So, for example, assume your datagrid's itemssource is bound to an observablecollection(of Shape). Each shape has two properties ShapeName and NumberOfSides. To ensure a user doesn't enter 0 or less for the number of sides then

(roughly)

Public Class Shape     Implements ComponentModel.INotifyPropertyChanged      Dim _ShapeName As String     Dim _NumberOfSides As Int32      Public Property ShapeName As String         Get             Return _ShapeName         End Get         Set(value As String)             _ShapeName = value             Me.DataStateChanged("ShapeName")         End Set     End Property      Public Property NumberOfSides As Int32         Get             Return _NumberOfSides         End Get         Set(value As Int32)             If value > 0 Then                 _NumberOfSides = value             End If             Me.DataStateChanged("NumberOfSides")         End Set     End Property   #Region "INotifyPropertyChanged Members"      Public Event PropertyChanged(ByVal sender As Object, ByVal e As ComponentModel.PropertyChangedEventArgs) _         Implements _             ComponentModel.INotifyPropertyChanged.PropertyChanged      Protected Sub DataStateChanged(ByVal propertyName As String)         ' Raise the event         If Not String.IsNullOrEmpty(propertyName) Then             RaiseEvent PropertyChanged(Me, _             New ComponentModel.PropertyChangedEventArgs(propertyName))         End If     End Sub #End Region  End Class 

Then within the datagrid

private sub me_CellEditEnding(sender As System.Object, e As System.Windows.Controls.DataGridCellEditEndingEventArgs) Handles me.CellEditEnding             If e.EditAction = DataGridEditAction.Commit Then                      Dim EditedItem As Shape = CType(e.Row.Item, Shape)                     Dim ValueBefore As int32 = EditedItem.NumberOfSides                       For Each currentExpression As BindingExpressionBase In e.EditingElement.BindingGroup.BindingExpressions                        'the value is changed or not by the update within the property                         currentExpression.UpdateSource()                     Next                       If ValueBefore = EditedItem.NumberOfSides then                         'The value update was unsuccessful                      End If                 End If     End Sub 


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!