Loading...
Loading...
Builds WPF on .NET 8+. Host builder, MVVM Toolkit, Fluent theme, performance, modern C# patterns.
npx skill4agent add novotnyllc/dotnet-artisan dotnet-wpf-modernnet8.0-windows<!-- MyWpfApp.csproj (SDK-style) -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" />
</ItemGroup>
</Project>.csprojpackages.configAssemblyInfo.csPackageReferencepackages.configApp.configdotnet publishServiceLocator// App.xaml.cs
public partial class App : Application
{
private readonly IHost _host;
public App()
{
_host = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: true);
})
.ConfigureServices((context, services) =>
{
// Services
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IProductService, ProductService>();
services.AddSingleton<ISettingsService, SettingsService>();
// HTTP client
services.AddHttpClient("api", client =>
{
client.BaseAddress = new Uri(
context.Configuration["ApiBaseUrl"] ?? "https://api.example.com");
});
// ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<ProductListViewModel>();
services.AddTransient<SettingsViewModel>();
// Windows and pages
services.AddSingleton<MainWindow>();
})
.Build();
}
protected override async void OnStartup(StartupEventArgs e)
{
await _host.StartAsync();
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
mainWindow.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
await _host.StopAsync();
_host.Dispose();
base.OnExit(e);
}
public static T GetService<T>() where T : class
{
var app = (App)Application.Current;
return app._host.Services.GetRequiredService<T>();
}
}// ViewModels/ProductListViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class ProductListViewModel : ObservableObject
{
private readonly IProductService _productService;
public ProductListViewModel(IProductService productService)
{
_productService = productService;
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SearchCommand))]
private string _searchTerm = "";
[ObservableProperty]
private ObservableCollection<Product> _products = [];
[ObservableProperty]
private bool _isLoading;
[RelayCommand]
private async Task LoadProductsAsync(CancellationToken ct)
{
IsLoading = true;
try
{
var items = await _productService.GetProductsAsync(ct);
Products = new ObservableCollection<Product>(items);
}
finally
{
IsLoading = false;
}
}
[RelayCommand(CanExecute = nameof(CanSearch))]
private async Task SearchAsync(CancellationToken ct)
{
var results = await _productService.SearchAsync(SearchTerm, ct);
Products = new ObservableCollection<Product>(results);
}
private bool CanSearch() => !string.IsNullOrWhiteSpace(SearchTerm);
}<Window x:Class="MyApp.Views.ProductListWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:MyApp.ViewModels"
d:DataContext="{d:DesignInstance vm:ProductListViewModel}">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="16">
<TextBox Text="{Binding SearchTerm, UpdateSourceTrigger=PropertyChanged}"
Width="300" Margin="0,0,8,0" />
<Button Content="Search" Command="{Binding SearchCommand}" />
</StackPanel>
<ListBox ItemsSource="{Binding Products}" Margin="16">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="4">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Margin="0,0,12,0" />
<TextBlock Text="{Binding Price, StringFormat='{}{0:C}'}" Foreground="Gray" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Window>[ObservableProperty]INotifyPropertyChanged[RelayCommand]ICommandCanExecute[NotifyPropertyChangedFor]PropertyChanged[NotifyCanExecuteChangedFor]CanExecute.NET 8+<!-- Enable trimming for smaller deployment -->
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<!-- WPF apps need partial trim mode due to reflection usage -->
</PropertyGroup>TrimMode=partialfullx:Type// Immutable data models
public record Product(string Name, decimal Price, string Category);
// Records with computed properties
public record ProductViewModel(Product Product)
{
public string DisplayPrice => Product.Price.ToString("C");
public string Summary => $"{Product.Name} - {DisplayPrice}";
}// Service with primary constructor (C# 12)
public class ProductService(HttpClient httpClient, ILogger<ProductService> logger)
: IProductService
{
public async Task<IReadOnlyList<Product>> GetProductsAsync(CancellationToken ct)
{
logger.LogInformation("Fetching products");
var response = await httpClient.GetAsync("/products", ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<List<Product>>(ct) ?? [];
}
}// Modern converter using pattern matching (C# 11+)
public class StatusToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
OrderStatus.Pending => Brushes.Orange,
OrderStatus.Processing => Brushes.Blue,
OrderStatus.Shipped => Brushes.Green,
OrderStatus.Cancelled => Brushes.Red,
_ => Brushes.Gray
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}// C# 12 collection expressions
[ObservableProperty]
private ObservableCollection<Product> _products = [];
// In methods
List<string> categories = ["Electronics", "Clothing", "Books"];<!-- App.xaml: enable Fluent theme (.NET 9+) via ThemeMode property -->
<Application x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ThemeMode="System"
StartupUri="MainWindow.xaml">
</Application>// App.xaml.cs: set theme programmatically (.NET 9+)
Application.Current.ThemeMode = ThemeMode.System; // or ThemeMode.Light / ThemeMode.Dark
// Per-window theming is also supported
mainWindow.ThemeMode = ThemeMode.Dark;NoneLightDarkSystem// Detect system theme
public static bool IsDarkTheme()
{
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
var value = key?.GetValue("AppsUseLightTheme");
return value is int i && i == 0;
}
// Listen for theme changes
SystemEvents.UserPreferenceChanged += (sender, args) =>
{
if (args.Category == UserPreferenceCategory.General)
{
// Theme may have changed; re-read and apply
ApplyTheme(IsDarkTheme() ? AppTheme.Dark : AppTheme.Light);
}
};<!-- Themes/DarkTheme.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="WindowBackground" Color="#1E1E1E" />
<SolidColorBrush x:Key="TextForeground" Color="#FFFFFF" />
<SolidColorBrush x:Key="AccentBrush" Color="#0078D7" />
</ResourceDictionary>// Switch themes at runtime
public void ApplyTheme(AppTheme theme)
{
var themeUri = theme switch
{
AppTheme.Dark => new Uri("Themes/DarkTheme.xaml", UriKind.Relative),
AppTheme.Light => new Uri("Themes/LightTheme.xaml", UriKind.Relative),
_ => throw new ArgumentOutOfRangeException(nameof(theme))
};
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(
new ResourceDictionary { Source = themeUri });
}App.configpackages.configPackageReferenceServiceLocatorAssemblyInfo.cs<PropertyGroup>BitmapEffectEffectShaderEffectDrawingContext.PushEffectVisualBrush{Binding}INotifyPropertyChanged[ObservableProperty]Dispatcher.InvokeawaitConfigureAwait(true)Dispatcher.InvokeBeginInvokeTrimMode=fullTrimMode=partial_host.StartAsync()OnStartup_host.StopAsync()OnExitIHostedService{DynamicResource SystemAccentColor}net8.0-windows