using System.ComponentModel;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls.Primitives;
namespace XFEEDToolBox.Controls;
/// <summary>Keeps the initiating button available to cancel its running operation.</summary>
public static class AsyncAction
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
"Command", typeof(ICommand), typeof(AsyncAction), new PropertyMetadata(null, Changed));
public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.RegisterAttached(
"CancelCommand", typeof(ICommand), typeof(AsyncAction), new PropertyMetadata(null, Changed));
public static readonly DependencyProperty IsRunningProperty = DependencyProperty.RegisterAttached(
"IsRunning", typeof(bool), typeof(AsyncAction), new PropertyMetadata(false, Changed));
private static readonly DependencyProperty StateProperty = DependencyProperty.RegisterAttached(
"State", typeof(object), typeof(AsyncAction), new PropertyMetadata(null));
public static ICommand? GetCommand(DependencyObject obj) => (ICommand?)obj.GetValue(CommandProperty);
public static void SetCommand(DependencyObject obj, ICommand? value) => obj.SetValue(CommandProperty, value);
public static ICommand? GetCancelCommand(DependencyObject obj) => (ICommand?)obj.GetValue(CancelCommandProperty);
public static void SetCancelCommand(DependencyObject obj, ICommand? value) => obj.SetValue(CancelCommandProperty, value);
public static bool GetIsRunning(DependencyObject obj) => (bool)obj.GetValue(IsRunningProperty);
public static void SetIsRunning(DependencyObject obj, bool value) => obj.SetValue(IsRunningProperty, value);
private static void Changed(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
if (sender is not ButtonBase button) throw new ArgumentException("AsyncAction requires a button.");
if (button.GetValue(StateProperty) is not State state)
{
state = new State(button);
button.SetValue(StateProperty, state);
}
state.Rebind();
}
private sealed class State : ICommand
{
private readonly ButtonBase button;
private IAsyncRelayCommand? action;
private ICommand? cancel;
private bool subscribed;
private bool showingCancel;
private bool cancellationRequested;
private object? idleContent;
private string idleLabel = "";
private string idleName = "";
private IconElement? idleIcon;
public event EventHandler? CanExecuteChanged;
public State(ButtonBase button)
{
this.button = button;
button.Loaded += OnLoaded;
button.Unloaded += OnUnloaded;
button.Command = this;
}
public void Rebind()
{
Unsubscribe();
action = GetCommand(button) as IAsyncRelayCommand;
cancel = GetCancelCommand(button);
if (button.IsLoaded) Subscribe();
Update();
}
private bool Running => action?.IsRunning == true || GetIsRunning(button);
public bool CanExecute(object? parameter) => Running
? !cancellationRequested && (cancel?.CanExecute(parameter) ?? action?.CanBeCanceled == true)
: action?.CanExecute(parameter) == true;
public void Execute(object? parameter)
{
if (!CanExecute(parameter)) return;
if (Running)
{
cancellationRequested = true;
if (cancel is not null) cancel.Execute(parameter);
if (action?.CanBeCanceled == true) action.Cancel();
Update();
}
else action!.Execute(parameter);
}
private void OnLoaded(object sender, RoutedEventArgs args) { Subscribe(); Update(); }
private void OnUnloaded(object sender, RoutedEventArgs args) => Unsubscribe();
private void Subscribe()
{
if (subscribed) return;
if (action is not null) { action.PropertyChanged += OnChanged; action.CanExecuteChanged += OnCanExecuteChanged; }
if (cancel is not null) cancel.CanExecuteChanged += OnCanExecuteChanged;
subscribed = true;
}
private void Unsubscribe()
{
if (!subscribed) return;
if (action is not null) { action.PropertyChanged -= OnChanged; action.CanExecuteChanged -= OnCanExecuteChanged; }
if (cancel is not null) cancel.CanExecuteChanged -= OnCanExecuteChanged;
subscribed = false;
}
private void OnChanged(object? sender, PropertyChangedEventArgs args) => RequestUpdate();
private void OnCanExecuteChanged(object? sender, EventArgs args) => RequestUpdate();
private void RequestUpdate()
{
if (button.DispatcherQueue.HasThreadAccess) Update(); else button.DispatcherQueue.TryEnqueue(Update);
}
private void Update()
{
if (!Running) cancellationRequested = false;
if (button.IsLoaded && Running != showingCancel)
{
if (Running)
{
idleName = AutomationProperties.GetName(button);
if (button is AppBarButton appBar)
{
idleLabel = appBar.Label; idleIcon = appBar.Icon;
appBar.Label = "取消"; appBar.Icon = new SymbolIcon(Symbol.Cancel);
}
else { idleContent = button.Content; button.Content = "取消"; }
AutomationProperties.SetName(button, "取消");
}
else
{
if (button is AppBarButton appBar) { appBar.Label = idleLabel; appBar.Icon = idleIcon; }
else button.Content = idleContent;
AutomationProperties.SetName(button, idleName);
}
showingCancel = Running;
}
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
using System.ComponentModel;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls.Primitives;
namespace XFEEDToolBox.Controls;
/// <summary>Keeps the initiating button available to cancel its running operation.</summary>
public static class AsyncAction
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
"Command", typeof(ICommand), typeof(AsyncAction), new PropertyMetadata(null, Changed));
public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.RegisterAttached(
"CancelCommand", typeof(ICommand), typeof(AsyncAction), new PropertyMetadata(null, Changed));
public static readonly DependencyProperty IsRunningProperty = DependencyProperty.RegisterAttached(
"IsRunning", typeof(bool), typeof(AsyncAction), new PropertyMetadata(false, Changed));
private static readonly DependencyProperty StateProperty = DependencyProperty.RegisterAttached(
"State", typeof(object), typeof(AsyncAction), new PropertyMetadata(null));
public static ICommand? GetCommand(DependencyObject obj) => (ICommand?)obj.GetValue(CommandProperty);
public static void SetCommand(DependencyObject obj, ICommand? value) => obj.SetValue(CommandProperty, value);
public static ICommand? GetCancelCommand(DependencyObject obj) => (ICommand?)obj.GetValue(CancelCommandProperty);
public static void SetCancelCommand(DependencyObject obj, ICommand? value) => obj.SetValue(CancelCommandProperty, value);
public static bool GetIsRunning(DependencyObject obj) => (bool)obj.GetValue(IsRunningProperty);
public static void SetIsRunning(DependencyObject obj, bool value) => obj.SetValue(IsRunningProperty, value);
private static void Changed(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
if (sender is not ButtonBase button) throw new ArgumentException("AsyncAction requires a button.");
if (button.GetValue(StateProperty) is not State state)
{
state = new State(button);
button.SetValue(StateProperty, state);
}
state.Rebind();
}
private sealed class State : ICommand
{
private readonly ButtonBase button;
private IAsyncRelayCommand? action;
private ICommand? cancel;
private bool subscribed;
private bool showingCancel;
private bool cancellationRequested;
private object? idleContent;
private string idleLabel = "";
private string idleName = "";
private IconElement? idleIcon;
public event EventHandler? CanExecuteChanged;
public State(ButtonBase button)
{
this.button = button;
button.Loaded += OnLoaded;
button.Unloaded += OnUnloaded;
button.Command = this;
}
public void Rebind()
{
Unsubscribe();
action = GetCommand(button) as IAsyncRelayCommand;
cancel = GetCancelCommand(button);
if (button.IsLoaded) Subscribe();
Update();
}
private bool Running => action?.IsRunning == true || GetIsRunning(button);
public bool CanExecute(object? parameter) => Running
? !cancellationRequested && (cancel?.CanExecute(parameter) ?? action?.CanBeCanceled == true)
: action?.CanExecute(parameter) == true;
public void Execute(object? parameter)
{
if (!CanExecute(parameter)) return;
if (Running)
{
cancellationRequested = true;
if (cancel is not null) cancel.Execute(parameter);
if (action?.CanBeCanceled == true) action.Cancel();
Update();
}
else action!.Execute(parameter);
}
private void OnLoaded(object sender, RoutedEventArgs args) { Subscribe(); Update(); }
private void OnUnloaded(object sender, RoutedEventArgs args) => Unsubscribe();
private void Subscribe()
{
if (subscribed) return;
if (action is not null) { action.PropertyChanged += OnChanged; action.CanExecuteChanged += OnCanExecuteChanged; }
if (cancel is not null) cancel.CanExecuteChanged += OnCanExecuteChanged;
subscribed = true;
}
private void Unsubscribe()
{
if (!subscribed) return;
if (action is not null) { action.PropertyChanged -= OnChanged; action.CanExecuteChanged -= OnCanExecuteChanged; }
if (cancel is not null) cancel.CanExecuteChanged -= OnCanExecuteChanged;
subscribed = false;
}
private void OnChanged(object? sender, PropertyChangedEventArgs args) => RequestUpdate();
private void OnCanExecuteChanged(object? sender, EventArgs args) => RequestUpdate();
private void RequestUpdate()
{
if (button.DispatcherQueue.HasThreadAccess) Update(); else button.DispatcherQueue.TryEnqueue(Update);
}
private void Update()
{
if (!Running) cancellationRequested = false;
if (button.IsLoaded && Running != showingCancel)
{
if (Running)
{
idleName = AutomationProperties.GetName(button);
if (button is AppBarButton appBar)
{
idleLabel = appBar.Label; idleIcon = appBar.Icon;
appBar.Label = "取消"; appBar.Icon = new SymbolIcon(Symbol.Cancel);
}
else { idleContent = button.Content; button.Content = "取消"; }
AutomationProperties.SetName(button, "取消");
}
else
{
if (button is AppBarButton appBar) { appBar.Label = idleLabel; appBar.Icon = idleIcon; }
else button.Content = idleContent;
AutomationProperties.SetName(button, idleName);
}
showingCancel = Running;
}
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}