天天看點

Qt5 學習3 之 對話框資料傳遞、标準對話框 QMessageBox、信号槽、檔案對話框

對話框資料傳遞

對話框的出現用于完成一個簡單的或者是短期的任務。對話框與主視窗之間的資料互動相當重要。本節将講解如何在對話框和主視窗之間進行資料互動。按照前文的講解,對話框分為模态和非模态兩種。我們也将以這兩種為例,分别進行闡述。

模态對話框使用了exec()函數将其顯示出來。exec()函數的真正含義是開啟一個新的事件循環(我們會在後面的章節中詳細介紹有關事件的概念)。所謂事件循環,可以了解成一個無限循環。Qt 在開啟了事件循環之後,系統發出的各種事件才能夠被程式監聽到。這個事件循環相當于一種輪詢的作用。既然是無限循環,當然在開啟了事件循環的地方,代碼就會被阻塞,後面的語句也就不會被執行到。是以,對于使用了exec()顯示的模态對話框,我們可以在exec()函數之後直接從對話框的對象擷取到資料值。

void MainWindow::open()
{
    QDialog dialog(this);
    dialog.setWindowTitle(tr("Hello, dialog!"));
    dialog.exec();
    qDebug() << dialog.result();
}
           

模态對話框相對簡單,如果是非模态對話框,QDialog::show()函數會立即傳回,如果我們也這麼寫,就不可能取得使用者輸入的資料。因為show()函數不會阻塞主線程,show()立即傳回,使用者還沒有來得及輸入,就要執行後面的代碼,當然是不會有正确結果的。那麼我們就應該換一種思路擷取資料,那就是使用信号槽機制。

由于非模态對話框在關閉時可以調用QDialog::accept()或者QDialog::reject()或者更通用的QDialog::done()函數,是以我們可以在這裡發出信号。另外,如果找不到合适的信号發出點,我們可以重寫QDialog::closeEvent()函數,在這裡發出信号。在需要接收資料的視窗(這裡是主視窗)連接配接到這個信号即可

void UserAgeDialog::accept()
{
    emit userAgeChanged(newAge); // newAge is an int
    QDialog::accept();
}

// in main window:
void MainWindow::showUserAgeDialog()
{
    UserAgeDialog *dialog = new UserAgeDialog(this);
    connect(dialog, &UserAgeDialog::userAgeChanged, this, &MainWindow::setUserAge);
    dialog->show();
}

// ...

void MainWindow::setUserAge(int age)
{
    userAge = age;
}
           

不要擔心如果對話框關閉,是不是還能擷取到資料。因為 Qt 信号槽的機制保證,在槽函數在調用的時候,我們始終可以使用sender()函數擷取到 signal 的發出者。關于sender()函數,可以在文檔中找到更多的介紹。順便說一句,sender()函數的存在使我們可以利用這個函數,來實作一個隻能打開一個的非模态對話框(方法就是在對話框打開時在一個對話框映射表中記錄下标記,在對話框關閉時利用sender()函數判斷是不是該對話框,然後從映射表中将其删除)。

标準對話框 QMessageBox

Qt 的内置對話框大緻分為以下幾類:

QColorDialog:選擇顔色;

QFileDialog:選擇檔案或者目錄;

QFontDialog:選擇字型;

QInputDialog:允許使用者輸入一個值,并将其值傳回;

QMessageBox:模态對話框,用于顯示資訊、詢問問題等;

QPageSetupDialog:為列印機提供紙張相關的選項;

QPrintDialog:列印機配置;

QPrintPreviewDialog:列印預覽;

QProgressDialog:顯示操作過程。

QMessageBox用于顯示消息提示。我們一般會使用其提供的幾個 static 函數:

void about(QWidget * parent, const QString & title, const QString & text):顯示關于對話框。這是一個最簡單的對話框,其标題是 title,内容是 text,父視窗是 parent。對話框隻有一個 OK 按鈕。

void aboutQt(QWidget * parent, const QString & title = QString()):顯示關于 Qt 對話框。該對話框用于顯示有關 Qt 的資訊。

StandardButton critical(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton):顯示嚴重錯誤對話框。這個對話框将顯示一個紅色的錯誤符号。我們可以通過 buttons 參數指明其顯示的按鈕。預設情況下隻有一個 Ok 按鈕,我們可以使用StandardButtons類型指定多種按鈕。

StandardButton information(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton):QMessageBox::information()函數與QMessageBox::critical()類似,不同之處在于這個對話框提供一個普通資訊圖示。

StandardButton question(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = StandardButtons( Yes | No ), StandardButton defaultButton = NoButton):QMessageBox::question()函數與QMessageBox::critical()類似,不同之處在于這個對話框提供一個問号圖示,并且其顯示的按鈕是“是”和“否”兩個。

StandardButton warning(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton):QMessageBox::warning()函數與QMessageBox::critical()類似,不同之處在于這個對話框提供一個黃色歎号圖示。

if (QMessageBox::Yes == QMessageBox::question(this,
                                              tr("Question"),
                                              tr("Are you OK?"),
                                              QMessageBox::Yes | QMessageBox::No,
                                              QMessageBox::Yes)) {
    QMessageBox::information(this, tr("Hmmm..."), tr("I'm glad to hear that!"));
} else {
    QMessageBox::information(this, tr("Hmmm..."), tr("I'm sorry!"));
}
           

信号槽

#include <QObject>

// newspaper.h
class Newspaper : public QObject
{
    Q_OBJECT
public:
    Newspaper(const QString & name) :
        m_name(name)
    {
    }

    void send() const
    {
        emit newPaper(m_name);
    }

signals:
    void newPaper(const QString &name) const;

private:
    QString m_name;
};

// reader.h
#include <QObject>
#include <QDebug>

class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}

    void receiveNewspaper(const QString & name) const
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};

// main.cpp
#include <QCoreApplication>

#include "newspaper.h"
#include "reader.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    Newspaper newspaper("Newspaper A");
    Reader reader;
    QObject::connect(&newspaper, &Newspaper::newPaper,
                     &reader,    &Reader::receiveNewspaper);
    newspaper.send();

    return app.exec();
}
           

在main()函數中,我們使用connect()函數将newspaper對象的newPaper()信号與reader對象的receiveNewspaper()槽函數聯系起來。當newspaper發出這個信号時,reader相應的槽函數就會自動被調用。這裡我們使用了取址操作符,取到Newspaper::newPaper()信号的位址,同樣類似的取到了Reader::receiveNewspaper()函數位址。編譯器能夠利用這兩個位址,在編譯期對這個連接配接操作進行檢查,如果有個任何錯誤(包括對象沒有這個信号,或者信号參數不比對等),編譯時就會發現。

有重載的信号

QObject::connect(&newspaper,
                 static_cast<void (Newspaper:: *)(const QString &, const QDate &)>(&Newspaper::newPaper),
                 &reader,
                 &Reader::receiveNewspaper);
           

帶有預設參數的槽函數

Qt 允許信号和槽的參數數目不一緻:槽函數的參數數目可以比信号的參數少。這是因為,我們信号的參數實際是作為一種傳回值。正如普通的函數調用一樣,我們可以選擇忽略函數傳回值,但是不能使用一個并不存在的傳回值。如果槽函數的參數數目比信号的多,在槽函數中就使用到這些參數的時候,實際這些參數并不存在(因為信号的參數比槽的少,是以并沒有傳過來),函數就會報錯。這種情況往往有兩個原因:一是槽的參數就是比信号的少,此時我們可以像前面那種寫法直接連接配接。另外一個原因是,信号的參數帶有預設值

void QPushButton::clicked(bool checked = false)
           

有一種情況,槽函數的參數可以比信号的多,那就是槽函數的參數帶有預設值

signals:
    void newPaper(const QString &name);
// Reader
    void receiveNewspaper(const QString &name, const QDate &date = QDate::currentDate());
           

檔案對話框

openAction = new QAction(QIcon(":/images/file-open"), tr("&Open..."), this);
openAction->setShortcuts(QKeySequence::Open);
openAction->setStatusTip(tr("Open an existing file"));

saveAction = new QAction(QIcon(":/images/file-save"), tr("&Save..."), this);
saveAction->setShortcuts(QKeySequence::Save);
saveAction->setStatusTip(tr("Save a new file"));

QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
file->addAction(saveAction);

QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
toolBar->addAction(saveAction);

textEdit = new QTextEdit(this);
setCentralWidget(textEdit);
           
connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
connect(saveAction, &QAction::triggered, this, &MainWindow::saveFile);

/// !!!Qt4
connect(openAction, SIGNAL(triggered()), this, SLOT(openFile()));
connect(saveAction, SIGNAL(triggered()), this, SLOT(saveFile()));
           
void MainWindow::openFile()
{
    QString path = QFileDialog::getOpenFileName(this,
                                                tr("Open File"),
                                                ".",
                                                tr("Text Files(*.txt)"));
    if(!path.isEmpty()) {
        QFile file(path);
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QMessageBox::warning(this, tr("Read File"),
                                 tr("Cannot open file:\n%1").arg(path));
            return;
        }
        QTextStream in(&file);
        textEdit->setText(in.readAll());
        file.close();
    } else {
        QMessageBox::warning(this, tr("Path"),
                             tr("You did not select any file."));
    }
}

void MainWindow::saveFile()
{
    QString path = QFileDialog::getSaveFileName(this,
                                                tr("Open File"),
                                                ".",
                                                tr("Text Files(*.txt)"));
    if(!path.isEmpty()) {
        QFile file(path);
        if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
            QMessageBox::warning(this, tr("Write File"),
                                       tr("Cannot open file:\n%1").arg(path));
            return;
        }
        QTextStream out(&file);
        out << textEdit->toPlainText();
        file.close();
    } else {
        QMessageBox::warning(this, tr("Path"),
                             tr("You did not select any file."));
    }
}
           
QString getOpenFileName(QWidget * parent = 0,
                        const QString & caption = QString(),
                        const QString & dir = QString(),
                        const QString & filter = QString(),
                        QString * selectedFilter = 0,
                        Options options = 0)
           

parent:父視窗。我們前面介紹過,Qt 的标準對話框提供靜态函數,用于傳回一個模态對話框(在一定程度上這就是外觀模式的一種展現);

caption:對話框标題;

dir:對話框打開時的預設目錄,“.” 代表程式運作目錄,“/” 代表目前盤符的根目錄(特指 Windows 平台;Linux 平台當然就是根目錄),這個參數也可以是平台相關的,比如“C:\”等;

filter:過濾器。我們使用檔案對話框可以浏覽很多類型的檔案,但是,很多時候我們僅希望打開特定類型的檔案。比如,文本編輯器希望打開文本檔案,圖檔浏覽器希望打開圖檔檔案。過濾器就是用于過濾特定的字尾名。如果我們使用“Image Files(.jpg .png)”,則隻能顯示字尾名是 jpg 或者 png 的檔案。如果需要多個過濾器,使用“;;”分割,比如“JPEG Files(.jpg);;PNG Files(.png)”;

selectedFilter:預設選擇的過濾器;

options:對話框的一些參數設定,比如隻顯示檔案夾等等,它的取值是enum QFileDialog::Option,每個選項可以使用 | 運算組合起來。

QFileDialog::getOpenFileName()傳回值是選擇的檔案路徑。我們将其指派給 path。通過判斷 path 是否為空,可以确定使用者是否選擇了某一檔案。隻有當使用者選擇了一個檔案時,我們才執行下面的操作。在saveFile()中使用的QFileDialog::getSaveFileName()也是類似的。使用這種靜态函數,在 Windows、Mac OS 上面都是直接調用本地對話框,但是 Linux 上則是QFileDialog自己的模拟。這暗示了,如果你不使用這些靜态函數,而是直接使用QFileDialog進行設定,就像我們前面介紹的 QMessageBox 的設定一樣,那麼得到的對話框很可能與系統對話框的外觀不一緻。這一點是需要注意的。

首先,我們建立一個QFile對象,将使用者選擇的檔案路徑傳遞給這個對象。然後我們需要打開這個檔案,使用的是QFile::open(),其參數是指定的打開方式,這裡我們使用隻讀方式和文本方式打開這個檔案(因為我們選擇的是字尾名 txt 的檔案,可以認為是文本檔案。當然,在實際應用中,可能需要進行進一步的判斷)。QFile::open()打開成功則傳回 true,由此繼續進行下面的操作:使用QTextStream::readAll()讀取檔案所有内容,然後将其指派給QTextEdit顯示出來。最後不要忘記關閉檔案。另外,saveFile()函數也是類似的,隻不過最後一步,我們使用<<重定向,将QTextEdit的内容輸出到一個檔案中。

qt5