ComputedAnimations.WPF 0.3.7-beta

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

// Install ComputedAnimations.WPF as a Cake Tool
#tool nuget:?package=ComputedAnimations.WPF&version=0.3.7-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

ComputedConverters.WPF NuGet

ComputedConverters.Avalonia (TBD)

ValueConverters.Avalonia Version

Usage

Add XML namespace to your XAML file:

<Window xmlns:c="http://schemas.github.com/computedconverters/2024/xaml">
</Window>

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

And also ReactiveCollection<T> / 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))
    }
}

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 Markup

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 Markup

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" });
    }
}

Examples

Test

VSEnc

Thanks

https://github.com/emako/ComputedConverters.NET/discussions/2

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 114 11/15/2024
0.4.7-beta 195 10/13/2024
0.4.6-beta 108 10/11/2024
0.4.5-beta 93 9/25/2024
0.4.4-beta 92 9/19/2024
0.4.3-beta 69 9/19/2024
0.4.2-beta 72 9/19/2024
0.4.1-beta 73 9/19/2024
0.4.0-beta 73 9/19/2024
0.3.9-beta 72 9/19/2024
0.3.8-beta 77 9/18/2024
0.3.7-beta 79 9/12/2024