ComputedBehaviors.WPF 0.4.5-beta

This is a prerelease version of ComputedBehaviors.WPF.
There is a newer version of this package available.
See the version list below for details.
dotnet add package ComputedBehaviors.WPF --version 0.4.5-beta                
NuGet\Install-Package ComputedBehaviors.WPF -Version 0.4.5-beta                
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="ComputedBehaviors.WPF" Version="0.4.5-beta" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ComputedBehaviors.WPF --version 0.4.5-beta                
#r "nuget: ComputedBehaviors.WPF, 0.4.5-beta"                
#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.
// Install ComputedBehaviors.WPF as a Cake Addin
#addin nuget:?package=ComputedBehaviors.WPF&version=0.4.5-beta&prerelease

// Install ComputedBehaviors.WPF as a Cake Tool
#tool nuget:?package=ComputedBehaviors.WPF&version=0.4.5-beta&prerelease                

GitHub license Actions

ComputedConverters.NET

ComputedConverters provides you with XAML markup that allows you to write inline converters (Vue-like computed method) and expand some converters commonly used.

Support framework

WPF Avalonia
ComputedConverters ComputedConverters.WPF NuGet ComputedConverters.Avalonia (TBD)
ComputedAnimations ComputedAnimations.WPF NuGet /
ComputedBehaviors ComputedBehaviors.WPF NuGet /
ValueConverters ValueConverters Version ValueConverters.Avalonia Version

Usage

Add XML namespace to your XAML file:

<Application xmlns:a="http://schemas.github.com/computedanimations/2024/xaml"
      xmlns:b="http://schemas.github.com/computedbehaviors/2024/xaml"
      xmlns:c="http://schemas.github.com/computedconverters/2024/xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <a:ComputedAnimationsResources />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

[!TIP]

Sample code is not fully introduced, more features please pay more attention to the source code!

1. Reactivity

Reactivity is a vue-like MVVM concept.

1.1 Reactive Definition
  • Use ComputedConverters only.
public class ViewModel : Reactive
{
}
  • Recommend: Use ComputedConverters with CommunityToolkit.Mvvm.
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
[ObservableObject]
public partial class ViewModel : ReactiveObject
{
}

Additionally ReactiveCollection<T> and Ref<T> are availabled.

ReactiveCollection<T> is similar to Vue reactive(T[]).

Ref<T> is similar to Vue ref(T).

1.2 Computed

Computed property that is an instance method of the ReactiveObject base class.

[ObservableObject]
public partial class ViewModel : ReactiveObject
{
    [ObservableProperty]
    private double width = 10d;

    [ObservableProperty]
    private double height = 10d;

    public double Area => Computed(() => Width * Height);
}
1.3 Watch

Subscribe to an observable expression and trigger a callback function when its value changes.

[ObservableObject]
public partial class ViewModel : ReactiveObject
{
    public ViewModel()
    {
		Watch(() => Width * Height, area => Debug.WriteLine(area));
    }
}
1.4 WatchDeep

Deep traversal subscribes to an observable object and triggers a callback function when its properties, or the properties of its properties, change.

[ObservableObject]
public partial class ViewModel : ReactiveObject
{
    public ViewModel()
    {
		WatchDeep(obj, path => Debug.WriteLine(path))
    }
}
1.5 Mapper

IL based auto mapper will auto copy the property from source to target object with the same property name.

For more configuration, to set the ICloneableAttribute, ITypeConverterAttribute and NotMappedAttribute for target property attribute.

TestMapperModel model = new();
TestMapperViewModel viewModel = new();
viewModel = model.MapTo(viewModel); // Auto copy the properties from model to viewModel.

2. Value Converters

2.1 ValueConverterGroup

Supports continuous value converters from group.

<c:ValueConverterGroup x:Key="NullToVisibilityConverter">
    <c:IsNullConverter />
    <c:BoolToVisibilityConverter />
</c:ValueConverterGroup>
2.2 BoolToVisibilityConverter
<TextBlock
  xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=System.Runtime"
  Text="Visiable on debugger attached."
  Visibility="{c:Converter Value={x:Static diagnostics:Debugger.IsAttached},
                           Converter={x:Static c:BoolToVisibilityConverter.Instance}}" />
2.3 EnumLocaleDescriptionConverter
<TextBlock Text="{Binding TestLocaleEnumValue, Converter={x:Static c:EnumLocaleDescriptionConverter.Instance}}" />
class ViewModel
{
    public TestLocaleEnum testLocaleEnumValue { get; set; } = TestLocaleEnum.Second;
}

enum TestLocaleEnum
{
    [LocaleDescription("en", "First", isFallback: true)]
    [LocaleDescription("ja", "ファースト")]
    [LocaleDescription("zh", "第一个")]
    First = 1,

    [LocaleDescription("en", "Second", isFallback: true)]
    [LocaleDescription("ja", "セカンド")]
    [LocaleDescription("zh", "第二个")]
    Second = 2,

    [LocaleDescription("en", "Third", isFallback: true)]
    [LocaleDescription("ja", "サード")]
    [LocaleDescription("zh", "第三个")]
    Third = 3
}
2.4 EnumWrapperConverter

EnumWrapperConverter is used to display localized enums. The concept is fairly simple: Enums are annotated with localized string resources and wrapped into EnumWrapper. The view uses the EnumWrapperConverter to extract the localized string resource from the resx file. Following step-by-step instructions show how to localize and bind a simple enum type in a WPF view:

  1. Define new public enum type and annotate enum values with [Display] attributes:
[DataContract] 
public enum PartyMode 
{ 
    [EnumMember] 
    [Display(Name = "PartyMode_Off", ResourceType = typeof(PartyModeResources))] 
    Off, 

    // … 
} 
  1. Create StringResources.resx and define strings with appropriate keys (e.g. "PartyMode__Off"). Make sure PublicResXFileCodeGenerator is used to generate the .Designer.cs file. (If ResXFileCodeGenerator is used, the resource lookup operations may require more time to complete).
  2. Create StringResources.resx for other languages (e.g. StringResources.de.resx) and translate all strings accordingly. Use Multilingual App Toolkit for easy localization of the defined string resources.
  3. Expose enum property in the ViewModel.
[ObservableProperty]
private PartyMode partyMode;
  1. Bind to enum property in the View and define Converter={StaticResource EnumWrapperConverter}.
<Label Content="{Binding PartyMode, Converter={StaticResource EnumWrapperConverter}}" /> 

3. Computed Markups

3.1 Setup

Add the namespaces that it will need to know about (before any xaml loaded).

public partial class App : Application
{
    public App() : base()
    {
        // Add the System namespace so we can use primitive types (i.e. int, etc.).
        EquationTokenizer.AddNamespace(typeof(object));
        // Add the System.Windows namespace so we can use Visibility.Collapsed, etc.
        EquationTokenizer.AddNamespace(typeof(System.Windows.Visibility));
    }
}

Other using cases

EquationTokenizer.AddNamespace("System", Assembly.GetAssembly(typeof(object)));
EquationTokenizer.AddAssembly(typeof(object).Assembly);
EquationTokenizer.AddExtensionMethods(typeof(Enumerable));
3.2 Computed

Here is a binding with a Boolean to System.Visibility converter written with ComputedConverter:

<Control Visibility="{c:Computed '$P ? Visibility.Visible : Visibility.Collapsed', P={Binding ShowElement}}" />

Following are two more examples of valid converter code:

'$P != null ? $P.GetType() == typeof(int) : false'
'(Double.Parse($P) + 123.0).ToString(\\’0.00\\’)'

Constructors and object initializers are also valid:

<Control FontSize="{c:Computed 'new Dictionary\[string, int\]() { { \\'Sml\\', 16 }, { \\'Lrg\\', 32 } }\[$P\]', P={Binding TestIndex}}" />
<Control Content="{c:Computed 'new TestObject(1,2,3) { FieldOne = \\'Hello\\', FieldTwo = \\'World\\' }}" />

The following shows how to write a two-way binding:

<Control Height="{c:Computed '$P * 10', ConvertBack='$value * 0.1', P={Binding TestWidth, Mode=TwoWay}}" />
3.3 MultiComputed

Multibinding work in a very similar way.

The following demonstrates an inline multibinding:

<Control Angle="{c:MultiComputed 'Math.Atan2($P0, $P1) * 180 / 3.14159', P0={Binding ActualHeight, ElementName=rootElement}, P1={Binding ActualWidth, ElementName=rootElement}}" />
3.4 ComputedConverter

Converters can also be created independently of the ComputedConverter binding extensions. This allows an extra level of flexibility. The following is an example of this:

<Control Width="{Binding Data, Converter={c:ComputedConverter '$P * 10', ConvertBack='$value * 0.1'}}" />

Local Variables

Local variables can be used through a lambda expression like syntax. Local variables have precedence over binding variables and are only valid with the scope of the lambda expression in which they are declared. The following shows this:

<Control IsEnabled="{c:Computed '(Loc = $P.Value, A = $P.Show) => $Loc != null ## $A', P={Binding Obj}}" />

* Note that the "&&" operator must be written as "&&" in XML.

** Due to a bug with the designer, using "&" in a markup extension breaks Intellisense. Instead of two ampersands, use the alternate syntax "##". "#" also works for bitwise and operations.

Lambda Expressions

Support for lambda expressions is limited, but its support is sufficient to allow LINQ to be used. They look quite similar to conventional C# lambda expressions, but there are a few important differences. First off, block expressions are not supported. Only single, inline statements are allowed. Also, the expression must return a value. Additionally, the function will default to object for all generic parameters (eg. Func<object, object>). This can be overridden with typecast operators. The following shows this:

<Control ItemsSource="{c:Computed '$P.Where(( (int)i ) => (bool)($i % 2 == 0))', P={Binding Source}}" />

*Note: The parameters must always be enclosed by parenthesis.

Null Propagation Operator

The null propagation operator allows safe property/field, method, and indexer access on objects. When used, if the target object is null, instead of throwing an exception null is returned. The operator is implemented by "?".

Instead of this:

'$P != null ? $P.Value : null'

You can write this:

'$P?.Value'

This can be combined with the null coalescing operator to change this:

'$P != null ? $P.GetType() == typeof(int) : false'

Into this:

'($P?.GetType() == typeof(int)) ?? false'

This operator is particularly useful in long statements where there are multiple accessors that could throw null exceptions. In this example, we assume Data can never be null when Value is not null.

'$P?.Value?.Data.ArrayData?\[4\]'
3.5 ComputedEvent

This markup extension allows you to create event handlers inline. Aside from allowing void functions, the code is identical to ComputedConverters. However, ComputedEvent exposes a number of variables by default.

$sender - The sender of the event.

$eventArgs - The arguments object of the event.

$dataContext - The data context of the sender.

$V0-$V9 - The values set on the ComputedEvent Vx properties.

$P0-$P4 - The values of the ComputedEvent.P0-ComputedEvent.P4 inherited attached properties on sender.

${name} - Any element within the name scope where {name} is the value of x:Name on that element.

An example:

<StackPanel c:ComputedEvent.P0="{Binding SomeValue}">
   <TextBlock x:Name="textField" />
   <Button Content="Click Me" Click="{c:ComputedEvent '$textField.Text = $dataContext.Transform($P0.Value)'}" />
</StackPanel>

The P0-P4 values are useful for crossing name scope boundaries like DataTemplates. Typically, when set outside a DataTemplate they will be inherited by the contents inside the DataTemplate. This allows you to easily make external controls and values accessible from inside DataTemplates.

3.6 ComputedValue

This markup extension evaluates exactly like a ComputedConverter except there are no P0-P9 variables, and it is evaluated at load. The markup extension returns the result of the expression.

4. Useful Markups

4.1 DynamicResource

Enable DynamicResource to support Binding.

 <Application.Resources>  
    <c:String
          x:Key="Guid"
          Value="b5ffd5f4-12c1-49ae-bb40-18da2f7643a7" />
</Application.Resources>

<TextBlock Text="{DynamicResource Guid}" />
<TextBlock Text="{c:DynamicResource Guid}" />
<TextBlock Text="{c:DynamicResource {Binding GuidKey}}" />
<TextBlock Text="{c:DynamicResource {Binding GuidKey, Mode=OneTime}}" />
[ObservableObject]
public partial class ViewModel : ReactiveObject
{
    [ObservableProperty]
    private string? guidKey = "Guid";
}
4.2 EventBinding

Binding any event from a delegate to an ICommand.

Note that there are differences with ComputedEvent.

<Window xmlns:c="http://schemas.github.com/computedconverters/2024/xaml"
        Drop="{c:EventBinding DropCommand}">
</Window>
[RelayCommand]
private void Drop(RelayEventParameter param)
{
    (object sender, DragEventArgs e) = param.Deconstruct<DragEventArgs>();
    // ...
}
4.3 Command

Used when RelayCommand is not used.

<Element Command={c:Command Execute} />
<Element Command={c:Command ExecuteWithArgumentAsync, CanExecute}
         CommandParameter={Binding Argument} />
class ViewModel
{
    public void Execute() {}

    public void ExecuteWithArgument(string arg) {}

    // The `Execute` method supports async, and its default `Can Execute` method will disable the command when it is busy.

    public Task ExecuteAsync() => Task.Completed;

    public Task ExecuteWithArgumentAsync(string arg) => Task.Completed;

    // The `Can Execute` method does not support async.

    public bool CanExecute() => true;

    public bool CanExecuteWithArgument(string arg) => true;
}
4.4 IfExtension

Use the Conditional expression in XAML.

<Button Command="{c:If {Binding BoolProperty},
                            {Binding OkCommand},
                            {Binding CancelCommand}}" />
<UserControl>
    <c:If Condition="{Binding IsLoading}">
        <c:If.True>
            <views:LoadingView />
        </c:If.True>
        <c:If.False>
            <views:LoadedView />
        </c:If.False>
    </c:If>
</UserControl>
4.5 SwitchExtension

Use the Switch expression in XAML.

<Image Source="{c:Switch {Binding FileType},
                              {c:Case {x:Static res:FileType.Music}, {StaticResource MusicIcon}},
                              {c:Case {x:Static res:FileType.Video}, {StaticResource VideoIcon}},
                              {c:Case {x:Static res:FileType.Picture}, {StaticResource PictureIcon}},
                              ...
                              {c:Case {StaticResource UnknownFileIcon}}}" />
<UserControl>
    <c:Switch To="{Binding SelectedViewName}">
        <c:Case Label="View1">
            <views:View1 />
        </c:Case>
        <c:Case Label="{x:Static res:Views.View2}">
            <views:View2 />
        </c:Case>
        <c:Case>
            <views:View404 />
        </c:Case>
    </c:Switch>
</UserControl>
4.6 Unbinding

Provide Binding/Markup value for some one not support binding.

<TextBlock Text="{DynamicResource {c:Unbinding {Binding GuidKey}}}" />

<TextBlock Text="{DynamicResource Guid}" />
class ViewModel
{
    private string GuidKey = "Guid";
}
4.7 Static

Difference from x:Static: supports static singleton.

<Label Content="{Binding StaticProperty, Source={c:Static local:StaticClass.Instance}}" />
public class StaticClass
{
    public static StaticClass Instance { get; } = new StaticClass();
    public string StaticProperty { get; set; } = "I'm a StaticProperty";
}
4.8 Converter

Use IValueConverter without Binding.

<TextBlock
    xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=System.Runtime"
    Text="Visiable on debugger attached."
    Visibility="{c:Converter Value={x:Static diagnostics:Debugger.IsAttached},
                                   Converter={x:Static c:BoolToVisibilityConverter.Instance}}" />
4.9 StringFormat

Use StringFormat without Binding and support Array<string>.

<ui:TitleBar.Title>
    <c:StringFormat Format="{}{0} v{1}">
        <c:StringFormat.Values>
            <x:Array Type="{c:TypeofString}">
                <x:Array.Items>
                    <c:String Value="ComputedConverters.Test" />
                    <x:Static Member="local:AppConfig.Version" />
                </x:Array.Items>
            </x:Array>
        </c:StringFormat.Values>
    </c:StringFormat>
</ui:TitleBar.Title>
4.10 ServiceLocator

Getting IoC support in xaml.

<Grid>
    <Grid.Resources>
        <c:SetServiceLocator Value="{c:Unbinding {Binding ServiceProvider, Source={c:Static local:App.Current}}}" />
    </Grid.Resources>
    <c:ServiceLocator Type="{x:Type local:ServiceLocatorTestPage}" />
</Grid>
public partial class App : Application
{
    public ServiceProvider ServiceProvider { get; }
}
public class ServiceLocatorTestPage : UserControl
{
    public ServiceLocatorTestPage()
    {
        AddChild(new TextBlock { Text = "I'm a page named ServiceLocatorTestPage" });
    }
}

5. ComputedBehaviors

Behaviors that make non bindable property to be bindable property.

<Button Height="50" Content="Please mouse over here">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseEnter">
            
            <b:ButtonSetStateToSourceAction Source="{Binding ButtonMouseOver, Mode=TwoWay}" Property="IsMouseOver" />
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseLeave">
            
            <b:ButtonSetStateToSourceAction Source="{Binding ButtonMouseOver, Mode=TwoWay}" Property="IsMouseOver" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>
<TextBox>
    <i:Interaction.Behaviors>
        <b:TextBoxBindingSupportBehavior
            SelectedText="{Binding SelectedText}"
            SelectionLength="{Binding SelectionLength}"
            SelectionStart="{Binding SelectionStart}" />
    </i:Interaction.Behaviors>
</TextBox>
<PasswordBox>
    <i:Interaction.Behaviors>
        <b:PasswordBoxBindingSupportBehavior Password="{Binding Password}" />
    </i:Interaction.Behaviors>
</PasswordBox>

6. ComputedAnimations

<Border a:Animations.Primary="{StaticResource FadeIn}" />

More Animations

Examples

Test / VSEnc

Thanks

Idea list here

https://github.com/OrgEleCho/EleCho.WpfSuite https://github.com/JohannesMoersch/QuickConverter https://github.com/thomasgalliker/ValueConverters.NET https://github.com/CommunityToolkit/Maui https://github.com/XAMLMarkupExtensions/XAMLMarkupExtensions https://github.com/DingpingZhang/WpfExtensions https://github.com/Kinnara/ModernWpf https://github.com/runceel/Livet https://github.com/XamlFlair/XamlFlair

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. 
.NET Framework net47 is compatible.  net471 is compatible.  net472 is compatible.  net48 is compatible.  net481 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
0.4.8 127 11/15/2024
0.4.7-beta 190 10/13/2024
0.4.6-beta 104 10/11/2024
0.4.5-beta 101 9/25/2024
0.4.4-beta 90 9/19/2024
0.4.3-beta 66 9/19/2024
0.4.2-beta 69 9/19/2024
0.4.1-beta 72 9/19/2024
0.4.0-beta 71 9/19/2024
0.3.9-beta 73 9/19/2024
0.3.8-beta 78 9/18/2024
0.3.7-beta 80 9/12/2024
0.3.6-beta 104 9/11/2024