谈到代码编辑器,基本功能的“撤销与重做”是必不可少的。
刚好最近看了设计模式的“命令模式”,做这个倒是正好
简单来说,就是把所以可以撤销的方法封装成类
这里有个简单的测试例子,演示了用“命令模式”实现的“撤销”功能
这里是两个基本接口
// 命令接口,所有能被编辑器接受命令都从这里继承
public interface ICommand
{
void Execute();
}
// 可撤销的命令借口,所有可撤销的命令都从这里继承
public interface IUndoCommand : ICommand
{
void Undo();
}
接下来是具体的命令
// 插入一个字符到编辑器的命令
public class InsertCharacterCommand : IUndoCommand
{
private CodeManager codeManager;
private int index;
private char ch;
public InsertCharacterCommand(CodeManager setCodeManager, int setIndex, char setCh)
{
this.codeManager = setCodeManager;
this.index = setIndex;
this.ch = setCh;
}
public void Execute()
{
codeManager.InserCharacter(index, ch);
}
public void Undo()
{
codeManager.RemoveCharacter(index);
}
}
这里用到了CodeManager,这个马上说,其他的就很简单了,实现了接口的两个函数Execute和Undo
而且这两个实际上函数都是调用的CodeManager的函数,所以挺简单了
// 管理文本的类,所有对文档的操作都在这里实现
public class CodeManager
{
private Stack<IUndoCommand> undoCommands; // 保存执行后,可以撤销的命令
private Stack<IUndoCommand> redoCommands; // 保存撤销后,可以重做的命令
private StringBuilder text; // 保存代码的地方
public string Text
{
get
{
return text.ToString();
}
}
public CodeManager()
{
undoCommands = new Stack<IUndoCommand>();
redoCommands = new Stack<IUndoCommand>();
text = new StringBuilder();
}
// 执行命令,并且添加命令到堆栈中
public void Execute(ICommand cmd)
{
cmd.Execute();
redoCommands.Clear(); // 当输入一个新的命令后,要清除可重做的命令。因为重做命令应该只是在撤销命令执行后,才能使用的。具体可以看看其它编辑器,然后自己试试
if( cmd is IUndoCommand )
{
undoCommands.Push(cmd as IUndoCommand);
}
else
{
undoCommands.Clear();
}
}
// 撤销
public void Undo()
{
if ( undoCommands.Count == 0 )
{
MessageBox.Show("不能撤销了");
return;
}
IUndoCommand cmd = undoCommands.Pop();
cmd.Undo();
redoCommands.Push(cmd);
}
// 重做
public void Redo()
{
if ( redoCommands.Count == 0 )
{
MessageBox.Show("不能重做了");
return;
}
IUndoCommand cmd = redoCommands.Pop();
cmd.Execute();
undoCommands.Push(cmd);
}
// 具体的对文本的操作函数
public void InserCharacter(int index, char ch)
{
text.Insert(index, ch);
}
public void RemoveCharacter(int index)
{
text.Remove(index, 1);
}
public char GetCharacter(int index)
{
return text[index];
}
}
这个类比较简单,代码都很容易看懂
// 编辑器
public class Coder
{
public CodeManager codeManager;
public Coder()
{
codeManager = new CodeManager();
}
// 插入字符函数,实例化具体的命令,并且让CodeManager去执行
public void InsertCharacter(int index, char ch)
{
InsertCharacterCommand cmd = new InsertCharacterCommand(codeManager, index, ch);
codeManager.Execute(cmd);
}
public void AppendCharacter(char ch)
{
InsertCharacter(codeManager.Text.Length, ch);
}
public void Undo()
{
codeManager.Undo();
}
public void Redo()
{
codeManager.Redo();
}
}
Coder类就是具体和用户打交道的类
Coder类也很简单,都是调用CodeManager的函数
到这里位置,撤销与重做功能就完成了
新建一个窗口,拖一个Label控件,在窗口中输入如下代码,可以看看效果
void MainFormKeyDown(object sender, KeyEventArgs e)
{
switch ( e.KeyCode )
{
case Keys.D0:
case Keys.D1:
case Keys.D2:
case Keys.D3:
case Keys.D4:
case Keys.D5:
case Keys.D6:
case Keys.D7:
case Keys.D8:
case Keys.D9:
coder.AppendCharacter(e.KeyCode.ToString()[1]);
break;
case Keys.Z:
coder.Undo();
break;
case Keys.Y:
coder.Redo();
break;
default:
break;
}
label1.Text = coder.codeManager.Text; // 这里显然不该这样,正确的做法是在Coder里实现一个函数,来间接的获取Text,但测试下就无所谓了
}
这个函数里,用0-9的数字键来模拟输入字符,用Z键模拟“撤销”,用Y键模拟“重做”
好了,执行试试,输入,撤销,重做。
怎么样,很简单对吧(当然,这里的命令都是很简单的命令,如果涉及到命令组合,要难很多)
本来到这里就结束的,但可能有人会问,怎么实现其它功能。那这里我就在实现一个删除字符的命令,刚好就与输入命令相反
很简单,只要继承IUndoCommand命令就行
// 删除字符命令
public class RemoveCharacterCommand : IUndoCommand
{
private CodeManager codeManager;
private int index;
private char ch;
public RemoveCharacterCommand(CodeManager setCodeManager, int setIndex)
{
this.codeManager = setCodeManager;
this.index = setIndex;
this.ch = ' ';
}
public void Execute()
{
this.ch = codeManager.GetCharacter(index);
codeManager.RemoveCharacter(index);
}
public void Undo()
{
codeManager.InserCharacter(index, ch);
}
}
然后再Coder中加入函数
public void RemoveCharacter(int index)
{
RemoveCharacterCommand cmd = new RemoveCharacterCommand(codeManager, index);
codeManager.Execute(cmd);
}
public void SubtractCharacter()
{
RemoveCharacter(codeManager.Text.Length - 1);
}
没做,就这点代码,没了(可以看到,都是调用CodeManager里的函数)
然后再switch(e.KeyCode)的分支中,加入一句
case Keys.Back:
coder.SubtractCharacter();
break;
就行了,试试效果吧