Russian Qt Forum

Qt => Общие вопросы => Тема начата: FluffyMan2000 от Декабрь 10, 2012, 15:40



Название: QMdiArea: слоты вызываются столько раз, сколько дочерних окон в QMdiArea
Отправлено: FluffyMan2000 от Декабрь 10, 2012, 15:40
доброго времени суток.

Есть QMdiArea в QMainWindow. Добавляю в нее дочерние окна DocWindow (объекты-наследники QTextEdit, как у М.Шлее в примерах). У QMainWindow есть слоты, в которых текущее дочернее окно приводится к DocWindow и вызывается нужный слот DocWindow. Но дело в том, что слоты эти вызываются столько раз, сколько дочерних окон в QMdiArea. Это при том, что я привожу ТЕКУЩЕЕ дочернее окно к DocWindow и вызываю слот КОНКРЕТНОГО экзэмпляра.

Понять не могу в чем ошибка. пол дня над ней сижу.

Проект во вложении.

DocWindow.h
Код:
#ifndef _DocWindow_h_
#define _DocWindow_h_

#include <QTextEdit>
#include <QMessageBox>

class DocWindow: public QTextEdit
{
Q_OBJECT
private:
    QString docName;

public:
    DocWindow(QWidget* pwgt = 0);

protected:
    void closeEvent(QCloseEvent *ev);

signals:
    void changeWindowTitle(const QString&);

public slots:
    void setDocumentName(const QString &name);
    QString getDocumentName();

    void setStartInTitle();

    void slotSave();
    void slotSaveAs();

    QMessageBox::StandardButton maybeSave();
};
#endif //_DocWindow_h_

DocWindow.cpp
Код:
#include <QtGui>
#include "DocWindow.h"

DocWindow::DocWindow(QWidget* pwgt/*=0*/) : QTextEdit(pwgt)
{
}

void DocWindow::slotSave()
{
    if (docName == "New document")
    {
        this->slotSaveAs();
    } else
    {
        QTextDocumentWriter writer(docName);
        bool success = writer.write(this->document());

        if (success)
        {
            this->document()->setModified(false);
            emit changeWindowTitle(docName);
        }
    }
}

void DocWindow::slotSaveAs()
{
    QString str = QFileDialog::getSaveFileName(0, docName, QApplication::applicationDirPath(), "Txt (*.txt);;Reach Text (*.html)");
    if (!str.isEmpty())
    {
        docName = str;
        slotSave();
    }
}

void DocWindow::setDocumentName(const QString &name)
{
    docName = name;
    emit changeWindowTitle(docName);
}

QMessageBox::StandardButton DocWindow::maybeSave()
{
    if (this->document()->isModified())
    {
        QMessageBox::StandardButton ret;
        ret = QMessageBox::warning(this, "Application", "The document \"" + docName + "\" has been modified.\n" "Do you want to save your changes?", QMessageBox::Save | QMessageBox::Discard);

        if (ret == QMessageBox::Save)
        {
            this->slotSave();
        }

        return ret;
    }
}

void DocWindow::closeEvent(QCloseEvent *ev)
{
    if (this->maybeSave() == QMessageBox::Cancel)
    {
        ev->ignore();
    } else
    {
        ev->accept();
    }
}

QString DocWindow::getDocumentName()
{
    return docName;
}

void DocWindow::setStartInTitle()
{
    this->setWindowTitle(docName + "*");
}

MainWindow.h
Код:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QCloseEvent>
#include <QMdiSubWindow>
#include "DocWindow.h"

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

protected:
    void closeEvent(QCloseEvent *ev);

public slots:
    void slotChangeWindowTitle(const QString&);

    DocWindow* newDocument();
    void loadDocument();

    void delegateForSave();
    void delegateForSaveAs();
    void delegateForUndo();
    void delegateForRedo();
    void delegateForCopy();
    void delegateForCut();
    void delegateForPaste();

    void checkForDocChanges();
};

#endif // MAINWINDOW_H

MainWindow.cpp
Код:
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "DocWindow.h"
#include <QFileDialog>
#include <QTextCodec>
#include <QMdiSubWindow>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ui->mainToolBar->addAction(ui->actionNew);
    ui->mainToolBar->addAction(ui->actionOpen);
    ui->mainToolBar->addSeparator();
    ui->mainToolBar->addAction(ui->actionSave);
    ui->mainToolBar->addAction(ui->actionSave_as);
    ui->mainToolBar->addSeparator();
    ui->mainToolBar->addAction(ui->actionTile);
    ui->mainToolBar->addAction(ui->actionCascade);

    ui->toolBarTextProperties->addAction(ui->actionBold);
    ui->toolBarTextProperties->addAction(ui->actionItalic);
    ui->toolBarTextProperties->addAction(ui->actionUnderlined);
    ui->toolBarTextProperties->addAction(ui->actionFont);
    ui->toolBarTextProperties->addAction(ui->actionColor);

    ui->toolBarTextEdit->addAction(ui->actionCopy);
    ui->toolBarTextEdit->addAction(ui->actionCut);
    ui->toolBarTextEdit->addAction(ui->actionPaste);
    ui->toolBarTextEdit->addAction(ui->actionUndo);
    ui->toolBarTextEdit->addAction(ui->actionRedo);

    connect(ui->actionTile, SIGNAL(triggered()), ui->mdiArea, SLOT(tileSubWindows()));
    connect(ui->actionCascade, SIGNAL(triggered()), ui->mdiArea, SLOT(cascadeSubWindows()));

    connect(ui->actionNew, SIGNAL(triggered()), this, SLOT(newDocument()));
    connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(loadDocument()));
    connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(close()));

    connect(ui->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(checkForDocChanges()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

DocWindow* MainWindow::newDocument()
{
    DocWindow *pDoc = new DocWindow;
    ui->mdiArea->addSubWindow(pDoc);
    pDoc->setAttribute(Qt::WA_DeleteOnClose);
    pDoc->setDocumentName("New document");
    pDoc->setWindowTitle(pDoc->getDocumentName() + "*");
    pDoc->document()->setModified(true);
    pDoc->show();

    // активируем/деактивируем пункты меню
    ui->actionUndo->setEnabled(pDoc->document()->isUndoAvailable());
    ui->actionRedo->setEnabled(pDoc->document()->isRedoAvailable());
    ui->actionPaste->setEnabled(pDoc->canPaste());

    connect(pDoc, SIGNAL(changeWindowTitle(const QString&)), this, SLOT(slotChangeWindowTitle(const QString&)));
    connect(pDoc, SIGNAL(textChanged()), this, SLOT(checkForDocChanges()));

    connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(delegateForSave()));
    connect(ui->actionSave_as, SIGNAL(triggered()), this, SLOT(delegateForSaveAs()));
    connect(ui->actionUndo, SIGNAL(triggered()), this, SLOT(delegateForUndo()));
    connect(ui->actionRedo, SIGNAL(triggered()), this, SLOT(delegateForRedo()));
    connect(ui->actionCopy, SIGNAL(triggered()), this, SLOT(delegateForCopy()));
    connect(ui->actionCut, SIGNAL(triggered()), this, SLOT(delegateForCut()));
    connect(ui->actionPaste, SIGNAL(triggered()), this, SLOT(delegateForPaste()));

    return pDoc;
}

void MainWindow::slotChangeWindowTitle(const QString& str)
{
    qobject_cast<DocWindow*>(sender())->setWindowTitle(str);
}

void MainWindow::delegateForSave()
{
    qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget())->slotSave();
    qDebug() << "save";
}

void MainWindow::delegateForSaveAs()
{
    qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget())->slotSaveAs();
    qDebug() << "save as";
}

void MainWindow::loadDocument()
{
    QString name = QFileDialog::getOpenFileName(this, "Open File...", QString(), "Txt (*.txt);;Reach Text (*.html *.htm)");

    if (!name.isEmpty())
    {
        DocWindow *pDoc = this->newDocument();
        pDoc->setDocumentName(name);

        if (!QFile::exists(name))
        {
            return;
        }

        QFile file(name);
        if (!file.open(QFile::ReadOnly))
        {
            return;
        }

        QByteArray data = file.readAll();
        QTextCodec *codec = Qt::codecForHtml(data);
        QString str = codec->toUnicode(data);

        if (Qt::mightBeRichText(str))
        {
            pDoc->setHtml(str);
        } else
        {
            str = QString::fromUtf8(data);
            pDoc->setPlainText(str);
        }
    }
}

void MainWindow::closeEvent(QCloseEvent *ev)
{
    QList<QMdiSubWindow*> list = ui->mdiArea->subWindowList();
    bool saved = true;

    for (int i = 0; i < list.count(); i++)
    {
        if (qobject_cast<DocWindow*>(list.at(i)->widget())->document()->isModified())
        {
            saved = false;
            break;
        }
    }

    if (!saved)
    {
        QMessageBox::StandardButton ret;
        ret = QMessageBox::warning(this, "Application", "You have unsaved documents.\n" "Do you realy want to quit?", QMessageBox::Yes | QMessageBox::Cancel);

        if (ret == QMessageBox::Cancel)
        {
            ev->ignore();
        } else
        {
            ui->mdiArea->closeAllSubWindows();
            ev->accept();
        }
    }
}

void MainWindow::delegateForUndo()
{
    qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget())->document()->undo();
    qDebug() << "undo";
}

void MainWindow::delegateForRedo()
{
    qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget())->document()->redo();
    qDebug() << "redo";
}

void MainWindow::delegateForCopy()
{
    qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget())->copy();
    qDebug() << "copy";
}

void MainWindow::delegateForCut()
{
    qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget())->cut();
    qDebug() << "cut";
}

void MainWindow::delegateForPaste()
{
    qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget())->paste();
    qDebug() << "paste";
}

void MainWindow::checkForDocChanges()
{
    if (ui->mdiArea->subWindowList().count() != 0)
    {
        DocWindow *pDoc = qobject_cast<DocWindow*>(ui->mdiArea->currentSubWindow()->widget());

        if (pDoc != 0)
        {
            ui->actionUndo->setEnabled(pDoc->document()->isUndoAvailable());
            ui->actionRedo->setEnabled(pDoc->document()->isRedoAvailable());
            ui->actionPaste->setEnabled(pDoc->canPaste());

            if (pDoc->document()->isModified())
            {
                pDoc->setWindowTitle(pDoc->getDocumentName() + "*");
            }
        }
    }
}


Название: Re: QMdiArea: слоты вызываются столько раз, сколько дочерних окон в QMdiArea
Отправлено: mutineer от Декабрь 10, 2012, 16:49
Возьмем, например, слот delegateForSave(). При каждом создании дочернего окна ты привязываешь к этому слоту один и тот же сигнал. А согласно документации сколько раз ты вызвал connect для одной и той же пары сигнал-слот, столько раз слот и вызовется при испускании сигнала


Название: Re: QMdiArea: слоты вызываются столько раз, сколько дочерних окон в QMdiArea
Отправлено: FluffyMan2000 от Декабрь 10, 2012, 16:52
Возьмем, например, слот delegateForSave(). При каждом создании дочернего окна ты привязываешь к этому слоту один и тот же сигнал. А согласно документации сколько раз ты вызвал connect для одной и той же пары сигнал-слот, столько раз слот и вызовется при испускании сигнала

да. но я ведь в этом слоте привожу вызываю слот конкретного экземпляра. а как тогда в мое случае этого избежать?


Название: Re: QMdiArea: слоты вызываются столько раз, сколько дочерних окон в QMdiArea
Отправлено: mutineer от Декабрь 10, 2012, 16:54
Возьмем, например, слот delegateForSave(). При каждом создании дочернего окна ты привязываешь к этому слоту один и тот же сигнал. А согласно документации сколько раз ты вызвал connect для одной и той же пары сигнал-слот, столько раз слот и вызовется при испускании сигнала

да. но я ведь в этом слоте привожу вызываю слот конкретного экземпляра. а как тогда в мое случае этого избежать?

При вызове да, ты вызываешь метод конкретного экземпляра, но вот при connect ты привязываешься к слоту MainWindow::delegateForSave(). Избежать этого элементарно - выполнять привязку один раз, а не при каждом создании нового подокна


Название: Re: QMdiArea: слоты вызываются столько раз, сколько дочерних окон в QMdiArea
Отправлено: FluffyMan2000 от Декабрь 10, 2012, 16:57
Возьмем, например, слот delegateForSave(). При каждом создании дочернего окна ты привязываешь к этому слоту один и тот же сигнал. А согласно документации сколько раз ты вызвал connect для одной и той же пары сигнал-слот, столько раз слот и вызовется при испускании сигнала

да. но я ведь в этом слоте привожу вызываю слот конкретного экземпляра. а как тогда в мое случае этого избежать?


При вызове да, ты вызываешь метод конкретного экземпляра, но вот при connect ты привязываешься к слоту MainWindow::delegateForSave(). Избежать этого элементарно - выполнять привязку один раз, а не при каждом создании нового подокна

хм. а в таком случае привязку ЧЕГО осуществлять один раз? если объект еще не создан.


Название: Re: QMdiArea: слоты вызываются столько раз, сколько дочерних окон в QMdiArea
Отправлено: mutineer от Декабрь 10, 2012, 17:01
Код:
    connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(delegateForSave()));
    connect(ui->actionSave_as, SIGNAL(triggered()), this, SLOT(delegateForSaveAs()));
    connect(ui->actionUndo, SIGNAL(triggered()), this, SLOT(delegateForUndo()));
    connect(ui->actionRedo, SIGNAL(triggered()), this, SLOT(delegateForRedo()));
    connect(ui->actionCopy, SIGNAL(triggered()), this, SLOT(delegateForCopy()));
    connect(ui->actionCut, SIGNAL(triggered()), this, SLOT(delegateForCut()));
    connect(ui->actionPaste, SIGNAL(triggered()), this, SLOT(delegateForPaste()));

Вот набор привязок, которые ты выполняешь при каждом создании нового подокна. Какой объект тут еще не создан?


Название: Re: QMdiArea: слоты вызываются столько раз, сколько дочерних окон в QMdiArea
Отправлено: FluffyMan2000 от Декабрь 10, 2012, 17:07
Код:
    connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(delegateForSave()));
    connect(ui->actionSave_as, SIGNAL(triggered()), this, SLOT(delegateForSaveAs()));
    connect(ui->actionUndo, SIGNAL(triggered()), this, SLOT(delegateForUndo()));
    connect(ui->actionRedo, SIGNAL(triggered()), this, SLOT(delegateForRedo()));
    connect(ui->actionCopy, SIGNAL(triggered()), this, SLOT(delegateForCopy()));
    connect(ui->actionCut, SIGNAL(triggered()), this, SLOT(delegateForCut()));
    connect(ui->actionPaste, SIGNAL(triggered()), this, SLOT(delegateForPaste()));

Вот набор привязок, которые ты выполняешь при каждом создании нового подокна. Какой объект тут еще не создан?

Баааалин! как я мог так затупить. спасибо.

Вынес эти соединения в конструктор MainWindow