OutWit.Common.MVVM.WPF 2.0.4

dotnet add package OutWit.Common.MVVM.WPF --version 2.0.4
                    
NuGet\Install-Package OutWit.Common.MVVM.WPF -Version 2.0.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OutWit.Common.MVVM.WPF" Version="2.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OutWit.Common.MVVM.WPF" Version="2.0.4" />
                    
Directory.Packages.props
<PackageReference Include="OutWit.Common.MVVM.WPF" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OutWit.Common.MVVM.WPF --version 2.0.4
                    
#r "nuget: OutWit.Common.MVVM.WPF, 2.0.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OutWit.Common.MVVM.WPF@2.0.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OutWit.Common.MVVM.WPF&version=2.0.4
                    
Install as a Cake Addin
#tool nuget:?package=OutWit.Common.MVVM.WPF&version=2.0.4
                    
Install as a Cake Tool

OutWit.Common.MVVM.WPF

WPF-specific MVVM components and utilities, including source generator for automatic DependencyProperty generation.

Features

  • Source Generator for DependencyProperty: Automatically generate DependencyProperty from attributes
  • WPF Commands: Command and DelegateCommand with CommandManager integration
  • Binding Utilities: Helper methods for DependencyProperty registration
  • Visual Tree Traversal: Extension methods for navigating WPF visual tree
  • BindingProxy: Freezable binding proxy for DataContext access
  • Legacy Support: Obsolete BindableAttribute for backward compatibility

Installation

dotnet add package OutWit.Common.MVVM.WPF

This automatically includes:

  • OutWit.Common.MVVM (base cross-platform package)
  • OutWit.Common.MVVM.WPF.Generator (source generator)
  • OutWit.Common.Logging

Quick Start

Source Generator for DependencyProperty

The simplest way to create DependencyProperties:

using System.Windows.Controls;
using OutWit.Common.MVVM.WPF.Attributes;

namespace MyApp.Controls
{
    public partial class CustomButton : Button
    {
        [StyledProperty(DefaultValue = "Click Me")]
        public string Label { get; set; }

        [StyledProperty(AffectsMeasure = true)]
        public double IconSize { get; set; }
    }
}

Important: Mark your class as partial to allow source generator to add code.

The generator automatically creates:

// Generated code (you don't write this):
public static readonly DependencyProperty LabelProperty = ...;
public static readonly DependencyProperty IconSizeProperty = ...;

Advanced Property Generation

public partial class AdvancedControl : Control
{
    // Full explicit configuration
    [StyledProperty(
        DefaultValue = 100.0,
        AffectsMeasure = true,
        AffectsArrange = true,
        BindsTwoWayByDefault = true,
        OnChanged = nameof(OnWidthChanged))]
    public double CustomWidth { get; set; }

    private static void OnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (AdvancedControl)d;
        // Handle change
    }
}

Convention-Based Callbacks

The generator automatically discovers callback methods by naming convention:

public partial class SmartControl : Control
{
    // No need to specify OnChanged - automatically discovered!
    [StyledProperty(DefaultValue = "Hello")]
    public string Title { get; set; }

    // Convention: On{PropertyName}Changed
    private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (SmartControl)d;
        // Handle title change
    }

    // No need to specify Coerce - automatically discovered!
    [StyledProperty(DefaultValue = 100.0)]
    public double Width { get; set; }

    // Convention: {PropertyName}Coerce
    private static object WidthCoerce(DependencyObject d, object value)
    {
        return Math.Max(0, (double)value); // Ensure non-negative
    }
}

Benefits:

  • ? Less boilerplate code
  • ? Cleaner attributes
  • ? Compile-time safety
  • ? Override with explicit parameter when needed

Attached Properties

using OutWit.Common.MVVM.WPF.Attributes;

public static partial class MyAttachedProperties
{
    [AttachedProperty(DefaultValue = false)]
    public static bool IsHighlighted { get; set; }
}

// Usage in XAML:
// <Button local:MyAttachedProperties.IsHighlighted="True" />

WPF Commands

using OutWit.Common.MVVM.WPF.Commands;

public class MyViewModel
{
    public DelegateCommand SaveCommand { get; }

    public MyViewModel()
    {
        SaveCommand = new DelegateCommand(
            execute: _ => Save(),
            canExecute: _ => CanSave());
    }

    private void Save() { }
    private bool CanSave() => true;
}

Binding Utilities

Manual DependencyProperty registration (for when you can't use source generator):

using OutWit.Common.MVVM.WPF.Utils;

public class MyControl : Control
{
    public static readonly DependencyProperty TextProperty = 
        BindingUtils.Register<MyControl, string>(nameof(Text), "Default");

    public string Text
    {
        get => (string)GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }
}

Visual Tree Traversal

using OutWit.Common.MVVM.WPF.Utils;

// Find first child of specific type
var button = myPanel.FindFirstChildOf<Button>();

// Find with predicate
var redButton = myPanel.FindFirstChildOf<Button>(b => b.Background == Brushes.Red);

// Find all children
var allButtons = myPanel.FindAllChildrenOf<Button>();

// Find parent
var window = myButton.FindFirstParentOf<Window>();

BindingProxy for DataContext Access

<Window.Resources>
    <local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</Window.Resources>

<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    
                    <Button Command="{Binding Data.DeleteCommand, Source={StaticResource Proxy}}"
                            CommandParameter="{Binding}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGrid.Columns>
    </DataGrid>
</Window.Resources>

StyledProperty Options

Option Type Description
PropertyName string Override property name (default: {Name}Property)
DefaultValue object Default value
BindsTwoWayByDefault bool Enable two-way binding by default
AffectsMeasure bool Invalidate measure on change
AffectsArrange bool Invalidate arrange on change
AffectsRender bool Invalidate render on change
Inherits bool Value inherited by child elements
OnChanged string PropertyChangedCallback method name
Coerce string CoerceValueCallback method name

AttachedProperty Options

Option Type Description
PropertyName string Override property name
DefaultValue object Default value
Inherits bool Value inherited by child elements
OnChanged string PropertyChangedCallback method name
Coerce string CoerceValueCallback method name

Migration from Old BindableAttribute

See Migration Guide for detailed instructions.

Quick summary:

  1. Change [Bindable] to [StyledProperty]
  2. Remove manual DependencyProperty declarations
  3. Mark class as partial
  4. Move options to attribute parameters

Legacy BindableAttribute (Deprecated)

The old BindableAttribute using AspectInjector is still available but deprecated:

[Obsolete("Use StyledPropertyAttribute instead")]
public class BindableAttribute : Attribute { }

Recommendation: Migrate to StyledPropertyAttribute for better IDE support and debugging.

  • OutWit.Common.MVVM - Cross-platform base classes
  • OutWit.Common.MVVM.Avalonia - Avalonia-specific implementation
  • OutWit.Common.MVVM.Blazor - Blazor-specific implementation

License

Licensed under the Apache License, Version 2.0. See LICENSE.

Attribution (optional)

If you use OutWit.Common.MVVM.WPF in a product, a mention is appreciated (but not required), for example: "Powered by OutWit.Common.MVVM.WPF (https://ratner.io/)".

Trademark / Project name

"OutWit" and the OutWit logo are used to identify the official project by Dmitry Ratner.

You may:

  • refer to the project name in a factual way (e.g., "built with OutWit.Common.MVVM.WPF");
  • use the name to indicate compatibility (e.g., "OutWit.Common.MVVM.WPF-compatible").

You may not:

  • use "OutWit.Common.MVVM.WPF" as the name of a fork or a derived product in a way that implies it is the official project;
  • use the OutWit.Common.MVVM.WPF logo to promote forks or derived products without permission.
Product Compatible and additional computed target framework versions.
.NET net6.0-windows7.0 is compatible.  net7.0-windows was computed.  net7.0-windows7.0 is compatible.  net8.0-windows was computed.  net8.0-windows7.0 is compatible.  net9.0-windows was computed.  net9.0-windows7.0 is compatible.  net10.0-windows was computed.  net10.0-windows7.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.4 109 1/25/2026
2.0.3 109 1/8/2026
2.0.2 108 1/2/2026
2.0.1 101 1/2/2026
2.0.0 99 1/2/2026