У меня есть 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; }
}
}
Живой пример:

Любая идея, как заменить элемент, не нарушая порядок строк?
Спасибо
Редактировать
В итоге я использовал 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);
}
}
К сожалению, ограничение MVVM нельзя пересечь :(





Я думаю, что лучшим способом было бы отредактировать существующий экземпляр, а не удалять его и добавлять новый.
Просто заставьте Person реализовать INotifyPropertyChanged и отредактируйте свойства.
private void SelectedPersonChanged()
{
if (SelectedPerson != null)
{
var personFromDB = GetFromDB(SelectedPerson.Id);
SelectedPerson.Age = personFromDB.Age;
SelectedPerson.Name = personFromDB.Name;
}
}
Да, конечно, на простом примере. Но моя модель намного сложнее (дочерняя коллекция, ссылочный тип и т. д.). Я хотел бы избежать этого решения, если это возможно
вы можете использовать отражение для глубокого автоматического копирования всех свойств для каждого класса. GridView просто выполняет свою работу: реагирует на событие ItemAdded (или даже ItemReplaced, когда я немного гуглю). Я не знаю, как обмануть DataGrid, но мой ответ сделает то, что вы хотите
Кажется, решения не так много, в итоге я использовал AutoMapper для копирования personFromDB в SelectedPerson. Я отредактирую свой вопрос и приму ваш ответ. Спасибо !
Вы можете попробовать отсортировать по нескольким полям. Вероятно, будет лучше всего, если ваши данные имеют поле идентификатора или другое уникальное поле, которое не изменяется... в противном случае оно все равно может прыгать, если, например, имя изменилось, когда вы перезагрузили его из базы данных.
Вот как вы могли бы сделать это с двумя перечисленными вами полями (Возраст/Имя). При сортировке по возрасту добавляется дополнительная сортировка по имени. А при сортировке по имени добавляется вторичная сортировка по возрасту.
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 - это настраиваемый элемент управления, который является универсальным. Я не знаю сорта заранее.
Вы пытались редактировать строки DataGrid сами? Это немного хакерски, но, возможно, это правильный путь (хотя и не очень MVVMy)