Wire up the disposer for cleaning up unused objects, i.e disposing a VM will remove it from the view

This commit is contained in:
TheXamlGuy
2024-01-12 21:05:42 +00:00
parent 814c806240
commit 2a773f26db
37 changed files with 323 additions and 206 deletions
@@ -1,18 +1,13 @@
namespace Hyperbar.Widget.Contextual;
namespace Hyperbar.Widget.Contextual;
public class ContextualWidgetViewModel :
ObservableCollectionViewModel<IWidgetComponentViewModel>,
public class ContextualWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
IEnumerable<IWidgetComponentViewModel> items) :
ObservableCollectionViewModel<IWidgetComponentViewModel>(serviceFactory, mediator, disposer, items),
IWidgetViewModel,
ITemplatedViewModel
{
public ContextualWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
IEnumerable<IWidgetComponentViewModel> items) : base(serviceFactory, mediator, items)
{
TemplateFactory = templateFactory;
}
public ITemplateFactory TemplateFactory { get; }
public ITemplateFactory TemplateFactory => templateFactory;
}
@@ -3,22 +3,17 @@
namespace Hyperbar.Windows.Primary;
public class MediaController :
INotificationHandler<PlayRequest>,
IDisposable
{
public MediaController(GlobalSystemMediaTransportControlsSession session)
INotificationHandler<PlayRequest>
{
private readonly GlobalSystemMediaTransportControlsSession session;
public MediaController(GlobalSystemMediaTransportControlsSession session,
IMediator mediator)
{
this.session = session;
mediator.Subscribe(this);
}
public void Dispose()
{
throw new NotImplementedException();
}
public ValueTask Handle(PlayRequest notification,
CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async ValueTask Handle(PlayRequest notification, CancellationToken cancellationToken) =>
await session.TryPlayAsync();
}
@@ -1,47 +0,0 @@
using Windows.Media.Control;
namespace Hyperbar.Windows.Primary;
public class MediaControllerInitializer :
IInitializer
{
private readonly List<GlobalSystemMediaTransportControlsSession> sessions = [];
public async Task InitializeAsync()
{
GlobalSystemMediaTransportControlsSessionManager mediaTransportControlsSessionManager =
await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged;
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
mediaTransportControlsSessionManager.GetSessions();
foreach (var session in sessions)
{
this.sessions.Add(session);
}
}
private void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender,
SessionsChangedEventArgs args)
{
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
sender.GetSessions();
foreach (var session in this.sessions.ToList())
{
if (!sessions.Contains(session))
{
this.sessions.Remove(session);
}
}
foreach (var session in sessions)
{
if (!this.sessions.Contains(session))
{
this.sessions.Add(session);
}
}
}
}
@@ -0,0 +1,93 @@
using System.Collections.Concurrent;
using System.Threading.Channels;
using Windows.Media.Control;
namespace Hyperbar.Windows.Primary;
public class MediaControllerManager :
IInitializer
{
private readonly ConcurrentDictionary<GlobalSystemMediaTransportControlsSession, MediaController> cachedSessions = [];
private readonly IMediator mediator;
private readonly Queue<MediaController> mediaControllers;
private readonly IServiceFactory serviceFactory;
public MediaControllerManager(IServiceFactory serviceFactory,
IMediator mediator,
Queue<MediaController> mediaControllers)
{
this.serviceFactory = serviceFactory;
this.mediator = mediator;
this.mediaControllers = mediaControllers;
}
private Channel<MediaController> d;
public async Task InitializeAsync()
{
d = Channel.CreateUnbounded<MediaController>();
_ = Task.Run(async () => {
await foreach (var coordinates in d.Reader.ReadAllAsync())
{
Console.WriteLine(coordinates);
}
});
GlobalSystemMediaTransportControlsSessionManager mediaTransportControlsSessionManager =
await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged;
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
mediaTransportControlsSessionManager.GetSessions();
foreach (GlobalSystemMediaTransportControlsSession session in sessions)
{
await InitializeSessionAsync(session);
}
}
private async Task InitializeSessionAsync(GlobalSystemMediaTransportControlsSession session)
{
if (serviceFactory.Create<MediaController>(session) is MediaController mediaController)
{
await d.Writer.WriteAsync(mediaController);
mediaControllers.Enqueue(mediaController);
cachedSessions.TryAdd(session, mediaController);
await mediator.PublishAsync(new Created<MediaController>(mediaController));
}
}
private async void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender,
SessionsChangedEventArgs args)
{
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
sender.GetSessions();
foreach (KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController> session in
cachedSessions)
{
if (!sessions.Contains(session.Key))
{
cachedSessions.TryRemove(session);
}
}
foreach (GlobalSystemMediaTransportControlsSession session in sessions)
{
await InitializeSessionAsync(session);
}
}
private void RemoveSession(GlobalSystemMediaTransportControlsSession session)
{
if (serviceFactory.Create<MediaController>(session) is MediaController mediaController)
{
cachedSessions.TryAdd(session, mediaController);
}
}
}
@@ -1,20 +1,17 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Hyperbar.Windows.Primary;
public partial class MediaInformationViewModel :
WidgetComponentViewModel
{
[ObservableProperty]
private string title = "this is a test";
[ObservableProperty]
private string description = "this is a test description";
public MediaInformationViewModel(ITemplateFactory templateFactory) : base(templateFactory)
{
}
}
//public class MediaControllerViewModelFactory(IServiceFactory service,
// IMediator mediator,
// Queue<MediaController> mediaControllers) :
// IFactory<MediaControllerViewModel>
//{
// public MediaControllerViewModel Create()
// {
// throw new NotImplementedException();
// }
//}
public class MediaControllerViewModel :
ObservableCollectionViewModel<WidgetComponentViewModel>,
@@ -22,7 +19,8 @@ public class MediaControllerViewModel :
{
public MediaControllerViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator) : base(serviceFactory, mediator)
IMediator mediator,
IDisposer disposer) : base(serviceFactory, mediator, disposer)
{
TemplateFactory = templateFactory;
@@ -9,10 +9,8 @@ public class MediaControllerWidgetProvider :
{
public void Create(HostBuilderContext comtext, IServiceCollection services) =>
services.AddWidgetTemplate<MediaControllerWidgetViewModel, MediaControllerWidgetView>()
.AddTransient<IInitializer, MediaControllerInitializer>()
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>()
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>()
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>()
.AddContentTemplate<MediaInformationViewModel, MediaInformationView>();
.AddSingleton<Queue<MediaController>>()
.AddSingleton<IInitializer, MediaControllerManager>()
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>();
}
@@ -3,8 +3,8 @@
public class MediaControllerWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
IEnumerable<MediaControllerViewModel> items) :
ObservableCollectionViewModel<MediaControllerViewModel>(serviceFactory, mediator, items),
IDisposer disposer) :
ObservableCollectionViewModel<MediaControllerViewModel>(serviceFactory, mediator, disposer),
IWidgetViewModel,
ITemplatedViewModel
{
@@ -0,0 +1,17 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Hyperbar.Windows.Primary;
public partial class MediaInformationViewModel :
WidgetComponentViewModel
{
[ObservableProperty]
private string title = "this is a test";
[ObservableProperty]
private string description = "this is a test description";
public MediaInformationViewModel(IServiceFactory serviceFactory, IMediator mediator, IDisposer disposer, ITemplateFactory templateFactory) : base(serviceFactory, mediator, disposer, templateFactory)
{
}
}
@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="System.Runtime.Caching" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hyperbar\Hyperbar.csproj" />
@@ -6,7 +6,7 @@ namespace Hyperbar.Windows.Primary;
[JsonDerivedType(typeof(ProcessCommandConfiguration), typeDiscriminator: "ProcessCommand")]
public class PrimaryCommandConfiguration
{
public required string Id { get; set; }
public required Guid Id { get; set; }
public required string Icon { get; set; }
}
@@ -5,6 +5,6 @@ public class PrimaryWidgetConfiguration :
{
public static PrimaryWidgetConfiguration Defaults => new()
{
new KeyAcceleratorCommandConfiguration { Id = $"{Guid.NewGuid()}", Icon = "\uE720", Key = 91, Modifiers = [] }
new KeyAcceleratorCommandConfiguration { Id = Guid.NewGuid(), Icon = "\uE720", Key = 91, Modifiers = [] }
};
}
@@ -8,9 +8,10 @@ public class PrimaryWidgetProvider :
{
public void Create(HostBuilderContext comtext, IServiceCollection services) =>
services.AddConfiguration<PrimaryWidgetConfiguration>()
.AddTransient<IFactory<IEnumerable<IWidgetComponentViewModel>>, WidgetComponentViewModelFactory>()
.AddTransient<IViewModelEnumerator<IWidgetComponentViewModel>, WidgetComponentViewModelEnumerator>()
.AddTransient<IViewModelFactory<PrimaryCommandConfiguration, IWidgetComponentViewModel?>, WidgetComponentViewModelFactory>()
.AddWidgetTemplate<PrimaryWidgetViewModel>()
.AddNotificationPipeline<ConfigurationChanged<PrimaryWidgetConfiguration>,
ValueChanging<IEnumerable<IWidgetComponentViewModel>>>();
CollectionChanged<IEnumerable<IWidgetComponentViewModel>>>();
}
@@ -4,8 +4,9 @@ namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
IFactory<IEnumerable<IWidgetComponentViewModel>> factory) :
ObservableCollectionViewModel<IWidgetComponentViewModel>(serviceFactory, mediator, factory),
IDisposer disposer,
IViewModelEnumerator<IWidgetComponentViewModel> enumerator) :
ObservableCollectionViewModel<IWidgetComponentViewModel>(serviceFactory, mediator, disposer, enumerator),
IWidgetViewModel,
ITemplatedViewModel
{
@@ -0,0 +1,28 @@
using CommunityToolkit.Mvvm.Input;
namespace Hyperbar.Windows.Primary;
public class WidgetComponentViewModelFactory(IServiceFactory service,
IMediator mediator) :
IViewModelFactory<PrimaryCommandConfiguration, IWidgetComponentViewModel?>
{
public async ValueTask<IWidgetComponentViewModel?> CreateAsync(PrimaryCommandConfiguration value)
{
if (value is KeyAcceleratorCommandConfiguration keyAcceleratorCommand)
{
return await ValueTask.FromResult(service.Create<WidgetButtonViewModel>(keyAcceleratorCommand.Id, keyAcceleratorCommand.Icon,
new RelayCommand(async () => await mediator.SendAsync(new KeyAcceleratorRequest((VirtualKey)
keyAcceleratorCommand.Key, keyAcceleratorCommand.Modifiers?
.Select(modifier => (VirtualKey)modifier).ToArray())))));
}
if (value is ProcessCommandConfiguration commandConfiguration)
{
return await ValueTask.FromResult(service.Create<WidgetButtonViewModel>(commandConfiguration.Id,
commandConfiguration.Icon, new RelayCommand(async () =>
await mediator.SendAsync(new ProcessRequest(commandConfiguration.Path)))));
}
return default;
}
}
@@ -0,0 +1,14 @@
namespace Hyperbar.Windows.Primary;
public class WidgetComponentViewModelEnumerator(PrimaryWidgetConfiguration configuration,
IViewModelFactory<PrimaryCommandConfiguration, IWidgetComponentViewModel?> factory) :
IViewModelEnumerator<IWidgetComponentViewModel>
{
public async IAsyncEnumerable<IWidgetComponentViewModel?> Next()
{
foreach (PrimaryCommandConfiguration item in configuration)
{
yield return await factory.CreateAsync(item);
}
}
}
@@ -1,26 +0,0 @@
namespace Hyperbar.Windows.Primary;
public class WidgetComponentViewModelFactory(PrimaryWidgetConfiguration configuration,
IServiceFactory service,
IMediator mediator) :
IFactory<IEnumerable<IWidgetComponentViewModel>>
{
public IEnumerable<IWidgetComponentViewModel> Create()
{
foreach (PrimaryCommandConfiguration item in configuration)
{
if (item is KeyAcceleratorCommandConfiguration keyAcceleratorCommandConfiguration)
{
yield return service.Create<WidgetButtonViewModel>(keyAcceleratorCommandConfiguration.Icon, new Action(async () =>
await mediator.SendAsync(new KeyAcceleratorRequest((VirtualKey)keyAcceleratorCommandConfiguration.Key,
keyAcceleratorCommandConfiguration.Modifiers?.Select(modifier => (VirtualKey)modifier).ToArray()))));
}
if (item is ProcessCommandConfiguration processCommandConfiguration)
{
yield return service.Create<WidgetButtonViewModel>(processCommandConfiguration.Icon, new Action(async () =>
await mediator.SendAsync(new ProcessRequest(processCommandConfiguration.Path))));
}
}
}
}
+3 -1
View File
@@ -36,6 +36,8 @@ public partial class App :
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!)));
services.AddSingleton<IMediator, Mediator>();
services.AddSingleton<IDisposer, Disposer>();
services.AddHostedService<AppService>();
services.AddTransient<IInitializer, AppInitializer>();
@@ -45,7 +47,7 @@ public partial class App :
services.AddContentTemplate<WidgetBarViewModel, WidgetBarView>();
services.AddWidgetProvider<MediaControllerWidgetProvider>();
//services.AddWidgetProvider<MediaControllerWidgetProvider>();
services.AddWidgetProvider<PrimaryWidgetProvider>();
services.AddTransient(provider =>
+1
View File
@@ -30,6 +30,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.231202003-experimental1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25936-preview" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
@@ -35,6 +35,7 @@ namespace Hyperbar.Windows
isolatedServices.AddTransient<ITemplateFactory, TemplateFactory>();
isolatedServices.AddSingleton<IMediator, Mediator>();
isolatedServices.AddSingleton<IDisposer, Disposer>();
isolatedServices.AddSingleton<IVirtualKeyboard, VirtualKeyboard>();
+2 -2
View File
@@ -17,8 +17,8 @@
Width="{StaticResource ButtonWidth}"
Height="{StaticResource ButtonHeight}"
Padding="{StaticResource ButtonPadding}"
FontSize="16"
Command="{Binding Click}"
Content="{Binding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}" />
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16" />
</UserControl>
+8
View File
@@ -3,12 +3,20 @@
x:Class="Hyperbar.Windows.WidgetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:ui="using:Hyperbar.Windows.UI">
<ItemsControl ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Loaded">
<interactions:InvokeCommandAction Command="{Binding Initialize}" />
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ItemsControl>
</UserControl>
+1 -10
View File
@@ -1,5 +1,4 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
namespace Hyperbar.Windows;
@@ -7,13 +6,5 @@ public sealed partial class WidgetView :
UserControl,
IWidgetView
{
public WidgetView()
{
InitializeComponent();
}
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
base.OnKeyDown(e);
}
public WidgetView() => InitializeComponent();
}
-6
View File
@@ -1,6 +0,0 @@
namespace Hyperbar;
public interface IMapping<TFrom, TTo>
{
TTo Create();
}
+6
View File
@@ -0,0 +1,6 @@
namespace Hyperbar;
public interface IViewModelFactory<TIn, TOut>
{
ValueTask<TOut> CreateAsync(TIn value);
}
-6
View File
@@ -1,6 +0,0 @@
namespace Hyperbar;
public interface IFactory<T>
{
T Create();
}
@@ -10,9 +10,6 @@ public class NotficationPipelineHandler<TFromNotification, ToNotification>(IMedi
{
private readonly IMediator mediator = mediator;
public ValueTask Handle(TFromNotification notification, CancellationToken cancellationToken)
{
return mediator.PublishAsync(new ToNotification(),
cancellationToken);
}
public ValueTask Handle(TFromNotification notification, CancellationToken cancellationToken) =>
mediator.PublishAsync(new ToNotification(), cancellationToken);
}
+3
View File
@@ -0,0 +1,3 @@
namespace Hyperbar;
public record CollectionChanged<TValue> : INotification;
-5
View File
@@ -1,5 +0,0 @@
using System.Collections;
namespace Hyperbar;
public record ValueChanging<TValue> : INotification;
+3
View File
@@ -0,0 +1,3 @@
namespace Hyperbar;
public record Created<TValue>(TValue? Value = default) : INotification;
@@ -8,4 +8,4 @@ public interface IObservableCollectionViewModel<TItem> :
IList,
IReadOnlyList<TItem>,
INotifyCollectionChanged,
INotificationHandler<ValueChanging<IEnumerable<TItem>>>;
IInitializer;
+6
View File
@@ -0,0 +1,6 @@
namespace Hyperbar;
public interface IViewModelEnumerator<TItem>
{
IAsyncEnumerable<TItem?> Next();
}
+58 -22
View File
@@ -1,7 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reactive.Disposables;
using System.Windows.Input;
namespace Hyperbar;
@@ -11,14 +14,19 @@ public partial class ObservableCollectionViewModel<TItem> :
{
public ObservableCollection<TItem> collection = [];
private readonly SynchronizationContext? context;
private readonly IViewModelEnumerator<TItem>? enumerator;
private readonly IServiceFactory serviceFactory;
private readonly IDisposer disposer;
public ObservableCollectionViewModel(IServiceFactory serviceFactory,
IMediator mediator)
IMediator mediator,
IDisposer disposer)
{
context = SynchronizationContext.Current;
this.serviceFactory = serviceFactory;
this.disposer = disposer;
mediator.Subscribe(this);
collection.CollectionChanged += OnCollectionChanged;
@@ -26,28 +34,44 @@ public partial class ObservableCollectionViewModel<TItem> :
public ObservableCollectionViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IFactory<IEnumerable<TItem>> factory)
IDisposer disposer,
IViewModelEnumerator<TItem> enumerator)
{
context = SynchronizationContext.Current;
this.serviceFactory = serviceFactory;
this.disposer = disposer;
this.enumerator = enumerator;
mediator.Subscribe(this);
collection.CollectionChanged += OnCollectionChanged;
if (factory is not null && factory.Create() is { } items)
if (enumerator is not null)
{
AddRange(factory.Create());
Task.Run(async () =>
{
await foreach (TItem? item in enumerator.Next())
{
if (item is not null)
{
Add(item);
}
}
});
}
}
public ObservableCollectionViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
IEnumerable<TItem> items)
{
context = SynchronizationContext.Current;
this.serviceFactory = serviceFactory;
this.disposer = disposer;
mediator.Subscribe(this);
collection.CollectionChanged += OnCollectionChanged;
@@ -59,6 +83,10 @@ public partial class ObservableCollectionViewModel<TItem> :
public int Count => collection.Count;
public ICommand Initialize => new AsyncRelayCommand(InitializeAsync);
public bool Initialized { get; private set; }
bool IList.IsFixedSize => false;
bool ICollection<TItem>.IsReadOnly => false;
@@ -157,6 +185,7 @@ public partial class ObservableCollectionViewModel<TItem> :
context?.Post(state => Add(item), null);
}
}
public void Clear() => ClearItems();
public bool Contains(TItem item) => collection.Contains(item);
@@ -171,19 +200,21 @@ public partial class ObservableCollectionViewModel<TItem> :
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)collection).GetEnumerator();
//public ValueTask Handle(CollectionChanged<IEnumerable<TItem>> notification,
// CancellationToken cancellationToken)
//{
// context?.Post(state => Clear(), null);
// AddRange(notification.Items);
// return ValueTask.CompletedTask;
//}
public int IndexOf(TItem item) => collection.IndexOf(item);
int IList.IndexOf(object? value) => IsCompatibleObject(value) ? IndexOf((TItem)value!) : -1;
public Task InitializeAsync()
{
if (Initialized)
{
return Task.CompletedTask;
}
Initialized = true;
return Task.CompletedTask;
}
public void Insert(int index, TItem item) => InsertItem(index, item);
void IList.Insert(int index, object? value)
@@ -209,6 +240,7 @@ public partial class ObservableCollectionViewModel<TItem> :
Remove((TItem)value!);
}
}
public void RemoveAt(int index) => RemoveItem(index);
protected virtual void ClearItems() => collection.Clear();
@@ -217,7 +249,15 @@ public partial class ObservableCollectionViewModel<TItem> :
{
if (value is TItem item)
{
collection.Insert(index, item);
disposer.Add(item, Disposable.Create(() =>
{
if (Contains(item))
{
Remove(item);
}
}));
context?.Post(state => collection.Insert(index, item), null);
}
}
@@ -230,13 +270,9 @@ public partial class ObservableCollectionViewModel<TItem> :
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) =>
CollectionChanged?.Invoke(this, args);
public ValueTask Handle(ValueChanging<IEnumerable<TItem>> notification,
CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
public class ObservableCollectionViewModel(IServiceFactory serviceFactory, IMediator mediator) :
ObservableCollectionViewModel<object>(serviceFactory, mediator);
public class ObservableCollectionViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer) :
ObservableCollectionViewModel<object>(serviceFactory, mediator, disposer);
+3
View File
@@ -0,0 +1,3 @@
namespace Hyperbar;
public record Removed<TValue>(TValue Value) : INotification;
+3 -3
View File
@@ -1,11 +1,11 @@
namespace Hyperbar;
namespace Hyperbar;
public partial class WidgetBarViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
IEnumerable<WidgetContainerViewModel> items) :
ObservableCollectionViewModel<WidgetContainerViewModel>(serviceFactory, mediator, items),
ObservableCollectionViewModel<WidgetContainerViewModel>(serviceFactory, mediator, disposer, items),
ITemplatedViewModel
{
public ITemplateFactory TemplateFactory => templateFactory;
+14 -17
View File
@@ -3,23 +3,20 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar;
public partial class WidgetButtonViewModel :
WidgetComponentViewModel
{
[ObservableProperty]
private string? icon;
[ObservableProperty]
private IRelayCommand? click;
public WidgetButtonViewModel(ITemplateFactory templateFactory,
public partial class WidgetButtonViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
Guid id = default,
string? icon = null,
Action? action = null) : base(templateFactory)
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory)
{
this.icon = icon;
if (action is not null)
{
click = new RelayCommand(action);
}
}
[ObservableProperty]
private Guid id = id;
[ObservableProperty]
private string? icon = icon;
[ObservableProperty]
private IRelayCommand? click = command;
}
+12 -1
View File
@@ -2,8 +2,19 @@
namespace Hyperbar;
public partial class WidgetComponentViewModel(ITemplateFactory templateFactory) :
public class ObservableViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer) :
ObservableObject,
IDisposable
{
public void Dispose() => disposer.Dispose(this);
}
public partial class WidgetComponentViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory) : ObservableViewModel(serviceFactory, mediator, disposer),
IWidgetComponentViewModel,
ITemplatedViewModel
{
+2 -1
View File
@@ -6,9 +6,10 @@ namespace Hyperbar;
public partial class WidgetContainerViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
IEnumerable<IWidgetViewModel> items,
bool alternate) :
ObservableCollectionViewModel<IWidgetViewModel>(serviceFactory, mediator, items),
ObservableCollectionViewModel<IWidgetViewModel>(serviceFactory, mediator, disposer, items),
ITemplatedViewModel
{
[ObservableProperty]