La interfaz está localizada; las descripciones y documentos de las Skills se traducen automáticamente con IA a su idioma. El código, los comandos, rutas y términos técnicos se mantienen en el original, y prevalece el documento del autor.
Use when the user asks about WPF (.NET Framework or .NET Core/5+/6+), XAML desktop apps, MVVM with WPF, XAML data binding, or WPF custom controls. Do NOT use for: WinForms (desktop-winforms), WinUI 3 (desktop-winui3), or UWP (desktop-uwp).
Independent GitHub snapshot · Automated classification · Original documentation governs
name: desktop-wpf
description: >
Use when the user asks about WPF (.NET Framework or .NET Core/5+/6+), XAML desktop apps, MVVM with WPF, XAML data binding, or WPF custom controls. Do NOT use for: WinForms (desktop-winforms), WinUI 3 (desktop-winui3), or UWP (desktop-uwp).
version: "2.0.0"
author: "j4flmao"
license: "MIT"
compatibility:
claude-code: true
cursor: true
codex: true
windsurf: true
tags: [desktop, wpf, dotnet, xaml]
WPF
Purpose
Build Windows desktop applications using Windows Presentation Foundation (WPF) — .NET's XAML-based UI framework with powerful data binding, templating, styling, and hardware-accelerated rendering via DirectX. WPF excels at complex LOB applications with rich UI requirements.
What is the WPF app complexity?
├── Simple CRUD (single form, few fields)
│ → Window with data-bound controls
│ → RelCommand, INotifyPropertyChanged in code-behind
├── Standard LOB (master-detail, search, reports)
│ → MVVM with dedicated ViewModels
│ → Navigation via ContentControl + DataTemplate
│ → Entity Framework for data access
├── Complex dashboard (real-time, charts, drag-drop)
│ → MVVM with messaging (Mediator/Messenger)
│ → VirtualizingCollection for large data
│ → 3rd party charts or custom DrawingVisual rendering
└── Enterprise (modular, plugins, multiple teams)
→ Prism/MAUI for modularity, region navigation
→ MEF/DI for plugin loading
→ Separate assemblies per module
MVVM Core Pattern
View (XAML) — Data Binding, Commands, Triggers
↑ ↓
ViewModel — Observable Properties, ICommand, INotifyDataErrorInfo
↑
Model — Business Logic, Data Access, Validation Rules
Workflow
Step 1: Set Up WPF Project with MVVM
// App.xaml
<Application x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:MyApp"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles.xaml" />
<ResourceDictionary Source="Resources/Converters.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
// App.xaml.cs - Configure DI
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
var services = new ServiceCollection();
services.AddSingleton<IDataService, DataService>();
services.AddTransient<MainViewModel>();
services.AddTransient<MainWindow>();
var provider = services.BuildServiceProvider();
var window = provider.GetRequiredService<MainWindow>();
window.DataContext = provider.GetRequiredService<MainViewModel>();
window.Show();
}
}
Step 2: Create RelayCommand and BaseViewModel
// Infrastructure/RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object?> _execute;
private readonly Func<object?, bool>? _canExecute;
public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object? parameter) => _execute(parameter);
}
// Infrastructure/BaseViewModel.cs
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
Step 3: Build ViewModel with Commands
// ViewModels/MainViewModel.cs
public class MainViewModel : BaseViewModel
{
private readonly IDataService _dataService;
private string _searchText = string.Empty;
private Item? _selectedItem;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
LoadItemsCommand = new RelayCommand(async _ => await LoadItemsAsync());
DeleteItemCommand = new RelayCommand(async item => await DeleteItemAsync(item),
item => item is Item);
SaveCommand = new RelayCommand(async _ => await SaveAsync(), _ => CanSave);
}
public ObservableCollection<Item> Items { get; } = new();
public string SearchText
{
get => _searchText;
set
{
SetProperty(ref _searchText, value);
_ = FilterItemsAsync(); // Fire-and-forget with error handling
}
}
public Item? SelectedItem
{
get => _selectedItem;
set
{
SetProperty(ref _selectedItem, value);
OnPropertyChanged(nameof(CanSave));
}
}
public bool CanSave => SelectedItem != null;
public ICommand LoadItemsCommand { get; }
public ICommand DeleteItemCommand { get; }
public ICommand SaveCommand { get; }
private async Task LoadItemsAsync()
{
IsBusy = true;
try
{
var items = await _dataService.GetItemsAsync();
Items.Clear();
foreach (var item in items) Items.Add(item);
}
catch (Exception ex)
{
ErrorMessage = quot;Failed to load: {ex.Message}";
}
finally
{
IsBusy = false;
}
}
private async Task DeleteItemAsync(object? param)
{
if (param is Item item)
{
Items.Remove(item);
await _dataService.DeleteItemAsync(item.Id);
}
}
private async Task SaveAsync()
{
// Save logic
}
}
// Controls/WatermarkTextBox.cs - Custom control with additional property
public class WatermarkTextBox : TextBox
{
static WatermarkTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(WatermarkTextBox),
new FrameworkPropertyMetadata(typeof(WatermarkTextBox)));
}
public string Watermark
{
get => (string)GetValue(WatermarkProperty);
set => SetValue(WatermarkProperty, value);
}
public static readonly DependencyProperty WatermarkProperty =
DependencyProperty.Register(nameof(Watermark), typeof(string),
typeof(WatermarkTextBox), new PropertyMetadata(string.Empty));
}
Common Pitfalls
Pitfall
Description
Prevention
UI thread blocking
Heavy computation freezes app
async/await, BackgroundWorker, Task.Run with dispatcher
Memory leaks from event handlers
Subscribing without unsubscribing
WeakEvent pattern, -= in Dispose
Data Virtualization missing
Loading 10K+ rows freezes UI
VirtualizingCollection, async loading, paging
INotifyPropertyChanged overhead
Every property change triggers binding
Batch updates, delay signals, use SetProperty
No async commands
Button blocks while operation runs
Use AsyncRelayCommand or async void with error handling
Deep XAML nesting
Performance degradation from complexity
UserControl extraction, virtualization
Missing fallback values
Binding errors show nothing
TargetNullValue, FallbackValue on bindings
Ignoring Freezable objects
Shared resources modified at runtime
Freeze resources, use Frozen flag
No dispatcher for background updates
Cross-thread UI access exception
Check Dispatcher.CheckAccess, use InvokeAsync
Large resource dictionaries
Slow startup loading all resources
Split by theme/feature, merge lazily
Best Practices
Practice
Rationale
MVVM strictly enforced
Testability, separation of concerns
CommunityToolkit.Mvvm for source generators
Less boilerplate, compile-time verified
async/await for all I/O
Non-blocking UI, Task-based commands
VirtualizingPanel for lists
Efficient rendering of large collections
StaticResource over DynamicResource
Better performance, compile-time check
DataTemplates for type-based rendering
Automatic view resolution for ViewModel types
Attached behaviors for complex interactions
Reusable, declarative interaction patterns
Prism for modular enterprise apps
Region navigation, modularity, DI integration
WPF Performance Suite for profiling
Identify rendering, layout, binding bottlenecks
.NET 6+ for new WPF projects
.NET 6+, not .NET Framework
Architecture Patterns
Dialog Service (MVVM-Friendly)
public interface IDialogService
{
bool? ShowDialog(string title, object viewModel);
}
public class DialogService : IDialogService
{
public bool? ShowDialog(string title, object viewModel)
{
var window = new Window
{
Title = title,
Content = new ContentControl { Content = viewModel },
DataContext = viewModel,
Width = 400, Height = 300,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = Application.Current.MainWindow
};
return window.ShowDialog();
}
}
Mediator for ViewModel Communication
// WeakReference-based messaging between ViewModels
public class Mediator
{
private readonly Dictionary<Type, List<WeakReference>> _subscribers = new();
public void Subscribe<TMessage>(object subscriber, Action<TMessage> handler) { ... }
public void Unsubscribe<TMessage>(object subscriber) { ... }
public void Send<TMessage>(TMessage message) { ... }
}