diff --git a/samples/BehaviorsTestApplication/ViewModels/ItemViewModel.cs b/samples/BehaviorsTestApplication/ViewModels/ItemViewModel.cs index 42cdba25c..590a03cae 100644 --- a/samples/BehaviorsTestApplication/ViewModels/ItemViewModel.cs +++ b/samples/BehaviorsTestApplication/ViewModels/ItemViewModel.cs @@ -4,9 +4,10 @@ namespace BehaviorsTestApplication.ViewModels; public partial class ItemViewModel : ViewModelBase { - public ItemViewModel(string value) + public ItemViewModel(string value, string color = "Black") { _value = value; + _color = color; } [Reactive] @@ -15,5 +16,8 @@ public ItemViewModel(string value) [Reactive] public partial ObservableCollection? Items { get; set; } + [Reactive] + public partial string? Color { get; set; } + public override string ToString() => _value ?? string.Empty; } diff --git a/samples/BehaviorsTestApplication/ViewModels/MainWindowViewModel.cs b/samples/BehaviorsTestApplication/ViewModels/MainWindowViewModel.cs index 0e9a1960c..af08615df 100644 --- a/samples/BehaviorsTestApplication/ViewModels/MainWindowViewModel.cs +++ b/samples/BehaviorsTestApplication/ViewModels/MainWindowViewModel.cs @@ -36,7 +36,7 @@ public MainWindowViewModel() ResetMoveCommand = ReactiveCommand.Create(() => Position = 100.0); Items = [ - new("First Item") + new("First Item", "Red") { Items = [ @@ -44,7 +44,7 @@ public MainWindowViewModel() ] }, - new("Second Item") + new("Second Item", "Green") { Items = [ @@ -52,7 +52,7 @@ public MainWindowViewModel() ] }, - new("Third Item") + new("Third Item", "Blue") { Items = [ @@ -60,7 +60,7 @@ public MainWindowViewModel() ] }, - new("Fourth Item") + new("Fourth Item", "Orange") { Items = [ @@ -68,7 +68,7 @@ public MainWindowViewModel() ] }, - new("Fifth Item") + new("Fifth Item", "Purple") { Items = [ @@ -76,7 +76,7 @@ public MainWindowViewModel() ] }, - new("Sixth Item") + new("Sixth Item", "Pink") { Items = [ diff --git a/samples/BehaviorsTestApplication/Views/MainView.axaml b/samples/BehaviorsTestApplication/Views/MainView.axaml index 3856c46a3..ba6bf6f02 100644 --- a/samples/BehaviorsTestApplication/Views/MainView.axaml +++ b/samples/BehaviorsTestApplication/Views/MainView.axaml @@ -64,5 +64,8 @@ + + + diff --git a/samples/BehaviorsTestApplication/Views/Pages/BehaviorCollectionTemplateView.axaml b/samples/BehaviorsTestApplication/Views/Pages/BehaviorCollectionTemplateView.axaml new file mode 100644 index 000000000..ef5e61947 --- /dev/null +++ b/samples/BehaviorsTestApplication/Views/Pages/BehaviorCollectionTemplateView.axaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + diff --git a/samples/BehaviorsTestApplication/Views/Pages/BehaviorCollectionTemplateView.axaml.cs b/samples/BehaviorsTestApplication/Views/Pages/BehaviorCollectionTemplateView.axaml.cs new file mode 100644 index 000000000..b8767c9e3 --- /dev/null +++ b/samples/BehaviorsTestApplication/Views/Pages/BehaviorCollectionTemplateView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace BehaviorsTestApplication.Views.Pages; + +public partial class BehaviorCollectionTemplateView : UserControl +{ + public BehaviorCollectionTemplateView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} diff --git a/src/Avalonia.Xaml.Interactions.Custom/Actions/AddClassAction.cs b/src/Avalonia.Xaml.Interactions.Custom/Actions/AddClassAction.cs index eced00e44..f1fcd72c4 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/Actions/AddClassAction.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/Actions/AddClassAction.cs @@ -6,7 +6,7 @@ namespace Avalonia.Xaml.Interactions.Custom; /// /// Adds a specified to the collection when invoked. /// -public class AddClassAction : Avalonia.Xaml.Interactivity.Action +public class AddClassAction : Avalonia.Xaml.Interactivity.StyledElementAction { /// /// Identifies the avalonia property. @@ -27,7 +27,7 @@ public class AddClassAction : Avalonia.Xaml.Interactivity.Action AvaloniaProperty.Register(nameof(RemoveIfExists)); /// - /// Gets or sets the class name that should be added. This is a avalonia property. + /// Gets or sets the class name that should be added. This is an avalonia property. /// public string ClassName { @@ -36,7 +36,7 @@ public string ClassName } /// - /// Gets or sets the target styled element that class name that should be added to. This is a avalonia property. + /// Gets or sets the target styled element that class name that should be added to. This is an avalonia property. /// [ResolveByName] public StyledElement? StyledElement @@ -46,7 +46,7 @@ public StyledElement? StyledElement } /// - /// Gets or sets the flag indicated whether to remove the class if already exists before adding. This is a avalonia property. + /// Gets or sets the flag indicated whether to remove the class if already exists before adding. This is an avalonia property. /// public bool RemoveIfExists { diff --git a/src/Avalonia.Xaml.Interactions.Custom/Actions/ChangeAvaloniaPropertyAction.cs b/src/Avalonia.Xaml.Interactions.Custom/Actions/ChangeAvaloniaPropertyAction.cs index 7c82764fc..2aa7b0756 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/Actions/ChangeAvaloniaPropertyAction.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/Actions/ChangeAvaloniaPropertyAction.cs @@ -11,7 +11,7 @@ namespace Avalonia.Xaml.Interactions.Custom; /// An action that will change a specified Avalonia property to a specified value when invoked. /// [RequiresUnreferencedCode("This functionality is not compatible with trimming.")] -public class ChangeAvaloniaPropertyAction : Avalonia.Xaml.Interactivity.Action +public class ChangeAvaloniaPropertyAction : Avalonia.Xaml.Interactivity.StyledElementAction { /// /// Identifies the avalonia property. @@ -32,7 +32,7 @@ public class ChangeAvaloniaPropertyAction : Avalonia.Xaml.Interactivity.Action AvaloniaProperty.Register(nameof(Value)); /// - /// Gets or sets the name of the Avalonia property to change. This is a avalonia property. + /// Gets or sets the name of the Avalonia property to change. This is an avalonia property. /// public AvaloniaProperty? TargetProperty { @@ -41,7 +41,7 @@ public AvaloniaProperty? TargetProperty } /// - /// Gets or sets the value to set. This is a avalonia property. + /// Gets or sets the value to set. This is an avalonia property. /// public object? Value { @@ -51,7 +51,7 @@ public object? Value /// /// Gets or sets the Avalonia object whose property will be changed. - /// If is not set or cannot be resolved, the sender of will be used. This is a avalonia property. + /// If is not set or cannot be resolved, the sender of will be used. This is an avalonia property. /// [ResolveByName] public AvaloniaObject? TargetObject diff --git a/src/Avalonia.Xaml.Interactions.Custom/Actions/CloseNotificationAction.cs b/src/Avalonia.Xaml.Interactions.Custom/Actions/CloseNotificationAction.cs index 83bbad5d9..bc7057d17 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/Actions/CloseNotificationAction.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/Actions/CloseNotificationAction.cs @@ -5,7 +5,7 @@ namespace Avalonia.Xaml.Interactions.Custom; /// /// /// -public class CloseNotificationAction : Avalonia.Xaml.Interactivity.Action +public class CloseNotificationAction : Avalonia.Xaml.Interactivity.StyledElementAction { /// /// diff --git a/src/Avalonia.Xaml.Interactions.Custom/Actions/FocusControlAction.cs b/src/Avalonia.Xaml.Interactions.Custom/Actions/FocusControlAction.cs index 5a6dae2a2..9d0c661c4 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/Actions/FocusControlAction.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/Actions/FocusControlAction.cs @@ -7,7 +7,7 @@ namespace Avalonia.Xaml.Interactions.Custom; /// /// Focuses the associated or target control when executed. /// -public class FocusControlAction : Avalonia.Xaml.Interactivity.Action +public class FocusControlAction : Avalonia.Xaml.Interactivity.StyledElementAction { /// /// Identifies the avalonia property. @@ -16,7 +16,7 @@ public class FocusControlAction : Avalonia.Xaml.Interactivity.Action AvaloniaProperty.Register(nameof(TargetControl)); /// - /// Gets or sets the target control. This is a avalonia property. + /// Gets or sets the target control. This is an avalonia property. /// [ResolveByName] public Control? TargetControl diff --git a/src/Avalonia.Xaml.Interactions.Custom/Actions/PopupAction.cs b/src/Avalonia.Xaml.Interactions.Custom/Actions/PopupAction.cs index 52084728c..bed17fb95 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/Actions/PopupAction.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/Actions/PopupAction.cs @@ -10,7 +10,7 @@ namespace Avalonia.Xaml.Interactions.Custom; /// An action that displays a for the associated control when executed. /// /// If the associated control is of type than popup inherits control . -public class PopupAction : Avalonia.Xaml.Interactivity.Action +public class PopupAction : Avalonia.Xaml.Interactivity.StyledElementAction { private Popup? _popup; @@ -21,7 +21,7 @@ public class PopupAction : Avalonia.Xaml.Interactivity.Action AvaloniaProperty.Register(nameof(Child)); /// - /// Gets or sets the popup Child control. This is a avalonia property. + /// Gets or sets the popup Child control. This is an avalonia property. /// [Content] public Control? Child diff --git a/src/Avalonia.Xaml.Interactions.Custom/Actions/RemoveClassAction.cs b/src/Avalonia.Xaml.Interactions.Custom/Actions/RemoveClassAction.cs index df099531e..31e3901d2 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/Actions/RemoveClassAction.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/Actions/RemoveClassAction.cs @@ -6,7 +6,7 @@ namespace Avalonia.Xaml.Interactions.Custom; /// /// Removes a specified from collection when invoked. /// -public class RemoveClassAction : Avalonia.Xaml.Interactivity.Action +public class RemoveClassAction : Avalonia.Xaml.Interactivity.StyledElementAction { /// /// Identifies the avalonia property. @@ -21,7 +21,7 @@ public class RemoveClassAction : Avalonia.Xaml.Interactivity.Action AvaloniaProperty.Register(nameof(StyledElement)); /// - /// Gets or sets the class name that should be removed. This is a avalonia property. + /// Gets or sets the class name that should be removed. This is an avalonia property. /// public string ClassName { @@ -30,7 +30,7 @@ public string ClassName } /// - /// Gets or sets the target styled element that class name that should be removed from. This is a avalonia property. + /// Gets or sets the target styled element that class name that should be removed from. This is an avalonia property. /// [ResolveByName] public StyledElement? StyledElement diff --git a/src/Avalonia.Xaml.Interactions.Custom/Button/ButtonClickEventTriggerBehavior.cs b/src/Avalonia.Xaml.Interactions.Custom/Button/ButtonClickEventTriggerBehavior.cs index 6f9f7c08c..fb5e19e3d 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/Button/ButtonClickEventTriggerBehavior.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/Button/ButtonClickEventTriggerBehavior.cs @@ -19,7 +19,7 @@ public class ButtonClickEventTriggerBehavior : StyledElementTrigger [RequiresUnreferencedCode("This functionality is not compatible with trimming.")] -public class CallMethodAction : Avalonia.Xaml.Interactivity.Action +public class CallMethodAction : Avalonia.Xaml.Interactivity.StyledElementAction { private Type? _targetObjectType; private readonly List _methodDescriptors = []; @@ -33,7 +33,7 @@ public class CallMethodAction : Avalonia.Xaml.Interactivity.Action AvaloniaProperty.Register(nameof(TargetObject)); /// - /// Gets or sets the name of the method to invoke. This is a avalonia property. + /// Gets or sets the name of the method to invoke. This is an avalonia property. /// public string? MethodName { @@ -42,7 +42,7 @@ public string? MethodName } /// - /// Gets or sets the object that exposes the method of interest. This is a avalonia property. + /// Gets or sets the object that exposes the method of interest. This is an avalonia property. /// [ResolveByName] public object? TargetObject diff --git a/src/Avalonia.Xaml.Interactions/Core/ChangePropertyAction.cs b/src/Avalonia.Xaml.Interactions/Core/ChangePropertyAction.cs index f301c892a..40d47ddc2 100644 --- a/src/Avalonia.Xaml.Interactions/Core/ChangePropertyAction.cs +++ b/src/Avalonia.Xaml.Interactions/Core/ChangePropertyAction.cs @@ -11,7 +11,7 @@ namespace Avalonia.Xaml.Interactions.Core; /// An action that will change a specified property to a specified value when invoked. /// [RequiresUnreferencedCode("This functionality is not compatible with trimming.")] -public class ChangePropertyAction : Avalonia.Xaml.Interactivity.Action +public class ChangePropertyAction : Avalonia.Xaml.Interactivity.StyledElementAction { private static readonly char[] s_trimChars = ['(', ')']; private static readonly char[] s_separator = ['.']; @@ -90,7 +90,7 @@ public class ChangePropertyAction : Avalonia.Xaml.Interactivity.Action AvaloniaProperty.Register(nameof(Value)); /// - /// Gets or sets the name of the property to change. This is a avalonia property. + /// Gets or sets the name of the property to change. This is an avalonia property. /// public string? PropertyName { @@ -99,7 +99,7 @@ public string? PropertyName } /// - /// Gets or sets the value to set. This is a avalonia property. + /// Gets or sets the value to set. This is an avalonia property. /// public object? Value { @@ -109,7 +109,7 @@ public object? Value /// /// Gets or sets the object whose property will be changed. - /// If is not set or cannot be resolved, the sender of will be used. This is a avalonia property. + /// If is not set or cannot be resolved, the sender of will be used. This is an avalonia property. /// [ResolveByName] public object? TargetObject diff --git a/src/Avalonia.Xaml.Interactions/Core/DataTriggerBehavior.cs b/src/Avalonia.Xaml.Interactions/Core/DataTriggerBehavior.cs index eee6855c6..ee209d1ba 100644 --- a/src/Avalonia.Xaml.Interactions/Core/DataTriggerBehavior.cs +++ b/src/Avalonia.Xaml.Interactions/Core/DataTriggerBehavior.cs @@ -31,7 +31,7 @@ public class DataTriggerBehavior : StyledElementTrigger AvaloniaProperty.Register(nameof(Value)); /// - /// Gets or sets the bound object that the will listen to. This is a avalonia property. + /// Gets or sets the bound object that the will listen to. This is an avalonia property. /// public object? Binding { @@ -40,7 +40,7 @@ public object? Binding } /// - /// Gets or sets the type of comparison to be performed between and . This is a avalonia property. + /// Gets or sets the type of comparison to be performed between and . This is an avalonia property. /// public ComparisonConditionType ComparisonCondition { @@ -49,7 +49,7 @@ public ComparisonConditionType ComparisonCondition } /// - /// Gets or sets the value to be compared with the value of . This is a avalonia property. + /// Gets or sets the value to be compared with the value of . This is an avalonia property. /// public object? Value { diff --git a/src/Avalonia.Xaml.Interactions/Core/EventTriggerBehavior.cs b/src/Avalonia.Xaml.Interactions/Core/EventTriggerBehavior.cs index 58ed6440d..cee15f962 100644 --- a/src/Avalonia.Xaml.Interactions/Core/EventTriggerBehavior.cs +++ b/src/Avalonia.Xaml.Interactions/Core/EventTriggerBehavior.cs @@ -33,7 +33,7 @@ public class EventTriggerBehavior : StyledElementTrigger private bool _isLoadedEventRegistered; /// - /// Gets or sets the name of the event to listen for. This is a avalonia property. + /// Gets or sets the name of the event to listen for. This is an avalonia property. /// public string? EventName { @@ -43,7 +43,7 @@ public string? EventName /// /// Gets or sets the source object from which this behavior listens for events. - /// If is not set, the source will default to . This is a avalonia property. + /// If is not set, the source will default to . This is an avalonia property. /// [ResolveByName] public object? SourceObject diff --git a/src/Avalonia.Xaml.Interactions/Core/InvokeCommandAction.cs b/src/Avalonia.Xaml.Interactions/Core/InvokeCommandAction.cs index 02e5c046b..2720133a7 100644 --- a/src/Avalonia.Xaml.Interactions/Core/InvokeCommandAction.cs +++ b/src/Avalonia.Xaml.Interactions/Core/InvokeCommandAction.cs @@ -6,7 +6,7 @@ namespace Avalonia.Xaml.Interactions.Core; /// /// Executes a specified when invoked. /// -public class InvokeCommandAction : Interactivity.Action +public class InvokeCommandAction : Interactivity.StyledElementAction { /// /// Identifies the avalonia property. @@ -40,7 +40,7 @@ public class InvokeCommandAction : Interactivity.Action AvaloniaProperty.Register(nameof(InputConverterLanguage), string.Empty); /// - /// Gets or sets the command this action should invoke. This is a avalonia property. + /// Gets or sets the command this action should invoke. This is an avalonia property. /// public ICommand? Command { diff --git a/src/Avalonia.Xaml.Interactivity/StyledElementAction.cs b/src/Avalonia.Xaml.Interactivity/StyledElementAction.cs new file mode 100644 index 000000000..d1bb1fe3d --- /dev/null +++ b/src/Avalonia.Xaml.Interactivity/StyledElementAction.cs @@ -0,0 +1,45 @@ +using Avalonia.Controls; + +namespace Avalonia.Xaml.Interactivity; + +/// +/// A base class for action that calls a method on a specified object when invoked. +/// +public abstract class StyledElementAction : StyledElement, IAction +{ + /// + /// Identifies the avalonia property. + /// + public static readonly StyledProperty IsEnabledProperty = + AvaloniaProperty.Register(nameof(IsEnabled), defaultValue: true); + + /// + /// Gets or sets a value indicating whether this instance is enabled. + /// + /// true if this instance is enabled; otherwise, false. + public bool IsEnabled + { + get => GetValue(IsEnabledProperty); + set => SetValue(IsEnabledProperty, value); + } + + /// + /// Executes the action. + /// + /// The that is passed to the action by the behavior. Generally this is or a target object. + /// The value of this parameter is determined by the caller. + /// An example of parameter usage is EventTriggerBehavior, which passes the EventArgs as a parameter to its actions. + /// Returns the result of the action. + public abstract object? Execute(object? sender, object? parameter); + + internal void AttachActionToLogicalTree(StyledElement parent) + { + ((ISetLogicalParent)this).SetParent(null); + ((ISetLogicalParent)this).SetParent(parent); + } + + internal void DetachActionFromLogicalTree() + { + ((ISetLogicalParent)this).SetParent(null); + } +} diff --git a/src/Avalonia.Xaml.Interactivity/StyledElementBehavior.cs b/src/Avalonia.Xaml.Interactivity/StyledElementBehavior.cs index 4ed546f7a..1d6f80d0d 100644 --- a/src/Avalonia.Xaml.Interactivity/StyledElementBehavior.cs +++ b/src/Avalonia.Xaml.Interactivity/StyledElementBehavior.cs @@ -187,21 +187,18 @@ protected virtual void OnUnloaded() { } - private void AttachBehaviorToLogicalTree() + internal virtual void AttachBehaviorToLogicalTree() { - if (AssociatedObject is not StyledElement styledElement) + if (AssociatedObject is not StyledElement styledElement || styledElement.Parent is null) { return; } - if (styledElement.Parent is not null) - { - ((ISetLogicalParent)this).SetParent(null); - ((ISetLogicalParent)this).SetParent(styledElement); - } + ((ISetLogicalParent)this).SetParent(null); + ((ISetLogicalParent)this).SetParent(styledElement); } - private void DetachBehaviorFromLogicalTree() + internal virtual void DetachBehaviorFromLogicalTree() { ((ISetLogicalParent)this).SetParent(null); } diff --git a/src/Avalonia.Xaml.Interactivity/StyledElementTrigger.cs b/src/Avalonia.Xaml.Interactivity/StyledElementTrigger.cs index bf91a756b..3eb2111c1 100644 --- a/src/Avalonia.Xaml.Interactivity/StyledElementTrigger.cs +++ b/src/Avalonia.Xaml.Interactivity/StyledElementTrigger.cs @@ -16,8 +16,41 @@ public abstract class StyledElementTrigger : StyledElementBehavior, ITrigger private ActionCollection? _actions; /// - /// Gets the collection of actions associated with the behavior. This is a avalonia property. + /// Gets the collection of actions associated with the behavior. This is an avalonia property. /// [Content] public ActionCollection Actions => _actions ??= []; + + internal override void AttachBehaviorToLogicalTree() + { + base.AttachBehaviorToLogicalTree(); + + if (AssociatedObject is not StyledElement styledElement || styledElement.Parent is null) + { + return; + } + + var parent = this; + + foreach (var action in Actions) + { + if (action is StyledElementAction styledElementAction) + { + styledElementAction.AttachActionToLogicalTree(parent); + } + } + } + + internal override void DetachBehaviorFromLogicalTree() + { + base.DetachBehaviorFromLogicalTree(); + + foreach (var action in Actions) + { + if (action is StyledElementAction styledElementAction) + { + styledElementAction.DetachActionFromLogicalTree(); + } + } + } } diff --git a/src/Avalonia.Xaml.Interactivity/Trigger.cs b/src/Avalonia.Xaml.Interactivity/Trigger.cs index 2edae93c4..2a02312dc 100644 --- a/src/Avalonia.Xaml.Interactivity/Trigger.cs +++ b/src/Avalonia.Xaml.Interactivity/Trigger.cs @@ -16,7 +16,7 @@ public abstract class Trigger : Behavior, ITrigger private ActionCollection? _actions; /// - /// Gets the collection of actions associated with the behavior. This is a avalonia property. + /// Gets the collection of actions associated with the behavior. This is an avalonia property. /// [Content] public ActionCollection Actions => _actions ??= []; diff --git a/tests/Avalonia.Xaml.Interactivity.UnitTests/StubAction.cs b/tests/Avalonia.Xaml.Interactivity.UnitTests/StubAction.cs index edeccc4eb..7853503f5 100644 --- a/tests/Avalonia.Xaml.Interactivity.UnitTests/StubAction.cs +++ b/tests/Avalonia.Xaml.Interactivity.UnitTests/StubAction.cs @@ -1,6 +1,6 @@ namespace Avalonia.Xaml.Interactivity.UnitTests; -public class StubAction(object? returnValue) : Avalonia.Xaml.Interactivity.Action +public class StubAction(object? returnValue) : Avalonia.Xaml.Interactivity.StyledElementAction { public StubAction() : this(null) {