Directory.Packages.props
+2
-1
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f116374..9a468b9 100644
@@ -15,5 +15,6 @@
<PackageVersion Include="Avalonia.Fonts.Inter" Version="12.0.4" />
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.2" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="11.0.0-preview.4.26230.115" />
</ItemGroup>
</Project>
</Project>
\ No newline at end of file
src/App/App.axaml
+0
-5
diff --git a/src/App/App.axaml b/src/App/App.axaml
index 8a3b957..78f2f97 100644
@@ -1,13 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MMirror.App.App"
xmlns:local="using:MMirror.App"
RequestedThemeVariant="Dark">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
</Application.Styles>
src/App/App.axaml.cs
+11
-2
diff --git a/src/App/App.axaml.cs b/src/App/App.axaml.cs
index 2799045..c4018e7 100644
@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using MMirror.App.ViewModels;
using MMirror.App.Views;
@@ -15,13 +16,21 @@ public partial class App : Application
public override void OnFrameworkInitializationCompleted()
{
var collection = new ServiceCollection();
collection.AddViewModels();
var services = collection.BuildServiceProvider();
DataTemplates.Add(new ViewLocator(services));
var vm = services.GetRequiredService<MainViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow { DataContext = new MainViewModel() };
desktop.MainWindow = new MainWindow { DataContext = vm };
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
{
singleView.MainView = new MainView { DataContext = new MainViewModel() };
singleView.MainView = new MainView { DataContext = vm };
}
base.OnFrameworkInitializationCompleted();
src/App/App.csproj
+1
-0
diff --git a/src/App/App.csproj b/src/App/App.csproj
index ad35dda..42285b2 100644
@@ -22,5 +22,6 @@
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
</Project>
src/App/ViewLocator.cs
+9
-24
diff --git a/src/App/ViewLocator.cs b/src/App/ViewLocator.cs
index a814054..b6590b7 100644
@@ -1,38 +1,23 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Microsoft.Extensions.DependencyInjection;
using MMirror.App.ViewModels;
using MMirror.App.Views;
namespace MMirror.App;
/// <summary>
/// Given a view model, returns the corresponding view if possible.
/// </summary>
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator"
)]
public class ViewLocator : IDataTemplate
public class ViewLocator(IServiceProvider services) : IDataTemplate
{
public Control? Build(object? param)
{
if (param is null)
return null;
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null)
public Control Build(object? data) =>
data switch
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
MainViewModel => services.GetRequiredService<MainView>(),
_ => new TextBlock { Text = $"No view for {data?.GetType().Name}" },
};
public bool Match(object? data)
{
return data is ViewModelBase;
}
public bool Match(object? data) => data is ViewModelBase;
}
src/App/ViewModels/MainViewModel.cs
+49
-2
diff --git a/src/App/ViewModels/MainViewModel.cs b/src/App/ViewModels/MainViewModel.cs
index 0dc7f9b..a8bd765 100644
@@ -1,6 +1,53 @@
namespace MMirror.App.ViewModels;
using System;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace MMirror.App.ViewModels;
public partial class MainViewModel : ViewModelBase
{
public string Greeting { get; } = "Welcome to Avalonia!";
private readonly TimeProvider timeProvider;
private readonly DispatcherTimer dispatcherTimer;
[ObservableProperty]
public partial DateTimeOffset CurrentTime { get; private set; }
public MainViewModel(TimeProvider timeProvider)
{
this.timeProvider = timeProvider;
CurrentTime = timeProvider.GetLocalNow();
dispatcherTimer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
dispatcherTimer.Tick += OnTick;
}
public void SubscribeToUpdates()
{
dispatcherTimer.Start();
}
private void OnTick(object? sender, EventArgs e)
{
CurrentTime = timeProvider.GetLocalNow();
}
public void UnsubscribeFromUpdates()
{
dispatcherTimer.Stop();
}
}
internal static class MainViewModelServiceCollectionExtensions
{
extension(IServiceCollection services)
{
public IServiceCollection AddMainViewModel()
{
services.TryAddSingleton(TimeProvider.System);
services.AddTransient<MainViewModel>();
return services;
}
}
}
src/App/ViewModels/ServiceCollectionExtensions.cs
+11
-0
diff --git a/src/App/ViewModels/ServiceCollectionExtensions.cs b/src/App/ViewModels/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..388f511
@@ -0,0 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
namespace MMirror.App.ViewModels;
internal static class ServiceCollectionExtensions
{
extension(IServiceCollection services)
{
public IServiceCollection AddViewModels() => services.AddMainViewModel();
}
}
src/App/Views/MainView.axaml
+2
-3
diff --git a/src/App/Views/MainView.axaml b/src/App/Views/MainView.axaml
index 7498754..881bce4 100644
@@ -7,13 +7,12 @@
x:Class="MMirror.App.Views.MainView"
x:DataType="vm:MainViewModel">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainViewModel />
</Design.DataContext>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="8">
<TextBlock Text="{Binding Greeting}"/>
<TextBlock Text="{Binding CurrentTime, StringFormat=HH:mm:ss}"/>
<TextBlock Text="{Binding CurrentTime, StringFormat=yyyy-MM-dd}"/>
<Rectangle Width="32" Height="32" Fill="#bada55" />
</StackPanel>
</UserControl>
src/App/Views/MainView.axaml.cs
+22
-0
diff --git a/src/App/Views/MainView.axaml.cs b/src/App/Views/MainView.axaml.cs
index 44f20ae..ac48bc1 100644
@@ -1,4 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using MMirror.App.ViewModels;
namespace MMirror.App.Views;
@@ -8,4 +10,24 @@ public partial class MainView : UserControl
{
InitializeComponent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (DataContext is MainViewModel vm)
{
vm.SubscribeToUpdates();
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
if (DataContext is MainViewModel vm)
{
vm.UnsubscribeFromUpdates();
}
base.OnDetachedFromVisualTree(e);
}
}
src/App/Views/ServiceCollectionExtensions.cs
+11
-0
diff --git a/src/App/Views/ServiceCollectionExtensions.cs b/src/App/Views/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..60282f7
@@ -0,0 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
namespace MMirror.App.Views;
internal static class ServiceCollectionExtensions
{
extension(IServiceCollection services)
{
public IServiceCollection AddViews() => services.AddTransient<MainView>().AddTransient<MainWindow>();
}
}
src/App/packages.lock.json
+7
-1
diff --git a/src/App/packages.lock.json b/src/App/packages.lock.json
index f4238bc..48a3ef9 100644
@@ -76,6 +76,12 @@
"resolved": "1.2.6",
"contentHash": "KMSJG+jfk7vjP52QkWB99qWespXCPAzG/IaMCMRHYWumJEAGKQYm2HtyWG6eqnOwDitH96i1cqq5EVesyOtPmg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Direct",
"requested": "[11.0.0-preview.4.26230.115, )",
"resolved": "11.0.0-preview.4.26230.115",
"contentHash": "v+F34r5W5XgR95xG0Te2KyYmCDnkc4kljLMGEZ30WKQjAzuXz/pLTEvsRNYxNJUzfKPI0H4oqLglyHZkCe1lYw=="
},
"Avalonia.Angle.Windows.Natives": {
"type": "Transitive",
"resolved": "2.1.27548.20260419",
@@ -506,4 +512,4 @@
}
}
}
}
}
\ No newline at end of file