Last active
June 20, 2022 15:29
-
-
Save RolandPheasant/3bba5e32f2eefb70c538ce4acabf17cb to your computer and use it in GitHub Desktop.
Extension for a transform with an inline update
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class DynamicDataEx | |
{ | |
/// <summary> | |
/// Transforms the items, and when an update is received, allows the preservation of the previous view model | |
/// </summary> | |
/// <typeparam name="TObject">The type of the object.</typeparam> | |
/// <typeparam name="TKey">The type of the key.</typeparam> | |
/// <typeparam name="TDestination">The type of the destination.</typeparam> | |
/// <param name="source">The source.</param> | |
/// <param name="transformFactory">The transform factory.</param> | |
/// <param name="updateAction">Apply changes to the original. Example (previousTransformedItem, newOriginalItem) => previousTransformedItem.Value = newOriginalItem </param> | |
/// <returns></returns> | |
public static IObservable<IChangeSet<TDestination, TKey>> TransformWithInlineUpdate<TObject, TKey, TDestination>(this IObservable<IChangeSet<TObject, TKey>> source, | |
Func<TObject, TDestination> transformFactory, | |
Action<TDestination, TObject> updateAction = null) | |
{ | |
return source.Scan((ChangeAwareCache<TDestination, TKey>)null, (cache, changes) => | |
{ | |
if (cache == null) | |
cache = new ChangeAwareCache<TDestination, TKey>(changes.Count); | |
foreach (var change in changes) | |
{ | |
switch (change.Reason) | |
{ | |
case ChangeReason.Add: | |
cache.AddOrUpdate(transformFactory(change.Current), change.Key); | |
break; | |
case ChangeReason.Update: | |
{ | |
if (updateAction == null) continue; | |
var previous = cache.Lookup(change.Key) | |
.ValueOrThrow(()=> new MissingKeyException($"{change.Key} is not found.")); | |
updateAction(previous, change.Current); | |
//send a refresh as this will force downstream operators | |
cache.Refresh(change.Key); | |
} | |
break; | |
case ChangeReason.Remove: | |
cache.Remove(change.Key); | |
break; | |
case ChangeReason.Refresh: | |
cache.Refresh(change.Key); | |
break; | |
case ChangeReason.Moved: | |
//Do nothing ! | |
break; | |
} | |
} | |
return cache; | |
}).Select(cache => cache.CaptureChanges()); | |
} | |
} |
It's next on my list reactivemarbles/DynamicData#323
The TransformWithInlineUpdate
doesn't trigger the property's notification event, so the binded property on the UI won't refresh.
internal class MainWindowViewModel : ReactiveObject, IDisposable
{
private readonly SourceCache<TrackDto, int> _tracksCache = new(track => track.Id);
private readonly IDisposable _cleanup;
private readonly ReadOnlyObservableCollection<TrackViewModel> _tracks;
private bool _disposedValue;
public ReadOnlyObservableCollection<TrackViewModel> InboundTracks => _tracks;
public MainWindowViewModel()
{
var tracksViewModelCache = _tracksCache
.Connect()
.TransformWithInlineUpdate(trackDto => new TrackViewModel(trackDto), (existingTrack, updateTrackDto) => existingTrack.Update(updateTrackDto));
var tracksSourceListCleanup = tracksViewModelCache
.AutoRefresh()
.Sort(SortExpressionComparer<TrackViewModel>.Ascending(track => track.Id))
.ObserveOn(new DispatcherScheduler(Application.Current.Dispatcher))
.Bind(out _tracks)
.DisposeMany()
.Subscribe((_) => Console.WriteLine(_tracksCache.Count));
var expiredManagerCleanup = tracksViewModelCache
.WhenPropertyChanged(track => track.State)
.Subscribe(x =>
{
if (x.Value is Models.State.Expired)
{
_tracksCache.RemoveKey(x.Sender.Id);
}
});
var generator = new GeneratorService();
var generatorCleanup = generator.Tracks
.Subscribe(trackDtos => _tracksCache.Edit(innerTracks =>
{
foreach (var trackDto in trackDtos)
{
if (trackDto.Time >= GetFutureToleranceTime() || trackDto.Time <= GetExpiredToleranceTime())
{
continue;
}
innerTracks.AddOrUpdate(trackDto);
}
}));
_cleanup = new CompositeDisposable(tracksSourceListCleanup, expiredManagerCleanup, generatorCleanup);
}
private static DateTime GetFutureToleranceTime() => DateTime.Now + TimeSpan.FromSeconds(2);
private static DateTime GetExpiredToleranceTime() => DateTime.Now - TimeSpan.FromSeconds(10);
#region IDispoable
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_cleanup.Dispose();
}
_disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}
That will be accommodated for when I port the operator into the library.
The existing transform operator by default also does not respond to a refresh but, has a "transformOnRefresh" flag which the user can opt into.
For now, you can simply add an extra line of code to the snippet.
case ChangeReason.Update:
{
should become
case ChangeReason.Refresh:
case ChangeReason.Update:
{
and it will do as you expect
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It'd be awesome if you added this to the library.