Skip to content

Commit f316f34

Browse files
Merge pull request #22 from wieslawsoltes/BindingTriggerBehavior
Binding trigger behavior
2 parents e5a6781 + 0517891 commit f316f34

File tree

6 files changed

+300
-93
lines changed

6 files changed

+300
-93
lines changed

samples/BehaviorsTestApplication/Views/MainView.axaml

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<TabItem Header="DataTriggerBehavior">
2323
<pages:DataTriggerBehaviorView />
2424
</TabItem>
25+
<TabItem Header="BindingTriggerBehavior">
26+
<pages:BindingTriggerBehaviorView />
27+
</TabItem>
2528
<TabItem Header="ValueChangedTriggerBehavior">
2629
<pages:ValueChangedTriggerBehaviorView />
2730
</TabItem>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<UserControl x:Class="BehaviorsTestApplication.Views.Pages.BindingTriggerBehaviorView"
2+
xmlns="https://github.com/avaloniaui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:vm="using:BehaviorsTestApplication.ViewModels"
7+
x:DataType="vm:MainWindowViewModel"
8+
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="450">
9+
<Design.DataContext>
10+
<vm:MainWindowViewModel />
11+
</Design.DataContext>
12+
<Grid RowDefinitions="*,Auto">
13+
<Rectangle Name="DataTriggerRectangle"
14+
Grid.Row="0" Margin="5"
15+
Fill="{DynamicResource BlueBrush}"
16+
Stroke="{DynamicResource GrayBrush}"
17+
StrokeThickness="5">
18+
<Interaction.Behaviors>
19+
<BindingTriggerBehavior Binding="{Binding #Slider.Value}"
20+
ComparisonCondition="GreaterThan"
21+
Value="50">
22+
<ChangePropertyAction TargetObject="DataTriggerRectangle"
23+
PropertyName="Fill"
24+
Value="{DynamicResource YellowBrush}" />
25+
</BindingTriggerBehavior>
26+
<BindingTriggerBehavior Binding="{Binding #Slider.Value}"
27+
ComparisonCondition="LessThanOrEqual"
28+
Value="50">
29+
<ChangePropertyAction TargetObject="DataTriggerRectangle"
30+
PropertyName="Fill"
31+
Value="{DynamicResource BlueBrush}" />
32+
</BindingTriggerBehavior>
33+
</Interaction.Behaviors>
34+
</Rectangle>
35+
<StackPanel Grid.Row="1"
36+
Margin="5,0,5,5"
37+
Orientation="Horizontal"
38+
HorizontalAlignment="Center"
39+
VerticalAlignment="Center">
40+
<TextBlock Text="{Binding #Slider.Value}"
41+
VerticalAlignment="Center"
42+
Width="50"
43+
Foreground="{DynamicResource GrayBrush}" />
44+
<Slider Name="Slider" Width="400" IsSnapToTickEnabled="True" TickFrequency="1" />
45+
</StackPanel>
46+
</Grid>
47+
</UserControl>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Markup.Xaml;
3+
4+
namespace BehaviorsTestApplication.Views.Pages;
5+
6+
public partial class BindingTriggerBehaviorView : UserControl
7+
{
8+
public BindingTriggerBehaviorView()
9+
{
10+
InitializeComponent();
11+
}
12+
13+
private void InitializeComponent()
14+
{
15+
AvaloniaXamlLoader.Load(this);
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using Avalonia.Data;
4+
using Avalonia.Reactive;
5+
using Avalonia.Xaml.Interactivity;
6+
7+
namespace Avalonia.Xaml.Interactions.Custom;
8+
9+
/// <summary>
10+
/// A behavior that performs actions when the bound data meets a specified condition.
11+
/// </summary>
12+
[RequiresUnreferencedCode("This functionality is not compatible with trimming.")]
13+
public class BindingTriggerBehavior : StyledElementTrigger
14+
{
15+
/// <summary>
16+
/// Identifies the <seealso cref="Binding"/> avalonia property.
17+
/// </summary>
18+
public static readonly StyledProperty<IBinding?> BindingProperty =
19+
AvaloniaProperty.Register<BindingTriggerBehavior, IBinding?>(nameof(Binding));
20+
21+
/// <summary>
22+
/// Identifies the <seealso cref="ComparisonCondition"/> avalonia property.
23+
/// </summary>
24+
public static readonly StyledProperty<ComparisonConditionType> ComparisonConditionProperty =
25+
AvaloniaProperty.Register<BindingTriggerBehavior, ComparisonConditionType>(nameof(ComparisonCondition));
26+
27+
/// <summary>
28+
/// Identifies the <seealso cref="Value"/> avalonia property.
29+
/// </summary>
30+
public static readonly StyledProperty<object?> ValueProperty =
31+
AvaloniaProperty.Register<BindingTriggerBehavior, object?>(nameof(Value));
32+
33+
private static readonly StyledProperty<object?> BindingValueProperty =
34+
AvaloniaProperty.Register<BindingTriggerBehavior, object?>(nameof(BindingValue));
35+
36+
private IDisposable? _dispose;
37+
38+
/// <summary>
39+
/// Gets or sets the bound object that the <see cref="BindingTriggerBehavior"/> will listen to. This is an avalonia property.
40+
/// </summary>
41+
[AssignBinding]
42+
public IBinding? Binding
43+
{
44+
get => GetValue(BindingProperty);
45+
set => SetValue(BindingProperty, value);
46+
}
47+
48+
/// <summary>
49+
/// Gets or sets the type of comparison to be performed between <see cref="BindingTriggerBehavior.Binding"/> and <see cref="BindingTriggerBehavior.Value"/>. This is an avalonia property.
50+
/// </summary>
51+
public ComparisonConditionType ComparisonCondition
52+
{
53+
get => GetValue(ComparisonConditionProperty);
54+
set => SetValue(ComparisonConditionProperty, value);
55+
}
56+
57+
/// <summary>
58+
/// Gets or sets the value to be compared with the value of <see cref="BindingTriggerBehavior.Binding"/>. This is an avalonia property.
59+
/// </summary>
60+
public object? Value
61+
{
62+
get => GetValue(ValueProperty);
63+
set => SetValue(ValueProperty, value);
64+
}
65+
66+
private object? BindingValue
67+
{
68+
get => GetValue(BindingValueProperty);
69+
set => SetValue(BindingValueProperty, value);
70+
}
71+
72+
static BindingTriggerBehavior()
73+
{
74+
ComparisonConditionProperty.Changed.Subscribe(
75+
new AnonymousObserver<AvaloniaPropertyChangedEventArgs<ComparisonConditionType>>(OnValueChanged));
76+
77+
ValueProperty.Changed.Subscribe(
78+
new AnonymousObserver<AvaloniaPropertyChangedEventArgs<object?>>(OnValueChanged));
79+
80+
BindingValueProperty.Changed.Subscribe(
81+
new AnonymousObserver<AvaloniaPropertyChangedEventArgs<object?>>(OnValueChanged));
82+
}
83+
84+
/// <inheritdoc />
85+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
86+
{
87+
base.OnPropertyChanged(change);
88+
89+
if (change.Property == BindingProperty)
90+
{
91+
_dispose?.Dispose();
92+
93+
var newValue = change.GetNewValue<IBinding?>();
94+
if (newValue is not null)
95+
{
96+
_dispose = Bind(BindingValueProperty, newValue);
97+
}
98+
}
99+
}
100+
101+
/// <inheritdoc />
102+
protected override void OnDetaching()
103+
{
104+
base.OnDetaching();
105+
106+
_dispose?.Dispose();
107+
}
108+
109+
private static void OnValueChanged(AvaloniaPropertyChangedEventArgs args)
110+
{
111+
if (args.Sender is not BindingTriggerBehavior behavior || behavior.AssociatedObject is null)
112+
{
113+
return;
114+
}
115+
116+
if (!behavior.IsEnabled)
117+
{
118+
return;
119+
}
120+
121+
// NOTE: In UWP version binding null check is not present but Avalonia throws exception as Bindings are null when first initialized.
122+
var binding = behavior.BindingValue;
123+
if (binding is not null)
124+
{
125+
// Some value has changed--either the binding value, reference value, or the comparison condition. Re-evaluate the equation.
126+
if (ComparisonConditionTypeHelper.Compare(behavior.BindingValue, behavior.ComparisonCondition,
127+
behavior.Value))
128+
{
129+
Interaction.ExecuteActions(behavior.AssociatedObject, behavior.Actions, args);
130+
}
131+
}
132+
}
133+
}

src/Avalonia.Xaml.Interactions/Core/DataTriggerBehavior.cs

+2-93
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
using System.Diagnostics.CodeAnalysis;
3-
using System.Globalization;
1+
using System.Diagnostics.CodeAnalysis;
42
using Avalonia.Reactive;
53
using Avalonia.Xaml.Interactivity;
64

@@ -69,95 +67,6 @@ static DataTriggerBehavior()
6967
new AnonymousObserver<AvaloniaPropertyChangedEventArgs<object?>>(OnValueChanged));
7068
}
7169

72-
private static bool Compare(object? leftOperand, ComparisonConditionType operatorType, object? rightOperand)
73-
{
74-
if (leftOperand is not null && rightOperand is not null)
75-
{
76-
var value = rightOperand.ToString();
77-
var destinationType = leftOperand.GetType();
78-
if (value is not null)
79-
{
80-
rightOperand = TypeConverterHelper.Convert(value, destinationType);
81-
}
82-
}
83-
84-
var leftComparableOperand = leftOperand as IComparable;
85-
var rightComparableOperand = rightOperand as IComparable;
86-
if (leftComparableOperand is not null && rightComparableOperand is not null)
87-
{
88-
return EvaluateComparable(leftComparableOperand, operatorType, rightComparableOperand);
89-
}
90-
91-
switch (operatorType)
92-
{
93-
case ComparisonConditionType.Equal:
94-
return Equals(leftOperand, rightOperand);
95-
96-
case ComparisonConditionType.NotEqual:
97-
return !Equals(leftOperand, rightOperand);
98-
99-
case ComparisonConditionType.LessThan:
100-
case ComparisonConditionType.LessThanOrEqual:
101-
case ComparisonConditionType.GreaterThan:
102-
case ComparisonConditionType.GreaterThanOrEqual:
103-
{
104-
throw leftComparableOperand switch
105-
{
106-
null when rightComparableOperand is null => new ArgumentException(string.Format(
107-
CultureInfo.CurrentCulture,
108-
"Binding property of type {0} and Value property of type {1} cannot be used with operator {2}.",
109-
leftOperand?.GetType().Name ?? "null", rightOperand?.GetType().Name ?? "null",
110-
operatorType.ToString())),
111-
null => new ArgumentException(string.Format(CultureInfo.CurrentCulture,
112-
"Binding property of type {0} cannot be used with operator {1}.",
113-
leftOperand?.GetType().Name ?? "null", operatorType.ToString())),
114-
_ => new ArgumentException(string.Format(CultureInfo.CurrentCulture,
115-
"Value property of type {0} cannot be used with operator {1}.",
116-
rightOperand?.GetType().Name ?? "null", operatorType.ToString()))
117-
};
118-
}
119-
}
120-
121-
return false;
122-
}
123-
124-
/// <summary>
125-
/// Evaluates both operands that implement the IComparable interface.
126-
/// </summary>
127-
private static bool EvaluateComparable(IComparable leftOperand, ComparisonConditionType operatorType, IComparable rightOperand)
128-
{
129-
object? convertedOperand = null;
130-
try
131-
{
132-
convertedOperand = Convert.ChangeType(rightOperand, leftOperand.GetType(), CultureInfo.CurrentCulture);
133-
}
134-
catch (FormatException)
135-
{
136-
// FormatException: Convert.ChangeType("hello", typeof(double), ...);
137-
}
138-
catch (InvalidCastException)
139-
{
140-
// InvalidCastException: Convert.ChangeType(4.0d, typeof(Rectangle), ...);
141-
}
142-
143-
if (convertedOperand is null)
144-
{
145-
return operatorType == ComparisonConditionType.NotEqual;
146-
}
147-
148-
var comparison = leftOperand.CompareTo((IComparable)convertedOperand);
149-
return operatorType switch
150-
{
151-
ComparisonConditionType.Equal => comparison == 0,
152-
ComparisonConditionType.NotEqual => comparison != 0,
153-
ComparisonConditionType.LessThan => comparison < 0,
154-
ComparisonConditionType.LessThanOrEqual => comparison <= 0,
155-
ComparisonConditionType.GreaterThan => comparison > 0,
156-
ComparisonConditionType.GreaterThanOrEqual => comparison >= 0,
157-
_ => false
158-
};
159-
}
160-
16170
private static void OnValueChanged(AvaloniaPropertyChangedEventArgs args)
16271
{
16372
if (args.Sender is not DataTriggerBehavior behavior || behavior.AssociatedObject is null)
@@ -175,7 +84,7 @@ private static void OnValueChanged(AvaloniaPropertyChangedEventArgs args)
17584
if (binding is not null)
17685
{
17786
// Some value has changed--either the binding value, reference value, or the comparison condition. Re-evaluate the equation.
178-
if (Compare(behavior.Binding, behavior.ComparisonCondition, behavior.Value))
87+
if (ComparisonConditionTypeHelper.Compare(behavior.Binding, behavior.ComparisonCondition, behavior.Value))
17988
{
18089
Interaction.ExecuteActions(behavior.AssociatedObject, behavior.Actions, args);
18190
}

0 commit comments

Comments
 (0)