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

2017-09-19  本文已影响0人  UnSkyToo

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

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

就行了,试试效果吧

上一篇 下一篇

猜你喜欢

热点阅读