zoukankan      html  css  js  c++  java
  • 【C#/WPF】图像变换的Undo撤销——用Stack命令栈

    需求:
    图层中有一张图片,可以对该图层进行平移、缩放、旋转操作,现在要求做Undo撤销功能,使得图层回复上一步操作时的状态。

    关于图像的平移、缩放、旋转,可以参考在下的另一篇博客的整理:

    http://blog.csdn.net/qq_18995513/article/details/72765269

    问题:
    C#中系统自带的Undo是针对文字编辑的撤销,而项目需求中是对图层图片的Transform变换属性的修改进行撤销。

    思路:

    • 图层是自定义的类,图层对象除了包含该图片外,带有大量的属性(比如很多其他自定义的属性),如果做Undo撤销是用一个List集合记录每一步操作时图层的所有属性,那么该List数据会很庞大,且保存了很多做Undo撤销时不需要的属性数据。
    • 改为记录各种操作命令Command,比如平移就只记录是平移操作的命令,并记下平移的X,Y值变化量。之后的Undo撤销就是执行反方向平移即可。
    • 因为Undo撤销是记录多步骤后,可以一步一步地往回撤,所以考虑改用Stack堆栈数据结构来记录每一步命令(而不是用List线性表)。
    • 命令栈的属性设计:
      • 一个记录操作类型的枚举属性,如记录当前修改操作是图像放大,则Undo撤销时执行图像缩小。
      • 一个记录附加数据的float[]数组,如记录当前修改操作的平移X轴往右移100,Y轴往上移200,则Undo撤销时执行反方向平移,即X轴往左移100,Y轴往下移200。当然,因为该图像可能还有很多其他类型的数据,为了命令栈的通用性,可以把这个数组类型改为Object[],即可存放任意附加数据。

    下面定义这样一个命令栈:CommandStack

    public class CommandStack
    {
        // 记录操作的类型
        public enum CommandType
        {
            Move,       // 平移
            ZoomIn,     // 放大
            ZoomOut,    // 缩小
            RotateLeft,     // 左转
            RotateRight,    // 右转
        }
    
        // 命令栈中存放的元素对象
        public class CommandInfo
        {
            public Image img { get; set; } // 被操作的前台Image控件
            public CommandType CommandType { get; set; } // 操作的类型
            public object[] Object { get; set; }         // 记录操作的数据
        }
    
        public static Stack UndoStack; // Undo撤销栈
        static CommandStack()
        {
            UndoStack = new Stack(); // 构造函数中实例化
        }
    
        /// <summary>
        /// 往Undo撤销命令栈中添加一个元素
        /// </summary>
        /// <param name="commandimgType">被操作的Image控件</param>
        /// <param name="commandType">命令的种类</param>
        /// <param name="obj">附带的数据</param>
        public static void Add(Image img, CommandType commandType, object[] obj = null)
        {
            CommandInfo commandInfo = new CommandInfo();
            commandInfo.Image = img;
            commandInfo.CommandType = commandType;
            commandInfo.Object = obj;
    
            // 压入栈中,这里没有考虑栈的容量
            UndoStack.Push(commandInfo);
        }
    
    }

    前台XAML中对该Image控件Transform组:

    <Image x:Name="targetImage">
        <Image.RenderTransform>
            <TransformGroup>
                <TranslateTransform/>
                <ScaleTransform/>
                <RotateTransform/>
            </TransformGroup>
        </Image.RenderTransform>
    </Image>

    平移图像后,将本次平移操作记入命令栈:X轴正方向+100,Y轴正方向+200。

    CommandStack.Add(targetImage, CommandType.Move, new object[]{ 100, 200 });

    Undo撤销按钮的操作:

    public void UndoCommand()
    {
        if (CommandStack.UndoStack.Count == 0)
        {
            // 已经撤销到头了
            MessageBox.Show("无法再往前撤销了!");
            return;
        }
    
        // 栈顶元素出栈,并获得它的引用
        CommandStack.CommandInfo commandInfo = CommandStack.UndoStack.Pop() as CommandStack.CommandInfo;
    
        // 获得被操作的Image控件
        Image img = commandInfo.Image;
    
        // 根据操作的类型,分类处理
        switch (commandInfo.CommandType)
        {
            case CommandStack.CommandType.Move:     // 撤销平移,X、Y值取相反的值
                double translationX = commandInfo.Parameters[0]; // 注意:是相对于上一次位置的平移,不是相对于原始位置的Offset!
                double translationY = commandInfo.Parameters[1];
                UndoMove(img, translationX, translationY);
                break;
    
            case CommandStack.CommandType.ZoomIn:   // 撤销放大,即要缩小
                ZoomOut(img);
                break;
    
            case CommandStack.CommandType.ZoomOut:  // 撤销缩小,即要放大
                ZoomIn(img);
                break;
    
            case CommandStack.CommandType.RotateLeft:   // 撤销左转,即要右转
                RotateRight(img);
                break;
    
            case CommandStack.CommandType.RotateRight:  // 撤销右转,即要左转
                RotateLeft(img);
                break;
        }
    }
    
    
    /// <summary>
    /// 撤销平移
    /// </summary>
    /// <param name="img">被操作的前台Image控件</param>
    /// <param name="translationX">X轴相对于上一次的偏移,不是相对于原始位置!</param>
    /// <param name="translationY">Y轴相对于上一次的偏移,不是相对于原始位置!</param>
    private void UndoMove(Image img, double translationX, double translationY)
    {
        TransformGroup tg = img.RenderTransform as TransformGroup;
        var tgnew = tg.CloneCurrentValue();
        if (tgnew != null)
        {
            TranslateTransform transform = tgnew.Children[0] as TranslateTransform;
            transform.X += translationX;
            transform.Y += translationY;
    
            // 重新给图像赋值Transform变换属性
            img.RenderTransform = tgnew;
        }
    }
    
    
    /// <summary>
    /// 图像缩小
    /// </summary>
    /// <param name="img">被操作的前台Image控件</param>
    public void ZoomOut(Image img)
    {
        TransformGroup tg = img.RenderTransform as TransformGroup;
        var tgnew = tg.CloneCurrentValue();
        if (tgnew != null)
        {
            ScaleTransform st = tgnew.Children[1] as ScaleTransform;
            img.RenderTransformOrigin = new Point(0.5, 0.5);
            if (st.ScaleX >= 0.2)
            {
                st.ScaleX -= 0.05;
                st.ScaleY -= 0.05;
            }
            else if (st.ScaleX <= -0.2)
            {
                st.ScaleX += 0.05;
                st.ScaleY -= 0.05;
            }
        }
    
        // 重新给图像赋值Transform变换属性
        img.RenderTransform = tgnew;
    }
    
    
    /// <summary>
    /// 图片放大
    /// </summary>
    /// <param name="img">被操作的前台Image控件</param>
    public void ZoomIn(Image img)
    {
        TransformGroup tg = img.RenderTransform as TransformGroup;
        var tgnew = tg.CloneCurrentValue();
        if (tgnew != null)
        {
            ScaleTransform st = tgnew.Children[1] as ScaleTransform;
            img.RenderTransformOrigin = new Point(0.5, 0.5);
            if (st.ScaleX > 0 && st.ScaleX <= 2.0)
            {
                st.ScaleX += 0.05;
                st.ScaleY += 0.05;
            }
            else if (st.ScaleX < 0 && st.ScaleX >= -2.0)
            {
                st.ScaleX -= 0.05;
                st.ScaleY += 0.05;
            }
        }
    
        // 重新给图像赋值Transform变换属性
        img.RenderTransform = tgnew;
    }
    
    /// <summary>
    /// 图片左转
    /// </summary>
    /// <param name="img">被操作的前台Image控件</param>
    public void RotateLeft(Image img)
    {
        TransformGroup tg = img.RenderTransform as TransformGroup;
        var tgnew = tg.CloneCurrentValue();
        if (tgnew != null)
        {
            RotateTransform rt = tgnew.Children[2] as RotateTransform;
            img.RenderTransformOrigin = new Point(0.5, 0.5);
            rt.Angle -= 5;
        }
    
        // 重新给图像赋值Transform变换属性
        img.RenderTransform = tgnew;
    }
    
    /// <summary>
    /// 图片右转
    /// </summary>
    /// <param name="img">被操作的前台Image控件</param>
    public void RotateRight(Image img)
    {
        TransformGroup tg = img.RenderTransform as TransformGroup;
        var tgnew = tg.CloneCurrentValue();
        if (tgnew != null)
        {
            RotateTransform rt = tgnew.Children[2] as RotateTransform;
            img.RenderTransformOrigin = new Point(0.5, 0.5);
            rt.Angle += 5;
        }
    
        // 重新给图像赋值Transform变换属性
        img.RenderTransform = tgnew;
    }

    题外话:
    如果还想做个Redo重做功能,即跟Undo撤销反向的功能,可以考虑用两个Stack栈。
    在CommandStack类中再加一个RedoStack栈,思路是把Undo撤销时UndoStack栈顶移出的元素存放到RedoStack栈中!

  • 相关阅读:
    设计模式学习——代理模式(Proxy Pattern)之 强制代理(强校验,防绕过)
    设计模式学习——代理模式(Proxy Pattern)
    设计模式学习——抽象工厂模式(Abstract Factory Pattern)
    最长字符串系列汇总
    窗口的最大值与最小值更新结构(滑动窗口)
    归并排序和归并排序应用(逆序对+小和)
    位运算在编程题的一些作用
    链表的排序(归并排序+快慢指针)
    Manacher算法解决最长回文子串长度问题
    回文数字的验证
  • 原文地址:https://www.cnblogs.com/guxin/p/csharp-wpf-image-transform-undo-by-command-stack.html
Copyright © 2011-2022 走看看