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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM