实现一个管理器,用于实现Redo和Undo。

UndoRedoManager类

    public class UndoRedoManager
    {
        private readonly Dictionary<AvaloniaObject, ObjectState> objectStates = new();
        private bool isRestoring;
        public event EventHandler? StateChanged;

        // 内部类,封装对象的状态
        private sealed class ObjectState
        {
            public List<(AvaloniaProperty Property, object? OldValue, object? NewValue)> History { get; set; } = new();
            public int HistoryIndex { get; set; }
            public List<IDisposable> Subscriptions { get; } = new();
            public HashSet<AvaloniaProperty> TrackedProperties { get; } = new();
        }

        // 缓存类型属性查找结果,避免重复反射
        private static readonly Dictionary<Type, Dictionary<string, AvaloniaProperty>> propertyCache = new();

        public void Attach<T>(T target, params Expression<Func<T, object>>[] properties) where T : AvaloniaObject
        {
            if (properties == null || properties.Length == 0)
            {
                InternAttach(target, Array.Empty<AvaloniaProperty>());
                return;
            }

            var avaloniaProperties = new AvaloniaProperty[properties.Length];
            var targetType = target.GetType();
            int validCount = 0;

            for (int i = 0; i < properties.Length; i++)
            {
                var expr = properties[i];
                var propPath = GetPropertyPath(expr.Body);
                if (string.IsNullOrEmpty(propPath))
                {
                    Console.WriteLine($"警告:无法解析属性表达式 '{expr}'");
                    continue;
                }

                var propName = propPath.Split('.').Last();
                var avaloniaProperty = GetAvaloniaProperty(targetType, propName);
                if (avaloniaProperty != null)
                {
                    avaloniaProperties[validCount++] = avaloniaProperty;
                }
                else
                {
                    Console.WriteLine($"警告:在类型 '{targetType.Name}' 上找不到属性 '{propName}'");
                }
            }

            if (validCount < avaloniaProperties.Length)
            {
                Array.Resize(ref avaloniaProperties, validCount);
            }

            InternAttach(target, avaloniaProperties);
        }

        public void Attach(AvaloniaObject target, params AvaloniaProperty[] properties)
        {
            InternAttach(target, properties);
        }

        private void InternAttach(AvaloniaObject target, AvaloniaProperty[] properties)
        {
            if (objectStates.ContainsKey(target))
            {
                Console.WriteLine($"提示:目标对象已附加,跳过重复附加");
                return;
            }

            var objectState = new ObjectState();
            objectStates[target] = objectState;

            if (properties == null || properties.Length == 0)
            {
                Console.WriteLine($"提示:{target.GetType().Name}未指定属性,将跟踪目标对象的所有 Avalonia 属性");
                properties = GetAllAvaloniaProperties(target);
            }

            objectState.TrackedProperties.UnionWith(properties);

            void Handler(object? sender, AvaloniaPropertyChangedEventArgs e)
            {
                if (isRestoring || e.Sender != target || !objectStates.TryGetValue(target, out var state))
                    return;

                if (state.TrackedProperties.Contains(e.Property))
                {
                    Record(target, e.Property, e.OldValue, e.NewValue);
                }
            }

            target.PropertyChanged += Handler;
            objectState.Subscriptions.Add(new DisposableAction(() => target.PropertyChanged -= Handler));

            if (target is Visual visual)
            {
                void DetachedHandler(object? s, VisualTreeAttachmentEventArgs a)
                {
                    Detach(target);
                    visual.DetachedFromVisualTree -= DetachedHandler;
                }
                visual.DetachedFromVisualTree += DetachedHandler;
                objectState.Subscriptions.Add(new DisposableAction(() => visual.DetachedFromVisualTree -= DetachedHandler));
            }
        }

        private static string GetPropertyPath(Expression expr)
        {
            if (expr == null) return "";

            var pathBuilder = new StringBuilder();
            var currentExpr = expr;
            var stack = new Stack<string>();

            while (currentExpr != null)
            {
                switch (currentExpr)
                {
                    case MemberExpression memberExpr:
                        stack.Push(memberExpr.Member.Name);
                        currentExpr = memberExpr.Expression;
                        break;

                    case UnaryExpression unaryExpr when unaryExpr.NodeType == ExpressionType.Convert:
                        currentExpr = unaryExpr.Operand;
                        break;

                    case MethodCallExpression methodCallExpr when methodCallExpr.Method.Name == "get_Item":
                        var indexValue = GetIndexValue(methodCallExpr);
                        stack.Push($"[{indexValue}]");
                        currentExpr = methodCallExpr.Object;
                        break;

                    case ParameterExpression:
                        currentExpr = null;
                        break;

                    case ConstantExpression constantExpr:
                        stack.Push(constantExpr.Value?.ToString() ?? "constant");
                        currentExpr = null;
                        break;

                    default:
                        Console.WriteLine($"警告:不支持的表达式类型 {currentExpr.GetType().Name}");
                        currentExpr = null;
                        break;
                }
            }

            while (stack.Count > 0)
            {
                var part = stack.Pop();
                if (pathBuilder.Length > 0)
                {
                    if (!part.StartsWith("[") && pathBuilder[pathBuilder.Length - 1] != '[')
                    {
                        pathBuilder.Append(".");
                    }
                }
                pathBuilder.Append(part);
            }

            return pathBuilder.Length > 0 ? pathBuilder.ToString() : "";
        }

        private static string GetIndexValue(MethodCallExpression methodCallExpr)
        {
            if (methodCallExpr.Arguments.Count == 0)
                return "";

            var indexArg = methodCallExpr.Arguments[0];

            if (indexArg is ConstantExpression constantExpr)
            {
                return constantExpr.Value?.ToString() ?? "";
            }

            if (indexArg is MemberExpression memberExpr)
            {
                return GetPropertyPath(memberExpr);
            }

            return "index";
        }

        private static AvaloniaProperty? GetAvaloniaProperty(Type type, string propertyName)
        {
            if (!propertyCache.TryGetValue(type, out var propertyMap))
            {
                propertyMap = new Dictionary<string, AvaloniaProperty>(StringComparer.OrdinalIgnoreCase);
                var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
                    .Where(f => typeof(AvaloniaProperty).IsAssignableFrom(f.FieldType));

                foreach (var field in fields)
                {
                    if (field.GetValue(null) is AvaloniaProperty property)
                    {
                        propertyMap[property.Name] = property;
                    }
                }

                propertyCache[type] = propertyMap;
            }

            propertyMap.TryGetValue(propertyName, out var result);
            return result;
        }

        private static AvaloniaProperty[] GetAllAvaloniaProperties(AvaloniaObject target)
        {
            var type = target.GetType();
            if (!propertyCache.TryGetValue(type, out var propertyMap))
            {
                propertyMap = new Dictionary<string, AvaloniaProperty>(StringComparer.OrdinalIgnoreCase);
                var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
                    .Where(f => typeof(AvaloniaProperty).IsAssignableFrom(f.FieldType));

                foreach (var field in fields)
                {
                    if (field.GetValue(null) is AvaloniaProperty property)
                    {
                        propertyMap[property.Name] = property;
                    }
                }

                propertyCache[type] = propertyMap;
            }

            Console.WriteLine($"提示:找到 {propertyMap.Count} 个属性");

            var result = new AvaloniaProperty[propertyMap.Count];
            propertyMap.Values.CopyTo(result, 0);
            return result;
        }

        public void Detach(AvaloniaObject target)
        {
            if (!objectStates.TryGetValue(target, out var state))
            {
                Console.WriteLine($"提示:目标对象未附加,无需分离");
                return;
            }

            for (int i = state.Subscriptions.Count - 1; i >= 0; i--)
            {
                state.Subscriptions[i].Dispose();
            }

            objectStates.Remove(target);
            Console.WriteLine($"提示:已成功分离目标对象");
        }

        private void Record(AvaloniaObject target, AvaloniaProperty property, object? oldValue, object? newValue)
        {
            if (Equals(oldValue, newValue)) return;
            if (!objectStates.TryGetValue(target, out var state)) return;

            var list = state.History;
            var index = state.HistoryIndex;

            if (index < list.Count)
            {
                list.RemoveRange(index, list.Count - index);
            }

            list.Add((property, oldValue, newValue));
            state.HistoryIndex = index + 1;
            StateChanged?.Invoke(this, EventArgs.Empty);
            Console.WriteLine($"记录:{target.GetType().Name}.{property.Name} 从 '{oldValue}' 变更为 '{newValue}'");
        }

        public void Undo(AvaloniaObject target)
        {
            if (!CanUndo(target) || !objectStates.TryGetValue(target, out var state))
            {
                return;
            }

            var index = state.HistoryIndex - 1;
            var entry = state.History[index];
            isRestoring = true;
            try
            {
                target.SetCurrentValue(entry.Property, entry.OldValue);
                state.HistoryIndex = index;
                StateChanged?.Invoke(this, EventArgs.Empty);
                Console.WriteLine($"撤销:{target.GetType().Name}.{entry.Property.Name}, '{entry.NewValue}' 恢复为 '{entry.OldValue}'");
            }
            finally
            {
                isRestoring = false;
            }
        }

        public void Redo(AvaloniaObject target)
        {
            if (!CanRedo(target) || !objectStates.TryGetValue(target, out var state))
            {
                return;
            }

            var index = state.HistoryIndex;
            var entry = state.History[index];
            isRestoring = true;
            try
            {
                target.SetCurrentValue(entry.Property, entry.NewValue);
                state.HistoryIndex = index + 1;
                StateChanged?.Invoke(this, EventArgs.Empty);
                Console.WriteLine($"重做:{target.GetType().Name}.{entry.Property.Name} , '{entry.OldValue}' 变更为 '{entry.NewValue}'");
            }
            finally
            {
                isRestoring = false;
            }
        }

        public bool CanUndo(AvaloniaObject target)
        {
            return objectStates.TryGetValue(target, out var state) && state.HistoryIndex > 0;
        }

        public bool CanRedo(AvaloniaObject target)
        {
            return objectStates.TryGetValue(target, out var state) && state.HistoryIndex < state.History.Count;
        }

        public IRelayCommand GetUndoCommand(AvaloniaObject target)
        {
            var command = new RelayCommand(
                () => Undo(target),
                () => CanUndo(target)
            );

            this.StateChanged += (_, _) => command.NotifyCanExecuteChanged();

            return command;
        }

        public IRelayCommand GetRedoCommand(AvaloniaObject target)
        {
            var command = new RelayCommand(
                () => Redo(target),
                () => CanRedo(target)
            );

            this.StateChanged += (_, _) => command.NotifyCanExecuteChanged();

            return command;
        }

        private sealed class DisposableAction : IDisposable
        {
            private Action? action;

            public DisposableAction(Action action) => this.action = action;

            public void Dispose()
            {
                var toExecute = Interlocked.Exchange(ref action, null);
                toExecute?.Invoke();
            }
        }
    }

MonitorCommands.axaml代码

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Height="320" Width="339.2"
         xmlns:local ="clr-namespace:AvaloniaUI;assembly=AvaloniaUI"
         x:DataType="local:MonitorCommands"
        x:Class="AvaloniaUI.MonitorCommands"
        Title="MonitorCommands">
    <Grid RowDefinitions="auto,*">
        <ToolBarTray Margin="5">
            <ToolBar>
                <Button Command="{Binding UndoCommand}">Undo</Button>
                <Button Command="{Binding RedoCommand}">Redo</Button>
            </ToolBar>
        </ToolBarTray>

        <TextBox Name="txt" Margin="5" Grid.Row="1"
             TextWrapping="Wrap" AcceptsReturn="True">
        </TextBox>
    </Grid>
</Window>

MonitorCommands.axmal.cs代码

using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using CommunityToolkit.Mvvm.Input;
using Shares.Avalonia;
using System;
using System.Windows.Input;

namespace AvaloniaUI;

public partial class MonitorCommands : Window
{
    private readonly UndoRedoManager manager = new UndoRedoManager();
    public IRelayCommand UndoCommand {  get; set; }
    public IRelayCommand RedoCommand { get; set; }
    public MonitorCommands()
    {
        InitializeComponent();

        manager.Attach(txt, TextBox.TextProperty);
        UndoCommand = manager.GetUndoCommand(txt);
        RedoCommand = manager.GetRedoCommand(txt);

        this.DataContext = this;

        /*

        var textBox1 = new TextBox();
        var textBox2 = new TextBox();

        manager.Attach(textBox1, x=>x.Text!);
        manager.Attach(textBox2, TextBox.TextProperty);

        textBox1.Text = "Hello";
        textBox1.Text = "World";

        textBox2.Text = "Foo";
        textBox2.Text = "Bar";

        Console.WriteLine("textBox1 当前 Text: " + textBox1.Text); // World
        Console.WriteLine("textBox2 当前 Text: " + textBox2.Text); // Bar

        manager.Undo(textBox1);
        Console.WriteLine("textBox1 Undo 后: " + textBox1.Text); // Hello

        manager.Redo(textBox2);
        Console.WriteLine("textBox2 Redo 后: " + textBox2.Text); // Bar(没有变化,因为 redo 栈为空)

        manager.Undo(textBox1);
        manager.Redo(textBox1);

        Console.WriteLine($"textBox1 Undo, Redo 后:{textBox1.Text}");
        Console.WriteLine("textBox1 CanUndo: " + manager.CanUndo(textBox1)); // True,初始值null存在
        Console.WriteLine("textBox2 CanRedo: " + manager.CanRedo(textBox2)); // False
        */
    }

}

运行效果

image

 

posted on 2025-08-23 15:16  dalgleish  阅读(15)  评论(0)    收藏  举报