Selected item/Value woes #901
-
I've been using DynamicData for a couple months now and think I am getting used to it. But one thing I keep having trouble with is Selection coherency. Now when the selected item is replaced with an updated value it gets deselected. It seems DynamicData either does a remove+add when there is no key, and a replace when there is. But in both cases WPF deselects the item at least momentarily to replace it but that is enough for it to trigger my viewmodel with the selected property to get the null value and then proceeed to clearing out all sorts of things connected to that. And then the listBox reads back the null value that it changed to reevaluate the selectedItem. So my question, is this a common problem for everyone with DynamicData? Is there a prescribed solution? Or is the answer "don't replace items and update the existing one instead"? Seems counter to the whole System.Reactive immutable methodology. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 10 replies
-
Yup.
I don't know if I'd QUITE say that, but the sentiment is right: it can feel wrong to transition from a layer of immutable state to highly-mutable state. But that's a fallacy, there's nothing wrong about it. The "immutable state store" pattern isn't the end goal, it's a means to an end. It works really well in your business layer and data layers, but it's a mistake to think that you should be extending it up to the View/ViewModel layers. WPF/UWP/all the XAML-based frameworks, they're simply not built that way. ViewModels work best when they are changed as infrequently as possible, and publish their own granular state changes, mainly because of how the binding system work. And at the end of the day, whatever mechanism you're using, your real end goal is to get your state changes merged into the Visual Element tree, which is very very much a mutable state structure. there's no getting around that. You can still use immutable/reactive state in your core, and still reap all the advantages of that, when using mutable ViewModels. Even if your ViewModels have little islands of state that they manage, you can still set it up to all be derived from the central state store. Also, avoid non-keyed collections within DynamicData like the plague. Unless you have a rare circumstance where it's impossible to derive an item key, the keyed collections and operators are going to run circles around the non-keyed equivalents, for performance. Mechanically, here's a couple examples... public record MyItemModel
{
public required int Id { get; init; }
public required string Name { get; init; }
public required string Description { get; init; }
}
public class MyItemViewModel
: INotifyPropertyChanged
{
public required int Id { get; init; }
public required string Name { get; set; }
public required string Description { get; set; }
}
using var source = new SourceCache<MyItemModel, int>(static => item.Id);
using var binding = source
.Connect()
.Filter(...)
.TransformWithInlineUpdate(
transformFactory: model => new MyItemViewModel()
{
Id = model.Id,
Name = model.Name,
Description = model.Description
},
updateAction: (viewModel, model) =>
{
viewModel.Name = model.Name;
viewModel.Description = model.Description;
})
transformOnRefresh: true)
.Sort(...)
.Bind(out var items)
.Subscribe(); public record MyItemModel
{
public required int Id { get; init; }
public required string Name { get; init; }
public required string Description { get; init; }
}
public class MyItemViewModel
: INotifyPropertyChanged,
IDisposable
{
public MyItemViewModel(
int itemId,
IObservableCache<MyItemModel, int> source)
{
Id = itemId;
var item = source
.Watch(itemId)
.Replay(1)
.RefCount();
item.Select(static item => item.Name)
.DistinctUntilChanged()
.Subscribe(name => Name = name)
.DisposeWith(...);
item.Select(static item => item.Description)
.DistinctUntilChanged()
.Subscribe(description => Description = description)
.DisposeWith(...);
}
public required int Id { get; private init; }
public required string Name { get; private set; }
public required string Description { get; private set; }
}
using var source = new SourceCache<MyItemModel, int>(static => item.Id);
using var binding = source
.Connect()
.Filter(...)
.DistinctValues(static item => item.Id)
.Transform(itemId => new MyItemViewModel(
itemId: itemId,
source: source))
.DisposeMany()
.Sort(...)
.Bind(out var items)
.Subscribe(); Me personally, my preference is option 2. It makes it a lot easier, in my mind, to track the flow of changes. |
Beta Was this translation helpful? Give feedback.
Yup.
I don't know if I'd QUITE say that, but the sentiment is right: it can feel wrong to transition from a layer of immutable state to highly-mutable state. But that's a fallacy, there's nothing wrong about it.
The "immutable state store" pattern isn't the end goal, it's a means to an end. It works really well in your business layer and data layers, but it's a mistake to think that you should be extending it up to the View/ViewModel layers. WPF/UWP/all the XAML-based frameworks, they're simply not built that way. ViewModels work best when they …