DotNetTools.Wpfkit
1.0.2
See the version list below for details.
dotnet add package DotNetTools.Wpfkit --version 1.0.2
NuGet\Install-Package DotNetTools.Wpfkit -Version 1.0.2
<PackageReference Include="DotNetTools.Wpfkit" Version="1.0.2" />
<PackageVersion Include="DotNetTools.Wpfkit" Version="1.0.2" />
<PackageReference Include="DotNetTools.Wpfkit" />
paket add DotNetTools.Wpfkit --version 1.0.2
#r "nuget: DotNetTools.Wpfkit, 1.0.2"
#:package DotNetTools.Wpfkit@1.0.2
#addin nuget:?package=DotNetTools.Wpfkit&version=1.0.2
#tool nuget:?package=DotNetTools.Wpfkit&version=1.0.2
DotNetTools.Wpfkit
A comprehensive WPF toolkit library that provides essential components for building modern Windows desktop applications with the MVVM pattern, command infrastructure, logging capabilities, and configuration management.
📑 Table of Contents
📋 Overview
DotNetTools.Wpfkit is a modern .NET library designed to accelerate WPF application development by providing reusable, production-ready components. Built on .NET 10.0, it embraces modern C# features including nullable reference types, implicit usings, and follows best practices for WPF development.
✨ Features
MVVM Pattern Support
- ObservableObject: Base class implementing
INotifyPropertyChangedwith helper methods - BaseViewModel: Feature-rich view model base class with common UI properties
- ObservableRangeCollection<T>: Enhanced observable collection supporting bulk operations
Command Infrastructure
- CommandBase: Abstract base class implementing
ICommandinterface - RelayCommand: Synchronous command with action and predicate support
- ActionCommand: Flexible command implementation with parameter support
- AsyncCommandBase: Abstract base for asynchronous command operations
- AsyncRelayCommand: Async command with built-in exception handling
Logging Infrastructure
- Serilog Integration: Built-in support for structured logging
- LogManager: Simplified logger creation with context-aware logging
- UserName Enricher: Custom enrichers for enhanced log metadata
Configuration Management
- AppSettingsUpdater: Utility for runtime appsettings.json manipulation
- Connection String Management: Easy database connection string updates
📦 Installation
NuGet Package
dotnet add package DotNetTools.Wpfkit
Or via Package Manager Console in Visual Studio:
Install-Package DotNetTools.Wpfkit
Or add directly to your .csproj:
<PackageReference Include="DotNetTools.Wpfkit" Version="1.0.0" />
Manual Installation
- Clone the repository
- Build the project
- Reference the DLL in your WPF application
git clone https://github.com/omostan/DotNetTools.Wpfkit
cd DotNetTools.Wpfkit
dotnet build
📋 Requirements
- .NET 10.0 or later
- Windows OS (for WPF support)
- Visual Studio 2022 or later (recommended)
Dependencies
- Serilog (v4.3.0+): Structured logging
- Tracetool.DotNet.Api (v14.0.0+): Advanced tracing capabilities
🚀 Usage
MVVM Components
ObservableObject
Base class for implementing property change notifications:
using DotNetTools.Wpfkit.MvvM;
public class MyModel : ObservableObject
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
private int _age;
public int Age
{
get => _age;
set => SetProperty(ref _age, value, onChanged: () => {
// Execute when age changes
OnPropertyChanged(nameof(IsAdult));
});
}
public bool IsAdult => Age >= 18;
}
BaseViewModel
Rich view model base class with common UI properties:
using DotNetTools.Wpfkit.MvvM;
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
Title = "My Application";
Subtitle = "Welcome Screen";
Icon = "icon.png";
}
public async Task LoadDataAsync()
{
IsBusy = true;
try
{
// Load data
}
finally
{
IsBusy = false;
}
}
}
Available Properties:
Title: Main title textSubtitle: Secondary descriptive textIcon: Icon path or resourceIsBusy: Indicates loading stateIsNotBusy: Inverse of IsBusyCanLoadMore: Pagination supportHeader: Header contentFooter: Footer content
ObservableRangeCollection<T>
Enhanced collection with bulk operations:
using DotNetTools.Wpfkit.MvvM;
var collection = new ObservableRangeCollection<string>();
// Add multiple items efficiently
var items = new[] { "Item1", "Item2", "Item3" };
collection.AddRange(items);
// Replace entire collection
collection.ReplaceRange(newItems);
// Remove multiple items
collection.RemoveRange(itemsToRemove);
// Replace with single item
collection.Replace(singleItem);
AddRange Notification Modes:
NotifyCollectionChangedAction.Add: Notify for each added item (default)NotifyCollectionChangedAction.Reset: Single reset notification
Command Infrastructure
The toolkit provides a comprehensive set of command implementations for both synchronous and asynchronous operations in MVVM applications.
CommandBase
Abstract base class that implements ICommand:
using DotNetTools.Wpfkit.Commands;
public class MyCustomCommand : CommandBase
{
public override void Execute(object? parameter)
{
// Your command logic here
}
public override bool CanExecute(object? parameter)
{
// Return true if command can execute
return base.CanExecute(parameter);
}
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
}
Features:
- Implements
ICommandinterface - Provides
CanExecuteChangedevent - Virtual
CanExecutemethod (returnstrueby default) - Abstract
Executemethod for derived classes - Protected
OnCanExecuteChanged()method to trigger re-evaluation
ActionCommand
Flexible command that accepts an action delegate and optional predicate:
using DotNetTools.Wpfkit.Commands;
public class MyViewModel : BaseViewModel
{
public ICommand SaveCommand { get; }
public ICommand DeleteCommand { get; }
public MyViewModel()
{
// Simple command
SaveCommand = new ActionCommand(
action: param => Save(param),
predicate: param => param != null && CanSave
);
// Command with validation
DeleteCommand = new ActionCommand(
action: param => Delete((string)param),
predicate: param => param is string id && !string.IsNullOrEmpty(id)
);
}
private bool CanSave => !string.IsNullOrEmpty(DataToSave);
private string DataToSave { get; set; }
private void Save(object data)
{
// Save logic
}
private void Delete(string id)
{
// Delete logic
}
}
Constructor Parameters:
action:Action<object>- The method to execute (required)predicate:Predicate<object>?- Condition to check if command can execute (optional)
Features:
- Integrates with
CommandManager.RequerySuggestedfor automatic UI updates - Parameter validation through predicate
- Throws
ArgumentNullExceptionif action is null
RelayCommand
Internal implementation extending ActionCommand with the same functionality:
using DotNetTools.Wpfkit.Commands;
// RelayCommand is internally used but provides the same API
var command = new RelayCommand(
action: param => Console.WriteLine($"Executed with {param}"),
predicate: param => param != null
);
AsyncCommandBase
Abstract base class for asynchronous command operations with automatic execution state management:
using DotNetTools.Wpfkit.Commands;
public class LoadDataCommand : AsyncCommandBase
{
private readonly DataService _dataService;
public LoadDataCommand(DataService dataService, Action<Exception> onException)
: base(onException)
{
_dataService = dataService;
}
protected override async Task ExecuteAsync(object parameter)
{
// Long-running async operation
var data = await _dataService.LoadDataAsync();
// Process data
await ProcessDataAsync(data);
}
}
Features:
- Prevents multiple concurrent executions (
IsExecutingstate) - Automatically disables command during execution
- Built-in exception handling and logging (using TraceTool)
- Invokes custom exception handler
- Updates
CanExecutestate automatically
Constructor Parameters:
onException:Action<Exception>- Callback invoked when an exception occurs
AsyncRelayCommand
Concrete implementation of async command for quick usage:
using DotNetTools.Wpfkit.Commands;
public class MyViewModel : BaseViewModel
{
public ICommand LoadDataCommand { get; }
public ICommand SaveDataCommand { get; }
public MyViewModel()
{
// Simple async command
LoadDataCommand = new AsyncRelayCommand(
callback: async () => await LoadDataAsync(),
onException: ex => ShowError(ex.Message)
);
// Async command with complex logic
SaveDataCommand = new AsyncRelayCommand(
callback: async () =>
{
IsBusy = true;
try
{
await SaveToServerAsync();
await SaveToLocalAsync();
ShowSuccess("Data saved successfully");
}
finally
{
IsBusy = false;
}
},
onException: ex =>
{
Logger.Error(ex, "Failed to save data");
ShowError($"Save failed: {ex.Message}");
}
);
}
private async Task LoadDataAsync()
{
// Load data from API
var data = await _apiClient.GetDataAsync();
Items.ReplaceRange(data);
}
private async Task SaveToServerAsync()
{
await _apiClient.SaveAsync(Data);
}
private async Task SaveToLocalAsync()
{
await _localDb.SaveAsync(Data);
}
private void ShowError(string message) { /* ... */ }
private void ShowSuccess(string message) { /* ... */ }
}
Constructor Parameters:
callback:Func<Task>- The async method to executeonException:Action<Exception>- Exception handler
Key Benefits:
- No need to manage
IsExecutingstate manually - Built-in exception handling
- Automatic
CanExecutemanagement - Prevents rapid clicking/double execution
- Integrated logging for errors
Complete ViewModel Example with Commands
using DotNetTools.Wpfkit.MvvM;
using DotNetTools.Wpfkit.Commands;
using System.Windows.Input;
public class CustomerViewModel : BaseViewModel
{
private readonly ICustomerService _customerService;
private readonly IDialogService _dialogService;
private string _searchText;
public string SearchText
{
get => _searchText;
set => SetProperty(ref _searchText, value);
}
private Customer _selectedCustomer;
public Customer SelectedCustomer
{
get => _selectedCustomer;
set => SetProperty(ref _selectedCustomer, value);
}
public ObservableRangeCollection<Customer> Customers { get; }
// Commands
public ICommand LoadCustomersCommand { get; }
public ICommand SearchCommand { get; }
public ICommand SaveCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand RefreshCommand { get; }
public CustomerViewModel(ICustomerService customerService, IDialogService dialogService)
{
_customerService = customerService;
_dialogService = dialogService;
Customers = new ObservableRangeCollection<Customer>();
// Async command for loading data
LoadCustomersCommand = new AsyncRelayCommand(
callback: LoadCustomersAsync,
onException: HandleException
);
// Sync command with parameter validation
SearchCommand = new ActionCommand(
action: param => SearchCustomers((string)param),
predicate: param => param is string text && !string.IsNullOrWhiteSpace(text)
);
// Async command with condition
SaveCommand = new AsyncRelayCommand(
callback: SaveCustomerAsync,
onException: HandleException
);
// Sync command with predicate
DeleteCommand = new ActionCommand(
action: param => DeleteCustomer(),
predicate: param => SelectedCustomer != null
);
// Async refresh command
RefreshCommand = new AsyncRelayCommand(
callback: RefreshCustomersAsync,
onException: HandleException
);
}
private async Task LoadCustomersAsync()
{
IsBusy = true;
Title = "Loading Customers...";
try
{
var customers = await _customerService.GetAllAsync();
Customers.ReplaceRange(customers);
Title = $"Customers ({Customers.Count})";
}
finally
{
IsBusy = false;
}
}
private void SearchCustomers(string searchText)
{
var filtered = Customers.Where(c =>
c.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
c.Email.Contains(searchText, StringComparison.OrdinalIgnoreCase)
).ToList();
Customers.ReplaceRange(filtered);
}
private async Task SaveCustomerAsync()
{
if (SelectedCustomer == null) return;
IsBusy = true;
try
{
await _customerService.SaveAsync(SelectedCustomer);
await _dialogService.ShowMessageAsync("Success", "Customer saved successfully");
await RefreshCustomersAsync();
}
finally
{
IsBusy = false;
}
}
private void DeleteCustomer()
{
if (SelectedCustomer == null) return;
Customers.Remove(SelectedCustomer);
SelectedCustomer = null;
}
private async Task RefreshCustomersAsync()
{
await LoadCustomersAsync();
}
private void HandleException(Exception ex)
{
IsBusy = false;
_dialogService.ShowError("Error", ex.Message);
}
}
XAML Binding Example:
<Window x:Class="MyApp.CustomerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10">
<TextBox x:Name="SearchBox" Width="200" Margin="0,0,10,0"/>
<Button Content="Search"
Command="{Binding SearchCommand}"
CommandParameter="{Binding Text, ElementName=SearchBox}"/>
<Button Content="Refresh"
Command="{Binding RefreshCommand}"
Margin="10,0,0,0"/>
</StackPanel>
<DataGrid Grid.Row="1"
ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer}"
AutoGenerateColumns="True"
Margin="10"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="10">
<Button Content="Load Customers"
Command="{Binding LoadCustomersCommand}"
Width="120"
Margin="0,0,10,0"/>
<Button Content="Save"
Command="{Binding SaveCommand}"
Width="80"
Margin="0,0,10,0"/>
<Button Content="Delete"
Command="{Binding DeleteCommand}"
Width="80"/>
</StackPanel>
<ProgressBar Grid.Row="1"
IsIndeterminate="True"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibilityConverter}}"
Height="5"
VerticalAlignment="Top"/>
</Grid>
</Window>
Command Best Practices
Choose the Right Command Type:
- Use
ActionCommandfor simple synchronous operations - Use
AsyncRelayCommandfor async operations (API calls, database operations) - Extend
CommandBaseorAsyncCommandBasefor complex custom logic
- Use
Exception Handling:
- Always provide an exception handler for async commands
- Log exceptions appropriately
- Show user-friendly error messages
- Consider retry logic for transient failures
UI State Management:
- Use
IsBusyproperty during long operations AsyncCommandBaseautomatically prevents concurrent execution- Update UI elements to reflect command state
- Use
Parameter Validation:
- Use predicates to validate command parameters
- Return
falsefromCanExecuteto disable UI elements - Validate parameter types before casting
Memory Management:
- Commands hold references to delegates and view models
- Be careful with closures capturing large objects
- Consider weak references for event handlers if needed
Logging
Setting Up the Logger
using DotNetTools.Wpfkit.Logging.Extensions;
using Serilog;
public class MyService
{
// Get logger for current class
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
public void DoWork()
{
Log.Me().Information("Starting work at line {LineNumber}");
try
{
// Your code here
Log.Me().Debug("Processing item");
}
catch (Exception ex)
{
Log.Me().Error(ex, "Failed to process item");
}
}
}
LogManager Features:
GetCurrentClassLogger(): Automatically creates logger with calling class contextMe()extension: Adds line number information to log entries
Serilog Configuration Example
using Serilog;
using DotNetTools.Wpfkit.Logging.Enrichers;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Enrich.With<UserNameEnricher>()
.WriteTo.Console()
.WriteTo.File("logs/app.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
Configuration Management
Updating Connection Strings
using DotNetTools.Wpfkit.Database;
// Update connection string in appsettings.json
string connectionString = "Data Source=myserver;Initial Catalog=mydb;";
AppSettingsUpdater.UpdateConnectionString(connectionString);
Features:
- Automatically locates appsettings.json in application directory
- Safely updates
ConnectDatabaseproperty - Handles "Data Source=" prefix trimming
- Comprehensive error handling and logging
- Writes formatted JSON (indented)
appsettings.json Structure:
{
"ConnectDatabase": "path/to/database.db",
"OtherSettings": "..."
}
📚 API Reference
MvvM Namespace
ObservableObject
protected bool SetProperty<T>(
ref T backingStore,
T value,
string propertyName = "",
Action onChanged = null,
Func<T, T, bool> validateValue = null)
- backingStore: Reference to the backing field
- value: New value to set
- propertyName: Property name (auto-filled via CallerMemberName)
- onChanged: Optional callback when value changes
- validateValue: Optional validation function
- Returns:
trueif property changed,falseotherwise
BaseViewModel Properties
| Property | Type | Description |
|---|---|---|
| Title | string | Main title text |
| Subtitle | string | Secondary descriptive text |
| Icon | string | Icon path or resource identifier |
| IsBusy | bool | Indicates if operation is in progress |
| IsNotBusy | bool | Inverse of IsBusy (auto-synchronized) |
| CanLoadMore | bool | Supports pagination scenarios |
| Header | string | Header content |
| Footer | string | Footer content |
ObservableRangeCollection<T> Methods
void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = Add)
void RemoveRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = Reset)
void Replace(T item)
void ReplaceRange(IEnumerable<T> collection)
Commands Namespace
CommandBase
public abstract class CommandBase : ICommand
{
public event EventHandler? CanExecuteChanged;
public virtual bool CanExecute(object? parameter);
public abstract void Execute(object? parameter);
protected void OnCanExecuteChanged();
}
ActionCommand
public class ActionCommand : ICommand
{
public ActionCommand(Action<object> action, Predicate<object>? predicate = null);
public bool CanExecute(object? parameter);
public void Execute(object? parameter);
public event EventHandler? CanExecuteChanged;
}
AsyncCommandBase
public abstract class AsyncCommandBase : CommandBase
{
public AsyncCommandBase(Action<Exception> onException);
protected abstract Task ExecuteAsync(object parameter);
public override bool CanExecute(object? parameter);
public override void Execute(object? parameter);
}
AsyncRelayCommand
public class AsyncRelayCommand : AsyncCommandBase
{
public AsyncRelayCommand(Func<Task> callback, Action<Exception> onException);
protected override Task ExecuteAsync(object parameter);
}
Command Comparison Table: | Command Type | Sync/Async | Use Case | Exception Handling | Concurrent Execution Prevention | |--------------|------------|----------|--------------------|---------------------------------| | CommandBase | Sync | Base for custom commands | Manual | No | | ActionCommand | Sync | Simple actions with parameters | Manual | No | | RelayCommand | Sync | Internal relay implementation | Manual | No | | AsyncCommandBase | Async | Base for async commands | Built-in | Yes | | AsyncRelayCommand | Async | Async operations | Built-in | Yes |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- Serilog (>= 4.3.0)
- Tracetool.DotNet.Api (>= 14.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.0.2: Added comprehensive Command Infrastructure - CommandBase, ActionCommand, RelayCommand, AsyncCommandBase, and AsyncRelayCommand with automatic state management, concurrent execution prevention, and built-in exception handling. See release-notes-v1.0.2.md for details.