ComputedBehaviors.WPF
0.4.5-beta
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
<PackageReference Include="ComputedBehaviors.WPF" Version="0.4.5-beta" />
paket add ComputedBehaviors.WPF --version 0.4.5-beta
#r "nuget: ComputedBehaviors.WPF, 0.4.5-beta"
// 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
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
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
withCommunityToolkit.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:
- 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,
// …
}
- 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).
- 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.
- Expose enum property in the ViewModel.
[ObservableProperty]
private PartyMode partyMode;
- 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}" />
Examples
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 | Versions 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. |
-
.NETFramework 4.7
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
-
.NETFramework 4.7.1
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
-
.NETFramework 4.7.2
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
-
.NETFramework 4.8
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
-
.NETFramework 4.8.1
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
-
net6.0-windows7.0
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
-
net7.0-windows7.0
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
-
net8.0-windows7.0
- Microsoft.Xaml.Behaviors.Wpf (>= 1.1.135)
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 |