Skip to content

About

Minimal WPF attached behaviors for MVVM with clear, testable interactivity, direct event-to-command and key-to-command routing, and reusable behavior templates.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

NuExt.Minimal.Behaviors.Wpf

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.

NuGet Ask DeepWiki Build License Downloads

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.

Why NuExt.Minimal.Behaviors.Wpf?

WPF behaviors are useful, but traditional interaction frameworks often require a stack of triggers and actions to connect one event to one command. NuExt.Minimal.Behaviors.Wpf keeps that relationship direct: a behavior subscribes to an event, resolves a command parameter using explicit rules, and invokes the command against a predictable target.

Behaviors can be declared inline, reused through BehaviorsTemplate, or selected dynamically with BehaviorsTemplateSelector. Their lifecycle is explicit: behaviors attach in declaration order, detach in reverse order, and roll back partial setup when attachment fails.

The result is a small, dependency-free behavior layer with clear XAML, deterministic command routing, reusable composition, and minimal runtime overhead.

Compatibility

  • WPF (.NET 8/9/10 and .NET Framework 4.6.2+)
  • Works with any MVVM framework.
  • No dependency on external behavior frameworks.

Core Components

  • Interaction — attached properties: Behaviors, BehaviorsTemplate, BehaviorsTemplateSelector, and BehaviorsTemplateSelectorParameter.
  • BehaviorCollection — manages ordered attachment, reverse-order detachment, dynamic changes, and resilient cleanup when lifecycle operations fail.
  • EventToCommand and KeyToCommand — ready-to-use behaviors with explicit command targeting and parameter resolution.

Core Principle

<!-- 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>

Quick Start

  1. Add the namespace:
    xmlns:nx="http://schemas.nuext.minimal/xaml"
  2. Attach a behavior:
    <Button Content="Click Me">
      <nx:Interaction.Behaviors>
        <nx:EventToCommand EventName="Click" Command="{Binding MyCommand}" />
      </nx:Interaction.Behaviors>
    </Button>
  3. Define the command in your ViewModel.

Dynamic Behaviors with BehaviorsTemplate

Define once, apply many times. The template supports two concise formats:

  • Single behavior via ContentControl.Content
  • Multiple behaviors via ItemsControl.Items

Single:

<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}" />

Multiple:

<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}" />

Dynamic Selection with BehaviorsTemplateSelector

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 the DataContext explicitly when needed.

Practical Scenarios

Command on Loaded

<nx:EventToCommand EventName="Loaded" Command="{Binding InitializeCommand}" />

Deterministic routed events

<ListView>
  <nx:Interaction.Behaviors>
    <nx:EventToCommand EventName="SelectionChanged"
                       Command="{Binding SelectionChangedCommand}"
                       PassEventArgsToCommand="True" />
  </nx:Interaction.Behaviors>
</ListView>

MVVM-friendly per-control shortcuts

<TextBox>
  <nx:Interaction.Behaviors>
    <nx:KeyToCommand Gesture="Ctrl+Enter" Command="{Binding SubmitCommand}" />
    <nx:KeyToCommand Gesture="Escape"     Command="{Binding CancelCommand}" />
  </nx:Interaction.Behaviors>
</TextBox>

Selector reevaluation on DataContext changes

<TextBox nx:Interaction.BehaviorsTemplateSelector="{StaticResource BehaviorSelector}"
         nx:Interaction.BehaviorsTemplateSelectorParameter="{Binding}" />

Handling already-handled routed events (Preview included)

<nx:EventToCommand EventName="PreviewMouseDown" ProcessHandledEvent="True"
                   Command="{Binding PreviewMouseDownCommand}" />

Deterministic command parameter resolution

<nx:EventToCommand EventName="SelectionChanged"
                   Command="{Binding SelectionChangedCommand}"
                   EventArgsParameterPath="AddedItems[0]" />

Creating a custom service (attachable)

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.

Example: AutoScrollSelectedItemService

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 listBox:
                    listBox.ScrollIntoView(item);
                    return;
                case DataGrid dataGrid:
                    dataGrid.ScrollIntoView(item);
                    return;
            }

            // Fallback: resolve container and bring it into view.
            if (AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) is FrameworkElement fe)
                fe.BringIntoView();
        }
    }
}

Usage in XAML

<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>

Notes & best practices

  • Attach/detach symmetry: subscribe in OnAttached, unsubscribe in OnDetaching. No dangling handlers.
  • Keep OnAttached cheap: defer heavy work via DispatcherPriority.Background or offload to a worker.
  • Thread safety: marshal UI access to Dispatcher.
  • Order matters: items in nx:Interaction.Behaviors attach top‑to‑bottom; place services before/after others if you need specific ordering.
  • Error handling: if OnAttached() fails, the framework calls OnDetaching() to roll back partial setup. Keep these methods symmetrical so cleanup remains safe after partial attachment.
  • Testability: keep logic behind clear, minimal methods to simplify unit testing.

Deterministic parameters & selector coordination

Parameter precedence (short):

  1. CommandParameter (if set) — wins; everything else is ignored.
  2. EventArgsConverter (if set):
    • value = (EventArgsParameterPath ? eventArgs[path] : eventArgs)
    • parameter = (EventArgsConverterParameter ?? sender)
  3. EventArgsParameterPath (no converter): eventArgs[path]
  4. SenderParameterPath (no converter): sender[path]
  5. Fallback: PassEventArgsToCommand ? eventArgs : null

When a converter is set (step 2), SenderParameterPath is intentionally ignored.
Without a converter, if both paths are provided, step 3 (EventArgsParameterPath) is considered before step 4 (SenderParameterPath).

Migration from Blend/Interactivity

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.

Performance Tips

  • Use explicit CommandParameter for 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.

Installation

Via NuGet:

dotnet add package NuExt.Minimal.Behaviors.Wpf

Or via Visual Studio:

  1. Go to Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution....
  2. Search for NuExt.Minimal.Behaviors.Wpf.
  3. Install.

Source Code Package (no external binary dependency)

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.Sources

Or via Visual Studio:

  1. Go to Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution....
  2. Search for NuExt.Minimal.Behaviors.Wpf.Sources.
  3. Install.

Ecosystem

Contributing

Issues and PRs are welcome. Keep changes minimal and performance-conscious.

License

MIT. See LICENSE.

About

Minimal WPF attached behaviors for MVVM with clear, testable interactivity, direct event-to-command and key-to-command routing, and reusable behavior templates.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages