基本概念
對話框是 GUI 程序中不可或缺的組成部分。很多不能或者不適合放入主窗口的功能組件都必須放在對話框中設置。對話框通常會是一個頂層窗口,出現在程序最上層,用於實現短期任務或者簡潔的用戶交互。
Qt 中使用QDialog類實現對話框。就像主窗口一樣,我們通常會設計一個類繼承QDialog。QDialog(及其子類,以及所有Qt::Dialog類型的類)的對於其 parent 指針都有額外的解釋:如果 parent 爲 NULL,則該對話框會作爲一個頂層窗口,否則則作爲其父組件的子對話框(此時,其默認出現的位置是 parent 的中心)。頂層窗口與非頂層窗口的區別在於,頂層窗口在任務欄會有自己的位置,而非頂層窗口則會共享其父組件的位置。
對話框分爲模態對話框和非模態對話框。
- 模態對話框,就是會阻塞同一應用程序中其它窗口的輸入。
模態對話框很常見,比如“打開文件”功能。你可以嘗試一下記事本的打開文件,當打開文件對話框出現時,我們是不能對除此對話框之外的窗口部分進行操作的。 - 與此相反的是非模態對話框,例如查找對話框,我們可以在顯示着查找對話框的同時,繼續對記事本的內容進行編輯。
(下面更詳細的示例)
標準對話框
所謂標準對話框,是 Qt 內置的一系列對話框,用於簡化開發。事實上,有很多對話框都是通用的,比如打開文件、設置顏色、打印設置等。這些對話框在所有程序中幾乎相同,因此沒有必要在每一個程序中都自己實現這麼一個對話框。
Qt 的內置對話框大致分爲以下幾類:
QColorDialog: 選擇顏色;
QFileDialog: 選擇文件或者目錄;
QFontDialog: 選擇字體;
QInputDialog: 允許用戶輸入一個值,並將其值返回;
QMessageBox: 模態對話框,用於顯示信息、詢問問題等;
QPageSetupDialog: 爲打印機提供紙張相關的選項;
QPrintDialog: 打印機配置;
QPrintPreviewDialog:打印預覽;
QProgressDialog: 顯示操作過程。
自定義消息框
Qt 支持模態對話框和非模態對話框。
模態與非模態的實現:
- 使用QDialog::exec()實現應用程序級別的模態對話框
- 使用QDialog::open()實現窗口級別的模態對話框
- 使用QDialog::show()實現非模態對話框。
模態對話框
Qt 有兩種級別的模態對話框:
- 應用程序級別的模態
當該種模態的對話框出現時,用戶必須首先對對話框進行交互,直到關閉對話框,然後才能訪問程序中其他的窗口。 - 窗口級別的模態
該模態僅僅阻塞與對話框關聯的窗口,但是依然允許用戶與程序中其它窗口交互。窗口級別的模態尤其適用於多窗口模式。
一般默認是應用程序級別的模態。
在下面的示例中,我們調用了exec()將對話框顯示出來,因此這就是一個模態對話框。當對話框出現時,我們不能與主窗口進行任何交互,直到我們關閉了該對話框。
QDialog dialog;
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();
非模態對話框
下面我們試着將exec()修改爲show(),看看非模態對話框:
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.show();
你會發現對話框竟然一閃而過!
這是因爲,show()函數不會阻塞當前線程,對話框會顯示出來,然後函數立即返回,代碼繼續執行。
注意,dialog 是建立在棧上的,show()函數返回,MainWindow::open()函數結束,dialog 超出作用域被析構,因此對話框消失了。知道了原因就好改了,我們將 dialog 改成堆上建立,當然就沒有這個問題了:
QDialog *dialog = new QDialog;
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
如果你足夠細心,應該發現上面的代碼是有問題的:dialog 存在內存泄露!dialog 使用 new 在堆上分配空間,卻一直沒有 delete。解決方案也很簡單:將 MainWindow 的指針賦給 dialog 即可。
QDialog *dialog = new QDialog(this);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
不過,這樣做有一個問題:如果我們的對話框不是在一個界面類中出現呢?由於QWidget的 parent 必須是QWidget指針,那就限制了我們不能將一個普通的 C++ 類指針傳給 Qt 對話框。另外,如果對內存佔用有嚴格限制的話,當我們將主窗口作爲 parent 時,主窗口不關閉,對話框就不會被銷燬,所以會一直佔用內存。在這種情景下,我們可以設置 dialog 的WindowAttribute:
QDialog *dialog = new QDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
setAttribute()函數設置對話框關閉時,自動銷燬對話框。
消息對話框
查看幫助文檔即可(實例中也有運用)
標準文件對話框
QFileDialog,也就是文件對話框。在本節中,我們將嘗試編寫一個簡單的文本文件編輯器,我們將使用QFileDialog來打開一個文本文件,並將修改過的文件保存到硬盤。
以上知識示例
結果
有很多,自己可以分步去去掉註釋運行查看
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QDialog>
#include <QMessageBox>
#include <QColorDialog>
#include <QFileDialog>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//點擊新建菜單項 彈出對話框
connect(ui->actionNew,&QAction::triggered,this,[=](){
//對話框 有兩種
// 模態對話框 (不可以對其他窗口進行操作) 非模態對話框 (可以對其他窗口操作)
// QDialog dlg(this);
// dlg.resize(200,100);
// dlg.exec(); //阻塞
// qDebug() << "彈出對話框!";
//非模態對話框創建
//QDialog dlg2(this); 創建到棧上 一閃而過
// QDialog * dlg2 = new QDialog(this);
// dlg2->resize(200,100);
// dlg2->show();
// //需要設置屬性 dlg2 55號
// dlg2->setAttribute(Qt::WA_DeleteOnClose);
//使用標準對話框 QMessageBox
//錯誤對話框
//QMessageBox::critical(this,"錯誤!","critical");
//信息對話框
//QMessageBox::information(this,"信息","info");
//詢問對話框
// 參數1 、父親 2、標題 3、提示內容 4 按鍵類型 5 關聯回車按鍵
// if(QMessageBox::Save == QMessageBox::question(this,"問題","question",QMessageBox::Save | QMessageBox::Cancel,QMessageBox::Cancel))
// {
// qDebug() << "點擊的是保存";
// }
// else
// {
// qDebug() << "點擊的是取消";
// }
//警告對話框
// QMessageBox::warning(this,"警告!","warning");
//選擇顏色對話框
// QColor color = QColorDialog::getColor(QColor(255,0,0));
// qDebug() << color.red() << color.green() << color.blue();
//文件對話框
// QString path = QFileDialog::getOpenFileName(this,"打開文件","C:\\Users\\zhangtao\\Desktop","(*.txt *.png)");
// qDebug() << path;
});
}
MainWindow::~MainWindow()
{
delete ui;
}
文件小案例(保存於打開的設計)
首先,我們需要創建一個帶有文本編輯功能的窗口。借用我們前面的程序代碼,應該可以很方便地完成:
openAction = new QAction(QIcon(":/images/file-open"),tr("&Open..."), this);
openAction->setStatusTip(tr("Open an existing file"));
saveAction = new QAction(QIcon(":/images/file-save"), tr("&Save..."), this);
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);
我們在菜單和工具欄添加了兩個動作:打開和保存。接下來是一個QTextEdit類,這個類用於顯示富文本文件。也就是說,它不僅僅用於顯示文本,還可以顯示圖片、表格等等。不過,我們現在只用它顯示純文本文件。QMainWindow有一個setCentralWidget()函數,可以將一個組件作爲窗口的中心組件,放在窗口中央顯示區。顯然,在一個文本編輯器中,文本編輯區就是這個中心組件,因此我們將QTextEdit作爲這種組件。
我們使用connect()函數,爲這兩個QAction對象添加響應的動作:
connect(openAction, &QAction::triggered,
this, &MainWindow::openFile);
connect(saveAction, &QAction::triggered,
this, &MainWindow::saveFile);
******下面是最主要的openFile()和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."));
}
}
在openFile()函數中,我們使用QFileDialog::getOpenFileName()來獲取需要打開的文件的路徑。這個函數原型如下:
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進行設置,那麼得到的對話框很可能與系統對話框的外觀不一致。這一點是需要注意的。