• Qt Undo Framework


    本篇主要目的是测试使用sublime text 2 + markdown 发布cnblog

    测试结果:非常好用啊有木有!!!所以连文章末尾的logo我都决定给人家保留!!!

    20130225 鬼猫猫 翻译http://www.cnblogs.com/muyr/

    在线阅读:Qt DevDays2007 TheQtUndo-Redo_framework.pdf

    下载:Qt DevDays2007 TheQtUndo-Redo_framework.pdf

    DevDays2007

    Qt undo/redo 框架

    • 基于Command设计模式
    • 支持命令压缩和命令合成
    • 提供了与工具包其他部分融合很好的widgets和actions

    术语(Terminology)

    • Command - 对文档的一个作用行为,比如
      • 图像编辑器的模糊操作
      • 文本处理器的剪切操作
        • 采样编辑器的最大化操作
    • Undo-stack - commands的堆栈
    • Document - 被应用程序编辑的内部数据,比如
      • 音频编辑器中的waveform(波形)
      • 图像编辑器中的bitmap(位图)

    基本的undo stack操作

    • Push

      image

    • Undo

      image

    • Redo

      image

    注意,push可能会删掉一些操作,如图

    image

    类们

    只有四个类!

    • QtUndoCommand - 用于修改document的对象的基类

    • QtUndoStack - QtUndoCommand对象的堆栈

    • QtUndoGroup - undo堆栈的组。很多应用程序允许用户同时打开超过一个文档,该类允许你把一组undo堆栈按一单个stack对待。

    • QtUndoView - 继承自QListWidget,用来展示undo堆栈的内容,以字符串形式

    实例

    前提说明:下面这个例子,我们将为一个文本编辑器实现undo/redo;文档我们就用一个简单的QString来代表;我们先实现文档中插入字符这样一个command

    commands的实现

    插入字符操作

    class InsertChars : public QUndoCommand 
    {
    public:
        InsertChars(int index, const QString &chars, QString *document)
            : QUndoCommand("Insert characters") {
            m_index = index;
            m_chars = chars;
            m_document  = document;
        }
    
        virtual void redo() {
            m_document->insert(m_index, m_chars);
        }
    
        virtual void undo() {
            m_document->remove(m_index, m_chars.length());
        }
    
    private:
        int m_index;
        QString m_chars;
        QString *m_document;
    };
    

    删除字符操作

    class RemoveChars : public QUndoCommand 
    {
    public:
        RemoveChars(int index, int count, QString *document)
            : QUndoCommand("Remove characters") {
            m_index = index;
            m_count = cout;
            m_document  = document;
        }
    
        virtual void redo() {
            m_removedChars = m_document->mid(m_index, m_count);
            m_document->remove(m_index, m_count);
        }
    
        virtual void undo() {
            m_document->insert(m_index, m_removedChars);
        }
    
    private:
        int m_index, m_count;
        QString m_removedChars;
        QString *m_document;
    };
    

    在文本编辑器中使用

    MyEditor::MyEditor(QWidget *parent) : QWidget(parent) {
        // …
        m_document = new QString;
        m_stack = new QUndoStack(this);
        m_toolBar->addAction(m_stack->createUndoAction);
        m_toolBar->addAction(m_stack->createRedoAction);
        // …
    }
    
    void MyEditor::keyPressEvent(QKeyEvent *event) {
        QString chars = events->text();
        int index = cursorIndex();
    
        switch (event->key()) {
            case Qt::Key_Backspace:
                if (index > 0) 
                    m_stack->push(new RemoveChars(index-1, 1, m_document));
                break;
            case Qt::Key_Delete:
                if (index < m_document.length())
                    m_stack->push(new RemoveChars(index, 1, m_document));
                break;
            default:
                if (!chars.isEmpty())
                    m_stack->push(new InsertChars(index, chars, m_document));
                break;
        }
    }
    

    command的压缩(compression)

    命令压缩,是一种把若干个commands压成一个command的行为。 典型的案例就是文本编辑器中输入一大堆文字,撤销,把这一大堆都撤销了。

    image

    主要用到了QUndoCommand的id()和mergeWith()方法。代码如下

    static const int InsertCharsId = 1000;
    static const int RemoveCharsId = 1001;
    //...
    
    int InsertChars::id() const {
        return InsertCharsId;
    }
    
    bool InsertChars::mergeWith(const QCommand *command) {
        // 该类型转换是安全的,因为stack检查过id()了
        InsertChars *other = static_cast<InsertChars* > (command);
    
        // 只有当其他插入的字符在我的字符后面时,才merge
        if (m_index + m_chars.length() != other->m_index)
            return false;
    
        // 把它merge了
        m_chars.append(other->m_chars);
        return true;
    }
    

    command的合成(composition)

    也就是传说中的宏(macros)

    通过合并一系列简单的commands,从而创建复杂的commands

    主要是用到了QUndoStack的beginMacro()和endMacro()方法。代码如下

    void MyEditor::replace(const QString &oldChars, const QString &newChars) {
        if(!m_document->contains(oldChars))
            return;
        QString title = QString("Replace '%1' with '%2'").arg(oldChars).arg(newChars);
    
        m_stack->beginMacro(title);
        int index = 0;
    
        for(;;) {
            index = m_document->indexOf(oldChars, index);
            if(index == -1)
                break;
            m_stack->push(new RemoveChars(index,oldChars.length(), m_document));
            m_stack->push(new InsertChars(index, newChars, m_document));
    
            index += newChars.length();
        }
        m_stack->endMacro();
    }
    

    高级command合成

    你大部分的需要,beginMacro()和endMacro()都能充分满足。

    每个command可以有很多子commands

    通过添加子command,构成一个复杂的command

    自定义的command合成有很大益处,你可以在push到stack之前,逐步构建command

    合成命令的undo顺序如下

    image

    合成命令的redo顺序如下

    image

    QUndoCommand* MyEditor::createReplaceCommand(const QString &oldChars, const QString &newChars) {
        QUndoCommand *replaceCommand = new QUndoCommand(QString("Replace '%1' with '%2'").arg(oldChars).arg(newChars));
    
        int offset = 0;
        int index = 0;
    
        for(;;) {
            index = m_document->indexOf(oldChars, index);
            if (index == -1)
                break;
            new RemoveChars(index + offset, oldChars.count(), m_document, replaceCommand);
            new InsertChars(index + offset, newChars, m_document, replaceCommand);
            index += newChars.cout();
            offset += newChars.count() - oldChars.cout();
        }
        return replaceCommand;
    }
    

    QUndoGroup

    一个应用程序,一般有若干个打开的文档,每个都拥有他们自己的undo stack。

    这些undo stack们可以放到一个undo group里

    该组group里的stack可以使用QUndoStack的setActive ()方法将自己设置为active stack。

    在同一时间,只能有一个stack是active的。

    void MyEditor::MyEditor(QWidget *parent) : QWidget(parent) 
    {
        //...
        m_undoGroup = new QUndoGroup(this);
        m_toolBar->addAction(m_undoGroup->createUndoAction(this));
        m_toolBar->addAction(m_undoGroup->createRedoAction(this));
        //...
    }
    
    Document *MyEditor::createDocument() {
        Document *doc = new Document(this);
        m_documents.append(doc);
        m_undoGroup->addStack(doc->undoStack());
        return doc;
    }
    
    bool Document::event(QEvent *event) {
        if( event->type() == QEvent::WindowActivate)
            m_undoStack->setActive(true);
        // ..
        return QWidget::event(event);
    }
    

    Tips

    • 按照commands来设计实现你的应用程序功能---后期很难增加undo/redo
    • Undo commands不应该储存指向document中实际对象的指针---储存其拷贝或者储存足够必要的用于重创建新对象的信息
    • 如果你非得想让commands里储存指向document中对象的指针时,你必须做到如下:
      • 当这些对象在document中被删除的时候,获得对象的多有权
      • 当该command实例被销毁时,delete掉你拥有的那个对象
    • 如果你十分渴望能改变或者移除stack里已经被push的command的话,你很可能会犯以下错误:
      • 你尝试在不只一个文档的情况下使用一个undo堆栈来代表。(原文You are trying to use one undo stack for something that needs to be represented as more than one document)
      • 你的command不是atomic(应该就是说该命令是有若干命令合成或压缩的,不是最最基本的命令)
    • 当命令修改了文档,立马更新该文档的state,使用QUndoStack的indexChanged()信号
      • 该更新信号不应该从command里发射。

    Powered by Sublog

  • 相关阅读:
    PHP函数include include_once require和require_once的区别
    PHP替换回车换行的三种方法
    PHP获取绝对路径dirname(__FILE__)和__DIR__比较
    jQuery实现倒计时重新发送短信验证码功能示例
    js人民币转大写
    js前端数据验证JS工具
    安卓动画学习笔记
    ActivityNotFoundException: No Activity found to handle Intent
    Android笔记
    再次踩bug:遍历删除list(java.util.ConcurrentModificationException)
  • 原文地址:https://www.cnblogs.com/muyr/p/3621385.html
Copyright © 2020-2023  润新知