自己动手设计代码编辑器——(三)撤销与重做

谈到代码编辑器,基本功能的“撤销与重做”是必不可少的。
刚好最近看了设计模式的“命令模式”,做这个倒是正好
简单来说,就是把所以可以撤销的方法封装成类
这里有个简单的测试例子,演示了用“命令模式”实现的“撤销”功能
这里是两个基本接口

// 命令接口,所有能被编辑器接受命令都从这里继承  
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;

就行了,试试效果吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,102评论 4 62
  • Game Programming Patterns -- Command 原文地址:http://gameprog...
    Felicx阅读 1,735评论 2 5
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,758评论 2 17
  • 我家小子所在的幼儿园开家长会,我让他妈妈去的,回来后呱唧呱唧地就给我讲家长会啦了些什么,估计被洗脑了,给我好一通宣...
    烦人的昵称阅读 394评论 0 0
  • 输入命令下载JDK: wget http://211.149.198.47/data/main/jdk-7u7-l...
    veraxs阅读 108评论 0 1