widget-settings
Widget settings persistence and UI patterns for the .NET 8 WPF widget host app. Use when adding settings to widgets, persisting configuration, or building settings dialogs.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework and standardized patterns for managing widget settings within .NET 8 WPF applications. It streamlines the process of implementing settings persistence, UI dialogs, and state management by providing clear architectural blueprints, MVVM-compliant code patterns, and best practices for JSON serialization to ensure consistent and reliable application state.
Use Cases
- Standardizing configuration persistence across multiple widgets in a desktop host application using JSON serialization.
- Implementing robust settings dialogs with explicit save/cancel logic and data validation using the MVVM pattern.
- Architecting a decoupled communication flow between widget ViewModels and persistence services to improve maintainability.
- Enforcing development best practices to avoid common anti-patterns like mutable settings objects or logic in code-behind.
- Migrating legacy configuration systems to a modern, immutable value object pattern in .NET 8.
| name | widget-settings |
|---|---|
| description | Widget settings persistence and UI patterns for the .NET 8 WPF widget host app. Use when adding settings to widgets, persisting configuration, or building settings dialogs. |
Widget Settings
Overview
Standardize how widgets store, retrieve, and expose their configuration settings.
Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Settings Dialog │────▶│ WidgetViewModel │────▶│ SettingsService │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌───────────────┐
│ WidgetInstance│
│ (SettingsJson)│
└───────────────┘
Implementation Pattern
1. Define Settings Value Object (Domain)
// 3SC.Domain/ValueObjects/ClockWidgetSettings.cs
public sealed class ClockWidgetSettings
{
public string TimeZoneId { get; init; } = TimeZoneInfo.Local.Id;
public bool Use24HourFormat { get; init; } = true;
public bool ShowSeconds { get; init; } = true;
public bool ShowTimeZoneLabel { get; init; } = false;
public static ClockWidgetSettings Default() => new();
}
2. Use JsonHelper for Serialization
// Save settings
var json = JsonHelper.Serialize(settings);
instance.SettingsJson = json;
// Load settings
var settings = JsonHelper.Deserialize<ClockWidgetSettings>(instance.SettingsJson)
?? ClockWidgetSettings.Default();
3. ViewModel Settings Property
public partial class ClockWidgetViewModel : ObservableObject
{
[ObservableProperty]
private ClockWidgetSettings _settings;
public void ApplySettings(ClockWidgetSettings newSettings)
{
Settings = newSettings;
// Reinitialize any services that depend on settings
InitializeClockService(newSettings);
}
}
4. Settings Dialog ViewModel
public partial class ClockSettingsViewModel : ObservableValidator
{
private readonly ClockWidgetSettings _original;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasChanges))]
private string _timeZoneId;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasChanges))]
private bool _use24HourFormat;
public bool HasChanges =>
TimeZoneId != _original.TimeZoneId ||
Use24HourFormat != _original.Use24HourFormat;
public ClockWidgetSettings ToSettings() => new()
{
TimeZoneId = TimeZoneId,
Use24HourFormat = Use24HourFormat,
// ...
};
}
Widget Settings Service
// 3SC/Services/WidgetSettingsService.cs
public class WidgetSettingsService
{
public T? GetSettings<T>(Guid widgetInstanceId) where T : class
{
// Load from DB and deserialize
}
public async Task SaveSettingsAsync<T>(Guid widgetInstanceId, T settings)
{
// Serialize and save to DB
}
}
Settings UI Pattern
Context Menu Entry
<MenuItem Header="Settings"
Click="OpenSettings_Click">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"/>
</MenuItem.Icon>
</MenuItem>
Settings Dialog Structure
<Window Title="Clock Settings" Width="400" Height="300">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Settings Form -->
<StackPanel Grid.Row="0">
<CheckBox Content="Use 24-hour format"
IsChecked="{Binding Use24HourFormat}"/>
<!-- More settings... -->
</StackPanel>
<!-- Actions -->
<StackPanel Grid.Row="1" Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Cancel" Command="{Binding CancelCommand}"/>
<Button Content="Save" Command="{Binding SaveCommand}"
IsEnabled="{Binding HasChanges}"/>
</StackPanel>
</Grid>
</Window>
Definition of Done (DoD)
- Settings defined as immutable value object in Domain
- Settings serialized as JSON in WidgetInstance.SettingsJson
- Default settings provided when none exist
- Settings dialog uses separate ViewModel
- Changes require explicit Save action
- Cancel discards unsaved changes
- Settings validated before save
- Widget refreshes when settings change
Key Files
| File | Purpose |
|---|---|
3SC.Domain/ValueObjects/*Settings.cs |
Settings value objects |
3SC/Services/WidgetSettingsService.cs |
Load/save orchestration |
3SC/Helpers/JsonHelper.cs |
JSON serialization |
3SC/ViewModels/*SettingsViewModel.cs |
Settings dialog VMs |
Anti-Patterns
- ❌ Storing settings in separate files per widget
- ❌ Mutable settings objects shared across threads
- ❌ Auto-saving on every change (use explicit save)
- ❌ Settings logic in code-behind
- ❌ Missing default values