NuExt.Minimal.Behaviors.Wpf is a minimalistic, production‑ready implementation of WPF Attached Behaviors for MVVM. It delivers deterministic, predictable interactivity with ready‑to‑use behaviors (EventToCommand, KeyToCommand) and template‑driven composition — dynamic behavior injection via BehaviorsTemplate and runtime selection via BehaviorsTemplateSelector.
Package ecosystem: The core package ships the foundational attached behavior infrastructure and essential behaviors for MVVM. For extra behaviors/services, see NuExt.Minimal.Mvvm.Wpf.
- Simplicity & maintainability. No trigger/action stacks. One behavior = one responsibility. Readable, testable, predictable.
- Dynamic composition.
BehaviorsTemplateandBehaviorsTemplateSelectorlet you define and reuse behavior sets with minimal XAML. - Practical coverage. Event-to-command binding, keyboard shortcuts, dynamic behavior sets — no hidden indirection.
- Deterministic semantics. Clear command targeting; no focus-based ambiguity.
- Performance-focused. Hot paths avoid allocations. No unnecessary plumbing.
- WPF (.NET 8/9/10 and .NET Framework 4.6.2+)
- Works with any MVVM framework.
- No dependency on external behavior frameworks.
Interaction– attached properties:Behaviors,BehaviorsTemplate,BehaviorsTemplateSelector,BehaviorsTemplateSelectorParameter.BehaviorCollection– observable collection managing behavior lifecycle.EventToCommand,KeyToCommand– ready-to-use behaviors with a predictable contract.
<!-- Prefer concise attached behaviors over trigger/action composition -->
<nx:Interaction.Behaviors>
<nx:EventToCommand EventName="Loaded" Command="{Binding LoadedCommand}" />
<nx:KeyToCommand Gesture="Ctrl+S" Command="{Binding SaveCommand}" />
<local:MyService />
</nx:Interaction.Behaviors>- Add the namespace:
xmlns:nx="http://schemas.nuext.minimal/xaml"
- Attach a behavior:
<Button Content="Click Me"> <nx:Interaction.Behaviors> <nx:EventToCommand EventName="Click" Command="{Binding MyCommand}" /> </nx:Interaction.Behaviors> </Button>
- Define the command in your ViewModel.
Define once, apply many times. The template supports two concise formats:
- Single behavior via
ContentControl.Content - Multiple behaviors via
ItemsControl.Items
<Window.Resources>
<DataTemplate x:Key="SaveBehavior">
<ContentControl>
<nx:KeyToCommand Gesture="Ctrl+S" Command="{Binding SaveCommand}" />
</ContentControl>
</DataTemplate>
</Window.Resources>
<TextBox nx:Interaction.BehaviorsTemplate="{StaticResource SaveBehavior}" /><Window.Resources>
<DataTemplate x:Key="EditBehaviors">
<ItemsControl>
<nx:KeyToCommand Gesture="F2" Command="{Binding StartEditCommand}" />
<nx:KeyToCommand Gesture="Ctrl+S" Command="{Binding SaveCommand}" />
<nx:KeyToCommand Gesture="Escape" Command="{Binding CancelCommand}" />
</ItemsControl>
</DataTemplate>
</Window.Resources>
<ListBox nx:Interaction.BehaviorsTemplate="{StaticResource EditBehaviors}" />Switch behavior sets at runtime:
<Window.Resources>
<DataTemplate x:Key="ReadOnlyTemplate">
<ContentControl>
<nx:KeyToCommand Gesture="F2" Command="{Binding StartEditCommand}" />
</ContentControl>
</DataTemplate>
<DataTemplate x:Key="EditableTemplate">
<ItemsControl>
<nx:KeyToCommand Gesture="Ctrl+S" Command="{Binding SaveCommand}" />
<nx:KeyToCommand Gesture="Escape" Command="{Binding CancelCommand}" />
</ItemsControl>
</DataTemplate>
<local:MyBehaviorSelector x:Key="BehaviorSelector"
ReadOnlyTemplate="{StaticResource ReadOnlyTemplate}"
EditableTemplate="{StaticResource EditableTemplate}" />
</Window.Resources>
<TextBox nx:Interaction.BehaviorsTemplateSelector="{StaticResource BehaviorSelector}" />public sealed class MyBehaviorSelector : DataTemplateSelector
{
public DataTemplate? ReadOnlyTemplate { get; set; }
public DataTemplate? EditableTemplate { get; set; }
public override DataTemplate? SelectTemplate(object? item, DependencyObject container)
=> (item is MyDataItem m && m.IsReadOnly) ? ReadOnlyTemplate : EditableTemplate;
}Tip: If no selector parameter is provided, the framework resolves the element’s data item (DataContext, Content, or Header). Bind
BehaviorsTemplateSelectorParameter="{Binding}"to drive selection from theDataContextexplicitly when needed.
<nx:EventToCommand EventName="Loaded" Command="{Binding InitializeCommand}" /><ListView>
<nx:Interaction.Behaviors>
<nx:EventToCommand EventName="SelectionChanged"
Command="{Binding SelectionChangedCommand}"
PassEventArgsToCommand="True" />
</nx:Interaction.Behaviors>
</ListView><TextBox>
<nx:Interaction.Behaviors>
<nx:KeyToCommand Gesture="Ctrl+Enter" Command="{Binding SubmitCommand}" />
<nx:KeyToCommand Gesture="Escape" Command="{Binding CancelCommand}" />
</nx:Interaction.Behaviors>
</TextBox><TextBox nx:Interaction.BehaviorsTemplateSelector="{StaticResource BehaviorSelector}"
nx:Interaction.BehaviorsTemplateSelectorParameter="{Binding}" /><nx:EventToCommand EventName="PreviewMouseDown" ProcessHandledEvent="True"
Command="{Binding PreviewMouseDownCommand}" /><nx:EventToCommand EventName="SelectionChanged"
Command="{Binding SelectionChangedCommand}"
EventArgsParameterPath="AddedItems[0]" />A meaningful non‑visual service can participate in nx:Interaction.Behaviors and solve a concrete UX task.
Example below: auto‑scroll the host control to the currently selected item for ListBox/ListView/DataGrid.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using Minimal.Behaviors.Wpf;
namespace MyApp.Behaviors
{
/// <summary>
/// Automatically scrolls the host selector (ListBox/ListView/DataGrid) to the current SelectedItem.
/// </summary>
public sealed class AutoScrollSelectedItemService : Behavior<Selector>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += OnSelectionChanged;
AssociatedObject.Dispatcher.InvokeAsync(ScrollToCurrent, DispatcherPriority.Loaded);
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= OnSelectionChanged;
base.OnDetaching();
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
AssociatedObject.Dispatcher.InvokeAsync(ScrollToCurrent, DispatcherPriority.Background);
}
private void ScrollToCurrent()
{
var item = AssociatedObject.SelectedItem;
if (item is null)
return;
// Prefer control-specific ScrollIntoView when available.
switch (AssociatedObject)
{
case ListBox lb:
lb.ScrollIntoView(item);
return;
case ListView lv:
lv.ScrollIntoView(item);
return;
case DataGrid dg:
dg.ScrollIntoView(item);
return;
}
// Fallback: resolve container and bring it into view.
if (AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) is FrameworkElement fe)
fe.BringIntoView();
}
}
}<Window
x:Class="MyApp.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:nx="http://schemas.nuext.minimal/xaml"
xmlns:local="clr-namespace:MyApp.Behaviors">
<Grid>
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding Selected, Mode=TwoWay}">
<nx:Interaction.Behaviors>
<local:AutoScrollSelectedItemService />
</nx:Interaction.Behaviors>
</ListView>
</Grid>
</Window>- Attach/detach symmetry: subscribe in
OnAttached, unsubscribe inOnDetaching. No dangling handlers. - Keep OnAttached cheap: defer heavy work via
DispatcherPriority.Backgroundor offload to a worker. - Thread safety: marshal UI access to
Dispatcher. - Order matters: items in
nx:Interaction.Behaviorsattach top‑to‑bottom; place services before/after others if you need specific ordering. - Error handling: do not swallow exceptions silently; ensure resources are released on failures.
- Testability: keep logic behind clear, minimal methods to simplify unit testing.
Parameter precedence (short):
CommandParameter(if set) — wins; everything else is ignored.EventArgsConverter(if set):
• value = (EventArgsParameterPath?eventArgs[path]:eventArgs)
• parameter = (EventArgsConverterParameter??sender)EventArgsParameterPath(no converter):eventArgs[path]SenderParameterPath(no converter):sender[path]- Fallback:
PassEventArgsToCommand ? eventArgs : null
When a converter is set (step 2),
SenderParameterPathis intentionally ignored.
Without a converter, if both paths are provided, step 3 (EventArgsParameterPath) is considered before step 4 (SenderParameterPath).
- Minimal and explicit API surface. No trigger/action stacks; behaviors are explicit and composable.
- Template-driven composition. Reuse behavior sets via data templates (single or multiple behaviors) — concise and flexible.
- Selector-driven switching. Change behavior sets at runtime with
BehaviorsTemplateSelectorand a single parameter binding. - Consistent WPF semantics. Clear command targeting; no implicit focus-based resolution.
- MVVM-first design. Clean event →
ICommandbinding with a well-defined parameter precedence. - Predictable behavior lifecycle. Dynamic composition does not leave dangling references.
- Source package option. Drop-in sources for straightforward embedding and debugging.
If your project uses Blend Behaviors, System.Windows.Interactivity, or Microsoft.Xaml.Behaviors, migration is straightforward. Minimal.Behaviors preserves the mental model but removes the heavy trigger/action stack.
| Blend / Interactivity | Minimal equivalent | Notes |
|---|---|---|
| Interaction.Behaviors | nx:Interaction.Behaviors | Same structure, predictable lifecycle. |
| EventTrigger + InvokeCommandAction | nx:EventToCommand | Direct event → ICommand binding. |
| KeyBinding / InputBinding | nx:KeyToCommand | Keyboard gestures per control, MVVM-friendly. |
| Reusable stacks in resources | BehaviorsTemplate | Define behavior sets once, attach anywhere. |
| Runtime switching via triggers | BehaviorsTemplateSelector | Cleaner, deterministic selection. |
No hidden dependencies, no extra assemblies, no performance traps. Your existing behavior patterns map directly to smaller, clearer equivalents.
- Use explicit
CommandParameterfor high-frequency events; avoid deep parameter extraction on high-frequency routes (e.g., mouse move). - Keep converters lightweight and pass only required data.
- Reuse behavior templates instead of repeating inline declarations.
- Control selector reevaluation: bind
BehaviorsTemplateSelectorParameter(typically to"{Binding}") to update only when needed.
Via NuGet:
dotnet add package NuExt.Minimal.Behaviors.WpfOr via Visual Studio:
- Go to
Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution.... - Search for
NuExt.Minimal.Behaviors.Wpf. - Install.
Prefer to vendor the framework and keep your app dependency‑flat?
Use the source package to embed the entire behavior infrastructure directly into your project:
- No external binary dependency — sources compile as part of your app.
- Easier debugging — step into the framework code without symbol servers.
- Deterministic builds — you control updates via package version pinning.
- Same API — identical public surface to the binary package.
This is ideal for teams that prefer zero external runtime dependencies and want to keep UI infrastructure fully in-tree.
NuGet: NuExt.Minimal.Behaviors.Wpf.Sources.
dotnet add package NuExt.Minimal.Behaviors.Wpf.SourcesOr via Visual Studio:
- Go to
Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution.... - Search for
NuExt.Minimal.Behaviors.Wpf.Sources. - Install.
- NuExt.Minimal.Mvvm
- NuExt.Minimal.Mvvm.SourceGenerator
- NuExt.Minimal.Mvvm.Wpf
- NuExt.Minimal.Mvvm.MahApps.Metro
- NuExt.System
- NuExt.System.Data
- NuExt.System.Data.SQLite
Issues and PRs are welcome. Keep changes minimal and performance-conscious.
MIT. See LICENSE.