ComputedConverters.WPF 0.3.0-beta

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

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

Avalonia (TBD)

Usage

Add XML namespace to your XAML file:

<Window xmlns:c="http://schemas.lemutec.cn/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 vuejs reactive(T[]).

Ref<T> is similar to vuejs 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

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.lemutec.cn/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>

Examples

Test

VSEnc

Thanks

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

Product Compatible and additional computed target framework versions.
.NET net5.0-windows7.0 is compatible.  net6.0-windows was computed.  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 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.
  • .NETFramework 4.7.2

    • No dependencies.
  • .NETFramework 4.8

    • No dependencies.
  • .NETFramework 4.8.1

    • No dependencies.
  • net5.0-windows7.0

    • No dependencies.
  • net6.0-windows7.0

    • No dependencies.
  • net7.0-windows7.0

    • No dependencies.
  • net8.0-windows7.0

    • No dependencies.

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.9 110 11/25/2024
0.4.8 122 11/15/2024
0.4.7-beta 195 10/13/2024
0.4.6-beta 103 10/11/2024
0.4.5-beta 104 9/25/2024
0.4.4-beta 96 9/19/2024
0.4.3-beta 68 9/19/2024
0.4.2-beta 69 9/19/2024
0.4.1-beta 71 9/19/2024
0.4.0-beta 74 9/19/2024
0.3.9-beta 79 9/19/2024
0.3.8-beta 76 9/18/2024
0.3.7-beta 76 9/12/2024
0.3.5-beta 95 9/9/2024
0.3.4-beta 76 9/9/2024
0.3.3-beta 85 9/6/2024
0.3.2-beta 81 9/6/2024
0.3.1-beta 78 9/6/2024
0.3.0-beta 81 9/6/2024
0.2.3-beta 83 9/4/2024
0.2.2-beta 76 9/4/2024
0.2.1-beta 77 9/4/2024
0.2.0-beta 80 9/1/2024
0.1.4-beta 71 8/29/2024
0.1.3-beta 84 8/26/2024
0.1.2-beta 73 8/26/2024
0.1.1-beta 80 8/26/2024
0.1.0-beta 69 8/26/2024