From 4c21b8ab80f55dceb81b08c6fcb7fbda8d6c39f9 Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Tue, 25 Feb 2025 07:03:47 -0500 Subject: [PATCH 1/6] Improve binding logic and logging in ActionMessage.cs - Updated binding initialization to use object initializer syntax for better readability. - Enhanced logging for `source` enabled status, including checks for `source.Name`. - Added logging to indicate whether `source` is enabled or disabled after `CanExecute` check. - Implemented null checks for `source` and its `DataContext` to prevent null reference exceptions. --- src/Caliburn.Micro.Platform/ActionMessage.cs | 24 +++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Caliburn.Micro.Platform/ActionMessage.cs b/src/Caliburn.Micro.Platform/ActionMessage.cs index 8e09cf48..e4f42750 100644 --- a/src/Caliburn.Micro.Platform/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/ActionMessage.cs @@ -315,8 +315,9 @@ void ElementLoaded(object sender, RoutedEventArgs e) Log.Info($"Binding {binding.Source}"); #elif (NET || CAL_NETCORE) && !WinUI3 && !WINDOWS_UWP - var binding = new Binding { - Path = new PropertyPath(Message.HandlerProperty), + var binding = new Binding + { + Path = new PropertyPath(Message.HandlerProperty), Source = currentElement }; #elif WINDOWS_UWP || WinUI3 @@ -536,7 +537,23 @@ public override string ToString() if (!hasBinding && context.CanExecute != null) { Log.Info($"ApplyAvailabilityEffect CanExecute {context.CanExecute()} - {context.Method.Name}"); - source.IsEnabled = context.CanExecute(); + if (!string.IsNullOrEmpty(source.Name)) + { + Log.Info($"ApplyAvailabilityEffect CanExecute {context.CanExecute()} - {context.Method.Name} - {source.Name}"); + source.IsEnabled = context.CanExecute(); + } + else + { + Log.Info("Skipping is enabled because source Name is not set"); + } + if (!source.IsEnabled) + { + Log.Info($"Disabled {source.Name}"); + } + else + { + Log.Info($"Enabled {source.Name}"); + } } #endif Log.Info($"ApplyAvailabilityEffect source enabled {source.IsEnabled}"); @@ -615,6 +632,7 @@ public override string ToString() } } #else + if (source != null && source.DataContext != null) { var target = source.DataContext; From bce4876464967858f3565b6b25d079c6471676fb Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Tue, 25 Feb 2025 07:12:02 -0500 Subject: [PATCH 2/6] update logging message --- src/Caliburn.Micro.Platform/ActionMessage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caliburn.Micro.Platform/ActionMessage.cs b/src/Caliburn.Micro.Platform/ActionMessage.cs index e4f42750..cb4aa587 100644 --- a/src/Caliburn.Micro.Platform/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/ActionMessage.cs @@ -544,7 +544,7 @@ public override string ToString() } else { - Log.Info("Skipping is enabled because source Name is not set"); + Log.Info("Skipping IsEnabled source because source Name is not set"); } if (!source.IsEnabled) { From 102b522a4c6b258f188f690833d9b6cc2fd046de Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Tue, 25 Feb 2025 16:36:32 -0500 Subject: [PATCH 3/6] Add IgnoreGuard property to ActionExecutionContext Enhanced ActionExecutionContext and ActionMessage classes with a new IgnoreGuard property to control guard condition checks during action execution. --- .../ActionExecutionContext.cs | 29 +++++++++++++------ src/Caliburn.Micro.Platform/ActionMessage.cs | 8 +++-- .../Platforms/Maui/ActionMessage.cs | 1 + 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs index 88e951dd..9f3a0f4c 100644 --- a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs +++ b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs @@ -33,7 +33,8 @@ namespace Caliburn.Micro /// /// The context used during the execution of an Action or its guard. /// - public class ActionExecutionContext : IDisposable { + public class ActionExecutionContext : IDisposable + { private WeakReference _message; private WeakReference _source; private WeakReference _target; @@ -51,6 +52,8 @@ public class ActionExecutionContext : IDisposable { /// public object EventArgs; + public bool IgnoreGuard { get; set; } + /// /// The actual method info to be invoked. /// @@ -59,7 +62,8 @@ public class ActionExecutionContext : IDisposable { /// /// The message being executed. /// - public ActionMessage Message { + public ActionMessage Message + { get { return _message == null ? null : _message.Target as ActionMessage; } set { _message = new WeakReference(value); } } @@ -67,7 +71,8 @@ public ActionMessage Message { /// /// The source from which the message originates. /// - public FrameworkElement Source { + public FrameworkElement Source + { get { return _source == null ? null : _source.Target as FrameworkElement; } set { _source = new WeakReference(value); } } @@ -75,7 +80,8 @@ public FrameworkElement Source { /// /// The instance on which the action is invoked. /// - public object Target { + public object Target + { get { return _target == null ? null : _target.Target; } set { _target = new WeakReference(value); } } @@ -83,7 +89,8 @@ public object Target { /// /// The view associated with the target. /// - public DependencyObject View { + public DependencyObject View + { get { return _view == null ? null : _view.Target as DependencyObject; } set { _view = new WeakReference(value); } } @@ -93,8 +100,10 @@ public DependencyObject View { /// /// The data key. /// Custom data associated with the context. - public object this[string key] { - get { + public object this[string key] + { + get + { if (_values == null) _values = new Dictionary(); @@ -103,7 +112,8 @@ public object this[string key] { return result; } - set { + set + { if (_values == null) _values = new Dictionary(); @@ -114,7 +124,8 @@ public object this[string key] { /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - public void Dispose() { + public void Dispose() + { Disposing(this, System.EventArgs.Empty); } diff --git a/src/Caliburn.Micro.Platform/ActionMessage.cs b/src/Caliburn.Micro.Platform/ActionMessage.cs index cb4aa587..b3b82626 100644 --- a/src/Caliburn.Micro.Platform/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/ActionMessage.cs @@ -161,6 +161,8 @@ public string MethodName set { SetValue(MethodNameProperty, value); } } + public bool IgnoreGuard { get; set; } + /// /// Gets the parameters to pass as part of the method invocation. /// @@ -366,7 +368,8 @@ void UpdateContext() _context = new ActionExecutionContext { Message = this, - Source = AssociatedObject + Source = AssociatedObject, + IgnoreGuard = IgnoreGuard }; PrepareContext(_context); @@ -529,7 +532,6 @@ public override string ToString() Log.Info($"context.CanExecute is null {context.CanExecute == null} "); if (context.CanExecute != null) { - Log.Info("HERE"); Log.Info($"ApplyAvailabilityEffect CanExecute {context.Method.Name}"); source.IsEnabled = context.CanExecute(); } @@ -537,7 +539,7 @@ public override string ToString() if (!hasBinding && context.CanExecute != null) { Log.Info($"ApplyAvailabilityEffect CanExecute {context.CanExecute()} - {context.Method.Name}"); - if (!string.IsNullOrEmpty(source.Name)) + if (!context.IgnoreGuard) { Log.Info($"ApplyAvailabilityEffect CanExecute {context.CanExecute()} - {context.Method.Name} - {source.Name}"); source.IsEnabled = context.CanExecute(); diff --git a/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs b/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs index 8c3bd56d..c212e4d1 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs @@ -49,6 +49,7 @@ public ActionMessage() /// The name of the method. public string MethodName { get; set; } + public bool IgnoreGuard { get; set; } /// /// The handler for the action. /// From 527f169f74bc7c7b1e3dde76b060c2fba7f4637b Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Sat, 8 Mar 2025 14:13:55 -0500 Subject: [PATCH 4/6] Add SkipAvailabilityResolution property for actions Introduce a new property `SkipAvailabilityResolution` in the `Caliburn.Micro` namespace to control the skipping of availability resolution during action execution. - Added `SkipAvailabilityResolution` to `ActionExecutionContext` and replaced `IgnoreGuard`. - Updated `ActionMessage` to include the new property for bypassing availability checks. - Defined a new dependency property `SkipAvailabilityResolutionProperty` in `Message.cs` for UI element attachment. - Modified `Parser.cs` to utilize the new property when interpreting message text. These changes enhance the flexibility of action execution by allowing developers to specify when to skip availability checks. --- src/Caliburn.Micro.Platform/Action.cs | 5 +- .../ActionExecutionContext.cs | 18 +++++++- src/Caliburn.Micro.Platform/ActionMessage.cs | 6 +-- src/Caliburn.Micro.Platform/Message.cs | 46 ++++++++++++++++++- src/Caliburn.Micro.Platform/Parser.cs | 6 +-- .../Platforms/Maui/ActionMessage.cs | 2 +- .../Platforms/Xamarin.Forms/ActionMessage.cs | 1 + 7 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/Caliburn.Micro.Platform/Action.cs b/src/Caliburn.Micro.Platform/Action.cs index 376756a5..0c8eb650 100644 --- a/src/Caliburn.Micro.Platform/Action.cs +++ b/src/Caliburn.Micro.Platform/Action.cs @@ -12,8 +12,8 @@ namespace Caliburn.Micro using System.Reflection; #elif WinUI3 using System.Linq; - using Microsoft.UI.Xaml; using System.Reflection; + using Microsoft.UI.Xaml; #elif XFORMS using UIElement = global::Xamarin.Forms.Element; using FrameworkElement = global::Xamarin.Forms.VisualElement; @@ -174,7 +174,8 @@ public static void Invoke(object target, string methodName, DependencyObject vie Message = message, View = view, Source = source, - EventArgs = eventArgs + EventArgs = eventArgs, + SkipAvailabilityResolution = Message.GetSkipAvailabilityResolution(view) }; if (parameters != null) diff --git a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs index 9f3a0f4c..68c887bc 100644 --- a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs +++ b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs @@ -52,12 +52,28 @@ public class ActionExecutionContext : IDisposable /// public object EventArgs; - public bool IgnoreGuard { get; set; } + /// + /// Gets or sets a value indicating whether to skip availability resolution. + /// + public bool SkipAvailabilityResolution + { + get + { + if (!_skipAvailabilityResolution) + { + //var skipProperty = DependencyPropertyHelper.Get + } + return _skipAvailabilityResolution; + } + + set => _skipAvailabilityResolution = value; + } /// /// The actual method info to be invoked. /// public MethodInfo Method; + private bool _skipAvailabilityResolution; /// /// The message being executed. diff --git a/src/Caliburn.Micro.Platform/ActionMessage.cs b/src/Caliburn.Micro.Platform/ActionMessage.cs index b3b82626..a55d1518 100644 --- a/src/Caliburn.Micro.Platform/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/ActionMessage.cs @@ -161,7 +161,7 @@ public string MethodName set { SetValue(MethodNameProperty, value); } } - public bool IgnoreGuard { get; set; } + public bool SkipAvailabilityResolution { get; set; } /// /// Gets the parameters to pass as part of the method invocation. @@ -369,7 +369,7 @@ void UpdateContext() { Message = this, Source = AssociatedObject, - IgnoreGuard = IgnoreGuard + SkipAvailabilityResolution = SkipAvailabilityResolution }; PrepareContext(_context); @@ -539,7 +539,7 @@ public override string ToString() if (!hasBinding && context.CanExecute != null) { Log.Info($"ApplyAvailabilityEffect CanExecute {context.CanExecute()} - {context.Method.Name}"); - if (!context.IgnoreGuard) + if (!context.SkipAvailabilityResolution) { Log.Info($"ApplyAvailabilityEffect CanExecute {context.CanExecute()} - {context.Method.Name} - {source.Name}"); source.IsEnabled = context.CanExecute(); diff --git a/src/Caliburn.Micro.Platform/Message.cs b/src/Caliburn.Micro.Platform/Message.cs index 658e4e8e..df2288f4 100644 --- a/src/Caliburn.Micro.Platform/Message.cs +++ b/src/Caliburn.Micro.Platform/Message.cs @@ -50,6 +50,7 @@ namespace Caliburn.Micro /// public static class Message { + internal const string Skip_Availability_Resolution = "SkipAvailabilityResolution"; internal static readonly DependencyProperty HandlerProperty = #if AVALONIA AvaloniaProperty.RegisterAttached("Handler", typeof(Message)); @@ -107,11 +108,28 @@ public static object GetHandler(DependencyObject d) "Attach", typeof(string), typeof(Message), - null, + null, OnAttachChanged ); #endif + /// + /// A property definition representing weather should check if the function can be executed + /// + public static readonly DependencyProperty SkipAvailabilityResolutionProperty = +#if AVALONIA + AvaloniaProperty.RegisterAttached(Skip_Availability_Resolution, typeof(Message)); +#else + DependencyPropertyHelper.RegisterAttached( + Skip_Availability_Resolution, + typeof(string), + typeof(Message), + null, + OnAttachChanged + ); +#endif + + #if AVALONIA static Message() { @@ -138,6 +156,29 @@ public static string GetAttach(DependencyObject d) return d.GetValue(AttachProperty) as string; } + /// + /// Sets the attached triggers and messages. + /// + /// The element to attach to. + /// The parsable attachment text. + public static void SetSkipAvailabilityResolution(DependencyObject d, string skip) + { + d.SetValue(SkipAvailabilityResolutionProperty, skip); + } + + /// + /// Gets the attached triggers and messages. + /// + /// The element that was attached to. + /// The parsable attachment text. + public static bool GetSkipAvailabilityResolution(DependencyObject d) + { + var value = d.GetValue(SkipAvailabilityResolutionProperty) as string; + if (!bool.TryParse(value, out bool result)) + result = false; + return result; + } + static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (object.ReferenceEquals(e.NewValue, e.OldValue)) @@ -162,7 +203,8 @@ static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventAr var allTriggers = visualElement != null ? visualElement.Triggers : new List(); - if (messageTriggers != null) { + if (messageTriggers != null) + { messageTriggers.Apply(x => allTriggers.Remove(x)); } diff --git a/src/Caliburn.Micro.Platform/Parser.cs b/src/Caliburn.Micro.Platform/Parser.cs index 5db13223..f49c0c69 100644 --- a/src/Caliburn.Micro.Platform/Parser.cs +++ b/src/Caliburn.Micro.Platform/Parser.cs @@ -76,7 +76,7 @@ public static class Parser /// The message text. /// The triggers parsed from the text. #if AVALONIA - public static IEnumerable Parse(DependencyObject target, string text) + public static IEnumerable Parse(DependencyObject target, string text) #else public static IEnumerable Parse(DependencyObject target, string text) #endif @@ -232,7 +232,7 @@ private static void AddActionToTrigger(DependencyObject target, TriggerAction me /// /// The parameters passed to the method are the the target of the trigger and string representing the trigger. public static Func CreateTrigger = (target, triggerText) => - + #else /// /// The function used to generate a trigger. @@ -302,7 +302,7 @@ public static TriggerAction CreateMessage(DependencyObject target, string messag /// public static Func InterpretMessageText = (target, text) => { - return new ActionMessage { MethodName = Regex.Replace(text, "^Action", string.Empty).Trim() }; + return new ActionMessage { MethodName = Regex.Replace(text, "^Action", string.Empty).Trim(), SkipAvailabilityResolution = Message.GetSkipAvailabilityResolution(target) }; }; /// diff --git a/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs b/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs index c212e4d1..ec89b25b 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Maui/ActionMessage.cs @@ -49,7 +49,7 @@ public ActionMessage() /// The name of the method. public string MethodName { get; set; } - public bool IgnoreGuard { get; set; } + public bool SkipAvailabilityResolution { get; set; } /// /// The handler for the action. /// diff --git a/src/Caliburn.Micro.Platform/Platforms/Xamarin.Forms/ActionMessage.cs b/src/Caliburn.Micro.Platform/Platforms/Xamarin.Forms/ActionMessage.cs index 2b7258dc..a191ad9b 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Xamarin.Forms/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Xamarin.Forms/ActionMessage.cs @@ -32,6 +32,7 @@ public class ActionMessage : TriggerActionBase, IHaveParameters /// True by default. public static bool ThrowsExceptions = true; + public bool SkipAvailabilityResolution { get; set; } = false; /// /// Creates an instance of . /// From f68f2fe354a0d984f292e59d831269293a7f3289 Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Sat, 8 Mar 2025 14:36:01 -0500 Subject: [PATCH 5/6] code suggestion --- src/Caliburn.Micro.Platform/ActionExecutionContext.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs index 68c887bc..fb938ed8 100644 --- a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs +++ b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs @@ -57,15 +57,7 @@ public class ActionExecutionContext : IDisposable /// public bool SkipAvailabilityResolution { - get - { - if (!_skipAvailabilityResolution) - { - //var skipProperty = DependencyPropertyHelper.Get - } - return _skipAvailabilityResolution; - } - + get => _skipAvailabilityResolution; set => _skipAvailabilityResolution = value; } From e3c9f5e09729241a99927dd4422783fd8d683a34 Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Sat, 8 Mar 2025 14:38:31 -0500 Subject: [PATCH 6/6] Update src/Caliburn.Micro.Platform/Message.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Caliburn.Micro.Platform/Message.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caliburn.Micro.Platform/Message.cs b/src/Caliburn.Micro.Platform/Message.cs index df2288f4..45bcd117 100644 --- a/src/Caliburn.Micro.Platform/Message.cs +++ b/src/Caliburn.Micro.Platform/Message.cs @@ -114,7 +114,7 @@ public static object GetHandler(DependencyObject d) #endif /// - /// A property definition representing weather should check if the function can be executed + /// A property definition representing whether should check if the function can be executed /// public static readonly DependencyProperty SkipAvailabilityResolutionProperty = #if AVALONIA