需求:
图层中有一张图片,可以对该图层进行平移、缩放、旋转操作,现在要求做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栈中!

【C#/WPF】图像变换的Undo撤销——用Stack命令栈的更多相关文章

  1. WPF Datagrid with some read-only rows - Stack Overflow

    原文:WPF Datagrid with some read-only rows - Stack Overflow up vote 21 down vote accepted I had the sa ...

  2. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  3. [CareerCup] 3.2 Min Stack 最小栈

    3.2 How would you design a stack which, in addition to push and pop, also has a function min which r ...

  4. C++数据结构之Stack(栈)

    stack,栈,是好比堆积木似的数据结构,从上之下堆积,取出时按"LIFO"-last int first out后进先出的规则.栈一般为线程所独有,也就是每个线程有其自有的栈,与 ...

  5. heap(堆)和stack(栈)的区别

    heap是堆,stack是栈 stack的空间由操作系统自动分配/释放,heap上的空间手动分配/释放. stack空间有限,heap是很大的自由存储区 C中的malloc函数分配的内存空间即在hea ...

  6. Stack(栈)

    Stack(栈)是一种后进先出的数据结构,下面介绍一下栈的具体运用: 一.Stack 中的 empty 函数 stack<int> s( 5 , 10) ; s.empty()  ;   ...

  7. 数据结构与算法之Stack(栈)——in dart

    用dart 语言实现一个简单的stack(栈).栈的内部用List实现. class Stack<E> { final List<E> _stack; final int ca ...

  8. heap是堆,stack是栈

    1.栈是用来存放基本类型的变量和引用类型的变量,堆用来存放new出来的对象和数组. 2.栈的存取速度快,但不灵活.堆的存取速度慢,但是存取灵活,空间动态分配. 3.栈在建立在连续的物理位置上,而堆只需 ...

  9. [LeetCode] Min Stack 最小栈

    Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. pu ...

随机推荐

  1. iOS支付宝支付集成

    概述 iOS支付宝支付集成 详细 代码下载:http://www.demodashi.com/demo/10729.html 支付宝和微信都是业界的老大哥,相信大家都有所觉得文档.SDK都是各种坑吧( ...

  2. 执行存储过程报错——ora-01031:权限不足

    1. 执行DDL报错 在oracle存储过程中,默认是可以直接执行DML和DQL的,但是执行CREATE这种的DDL则需要借助EXECUTE IMMEDIATE ···了,如下备份表语句 --抄表表备 ...

  3. [转载]在rhel 6 x86_64 上安装oracle 11g xe

    原文地址:在rhel 6 x86_64 上安装oracle 11g xe作者:pccom Oracle 11g xe for linux目前只有x86_64 版本,没有i386, i686 版本,如果 ...

  4. Google map API V3

    本文主要总结Google map API V3使用中最简单也是最常见的一些操作以及相关概念,如果需要更加详细的信息,请直接阅读Google提供的关于map的文档. google map api v3文 ...

  5. SELECT控件add方法 ie 类型不匹配

    s = document.createElement('select'); try{ //for ie8 or earlier s.add(new Option('text','value'),s.o ...

  6. windows平台安装redis服务

    有时候我们需要在windows平台上使用redis作为缓存服务,这个时候就需要将redis安装为window服务. 现在将我安装过程记录下来,方便自己或者有类似需求的同学参考.主要是参考网上现有的例子 ...

  7. Asp.Net Core 轻松学-一行代码搞定文件上传 JSONHelper

    Asp.Net Core 轻松学-一行代码搞定文件上传   前言     在 Web 应用程序开发过程中,总是无法避免涉及到文件上传,这次我们来聊一聊怎么去实现一个简单方便可复用文件上传功能:通过创建 ...

  8. 什么是IIS应用程序池

    IIS应用程序池是将一个或多个应用程序链接到一个或多个工作进程集合的配置.因为应用程序池中的应用程序与其他应用程序被工作进程边界分隔,所以某个应用程序池中的应用程序不会受到其他应用程序池中应用程序所产 ...

  9. python学习笔记——fork()创建多进程

    1 进程概述 引自 Python 多进程 fork()详解 1.1 进程 进程是程序的一次动态执行过程,它对应了从代码加载.执行到执行完毕的一个完整过程. 进程是系统进行资源分配和调度的一个独立单位. ...

  10. Jmeter -----计数器(counter)

    计数器的定义 Allows the user to create a counter that can be referenced anywhere in the Thread Group. The ...