Текстовые поля WPF DataGrid, сохраняющие предыдущие значения при множественных ошибках проверки

Я создал DataGrid с двумя столбцами, которые оба используют TextBoxs для редактирования свойств ViewModel. Когда в обоих столбцах есть ошибки проверки, а значения свойств изменяются из ViewModel, переход в режим редактирования в одной из ячеек сохраняет ранее отредактированное значение.

Вот короткий пример:

Посмотреть

<Window ...>
    <Window.DataContext>
        <ViewModels:MainPresenter />
    </Window.DataContext>

    <DockPanel>
        <Button Command="{Binding ResetValuesCommand}"
                Margin="5" DockPanel.Dock="Top">Reset Values</Button>

        <DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Value 1"
                    Binding="{Binding Value1, ValidatesOnDataErrors=True}" />
                <DataGridTextColumn Header="Value 2"
                    Binding="{Binding Value2, ValidatesOnDataErrors=True}" />
            </DataGrid.Columns>
        </DataGrid>
    </DockPanel>
</Window>

ViewModel

public class MainPresenter : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public IEnumerable<ItemPresenter> Items { get; }
        = new ObservableCollection<ItemPresenter> {new ItemPresenter()};

    public ICommand ResetValuesCommand => new ResetCommand(Items);

    private class ResetCommand : ICommand
    {
        private readonly IEnumerable<ItemPresenter> _items;

        public ResetCommand(IEnumerable<ItemPresenter> items) { _items = items; }

        public void Execute(object parameter) => _items.ToList().ForEach(i => i.Reset());

        public bool CanExecute(object parameter) => true;

        public event EventHandler CanExecuteChanged { add { } remove { } }
    }
}

public class ItemPresenter : INotifyPropertyChanged, IDataErrorInfo
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Value1 { get; set; } = "A";

    public string Value2 { get; set; } = "B";

    public string this[string columnName] => "ERROR";

    public string Error => "ERROR";

    public void Reset()
    {
        Value1 = "A";
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
        Value2 = "B";
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
    }
}

Действия по воспроизведению

При запуске приложения оба столбца выделяются как недействительные.

  • Дважды щелкните ячейку в столбце "Значение 1" (в настоящее время имеет значение "A") и измените ее, например, на "Z";
  • Нажмите ввод (или эквивалент), чтобы зафиксировать изменение;
  • Нажмите кнопку "Сбросить значения" (в результате чего ячейка, которую мы только что отредактировали, изменится на "A");
  • Дважды щелкните ячейку в столбце "Значение 1" еще раз.

Ячейка в столбце "Значение 1" переходит в режим редактирования, и значение снова отображается как "Z".

Следует отметить одну вещь: это происходит только тогда, когда другие столбцы имеют ошибки проверки. Если это единственный столбец с ошибкой проверки, тогда TextBox в режиме редактирования будет правильно показывать "A" при входе в режим редактирования.

Частичное исправление

Как ни странно, явно устанавливая режим в привязках к TwoWay (предположительно по умолчанию, поскольку это очевидное поведение) решает проблему.

Тем не менее, если я хочу некоторые пользовательские шаблоны ячеек (и заменить DataGridTextColumnс DataGridTemplateColumnss) но все еще использую TextBox для редактирования:

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Value 1">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBlock Text="{Binding Value1, ValidatesOnDataErrors=True}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBox Text="{Binding Value1, ValidatesOnDataErrors=True}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Value 2">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBlock Text="{Binding Value2, ValidatesOnDataErrors=True}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBox Text="{Binding Value2, ValidatesOnDataErrors=True}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Я сталкиваюсь с той же проблемой, но явно устанавливаю режимы привязки TwoWay не исправить это.

Я делаю что-то не так, что я упустил из виду? Или у кого-нибудь есть обходной путь для этого?

1 ответ

Решение

Я нашел обходной путь.

Если я перейду на использование INotifyDataErrorInfo (доступно только в.NET 4.5 и выше), затем работает как положено.

ViewModel

public class ItemPresenter : INotifyPropertyChanged, INotifyDataErrorInfo
{
    public event PropertyChangedEventHandler PropertyChanged;
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public string Value1 { get; set; } = "A";

    public string Value2 { get; set; } = "B";

    public IEnumerable GetErrors(string propertyName) => new[] { "ERROR" };

    public bool HasErrors => true;

    public void Reset()
    {
        Value1 = "A";
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value1)));
        Value2 = "B";
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value2)));
    }
}

Посмотреть

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Value 1">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBlock Text="{Binding Value1}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBox Text="{Binding Value1}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Value 2">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBlock Text="{Binding Value2}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
                    <TextBox Text="{Binding Value2}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>
Другие вопросы по тегам