Build a Config Panel Fast
Getting started: install and bind a Settings object
Install and namespaces
- Add the Toolkit Plus package to your WPF project.
- In XAML, import the namespace:
xmlns:xcad="<http://schemas.xceed.com/wpf/xaml/toolkit>"
Basic binding to Settings in MVVM
Create a Settings POCO and expose it on a SettingsViewModel. Bind PropertyGrid.SelectedObject to your Settings instance—no templates required.
Code: Settings model (baseline)
public class AppSettings
{
public string Environment { get; set; } = "Production";
public int RetryCount { get; set; } = 3;
public bool EnableAuditTrail { get; set; } = true;
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
public string ThemeColor { get; set; } = "#FFAA5500"; // brand accent
public string ReportsFolder { get; set; } = "C:\\\\Reports";
}
Code: ViewModel
public class SettingsViewModel : INotifyPropertyChanged
{
public AppSettings Settings { get; } = new();
public ICommand SaveCommand { get; }
public ICommand LoadCommand { get; }
public SettingsViewModel()
{
SaveCommand = new RelayCommand(_ => Save());
LoadCommand = new RelayCommand(_ => Load());
}
// Implement INotifyPropertyChanged, Save, Load below
}
Code: XAML (PropertyGrid)
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<xcad:PropertyGrid
SelectedObject="{Binding Settings}"
AutoGenerateProperties="True"
NameColumnWidth="240"
HelpVisible="True"
DescriptionVisibility="Visible"
IsCategorized="True" />
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button Content="Load" Command="{Binding LoadCommand}" Margin="0,0,8,0"/>
<Button Content="Save" Command="{Binding SaveCommand}" />
</StackPanel>
</Grid>
Custom editors and attributes that feel “editor-quality”
Use .NET attributes to group, label, and describe settings. When you need richer input (dropdowns, numeric spinners, color pickers, file pickers), swap editors cleanly via EditorAttribute or editor mappings.
Categorize and describe with attributes
Code: Settings model with attributes
using System.ComponentModel;
public class AppSettings
{
[Category("General")]
[DisplayName("Environment")]
[Description("Target environment for API calls.")]
public string Environment { get; set; } = "Production";
[Category("General")]
[DisplayName("Retry Count")]
[Description("Number of retry attempts for transient failures.")]
public int RetryCount { get; set; } = 3;
[Category("Security")]
[DisplayName("Enable Audit Trail")]
[Description("Record configuration changes for compliance.")]
public bool EnableAuditTrail { get; set; } = true;
[Category("Performance")]
[DisplayName("Timeout")]
[Description("Request timeout duration.")]
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
[Category("Branding")]
[DisplayName("Theme Color")]
[Description("Accent color for the application theme.")]
public string ThemeColor { get; set; } = "#FFAA5500";
[Category("Reports")]
[DisplayName("Reports Folder")]
[Description("Directory where generated reports are stored.")]
public string ReportsFolder { get; set; } = "C:\\\\Reports";
}
Swap in dropdowns, numeric spinners, color pickers, and file pickers
Approach A: Type-based editor mapping in XAML (keeps models clean).
Code: Editor mapping in XAML
<xcad:PropertyGrid SelectedObject="{Binding Settings}" AutoGenerateProperties="True" IsCategorized="True">
<xcad:PropertyGrid.EditorDefinitions>
<!-- Environment: ComboBox with fixed options -->
<xcad:EditorTemplateDefinition>
<xcad:EditorTemplateDefinition.TargetProperties>
<xcad:TargetPropertyDefinition PropertyName="Environment"/>
</xcad:EditorTemplateDefinition.TargetProperties>
<xcad:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding DataContext.Environments, RelativeSource={RelativeSource AncestorType=xcad:PropertyGrid}}" />
</DataTemplate>
</xcad:EditorTemplateDefinition.EditingTemplate>
</xcad:EditorTemplateDefinition>
<!-- RetryCount: NumericUpDown -->
<xcad:EditorTemplateDefinition>
<xcad:EditorTemplateDefinition.TargetProperties>
<xcad:TargetPropertyDefinition PropertyName="RetryCount"/>
</xcad:EditorTemplateDefinition.TargetProperties>
<xcad:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<xcad:IntegerUpDown Minimum="0" Maximum="10"
Value="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</xcad:EditorTemplateDefinition.EditingTemplate>
</xcad:EditorTemplateDefinition>
<!-- ThemeColor: ColorPicker -->
<xcad:EditorTemplateDefinition>
<xcad:EditorTemplateDefinition.TargetProperties>
<xcad:TargetPropertyDefinition PropertyName="ThemeColor"/>
</xcad:EditorTemplateDefinition.TargetProperties>
<xcad:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<xcad:ColorPicker
SelectedColor="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</xcad:EditorTemplateDefinition.EditingTemplate>
</xcad:EditorTemplateDefinition>
<!-- ReportsFolder: file picker -->
<xcad:EditorTemplateDefinition>
<xcad:EditorTemplateDefinition.TargetProperties>
<xcad:TargetPropertyDefinition PropertyName="ReportsFolder"/>
</xcad:EditorTemplateDefinition.TargetProperties>
<xcad:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<DockPanel>
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="260" />
<Button Content="Browse..." Margin="8,0,0,0" Command="{Binding DataContext.BrowseFolderCommand, RelativeSource={RelativeSource AncestorType=xcad:PropertyGrid}}" />
</DockPanel>
</DataTemplate>
</xcad:EditorTemplateDefinition.EditingTemplate>
</xcad:EditorTemplateDefinition>
</xcad:PropertyGrid.EditorDefinitions>
</xcad:PropertyGrid>
Code: ViewModel extras for editors
public ObservableCollection<string> Environments { get; } =
new(new[] { "Development", "Staging", "Production" });
public ICommand BrowseFolderCommand => new RelayCommand(_ =>
{
var dlg = new Microsoft.Win32.OpenFileDialog
{
CheckFileExists = false,
ValidateNames = false,
FileName = "Select Folder"
};
if (dlg.ShowDialog() == true)
{
// Get folder from selected path
var folder = System.IO.Path.GetDirectoryName(dlg.FileName);
if (!string.IsNullOrEmpty(folder))
Settings.ReportsFolder = folder;
}
});
Approach B: Attribute-driven editors (when you prefer annotating the model)
Use EditorAttribute to associate specific editors with properties. If you maintain a shared “Settings” assembly, keep it UI-agnostic and prefer XAML mapping (Approach A).
Validation and persistence that don’t fight MVVM
Live validation with INotifyDataErrorInfo
Implement INotifyDataErrorInfo on the ViewModel to surface inline validation (for example minimum timeouts, folder existence). PropertyGrid will display errors without modal dialogs.
Code: INotifyDataErrorInfo pattern
public partial class SettingsViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
private readonly Dictionary<string, List<string>> _errors = new();
public bool HasErrors => _errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
=> propertyName != null && _errors.TryGetValue(propertyName, out var list) ? list : null;
private void Validate()
{
ClearErrors(nameof(Settings.Timeout));
if (Settings.Timeout < TimeSpan.FromSeconds(5))
AddError(nameof(Settings.Timeout), "Timeout must be at least 5 seconds.");
ClearErrors(nameof(Settings.ReportsFolder));
if (string.IsNullOrWhiteSpace(Settings.ReportsFolder) || !Directory.Exists(Settings.ReportsFolder))
AddError(nameof(Settings.ReportsFolder), "Folder must exist.");
ClearErrors(nameof(Settings.RetryCount));
if (Settings.RetryCount is < 0 or > 10)
AddError(nameof(Settings.RetryCount), "RetryCount must be between 0 and 10.");
}
private void AddError(string prop, string error)
{
if (!_errors.TryGetValue(prop, out var list)) _errors[prop] = list = new List<string>();
if (!list.Contains(error)) list.Add(error);
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(prop));
}
private void ClearErrors(string prop)
{
if (_errors.Remove(prop)) ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(prop));
}
}
Wire validation to property changes (for example via setter notifications or a timer/debounce if needed).
Persist settings with System.Text.Json
Code: Save/Load helpers
using System.Text.Json;
private static readonly JsonSerializerOptions JsonOpts = new()
{
WriteIndented = true
};
private string SettingsFilePath =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyApp", "settings.json");
private void Save()
{
Directory.CreateDirectory(Path.GetDirectoryName(SettingsFilePath)!);
var json = JsonSerializer.Serialize(Settings, JsonOpts);
File.WriteAllText(SettingsFilePath, json);
}
private void Load()
{
if (!File.Exists(SettingsFilePath)) return;
var json = File.ReadAllText(SettingsFilePath);
var loaded = JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
// shallow copy to preserve SelectedObject binding
Settings.Environment = loaded.Environment;
Settings.RetryCount = loaded.RetryCount;
Settings.EnableAuditTrail = loaded.EnableAuditTrail;
Settings.Timeout = loaded.Timeout;
Settings.ThemeColor = loaded.ThemeColor;
Settings.ReportsFolder = loaded.ReportsFolder;
OnPropertyChanged(nameof(Settings));
Validate();
}
Call Load() on startup, Save() on user action or exit.
Theming: make it brand-consistent and accessible
Pair WPF PropertyGrid with Pro Themes for WPF so your admin panel inherits consistent focus states, contrast, and typography—especially important in dense settings UIs. It keeps your internal tools visually aligned with your product and cuts the time spent on polishing editor visuals.
- Explore Pro Themes for WPF to apply a cohesive, accessible theme across all controls.
- If your configs affect data behaviors, consider DataGrid for WPF for high-performance reviews and batch edits.
Final result
What you get: an admin/config panel where new properties appear instantly, categorized and labeled, with sensible default editors and inline validation. Add a property to your POCO, restart, and it’s live—no custom dialog work.
GIF-style walkthrough ideas
- Add a new property to AppSettings, rebuild, and watch it appear in PropertyGrid with a default editor.
- Change Environment via dropdown; validation overlays show live messages when values go out of range.
- Click Save; a toast or inline message confirms settings persisted to JSON.
Paste-ready snippet: minimal PropertyGrid page
Code: Minimal page XAML
<Page
xmlns="<http://schemas.microsoft.com/winfx/2006/xaml/presentation>"
xmlns:x="<http://schemas.microsoft.com/winfx/2006/xaml>"
xmlns:xcad="<http://schemas.xceed.com/wpf/xaml/toolkit>">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<xcad:PropertyGrid
SelectedObject="{Binding Settings}"
AutoGenerateProperties="True"
IsCategorized="True"
NameColumnWidth="240"
DescriptionVisibility="Visible"
HelpVisible="True"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button Content="Load" Command="{Binding LoadCommand}" Margin="0,0,8,0"/>
<Button Content="Save" Command="{Binding SaveCommand}"/>
</StackPanel>
</Grid>
</Page>
Internal links and related components
- Pro Themes for WPF: apply consistent, accessible styling across all editors.
- DataGrid for WPF: if configuration affects grid behavior (filters, formats, columns), pair with a fast, virtualized grid for admin views.
- Support: https://xceed.com/support/
Try It for yourself!
- Grab the sample and start customizing: https://xceed.com/trial/
- Questions about specific editors? Contact support: https://xceed.com/support/