threading fixes

This commit is contained in:
TheXamlGuy
2024-01-07 22:37:54 +00:00
parent bc29fbc805
commit 9a669e695e
25 changed files with 217 additions and 92 deletions
@@ -1,4 +1,5 @@
namespace Hyperbar.Widget.Contextual;
namespace Hyperbar.Widget.Contextual;
public class ContextualWidgetViewModel : public class ContextualWidgetViewModel :
ObservableCollectionViewModel<IWidgetComponentViewModel>, ObservableCollectionViewModel<IWidgetComponentViewModel>,
@@ -6,8 +7,9 @@ public class ContextualWidgetViewModel :
ITemplatedViewModel ITemplatedViewModel
{ {
public ContextualWidgetViewModel(ITemplateFactory templateFactory, public ContextualWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory, IServiceFactory serviceFactory,
IEnumerable<IWidgetComponentViewModel> items) : base(serviceFactory, items) IMediator mediator,
IEnumerable<IWidgetComponentViewModel> items) : base(serviceFactory, mediator, items)
{ {
TemplateFactory = templateFactory; TemplateFactory = templateFactory;
} }
@@ -0,0 +1,22 @@
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetConfigurationChangedHandler :
INotificationHandler<ConfigurationChanged<PrimaryWidgetConfiguration>>
{
private readonly IMediator mediator;
private readonly IEnumerable<IWidgetComponentViewModel> items;
public PrimaryWidgetConfigurationChangedHandler(IMediator mediator,
IEnumerable<IWidgetComponentViewModel> items)
{
this.mediator = mediator;
this.items = items;
}
public async ValueTask Handle(ConfigurationChanged<PrimaryWidgetConfiguration> notification,
CancellationToken cancellationToken)
{
await mediator.PublishAsync(new CollectionChanged<IEnumerable<IWidgetComponentViewModel>>(items),
cancellationToken);
}
}
@@ -7,8 +7,8 @@ public class PrimaryWidgetProvider :
IWidgetProvider IWidgetProvider
{ {
public void Create(HostBuilderContext comtext, IServiceCollection services) => public void Create(HostBuilderContext comtext, IServiceCollection services) =>
services.AddConfiguration(comtext.Configuration.GetSection(nameof(PrimaryWidgetConfiguration)), services.AddConfiguration< PrimaryWidgetConfiguration>(comtext.Configuration.GetSection(nameof(PrimaryWidgetConfiguration)))
PrimaryWidgetConfiguration.Defaults) .AddHandler<WidgetComponentMapping>()
.AddHandler<WidgetComponentMappingHandler>() .AddHandler<PrimaryWidgetConfigurationChangedHandler>()
.AddWidgetTemplate<PrimaryWidgetViewModel>(); .AddWidgetTemplate<PrimaryWidgetViewModel>();
} }
@@ -1,4 +1,5 @@
namespace Hyperbar.Windows.Primary;
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetViewModel : public class PrimaryWidgetViewModel :
ObservableCollectionViewModel<IWidgetComponentViewModel>, ObservableCollectionViewModel<IWidgetComponentViewModel>,
@@ -7,7 +8,8 @@ public class PrimaryWidgetViewModel :
{ {
public PrimaryWidgetViewModel(ITemplateFactory templateFactory, public PrimaryWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory, IServiceFactory serviceFactory,
IEnumerable<IWidgetComponentViewModel> items) : base(serviceFactory, items) IMediator mediator,
IEnumerable<IWidgetComponentViewModel> items) : base(serviceFactory, mediator, items)
{ {
TemplateFactory = templateFactory; TemplateFactory = templateFactory;
} }
@@ -1,13 +1,12 @@
namespace Hyperbar.Windows.Primary; namespace Hyperbar.Windows.Primary;
public class WidgetComponentMappingHandler(PrimaryWidgetConfiguration configuration, public class WidgetComponentMapping(PrimaryWidgetConfiguration configuration,
IServiceFactory service, IServiceFactory service,
IMediator mediator) : IMediator mediator) :
IMappingHandler<PrimaryWidgetConfiguration, IEnumerable<IWidgetComponentViewModel>> IMappingHandler<PrimaryWidgetConfiguration, IEnumerable<IWidgetComponentViewModel>>
{ {
public IEnumerable<IWidgetComponentViewModel> Map() public IEnumerable<IWidgetComponentViewModel> Handle()
{ {
foreach (IPrimaryCommandConfiguration item in configuration) foreach (IPrimaryCommandConfiguration item in configuration)
{ {
+7 -1
View File
@@ -4,6 +4,7 @@ using Hyperbar.Windows.Primary;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
namespace Hyperbar.Windows; namespace Hyperbar.Windows;
@@ -17,6 +18,10 @@ public partial class App :
{ {
base.OnLaunched(args); base.OnLaunched(args);
var context = new DispatcherQueueSynchronizationContext(
DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
IHost? host = Host.CreateDefaultBuilder() IHost? host = Host.CreateDefaultBuilder()
.UseContentRoot(AppContext.BaseDirectory) .UseContentRoot(AppContext.BaseDirectory)
.ConfigureAppConfiguration(config => .ConfigureAppConfiguration(config =>
@@ -31,10 +36,11 @@ public partial class App :
services.AddSingleton<IServiceFactory>(provider => services.AddSingleton<IServiceFactory>(provider =>
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!))); new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!)));
services.AddSingleton<IMediator, Mediator>();
services.AddHostedService<AppService>(); services.AddHostedService<AppService>();
services.AddTransient<IInitializer, AppInitializer>(); services.AddTransient<IInitializer, AppInitializer>();
services.AddTransient<ITemplateFactory, TemplateFactory>(); services.AddTransient<ITemplateFactory, TemplateFactory>();
services.AddTransient<ITemplateGeneratorFactory, TemplateGeneratorFactory>();
services.AddTransient<DesktopFlyout>(); services.AddTransient<DesktopFlyout>();
services.AddContentTemplate<CommandViewModel, CommandView>(); services.AddContentTemplate<CommandViewModel, CommandView>();
@@ -2,6 +2,7 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.UI.Dispatching;
namespace Hyperbar.Windows namespace Hyperbar.Windows
{ {
@@ -11,6 +12,9 @@ namespace Hyperbar.Windows
where TWidgetProvider : where TWidgetProvider :
IWidgetProvider, new() IWidgetProvider, new()
{ {
DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
TWidgetProvider builder = new(); TWidgetProvider builder = new();
IHost? host = new HostBuilder() IHost? host = new HostBuilder()
.UseContentRoot(AppContext.BaseDirectory) .UseContentRoot(AppContext.BaseDirectory)
@@ -37,7 +41,6 @@ namespace Hyperbar.Windows
isolatedServices.AddContentTemplate<WidgetButtonViewModel, WidgetButtonView>(); isolatedServices.AddContentTemplate<WidgetButtonViewModel, WidgetButtonView>();
isolatedServices.AddTransient<ITemplateFactory, TemplateFactory>(); isolatedServices.AddTransient<ITemplateFactory, TemplateFactory>();
isolatedServices.AddTransient<ITemplateGeneratorFactory, TemplateGeneratorFactory>();
builder.Create(context, isolatedServices); builder.Create(context, isolatedServices);
@@ -0,0 +1,11 @@
using Microsoft.UI.Xaml.Controls;
namespace Hyperbar.Windows;
public class DataTemplateConverter : ValueConverter<object, DataTemplateSelector>
{
protected override DataTemplateSelector? ConvertTo(object value, Type? targetType, object? parameter, string? language)
{
return new TemplateGenerator();
}
}
@@ -1,9 +0,0 @@
using Microsoft.UI.Xaml;
namespace Hyperbar.Windows
{
public interface ITemplateGeneratorFactory
{
DataTemplate Create();
}
}
@@ -1,19 +1,11 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Hyperbar.Windows; namespace Hyperbar.Windows;
public class TemplateFactory(ITemplateGeneratorFactory factory, public class TemplateFactory(IEnumerable<IContentTemplateDescriptor> descriptors,
IEnumerable<IContentTemplateDescriptor> descriptors,
IServiceProvider provider) : IServiceProvider provider) :
DataTemplateSelector,
ITemplateFactory ITemplateFactory
{ {
protected override DataTemplate SelectTemplateCore(object item) => factory.Create();
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) => factory.Create();
public object? Create(object key) public object? Create(object key)
{ {
if (descriptors.FirstOrDefault(x => x.Key == key) is IContentTemplateDescriptor descriptor) if (descriptors.FirstOrDefault(x => x.Key == key) is IContentTemplateDescriptor descriptor)
@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
namespace Hyperbar.Windows;
public class TemplateGenerator : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item)
{
string xamlString = @"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:desktop='using:Hyperbar.Windows'>
<desktop:TemplateGeneratorControl />
</DataTemplate>";
return (DataTemplate)XamlReader.Load(xamlString);
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
string xamlString = @"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:desktop='using:Hyperbar.Windows'>
<desktop:TemplateGeneratorControl />
</DataTemplate>";
return (DataTemplate)XamlReader.Load(xamlString);
}
}
@@ -1,19 +0,0 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Markup;
namespace Hyperbar.Windows;
public class TemplateGeneratorFactory :
ITemplateGeneratorFactory
{
public DataTemplate Create()
{
string xamlString = @"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:desktop='using:Hyperbar.Windows'>
<desktop:TemplateGeneratorControl />
</DataTemplate>";
return (DataTemplate)XamlReader.Load(xamlString);
}
}
@@ -0,0 +1,45 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Markup;
namespace Hyperbar.Windows;
public abstract class ValueConverter<TSource, TTarget> :
MarkupExtension,
IValueConverter
{
protected override object ProvideValue(IXamlServiceProvider serviceProvider)
{
return this;
}
public object? Convert(object value, Type targetType, object parameter, string language)
{
return ConvertTo((TSource)value, targetType, parameter, language);
}
public object? ConvertBack(object value, Type targetType, object parameter, string language)
{
return ConvertBackTo((TTarget)value, targetType, parameter, language);
}
public TTarget? Convert(TSource value)
{
return ConvertTo(value, null, null, null);
}
public TSource? ConvertBack(TTarget value)
{
return ConvertBackTo(value, null, null, null);
}
protected virtual TTarget? ConvertTo(TSource value, Type? targetType, object? parameter, string? language)
{
return default;
}
protected virtual TSource? ConvertBackTo(TTarget value, Type? targetType, object? parameter, string? language)
{
return default;
}
}
+3 -2
View File
@@ -2,8 +2,9 @@
<UserControl <UserControl
x:Class="Hyperbar.Windows.CommandView" x:Class="Hyperbar.Windows.CommandView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ItemsControl ItemTemplateSelector="{Binding TemplateFactory}" ItemsSource="{Binding}"> xmlns:windows="using:Hyperbar.Windows">
<ItemsControl ItemTemplateSelector="{Binding Mode=TwoWay, Converter={windows:DataTemplateConverter}}" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" /> <StackPanel Orientation="Horizontal" />
+3 -2
View File
@@ -6,8 +6,9 @@ public partial class CommandViewModel :
ITemplatedViewModel ITemplatedViewModel
{ {
public CommandViewModel(ITemplateFactory templateFactory, public CommandViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory, IServiceFactory serviceFactory,
IEnumerable<IWidgetViewModel> items) : base(serviceFactory, items) IMediator mediator,
IEnumerable<IWidgetViewModel> items) : base(serviceFactory, mediator, items)
{ {
TemplateFactory = templateFactory; TemplateFactory = templateFactory;
} }
+3 -2
View File
@@ -2,8 +2,9 @@
<UserControl <UserControl
x:Class="Hyperbar.Windows.WidgetView" x:Class="Hyperbar.Windows.WidgetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ItemsControl ItemTemplateSelector="{Binding TemplateFactory}" ItemsSource="{Binding}"> xmlns:windows="using:Hyperbar.Windows">
<ItemsControl ItemTemplateSelector="{Binding Mode=TwoWay, Converter={windows:DataTemplateConverter}}" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" /> <StackPanel Orientation="Horizontal" Spacing="8" />
@@ -0,0 +1,5 @@
namespace Hyperbar;
public record ConfigurationChanged<TConfiguration>(TConfiguration Configuration) : INotification
where TConfiguration :
class;
@@ -1,13 +1,7 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using System.Threading;
namespace Hyperbar; namespace Hyperbar;
public record ConfigurationChanged<TConfiguration>(TConfiguration Configuration) : INotification
where TConfiguration :
class;
public class ConfigurationInitializer<TConfiguration>(DefaultConfiguration<TConfiguration> defaults, public class ConfigurationInitializer<TConfiguration>(DefaultConfiguration<TConfiguration> defaults,
IConfigurationWriter<TConfiguration> writer, IConfigurationWriter<TConfiguration> writer,
IOptionsMonitor<TConfiguration> options, IOptionsMonitor<TConfiguration> options,
@@ -17,12 +11,16 @@ public class ConfigurationInitializer<TConfiguration>(DefaultConfiguration<TConf
{ {
public Task InitializeAsync() public Task InitializeAsync()
{ {
options.OnChange(args => options.OnChange(async args =>
{ {
mediator.PublishAsync(new ConfigurationChanged<TConfiguration>(args)); await mediator.PublishAsync(new ConfigurationChanged<TConfiguration>(args));
}); });
writer.Write(defaults.Configuration); if (defaults.Configuration is not null)
{
writer.Write(defaults.Configuration);
}
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
@@ -1,6 +1,8 @@
namespace Hyperbar; namespace Hyperbar;
public class DefaultConfiguration<TConfiguration>(TConfiguration configuration) public class DefaultConfiguration<TConfiguration>(TConfiguration? configuration = null)
where TConfiguration :
class
{ {
public TConfiguration Configuration => configuration; public TConfiguration? Configuration => configuration;
} }
@@ -21,7 +21,7 @@ public static class IServiceCollectionExtensions
{ {
Type notificationType = arguments[0]; Type notificationType = arguments[0];
services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), ServiceLifetime.Singleton)); services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime));
services.Add(new ServiceDescriptor(typeof(INotificationHandler<>).MakeGenericType(notificationType), services.Add(new ServiceDescriptor(typeof(INotificationHandler<>).MakeGenericType(notificationType),
provider => provider.GetRequiredService<THandler>(), lifetime)); provider => provider.GetRequiredService<THandler>(), lifetime));
} }
@@ -53,13 +53,24 @@ public static class IServiceCollectionExtensions
Type responseType = arguments[1]; Type responseType = arguments[1];
services.AddTransient(typeof(THandler)); services.AddTransient(typeof(THandler));
services.AddTransient(responseType, provider => ((dynamic)provider.GetRequiredService<THandler>()).Map()); services.AddTransient(responseType, provider =>
{
return ((dynamic)provider.GetRequiredService<THandler>()).Handle();
});
} }
} }
return services; return services;
} }
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services,
IConfiguration configuration)
where TConfiguration :
class, new()
{
return services.AddConfiguration<TConfiguration>(configuration, typeof(TConfiguration).Name, "Settings.json", null);
}
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services, public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services,
IConfiguration configuration, IConfiguration configuration,
TConfiguration? defaults = null) TConfiguration? defaults = null)
@@ -107,10 +118,11 @@ public static class IServiceCollectionExtensions
if (defaults is not null) if (defaults is not null)
{ {
services.AddTransient(provider => new DefaultConfiguration<TConfiguration>(defaults));
services.AddTransient<IInitializer, ConfigurationInitializer<TConfiguration>>();
} }
services.AddTransient(provider => new DefaultConfiguration<TConfiguration>(defaults));
services.AddTransient<IInitializer, ConfigurationInitializer<TConfiguration>>();
services.AddTransient<IWritableConfiguration<TConfiguration>, WritableConfiguration<TConfiguration>>(); services.AddTransient<IWritableConfiguration<TConfiguration>, WritableConfiguration<TConfiguration>>();
return services; return services;
} }
+1 -1
View File
@@ -2,5 +2,5 @@
public interface IMappingHandler<TFrom, TTo> : IHandler public interface IMappingHandler<TFrom, TTo> : IHandler
{ {
TTo Map(); TTo Handle();
} }
+2 -1
View File
@@ -1,6 +1,7 @@
namespace Hyperbar; namespace Hyperbar;
public interface INotificationHandler<in TNotification> public interface INotificationHandler<in TNotification> :
IHandler
where TNotification : where TNotification :
INotification INotification
{ {
+5 -7
View File
@@ -6,9 +6,9 @@ namespace Hyperbar;
public class Mediator(IServiceProvider provider) : public class Mediator(IServiceProvider provider) :
IMediator IMediator
{ {
private readonly ConditionalWeakTable<Type, dynamic> handlers = []; private readonly ConditionalWeakTable<Type, dynamic> addedHandlers = [];
public ValueTask PublishAsync<TNotification>(TNotification notification, public async ValueTask PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TNotification : where TNotification :
INotification INotification
@@ -16,7 +16,7 @@ public class Mediator(IServiceProvider provider) :
List<INotificationHandler<TNotification>> handlers = List<INotificationHandler<TNotification>> handlers =
provider.GetServices<INotificationHandler<TNotification>>().ToList(); provider.GetServices<INotificationHandler<TNotification>>().ToList();
foreach (KeyValuePair<Type, dynamic> handler in this.handlers) foreach (KeyValuePair<Type, dynamic> handler in addedHandlers)
{ {
if (handler.Key == typeof(TNotification)) if (handler.Key == typeof(TNotification))
{ {
@@ -26,14 +26,12 @@ public class Mediator(IServiceProvider provider) :
if (handlers.Count == 0) if (handlers.Count == 0)
{ {
return default;
} }
else if (handlers.Count == 1) else if (handlers.Count == 1)
{ {
return handlers[0].Handle(notification, cancellationToken); await handlers[0].Handle(notification, cancellationToken);
} }
return default;
} }
public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> request, public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> request,
@@ -82,7 +80,7 @@ public class Mediator(IServiceProvider provider) :
if (interfaceType.GetGenericArguments() is { Length: 1 } arguments) if (interfaceType.GetGenericArguments() is { Length: 1 } arguments)
{ {
Type notificationType = arguments[0]; Type notificationType = arguments[0];
handlers.Add(notificationType, subject); addedHandlers.Add(notificationType, subject);
} }
} }
} }
+5
View File
@@ -0,0 +1,5 @@
using System.Collections;
namespace Hyperbar;
public record CollectionChanged<TCollection>(TCollection Items) : INotification where TCollection : IEnumerable;
+28 -11
View File
@@ -3,19 +3,29 @@
namespace Hyperbar; namespace Hyperbar;
public class ObservableCollectionViewModel<TItem> : public class ObservableCollectionViewModel<TItem> :
ObservableCollection<TItem> ObservableCollection<TItem>,
INotificationHandler<CollectionChanged<IEnumerable<TItem>>>
{ {
private readonly IServiceFactory serviceFactory; private readonly IServiceFactory serviceFactory;
private SynchronizationContext? context;
public ObservableCollectionViewModel(IServiceFactory serviceFactory) public ObservableCollectionViewModel(IServiceFactory serviceFactory,
IMediator mediator)
{ {
context = SynchronizationContext.Current;
this.serviceFactory = serviceFactory; this.serviceFactory = serviceFactory;
mediator.Subscribe(this);
} }
public ObservableCollectionViewModel(IServiceFactory serviceFactory, public ObservableCollectionViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IEnumerable<TItem> items) IEnumerable<TItem> items)
{ {
context = SynchronizationContext.Current;
this.serviceFactory = serviceFactory; this.serviceFactory = serviceFactory;
mediator.Subscribe(this);
AddRange(items); AddRange(items);
} }
@@ -31,7 +41,7 @@ public class ObservableCollectionViewModel<TItem> :
where T : TItem where T : TItem
{ {
T? item = serviceFactory.Create<T>(parameters); T? item = serviceFactory.Create<T>(parameters);
Add(item); context?.Post(state => Add(item), null);
return item; return item;
} }
@@ -41,7 +51,7 @@ public class ObservableCollectionViewModel<TItem> :
TItem TItem
{ {
T? item = serviceFactory.Create<T>(); T? item = serviceFactory.Create<T>();
Add(item); context?.Post(state => Add(item), null);
return item; return item;
} }
@@ -50,12 +60,19 @@ public class ObservableCollectionViewModel<TItem> :
{ {
foreach (TItem? item in items) foreach (TItem? item in items)
{ {
Add(item); context?.Post(state => Add(item), null);
} }
} }
public ValueTask Handle(CollectionChanged<IEnumerable<TItem>> notification,
CancellationToken cancellationToken)
{
context?.Post(state => Clear(), null);
AddRange(notification.Items);
return ValueTask.CompletedTask;
}
} }
public class ObservableCollectionViewModel(IServiceFactory serviceFactory) : public class ObservableCollectionViewModel(IServiceFactory serviceFactory, IMediator mediator) :
ObservableCollectionViewModel<object>(serviceFactory) ObservableCollectionViewModel<object>(serviceFactory, mediator);
{
}