XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
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);
        }
    }
}