Files
Jan Ivar Z. Carlsen 090a405cc0 feat(stacks): add 5 .NET desktop stacks — WPF, WinUI 3, UWP, Avalonia, Uno Platform
Adds WPF, WinUI 3, UWP, Avalonia, and Uno Platform stacks (17 -> 22 total),
each with its own guidelines CSV, registered in core.py and synced to cli/assets.

- New stack CSVs in src/ and cli/assets/data/stacks/
- search.py / core.py registry updated
- smoke-stacks.sh: EXPECTED_STACK_COUNT 21 -> 22 with a smoke-test workflow
- platform template descriptions and README bumped to 22 technology stacks
2026-06-25 21:51:59 +02:00

22 KiB

1NoCategoryGuidelineDescriptionDoDon'tCode GoodCode BadSeverityDocs URL
22XAMLSet x:Class on root elementConnects XAML to its code-behind partial classx:Class on Window UserControl and PageMissing x:Class or mismatched namespace<Window x:Class="MyApp.MainWindow"><Window> without x:ClassHighhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/xaml/
33XAMLUse x:Name sparinglyOnly name elements accessed from code-behindx:Name when code-behind reference is neededNaming every element<TextBox x:Name="SearchBox"/>x:Name on every controlLowhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/xaml/
44XAMLPrefer attached properties for layoutGrid.Row Grid.Column DockPanel.Dock etcAttached properties for panel positioningMargin hacks for alignment<Button Grid.Row="1" Grid.Column="2"/><Button Margin="200,100,0,0"/>Mediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/properties/attached-properties-overview
55XAMLUse routed events for tree-wide handlingEvents bubble up or tunnel down the element tree letting parents handle child events with one handlerHandler at parent using TypeName.EventName syntax with e.Handled=true when consumedWiring identical handlers on every child when one parent handler suffices<StackPanel Button.Click="OnAnyButtonClick">Click="OnClick" repeated on every Button under a common parentMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/events/routed-events-overview
66Data BindingImplement INotifyPropertyChangedEnable UI updates when properties changeINotifyPropertyChanged on ViewModelsPublic properties without notificationpublic string Name { get => _name; set { if (_name != value) { _name = value; OnPropertyChanged(); } } }public string Name { get; set; } without notificationHighhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-property-change-notification
77Data BindingUse ObservableCollection for listsNotifies UI of add remove and resetObservableCollection<T> for bound collectionsList<T> or Array for bound ItemsSourcesObservableCollection<Item> Items { get; } = new();List<Item> Items { get; set; } = new();Highhttps://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1
88Data BindingSet DataContext at the right levelEnables binding for the visual subtreeDataContext on Window or root containerDataContext on every child control<Window DataContext="{Binding Source={StaticResource VM}}">Setting DataContext on each TextBlock individuallyMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/
99Data BindingPrefer Binding over code-behind assignmentsDeclarative binding keeps UI and logic separate{Binding Path=Name} in XAMLtextBlock.Text = viewModel.Name in code-behind<TextBlock Text="{Binding Name}"/>Loaded event handler that sets every propertyMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/data-binding-overview
1010Data BindingUse UpdateSourceTrigger appropriatelyControls when source updatesPropertyChanged for instant feedbackDefault LostFocus when search-as-you-type is neededText="{Binding Query, UpdateSourceTrigger=PropertyChanged}"Text="{Binding Query}" when search-as-you-type neededMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-control-when-the-textbox-text-updates-the-source
1111Data BindingUse IValueConverter for display transformsConvert data for presentation without changing the modelIValueConverter for bool-to-visibility etcVisibility properties on ViewModel<TextBlock Visibility="{Binding IsActive, Converter={StaticResource BoolToVis}}"/>public Visibility IsActiveVisibility => IsActive ? Visibility.Visible : Visibility.Collapsed;Mediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-convert-bound-data
1212Data BindingUse INotifyDataErrorInfo for validationSurface validation errors to the binding system instead of ad-hoc error UIObservableValidator with DataAnnotations attributesThrowing in setters or maintaining separate error propertiespublic partial class FormVm : ObservableValidator { [ObservableProperty][NotifyDataErrorInfo][Required] private string _email; }if (string.IsNullOrEmpty(Email)) ErrorMessage = "Required";Mediumhttps://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observablevalidator
1313LayoutUse Grid for complex layoutsRows and columns with proportional or fixed sizingGrid with RowDefinitions and ColumnDefinitionsCanvas with absolute positions for forms<Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions></Grid><Canvas><TextBox Canvas.Left="50" Canvas.Top="80"/></Canvas>Mediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/grid
1414LayoutUse StackPanel for linear contentSimple vertical or horizontal stackingStackPanel for toolbars and simple listsGrid with single column for linear content<StackPanel Orientation="Horizontal"><Button/><Button/></StackPanel><Grid><Grid.RowDefinitions>..12 Auto rows..</Grid.RowDefinitions></Grid>Mediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/stackpanel
1515LayoutUse DockPanel for docked regionsDock children to edges with last child fillingDockPanel for shell layouts (menu top sidebar left)Nested StackPanels to simulate docking<DockPanel><Menu DockPanel.Dock="Top"/><TreeView DockPanel.Dock="Left"/><Frame/></DockPanel>Nested StackPanels with fixed widths for shellMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/dockpanel
1616LayoutAvoid hardcoded sizesUse Auto Star and MinWidth/MaxWidthProportional sizing with * and AutoFixed pixel widths on resizable content<ColumnDefinition Width="2*"/><ColumnDefinition Width="350"/> on main contentMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/grid
1717LayoutUse ScrollViewer for overflowWrap content that may exceed available spaceScrollViewer around long forms or listsClipping content without scroll<ScrollViewer><StackPanel>...long content...</StackPanel></ScrollViewer><StackPanel> that clips off-screen itemsMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/scrollviewer
1818StylingUse Resource DictionariesCentralize colors brushes and stylesResourceDictionary in App.xaml for theme valuesInline colors and font sizes on every element<SolidColorBrush x:Key="PrimaryBrush" Color="#0078D4"/><Button Background="#0078D4"/> repeated everywhereMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/systems/xaml-resources-overview
1919StylingUse pack URIs for embedded resourcesReference embedded images fonts and resource dictionaries via the pack schemepack://application:,,, syntax for cross-assembly assetsFile-system paths for resources compiled into the assembly<Image Source="pack://application:,,,/MyApp;component/Resources/icon.png"/><Image Source="C:\Resources\icon.png"/>Mediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/app-development/pack-uris-in-wpf
2020StylingUse implicit stylesApply a Style to all instances of a TargetTypeStyle with TargetType and no x:Key for defaultsManually styling every Button instance<Style TargetType="Button"><Setter Property="Padding" Value="12,6"/></Style>Padding="12,6" on every ButtonMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/styles-templates-overview
2121StylingUse explicit styles with x:Key and BasedOnNamed variant styles that inherit from a base via BasedOnx:Key styles that BasedOn an implicit or named styleDuplicating setters across variants<Style x:Key="PrimaryButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">Copy-pasting 10 Setters into a second StyleMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/styles-templates-overview
2222StylingPrefer StaticResource over DynamicResourceStaticResource is resolved once and fasterStaticResource for values that do not change at runtimeDynamicResource for static theme valuesBackground="{StaticResource PrimaryBrush}"Background="{DynamicResource PrimaryBrush}" when theme never changesMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/systems/xaml-resources-overview
2323StylingUse ControlTemplate for full controlOverride default rendering of a controlControlTemplate when built-in styles are insufficientNesting extra panels to hide the default template<ControlTemplate TargetType="Button"><Border><ContentPresenter/></Border></ControlTemplate>Wrapping Button in Border to fake a custom lookMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/how-to-create-apply-template
2424StylingUse DataTemplate for data presentationDefine how data objects render in ItemsControlsDataTemplate for ListBox ComboBox and ItemsControl itemsToString overrides for display<DataTemplate DataType="{x:Type local:Person}"><TextBlock Text="{Binding FullName}"/></DataTemplate>Relying on ToString() in ListBoxMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/data-templating-overview
2525StylingUse Fluent theme on .NET 9+ThemeMode applies the Windows 11 Fluent style; values are Light Dark System and None (default Aero2)ThemeMode on Application or WindowLegacy Aero2 styling for new Windows 11 apps<Application ThemeMode="System"><Application> with default Aero2 lookMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/whats-new/net90
2626CommandsUse ICommand for user actionsDecouple UI actions from logicICommand implementations (RelayCommand DelegateCommand)Click event handlers in code-behind<Button Command="{Binding SaveCommand}"/><Button Click="OnSaveClick"/> with logic in code-behindHighhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/commanding-overview
2727CommandsUse CanExecute for enable/disableAutomatically disable controls when action unavailable; call NotifyCanExecuteChanged when state changesCanExecute returning false to disable buttonsIsEnabled binding to a separate boolnew RelayCommand(Save, () => !IsBusy)<Button IsEnabled="{Binding IsNotBusy}"/>Mediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/commanding-overview
2828CommandsUse RelayCommand or DelegateCommandAvoid implementing ICommand from scratch every timeRelayCommand (CommunityToolkit.Mvvm) or DelegateCommand (Prism)New ICommand class per command[RelayCommand] private void Save() { }class SaveCommand : ICommand { ... } for each actionMediumhttps://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/relaycommand
2929CommandsUse AsyncRelayCommand for async operationsTracks IsRunning and disables the command while it executes preventing re-entryAsyncRelayCommand or [RelayCommand] on async Task methodasync void event handlers in code-behind[RelayCommand] private async Task SaveAsync() { await _service.SaveAsync(); }private async void OnSaveClick(object s, RoutedEventArgs e) { await _service.SaveAsync(); }Mediumhttps://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/asyncrelaycommand
3030CommandsUse CommandParameter for contextPass data from the UI element to the command handlerCommandParameter for item-specific actionsRelying on SelectedItem in every command<Button Command="{Binding DeleteCommand}" CommandParameter="{Binding}"/>Command handler accessing SelectedItem directlyLowhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/commanding-overview
3131CommandsUse InputBindings for keyboard shortcutsBind keyboard gestures to commands without manual key handlingKeyBinding inside Window.InputBindingsCustom key handling in PreviewKeyDown<Window.InputBindings><KeyBinding Key="S" Modifiers="Ctrl" Command="{Binding SaveCommand}"/></Window.InputBindings>PreviewKeyDown handler checking for Ctrl+SMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/commanding-overview
3232PerformanceUse VirtualizingStackPanel for large listsOnly creates UI elements for visible items. ListBox/ListView virtualize by default; TreeView requires opt-inVirtualizingStackPanel.IsVirtualizing=True (set on TreeView; default for ListBox)Disabling virtualization on long lists<TreeView VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"/><ListBox ScrollViewer.CanContentScroll="False"/>Highhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/optimizing-performance-controls
3333PerformanceFreeze Freezable objectsFrozen brushes and geometries skip change trackingFreeze brushes and pens that do not changeMutable brushes used as static resourcesvar brush = new SolidColorBrush(Colors.Blue); brush.Freeze();new SolidColorBrush() without Freeze in resourcesMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/freezable-objects-overview
3434PerformanceUse DependencyProperty for custom-control binding targetsBinding target properties on custom controls must be DependencyProperties (sources can be plain CLR properties with INPC)Define DependencyProperty for properties bound TO on a custom controlPlain CLR properties as binding targets on custom controlspublic static readonly DependencyProperty NameProperty = ...public string Name { get; set; } as binding target on custom controlMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/properties/dependency-properties-overview
3535PerformanceUse async for long operationsKeep UI thread responsiveasync/await with Task.Run for CPU workSynchronous operations that freeze the UIawait Task.Run(() => HeavyComputation());HeavyComputation() on UI threadHighhttps://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/
3636PerformanceProfile with PerfView and Visual StudioMeasure before optimizingVisual Studio diagnostic tools and PerfViewGuessing at performance bottlenecksPerformance Profiler in Visual Studio (Alt+F2)Optimize without profilingMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/optimizing-wpf-application-performance
3737ThreadingUse Dispatcher for UI updatesUI elements can only be accessed from the UI threadDispatcher.Invoke or BeginInvoke from background threadsAccessing UI elements from background threadsApplication.Current.Dispatcher.Invoke(() => Status = "Done");textBlock.Text = "Done" from Task.RunHighhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/threading-model
3838ThreadingPrefer async/await over DispatcherModern async code returns to UI context automaticallyasync/await which resumes on captured SynchronizationContextManual Dispatcher.BeginInvoke for every callbackvar data = await LoadDataAsync(); Items = data;Dispatcher.BeginInvoke(() => Items = result) in callbackMediumhttps://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/
3939ThreadingUse Task.Run for CPU-bound workOffload intensive work from UI threadTask.Run for compute-bound workLong-running computations on UI threadvar result = await Task.Run(() => Compute());var result = Compute(); on UI threadHighhttps://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming
4040ThreadingReport progress from background tasksUpdate UI with progress during long operationsIProgress<T> with Task.RunPolling a shared variable for progressvar progress = new Progress<int>(p => ProgressBar.Value = p); await Task.Run(() => Work(progress));while (!done) { Thread.Sleep(100); check shared int; }Mediumhttps://learn.microsoft.com/en-us/dotnet/api/system.progress-1
4141ThreadingHandle DispatcherUnhandledExceptionCatch unhandled UI-thread exceptions to log them and prevent the default WPF crash dialogSubscribe in App.xaml or App.OnStartup and set e.Handled=true after loggingLetting WPF show its default crash dialog and silently shut down<Application DispatcherUnhandledException="App_OnUnhandledException">No global handler so any unhandled exception crashes the appHighhttps://learn.microsoft.com/en-us/dotnet/api/system.windows.application.dispatcherunhandledexception
4242AccessibilitySet AutomationPropertiesEnable screen reader supportAutomationProperties.Name on interactive controlsControls without automation names<Button AutomationProperties.Name="Save document"/><Button><Image Source="save.png"/></Button> without nameHighhttps://learn.microsoft.com/en-us/dotnet/api/system.windows.automation.automationproperties
4343AccessibilitySupport keyboard navigationAll functionality reachable via keyboardTab order and KeyboardNavigation propertiesMouse-only interactions<StackPanel KeyboardNavigation.TabNavigation="Cycle">Click handlers with no keyboard equivalentHighhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/focus-overview
4444AccessibilitySupport high contrast themesRespect Windows high contrast settingsSystemColors and SystemFonts resourcesHardcoded colors that disappear in high contrastForeground="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"Foreground="#333333" everywhereMediumhttps://learn.microsoft.com/en-us/dotnet/framework/ui-automation/accessibility-best-practices
4545AccessibilityUse appropriate control typesSemantic controls convey role to assistive techButton for actions CheckBox for togglesStyled TextBlock with click handler as fake button<Button Content="Submit"/><TextBlock MouseDown="OnSubmitClick" Text="Submit"/>Highhttps://learn.microsoft.com/en-us/dotnet/framework/ui-automation/accessibility-best-practices
4646AccessibilitySupport DPI scalingEnsure UI is crisp at all display scale factorsDevice-independent units and vector graphicsPixel-based bitmaps that blur at high DPI<Path Data="M 10,10 L 20,20"/> or DrawingImage<Image Source="icon_32x32.png"/> at 200% scalingMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/graphics
4747AccessibilityDeclare PerMonitorV2 DPI awarenessWPF defaults to System-DPI-aware unless you opt into PerMonitorV2 via app.manifestapp.manifest with dpiAwareness PerMonitorV2Default System DPI awareness for Windows 10/11 apps<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>No app.manifest leaving app at System DPIMediumhttps://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
4848ArchitectureUse MVVM patternSeparate View ViewModel and Model concernsMVVM with data binding and commandsLogic in code-behindViewModel with INotifyPropertyChanged and ICommandMainWindow.xaml.cs with all business logicHighhttps://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/
4949ArchitectureOverride App.OnStartup for app initializationWire DI build the host resolve MainWindow and parse command-line args in OnStartupOverride OnStartup when DI or argument parsing is neededRelying on StartupUri when MainWindow needs constructor injectionprotected override void OnStartup(StartupEventArgs e) { _host.Start(); _host.Services.GetRequiredService<MainWindow>().Show(); }<Application StartupUri="MainWindow.xaml"/> when MainWindow has constructor dependenciesMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/app-development/application-management-overview
5050ArchitectureUse dependency injectionWire Microsoft.Extensions.Hosting Generic Host in App.OnStartup and resolve ViewModels from the containerGeneric Host with Microsoft.Extensions.DependencyInjectionnew Service() in ViewModel constructorsservices.AddTransient<MainViewModel>();new MainViewModel(new DataService()) in App.xaml.csMediumhttps://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview
5151ArchitectureUse CommunityToolkit.MvvmSource generators reduce MVVM boilerplate[ObservableProperty] and [RelayCommand] attributesHand-written INotifyPropertyChanged for every property[ObservableProperty] private string _name;private string _name; public string Name { get ... set ... OnPropertyChanged ... }Mediumhttps://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/
5252ArchitectureKeep code-behind minimalCode-behind should only contain view-specific logicView logic like focus management and animations in code-behindBusiness logic and data access in code-behindLoaded handler that sets initial focusLoaded handler that calls database and populates gridMediumhttps://learn.microsoft.com/en-us/dotnet/desktop/wpf/xaml/
5353ArchitectureUse messaging for loose couplingCommunicate between ViewModels without referencesWeakReferenceMessenger from CommunityToolkit.MvvmDirect ViewModel-to-ViewModel referencesWeakReferenceMessenger.Default.Send(new ItemSavedMessage(item));MainViewModel.Instance.RefreshItems() from DetailViewModelMediumhttps://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger
5454TestingUnit test ViewModelsTest business logic independent of UIxUnit or NUnit tests on ViewModel methods and propertiesManual testing through the UI only[Fact] public void Save_WhenValid_SetsIsBusy() { vm.Save(); Assert.True(vm.IsBusy); }Clicking buttons in the running app to verifyMediumhttps://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/
5555TestingMock services in testsIsolate ViewModel from external dependenciesMoq or NSubstitute for service interfacesReal database calls in unit testsvar mock = new Mock<IDataService>(); var vm = new MainViewModel(mock.Object);new MainViewModel(new SqlDataService()) in testsMediumhttps://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices
5656TestingUse UI Automation for integration testsAutomated UI testing with Microsoft UI AutomationFlaUI or Appium 2 (appium-windows-driver) for end-to-end testsManual regression testing onlyAutomationElement.FindFirst(TreeScope.Children, condition)Manual click-through testingMediumhttps://learn.microsoft.com/en-us/dotnet/framework/ui-automation/ui-automation-overview