Замена элемента в ItemsSource, сводит сортировку DataGrid с ума

У меня есть DataGrid, отсортированный пользователем, нажав на заголовки. Когда элемент выбран, я должен обновить его из источника данных.

Когда я заменяю новый элемент в ItemsSource, строка перемещается. Сортировка должна быть по столбцу, все элементы должны иметь одинаковое значение для этого столбца.

MainWindow.xaml

<Window x:Class = "WpfApp4.MainWindow"
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"      
    xmlns:vm = "clr-namespace:WpfApp4"   
    mc:Ignorable = "d"
    Title = "MainWindow" Height = "450" Width = "800"
    DataContext = "{DynamicResource ResourceKey=viewModel}">

<Window.Resources>
    <vm:MainWindowViewModel x:Key = "viewModel" />
</Window.Resources>

<Grid>
    <DataGrid AutoGenerateColumns = "True" CanUserSortColumns = "True" 
              ItemsSource = "{Binding Persons}" SelectedItem = "{Binding SelectedPerson}"/>

</Grid>

MainWindowViewModel.cs

using System.Collections.ObjectModel;
using System.ComponentModel;

namespace WpfApp4
{
public class MainWindowViewModel : INotifyPropertyChanged
{
    public MainWindowViewModel()
    {
        Persons = new ObservableCollection<Person>()
        {
            new Person()
            {
                Name = "Foo", Age = 10
            },
             new Person()
            {
                Name = "Bar", Age = 10
            },
            new Person()
            {
                Name = "Yolo", Age = 10
            },
        };
    }

    private ObservableCollection<Person> _persons;
    public ObservableCollection<Person> Persons
    {
        get => _persons;
        set
        {
            _persons = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Persons"));
        }
    }        

    private Person _selectedPerson;
    public Person SelectedPerson
    {
        get => _selectedPerson;
        set
        {
            _selectedPerson = value;
            SelectedPersonChanged();
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedPerson"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void SelectedPersonChanged()
    {
        if (SelectedPerson != null)
        {
            //Get a refreshed instance of person from DataSource
            //For the purpose of the example, we admit that the values are the same
            Person updatedPerson = new Person() { Age = SelectedPerson.Age, Name = SelectedPerson.Name };

            //Update in collection
            int previousIndex = Persons.IndexOf(SelectedPerson);
            Persons[previousIndex] = updatedPerson;
            _selectedPerson = updatedPerson;               
        }
    }
}

public class Person  
{
    public string Name { get; set; }
    public int Age { get; set; }
}
}

Живой пример:

Замена элемента в ItemsSource, сводит сортировку DataGrid с ума

Любая идея, как заменить элемент, не нарушая порядок строк?

Спасибо

Редактировать

В итоге я использовал AutoMapper для копирования Person из источника данных в SelectedPerson.

В конструкторе ViewModel:

Mapper.Initialize(cfg => cfg.CreateMap<Person, Person>());            

private void SelectedPersonChanged()
    {
        if (SelectedPerson != null)
        {
            //Get a refreshed instance of person from DataSource
            //For the purpose of the example, we admit that the values are the same
            Person updatedPerson = new Person()
            {
                Age = SelectedPerson.Age, Name = SelectedPerson.Name
            };

            Mapper.Map(updatedPerson, SelectedPerson);               
        }
    }

Вы пытались редактировать строки DataGrid сами? Это немного хакерски, но, возможно, это правильный путь (хотя и не очень MVVMy)

Krzysztof Skowronek 30.01.2019 12:41

К сожалению, ограничение MVVM нельзя пересечь :(

OK cowboy 31.01.2019 09:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
41
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я думаю, что лучшим способом было бы отредактировать существующий экземпляр, а не удалять его и добавлять новый.

Просто заставьте Person реализовать INotifyPropertyChanged и отредактируйте свойства.

  private void SelectedPersonChanged()
    {
        if (SelectedPerson != null)
        {
            var personFromDB = GetFromDB(SelectedPerson.Id);

             SelectedPerson.Age = personFromDB.Age;
             SelectedPerson.Name = personFromDB.Name;
        }
    }

Да, конечно, на простом примере. Но моя модель намного сложнее (дочерняя коллекция, ссылочный тип и т. д.). Я хотел бы избежать этого решения, если это возможно

OK cowboy 30.01.2019 12:29

вы можете использовать отражение для глубокого автоматического копирования всех свойств для каждого класса. GridView просто выполняет свою работу: реагирует на событие ItemAdded (или даже ItemReplaced, когда я немного гуглю). Я не знаю, как обмануть DataGrid, но мой ответ сделает то, что вы хотите

Krzysztof Skowronek 30.01.2019 12:38

Кажется, решения не так много, в итоге я использовал AutoMapper для копирования personFromDB в SelectedPerson. Я отредактирую свой вопрос и приму ваш ответ. Спасибо !

OK cowboy 31.01.2019 10:31

Вы можете попробовать отсортировать по нескольким полям. Вероятно, будет лучше всего, если ваши данные имеют поле идентификатора или другое уникальное поле, которое не изменяется... в противном случае оно все равно может прыгать, если, например, имя изменилось, когда вы перезагрузили его из базы данных.

Вот как вы могли бы сделать это с двумя перечисленными вами полями (Возраст/Имя). При сортировке по возрасту добавляется дополнительная сортировка по имени. А при сортировке по имени добавляется вторичная сортировка по возрасту.

xaml:

<DataGrid AutoGenerateColumns = "True" CanUserSortColumns = "True"
    Sorting = "DataGrid_Sorting" 
    ItemsSource = "{Binding Persons}" SelectedItem = "{Binding SelectedPerson}"/>

код:

    private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
    {
        var v = CollectionViewSource.GetDefaultView((sender as DataGrid).ItemsSource);
        v.SortDescriptions.Clear();

        // Set column sort direction - otherwise you won't see the arrow on the column header
        if (!e.Column.SortDirection.HasValue)
            e.Column.SortDirection = ListSortDirection.Descending;
        e.Column.SortDirection = e.Column.SortDirection.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;

        // Add sort description
        v.SortDescriptions.Add(new SortDescription(e.Column.SortMemberPath, e.Column.SortDirection.Value));

        // Add secondary sort description (age or name)
        var secondarySort = e.Column.SortMemberPath == "Age" ? "Name" : "Age";
        v.SortDescriptions.Add(new SortDescription(secondarySort, ListSortDirection.Ascending));

        // Add more sort descriptions, as needed.

        // We handled it...
        e.Handled = true;
    }

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

OK cowboy 31.01.2019 10:27

Другие вопросы по теме