【C#/WPF】图像变换的Undo撤销——用Stack命令栈

简介: 原文:【C#/WPF】图像变换的Undo撤销——用Stack命令栈 需求: 图层中有一张图片,可以对该图层进行平移、缩放、旋转操作,现在要求做Undo撤销功能,使得图层回复上一步操作时的状态。
原文: 【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栈中!

目录
相关文章
|
2月前
|
数据采集 JavaScript C#
C#图像爬虫实战:从Walmart网站下载图片
C#图像爬虫实战:从Walmart网站下载图片
C# WPF 中 外部图标引入iconfont,无法正常显示问题 【小白记录】
本文介绍了在C# WPF应用程序中引入外部iconfont图标时可能遇到的显示问题及其解决方法:1) 检查资源路径和引入格式是否正确,确保字体文件引用格式为“#xxxx”,并正确指向字体文件位置;2) 确保图标资源被包含在程序集中,通过设置字体文件的生成操作为Resource(资源)来实现。
C# WPF 中 外部图标引入iconfont,无法正常显示问题 【小白记录】
|
2月前
|
编解码 C# 数据库
C# + WPF 音频播放器 界面优雅,体验良好
【9月更文挑战第18天】这是一个用 C# 和 WPF 实现的音频播放器示例,界面简洁美观,功能丰富。设计包括播放/暂停按钮、进度条、音量控制滑块、歌曲列表和专辑封面显示。功能实现涵盖音频播放、进度条控制、音量调节及歌曲列表管理。通过响应式设计、动画效果、快捷键支持和错误处理,提升用户体验。可根据需求扩展更多功能。
101 3
|
3月前
|
C#
C# WPF 将第三方DLL嵌入 exe
C# WPF 将第三方DLL嵌入 exe
73 0
|
3月前
|
前端开发 C# 容器
WPF/C#:实现导航功能
WPF/C#:实现导航功能
62 0
|
3月前
|
设计模式 测试技术 C#
WPF/C#:在WPF中如何实现依赖注入
WPF/C#:在WPF中如何实现依赖注入
61 0
|
3月前
|
前端开发 C# Windows
WPF/C#:如何实现拖拉元素
WPF/C#:如何实现拖拉元素
50 0
|
3月前
|
存储 C# 索引
WPF/C#:BusinessLayerValidation
WPF/C#:BusinessLayerValidation
33 0
|
5天前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
16 3
|
26天前
|
安全 C# 数据安全/隐私保护
实现C#编程文件夹加锁保护
【10月更文挑战第16天】本文介绍了两种用 C# 实现文件夹保护的方法:一是通过设置文件系统权限,阻止普通用户访问;二是使用加密技术,对文件夹中的文件进行加密,防止未授权访问。提供了示例代码和使用方法,适用于不同安全需求的场景。