diff --git a/Hyperbar.Windows.Contextual/ContextualWidgetViewModel.cs b/Hyperbar.Windows.Contextual/ContextualWidgetViewModel.cs index 7127b49..c9028fe 100644 --- a/Hyperbar.Windows.Contextual/ContextualWidgetViewModel.cs +++ b/Hyperbar.Windows.Contextual/ContextualWidgetViewModel.cs @@ -1,4 +1,5 @@ -namespace Hyperbar.Widget.Contextual; + +namespace Hyperbar.Widget.Contextual; public class ContextualWidgetViewModel : ObservableCollectionViewModel, @@ -6,8 +7,9 @@ public class ContextualWidgetViewModel : ITemplatedViewModel { public ContextualWidgetViewModel(ITemplateFactory templateFactory, - IServiceFactory serviceFactory, - IEnumerable items) : base(serviceFactory, items) + IServiceFactory serviceFactory, + IMediator mediator, + IEnumerable items) : base(serviceFactory, mediator, items) { TemplateFactory = templateFactory; } diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetConfigurationChangedHandler.cs b/Hyperbar.Windows.Primary/PrimaryWidgetConfigurationChangedHandler.cs new file mode 100644 index 0000000..211d64e --- /dev/null +++ b/Hyperbar.Windows.Primary/PrimaryWidgetConfigurationChangedHandler.cs @@ -0,0 +1,22 @@ +namespace Hyperbar.Windows.Primary; + +public class PrimaryWidgetConfigurationChangedHandler : + INotificationHandler> +{ + private readonly IMediator mediator; + private readonly IEnumerable items; + + public PrimaryWidgetConfigurationChangedHandler(IMediator mediator, + IEnumerable items) + { + this.mediator = mediator; + this.items = items; + } + + public async ValueTask Handle(ConfigurationChanged notification, + CancellationToken cancellationToken) + { + await mediator.PublishAsync(new CollectionChanged>(items), + cancellationToken); + } +} diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs b/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs index ec42705..c554074 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs @@ -7,8 +7,8 @@ public class PrimaryWidgetProvider : IWidgetProvider { public void Create(HostBuilderContext comtext, IServiceCollection services) => - services.AddConfiguration(comtext.Configuration.GetSection(nameof(PrimaryWidgetConfiguration)), - PrimaryWidgetConfiguration.Defaults) - .AddHandler() + services.AddConfiguration< PrimaryWidgetConfiguration>(comtext.Configuration.GetSection(nameof(PrimaryWidgetConfiguration))) + .AddHandler() + .AddHandler() .AddWidgetTemplate(); } \ No newline at end of file diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs b/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs index dae45db..f013476 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs @@ -1,4 +1,5 @@ - namespace Hyperbar.Windows.Primary; + +namespace Hyperbar.Windows.Primary; public class PrimaryWidgetViewModel : ObservableCollectionViewModel, @@ -7,7 +8,8 @@ public class PrimaryWidgetViewModel : { public PrimaryWidgetViewModel(ITemplateFactory templateFactory, IServiceFactory serviceFactory, - IEnumerable items) : base(serviceFactory, items) + IMediator mediator, + IEnumerable items) : base(serviceFactory, mediator, items) { TemplateFactory = templateFactory; } diff --git a/Hyperbar.Windows.Primary/WidgetComponentMapping.cs b/Hyperbar.Windows.Primary/WidgetComponentMapping.cs index 579c491..68f53ad 100644 --- a/Hyperbar.Windows.Primary/WidgetComponentMapping.cs +++ b/Hyperbar.Windows.Primary/WidgetComponentMapping.cs @@ -1,13 +1,12 @@  - namespace Hyperbar.Windows.Primary; -public class WidgetComponentMappingHandler(PrimaryWidgetConfiguration configuration, +public class WidgetComponentMapping(PrimaryWidgetConfiguration configuration, IServiceFactory service, IMediator mediator) : IMappingHandler> { - public IEnumerable Map() + public IEnumerable Handle() { foreach (IPrimaryCommandConfiguration item in configuration) { diff --git a/Hyperbar.Windows/App.xaml.cs b/Hyperbar.Windows/App.xaml.cs index 93e0121..6af1fab 100644 --- a/Hyperbar.Windows/App.xaml.cs +++ b/Hyperbar.Windows/App.xaml.cs @@ -4,6 +4,7 @@ using Hyperbar.Windows.Primary; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; namespace Hyperbar.Windows; @@ -17,6 +18,10 @@ public partial class App : { base.OnLaunched(args); + var context = new DispatcherQueueSynchronizationContext( + DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + IHost? host = Host.CreateDefaultBuilder() .UseContentRoot(AppContext.BaseDirectory) .ConfigureAppConfiguration(config => @@ -31,10 +36,11 @@ public partial class App : services.AddSingleton(provider => new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!))); + services.AddSingleton(); services.AddHostedService(); + services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddContentTemplate(); diff --git a/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs b/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs index 852c3eb..c6e8960 100644 --- a/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs +++ b/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.UI.Dispatching; namespace Hyperbar.Windows { @@ -11,6 +12,9 @@ namespace Hyperbar.Windows where TWidgetProvider : IWidgetProvider, new() { + DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + TWidgetProvider builder = new(); IHost? host = new HostBuilder() .UseContentRoot(AppContext.BaseDirectory) @@ -37,7 +41,6 @@ namespace Hyperbar.Windows isolatedServices.AddContentTemplate(); isolatedServices.AddTransient(); - isolatedServices.AddTransient(); builder.Create(context, isolatedServices); diff --git a/Hyperbar.Windows/Templates/DataTemplateConverter.cs b/Hyperbar.Windows/Templates/DataTemplateConverter.cs new file mode 100644 index 0000000..0c365d7 --- /dev/null +++ b/Hyperbar.Windows/Templates/DataTemplateConverter.cs @@ -0,0 +1,11 @@ +using Microsoft.UI.Xaml.Controls; + +namespace Hyperbar.Windows; + +public class DataTemplateConverter : ValueConverter +{ + protected override DataTemplateSelector? ConvertTo(object value, Type? targetType, object? parameter, string? language) + { + return new TemplateGenerator(); + } +} diff --git a/Hyperbar.Windows/Templates/ITemplateGeneratorFactory.cs b/Hyperbar.Windows/Templates/ITemplateGeneratorFactory.cs deleted file mode 100644 index 574ca9a..0000000 --- a/Hyperbar.Windows/Templates/ITemplateGeneratorFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.UI.Xaml; - -namespace Hyperbar.Windows -{ - public interface ITemplateGeneratorFactory - { - DataTemplate Create(); - } -} \ No newline at end of file diff --git a/Hyperbar.Windows/Templates/TemplateFactory.cs b/Hyperbar.Windows/Templates/TemplateFactory.cs index d04907f..ca5374e 100644 --- a/Hyperbar.Windows/Templates/TemplateFactory.cs +++ b/Hyperbar.Windows/Templates/TemplateFactory.cs @@ -1,19 +1,11 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; namespace Hyperbar.Windows; -public class TemplateFactory(ITemplateGeneratorFactory factory, - IEnumerable descriptors, +public class TemplateFactory(IEnumerable descriptors, IServiceProvider provider) : - DataTemplateSelector, ITemplateFactory { - protected override DataTemplate SelectTemplateCore(object item) => factory.Create(); - - protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) => factory.Create(); - public object? Create(object key) { if (descriptors.FirstOrDefault(x => x.Key == key) is IContentTemplateDescriptor descriptor) diff --git a/Hyperbar.Windows/Templates/TemplateGenerator.cs b/Hyperbar.Windows/Templates/TemplateGenerator.cs new file mode 100644 index 0000000..4e9e18a --- /dev/null +++ b/Hyperbar.Windows/Templates/TemplateGenerator.cs @@ -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 = @" + + + "; + + return (DataTemplate)XamlReader.Load(xamlString); + } + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + { + string xamlString = @" + + + "; + + return (DataTemplate)XamlReader.Load(xamlString); + } +} diff --git a/Hyperbar.Windows/Templates/TemplateGeneratorFactory.cs b/Hyperbar.Windows/Templates/TemplateGeneratorFactory.cs deleted file mode 100644 index 0e4196a..0000000 --- a/Hyperbar.Windows/Templates/TemplateGeneratorFactory.cs +++ /dev/null @@ -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 = @" - - - "; - - return (DataTemplate)XamlReader.Load(xamlString); - } -} \ No newline at end of file diff --git a/Hyperbar.Windows/Templates/ValueConverter.cs b/Hyperbar.Windows/Templates/ValueConverter.cs new file mode 100644 index 0000000..26513b5 --- /dev/null +++ b/Hyperbar.Windows/Templates/ValueConverter.cs @@ -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 : + 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; + } +} diff --git a/Hyperbar.Windows/Views/CommandView.xaml b/Hyperbar.Windows/Views/CommandView.xaml index 2c804ab..c0a7e1d 100644 --- a/Hyperbar.Windows/Views/CommandView.xaml +++ b/Hyperbar.Windows/Views/CommandView.xaml @@ -2,8 +2,9 @@ - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:windows="using:Hyperbar.Windows"> + diff --git a/Hyperbar.Windows/Views/CommandViewModel.cs b/Hyperbar.Windows/Views/CommandViewModel.cs index 3319c1a..204c2ba 100644 --- a/Hyperbar.Windows/Views/CommandViewModel.cs +++ b/Hyperbar.Windows/Views/CommandViewModel.cs @@ -6,8 +6,9 @@ public partial class CommandViewModel : ITemplatedViewModel { public CommandViewModel(ITemplateFactory templateFactory, - IServiceFactory serviceFactory, - IEnumerable items) : base(serviceFactory, items) + IServiceFactory serviceFactory, + IMediator mediator, + IEnumerable items) : base(serviceFactory, mediator, items) { TemplateFactory = templateFactory; } diff --git a/Hyperbar.Windows/Views/WidgetView.xaml b/Hyperbar.Windows/Views/WidgetView.xaml index 5baccbc..50d8e68 100644 --- a/Hyperbar.Windows/Views/WidgetView.xaml +++ b/Hyperbar.Windows/Views/WidgetView.xaml @@ -2,8 +2,9 @@ - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:windows="using:Hyperbar.Windows"> + diff --git a/Hyperbar/Configuration/ConfigurationChanged.cs b/Hyperbar/Configuration/ConfigurationChanged.cs new file mode 100644 index 0000000..a670958 --- /dev/null +++ b/Hyperbar/Configuration/ConfigurationChanged.cs @@ -0,0 +1,5 @@ +namespace Hyperbar; + +public record ConfigurationChanged(TConfiguration Configuration) : INotification + where TConfiguration : + class; diff --git a/Hyperbar/Configuration/ConfigurationInitializer.cs b/Hyperbar/Configuration/ConfigurationInitializer.cs index 246ae05..956414a 100644 --- a/Hyperbar/Configuration/ConfigurationInitializer.cs +++ b/Hyperbar/Configuration/ConfigurationInitializer.cs @@ -1,13 +1,7 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Options; -using System.Threading; +using Microsoft.Extensions.Options; namespace Hyperbar; -public record ConfigurationChanged(TConfiguration Configuration) : INotification - where TConfiguration : - class; - public class ConfigurationInitializer(DefaultConfiguration defaults, IConfigurationWriter writer, IOptionsMonitor options, @@ -17,12 +11,16 @@ public class ConfigurationInitializer(DefaultConfiguration + options.OnChange(async args => { - mediator.PublishAsync(new ConfigurationChanged(args)); + await mediator.PublishAsync(new ConfigurationChanged(args)); }); - writer.Write(defaults.Configuration); + if (defaults.Configuration is not null) + { + writer.Write(defaults.Configuration); + } + return Task.CompletedTask; } } diff --git a/Hyperbar/Configuration/DefaultConfiguration.cs b/Hyperbar/Configuration/DefaultConfiguration.cs index f35f8b1..27006af 100644 --- a/Hyperbar/Configuration/DefaultConfiguration.cs +++ b/Hyperbar/Configuration/DefaultConfiguration.cs @@ -1,6 +1,8 @@ namespace Hyperbar; -public class DefaultConfiguration(TConfiguration configuration) +public class DefaultConfiguration(TConfiguration? configuration = null) + where TConfiguration : + class { - public TConfiguration Configuration => configuration; + public TConfiguration? Configuration => configuration; } diff --git a/Hyperbar/Extensions/IServiceCollectionExtensions.cs b/Hyperbar/Extensions/IServiceCollectionExtensions.cs index 2cc0020..ca93bf6 100644 --- a/Hyperbar/Extensions/IServiceCollectionExtensions.cs +++ b/Hyperbar/Extensions/IServiceCollectionExtensions.cs @@ -21,7 +21,7 @@ public static class IServiceCollectionExtensions { 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), provider => provider.GetRequiredService(), lifetime)); } @@ -53,13 +53,24 @@ public static class IServiceCollectionExtensions Type responseType = arguments[1]; services.AddTransient(typeof(THandler)); - services.AddTransient(responseType, provider => ((dynamic)provider.GetRequiredService()).Map()); + services.AddTransient(responseType, provider => + { + return ((dynamic)provider.GetRequiredService()).Handle(); + }); } } return services; } + public static IServiceCollection AddConfiguration(this IServiceCollection services, + IConfiguration configuration) + where TConfiguration : + class, new() + { + return services.AddConfiguration(configuration, typeof(TConfiguration).Name, "Settings.json", null); + } + public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration, TConfiguration? defaults = null) @@ -107,10 +118,11 @@ public static class IServiceCollectionExtensions if (defaults is not null) { - services.AddTransient(provider => new DefaultConfiguration(defaults)); - services.AddTransient>(); } + services.AddTransient(provider => new DefaultConfiguration(defaults)); + services.AddTransient>(); + services.AddTransient, WritableConfiguration>(); return services; } diff --git a/Hyperbar/Mediators/IMappingHandler.cs b/Hyperbar/Mediators/IMappingHandler.cs index b3f0a5c..2b39e8f 100644 --- a/Hyperbar/Mediators/IMappingHandler.cs +++ b/Hyperbar/Mediators/IMappingHandler.cs @@ -2,5 +2,5 @@ public interface IMappingHandler : IHandler { - TTo Map(); + TTo Handle(); } \ No newline at end of file diff --git a/Hyperbar/Mediators/INotificationHandler.cs b/Hyperbar/Mediators/INotificationHandler.cs index 61e1d51..346d75b 100644 --- a/Hyperbar/Mediators/INotificationHandler.cs +++ b/Hyperbar/Mediators/INotificationHandler.cs @@ -1,6 +1,7 @@ namespace Hyperbar; -public interface INotificationHandler +public interface INotificationHandler : + IHandler where TNotification : INotification { diff --git a/Hyperbar/Mediators/Mediator.cs b/Hyperbar/Mediators/Mediator.cs index ca996b5..1ae56d0 100644 --- a/Hyperbar/Mediators/Mediator.cs +++ b/Hyperbar/Mediators/Mediator.cs @@ -6,9 +6,9 @@ namespace Hyperbar; public class Mediator(IServiceProvider provider) : IMediator { - private readonly ConditionalWeakTable handlers = []; + private readonly ConditionalWeakTable addedHandlers = []; - public ValueTask PublishAsync(TNotification notification, + public async ValueTask PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification @@ -16,7 +16,7 @@ public class Mediator(IServiceProvider provider) : List> handlers = provider.GetServices>().ToList(); - foreach (KeyValuePair handler in this.handlers) + foreach (KeyValuePair handler in addedHandlers) { if (handler.Key == typeof(TNotification)) { @@ -26,14 +26,12 @@ public class Mediator(IServiceProvider provider) : if (handlers.Count == 0) { - return default; } else if (handlers.Count == 1) { - return handlers[0].Handle(notification, cancellationToken); + await handlers[0].Handle(notification, cancellationToken); } - return default; } public ValueTask SendAsync(IRequest request, @@ -82,7 +80,7 @@ public class Mediator(IServiceProvider provider) : if (interfaceType.GetGenericArguments() is { Length: 1 } arguments) { Type notificationType = arguments[0]; - handlers.Add(notificationType, subject); + addedHandlers.Add(notificationType, subject); } } } diff --git a/Hyperbar/Views/CollectionChanged.cs b/Hyperbar/Views/CollectionChanged.cs new file mode 100644 index 0000000..f6cd378 --- /dev/null +++ b/Hyperbar/Views/CollectionChanged.cs @@ -0,0 +1,5 @@ +using System.Collections; + +namespace Hyperbar; + +public record CollectionChanged(TCollection Items) : INotification where TCollection : IEnumerable; diff --git a/Hyperbar/Views/ObservableCollectionViewModel.cs b/Hyperbar/Views/ObservableCollectionViewModel.cs index cb255f0..9ff237e 100644 --- a/Hyperbar/Views/ObservableCollectionViewModel.cs +++ b/Hyperbar/Views/ObservableCollectionViewModel.cs @@ -3,19 +3,29 @@ namespace Hyperbar; public class ObservableCollectionViewModel : - ObservableCollection + ObservableCollection, + INotificationHandler>> { private readonly IServiceFactory serviceFactory; - - public ObservableCollectionViewModel(IServiceFactory serviceFactory) + private SynchronizationContext? context; + public ObservableCollectionViewModel(IServiceFactory serviceFactory, + IMediator mediator) { + context = SynchronizationContext.Current; + this.serviceFactory = serviceFactory; + mediator.Subscribe(this); } - public ObservableCollectionViewModel(IServiceFactory serviceFactory, + public ObservableCollectionViewModel(IServiceFactory serviceFactory, + IMediator mediator, IEnumerable items) { + context = SynchronizationContext.Current; + this.serviceFactory = serviceFactory; + mediator.Subscribe(this); + AddRange(items); } @@ -31,7 +41,7 @@ public class ObservableCollectionViewModel : where T : TItem { T? item = serviceFactory.Create(parameters); - Add(item); + context?.Post(state => Add(item), null); return item; } @@ -41,7 +51,7 @@ public class ObservableCollectionViewModel : TItem { T? item = serviceFactory.Create(); - Add(item); + context?.Post(state => Add(item), null); return item; } @@ -50,12 +60,19 @@ public class ObservableCollectionViewModel : { foreach (TItem? item in items) { - Add(item); + context?.Post(state => Add(item), null); } } + + public ValueTask Handle(CollectionChanged> notification, + CancellationToken cancellationToken) + { + context?.Post(state => Clear(), null); + AddRange(notification.Items); + + return ValueTask.CompletedTask; + } } -public class ObservableCollectionViewModel(IServiceFactory serviceFactory) : - ObservableCollectionViewModel(serviceFactory) -{ -} \ No newline at end of file +public class ObservableCollectionViewModel(IServiceFactory serviceFactory, IMediator mediator) : + ObservableCollectionViewModel(serviceFactory, mediator); \ No newline at end of file