chore: scaffold Avalonia MVVM application and test project

This commit is contained in:
yyc12345 committed 2026-10-22 15:40:12 +08:00
1 parent 2f6ce2e27c
commit bb040ef89b
15 files changed
+266

No files matched your search

+1
View File
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
+17
View File
@@ -0,0 +1,17 @@
namespace SongTidy.Tests;
/// <summary>
/// Contains baseline tests that verify the test project is wired to the application assembly.
/// </summary>
[TestClass]
public sealed class ProjectSmokeTests {
/// <summary>
/// Verifies that the main application assembly can be loaded from the test project.
/// </summary>
[TestMethod]
public void MainAssemblyIsReferenced() {
var assemblyName = typeof(global::SongTidy.ViewModels.MainWindowViewModel).Assembly.GetName().Name;
Assert.AreEqual("SongTidy", assemblyName);
}
}
+22
View File
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" Version="4.0.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SongTidy\SongTidy.csproj" />
</ItemGroup>
</Project>
+4
View File
@@ -0,0 +1,4 @@
<Solution>
<Project Path="SongTidy.Tests/SongTidy.Tests.csproj" />
<Project Path="SongTidy/SongTidy.csproj" />
</Solution>
+19
View File
@@ -0,0 +1,19 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SongTidy.App"
xmlns:local="using:SongTidy"
xmlns:semi="https://irihi.tech/semi"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<!-- Use the Semi design theme instead of the default Fluent theme. -->
<semi:SemiTheme />
<!-- Apply Semi styling to the DataGrid control. -->
<semi:DataGridSemiTheme />
</Application.Styles>
</Application>
+25
View File
@@ -0,0 +1,25 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using SongTidy.ViewModels;
using SongTidy.Views;
namespace SongTidy;
public partial class App : Application {
public override void Initialize() {
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted() {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
desktop.MainWindow = new MainWindow {
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+32
View File
@@ -0,0 +1,32 @@
using System;
using Avalonia;
using Avalonia.ReactiveUI;
namespace SongTidy;
/// <summary>
/// Provides the application entry point and Avalonia configuration.
/// </summary>
sealed class Program {
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
/// <summary>
/// Builds the Avalonia application configuration.
/// </summary>
/// <remarks>
/// This method is also discovered by the Avalonia visual designer, so it must remain public.
/// </remarks>
/// <returns>The configured application builder.</returns>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.UseReactiveUI();
}
+32
View File
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.22" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.22" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.22" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.22" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.22">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.9" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageReference Include="Semi.Avalonia" Version="11.3.14" />
<PackageReference Include="Semi.Avalonia.DataGrid" Version="11.3.13" />
<PackageReference Include="IconPacks.Avalonia.Lucide" Version="2.0.0" />
</ItemGroup>
</Project>
+46
View File
@@ -0,0 +1,46 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using SongTidy.ViewModels;
namespace SongTidy;
/// <summary>
/// Resolves a view for a view model by replacing the "ViewModel" suffix
/// with "View" in the full type name.
/// </summary>
public class ViewLocator : IDataTemplate {
/// <summary>
/// Creates the view that corresponds to the supplied view model.
/// </summary>
/// <param name="param">The view model instance to resolve a view for.</param>
/// <returns>
/// The resolved view, or a diagnostic text block when no matching view exists.
/// </returns>
public Control? Build(object? param) {
if (param is null)
return null;
// Convention-based lookup: SongTidy.ViewModels.FooViewModel -> SongTidy.Views.FooView.
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null) {
return (Control)Activator.CreateInstance(type)!;
}
// Show the unresolved type name instead of failing silently during development.
return new TextBlock { Text = "Not Found: " + name };
}
/// <summary>
/// Determines whether the supplied data object is a view model handled by this locator.
/// </summary>
/// <param name="data">The data object to inspect.</param>
/// <returns><see langword="true"/> when the object derives from <see cref="ViewModelBase"/>.</returns>
public bool Match(object? data) {
return data is ViewModelBase;
}
}
@@ -0,0 +1,11 @@
namespace SongTidy.ViewModels;
/// <summary>
/// Represents the state and commands of the main application window.
/// </summary>
public class MainWindowViewModel : ViewModelBase {
/// <summary>
/// Gets a temporary greeting displayed by the template view.
/// </summary>
public string Greeting { get; } = "Welcome to Avalonia!";
}
+10
View File
@@ -0,0 +1,10 @@
using ReactiveUI;
namespace SongTidy.ViewModels;
/// <summary>
/// Serves as the common base class for all view models in the application,
/// providing ReactiveUI change notification support.
/// </summary>
public class ViewModelBase : ReactiveObject {
}
+20
View File
@@ -0,0 +1,20 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:SongTidy.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SongTidy.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="SongTidy">
<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:MainWindowViewModel/>
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Window>
+9
View File
@@ -0,0 +1,9 @@
using Avalonia.Controls;
namespace SongTidy.Views;
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="SongTidy.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>