Sau bài học trước về tín hiệu và slot với cả tá khái niệm mới mẻ, trong bài học này, chúng ta sẽ cùng xả hơi 1 chút với những thứ đơn giản hơn. Chúng ta sẽ cùng nhau tìm hiểu về những hộp thoại thông dụng của các ứng dụng cửa sổ.
Thế nào là 1 hộp thoại ? Đó là 1 cửa sổ với 1 tính năng cụ thể được định nghĩa từ trước. Có thể lấy ví dụ như hộp thoại “thông báo” có nhiệm vụ đơn giản là hiển thị 1 đoạn thông báo và không đưa ra cho người dùng lựa chọn nào khác ngoài việc bấm nút “OK”. Các hộp thoại khác mà chúng ta cũng thường hay bắt gặp như hộp thoại “Chọn tệp”, “Lưu tệp” hay “Chọn màu sắc”, vv…
Ở đây thì chúng ta sẽ không tự mình tạo ra các cửa sổ dạng này mà các lập trình viên thường tận dụng các chức năng của hệ thống để gọi ra những hộp thoại được xây dựng từ trước.
Thêm vào đó, Qt sẽ thích nghi với các hệ điều hành và tự động chỉnh sửa hộp thoại để phù hợp với phong cách của hệ điều hành mà chúng ta đang sử dụng.
Loại hộp thoại mà chúng ta thường xuyên bắt gặp nhất là hộp thoại thông báo.
Trong ví dụ của phần này, chúng ta sẽ tạo ra 1 cửa sổ trong đó chứa 1 chiếc nút được nối với 1 slot tự tạo. Slot này sẽ mở ra 1 hộp thoại. Nói tóm lại thì khi bạn ấn chiếc nút, 1 hộp thoại thông báo sẽ bật ra trên màn hình.
Hộp thoại thông báo trong Qt được quản lý bới lớp QMessageBox. Chúng ta trước hết có thể bắt đầu bằng cách thêm tiêu đề của lớp này <QMessageBox>
vào trong tệp CuaSo.h
.
Để chắc chắn là chúng ta xuất phát từ cùng 1 điểm, tôi cung cấp cho các bạn mã nguồn của lớp CuaSo của tôi, bao gồm trong đó là tệp CuaSo.h
và CuaSo.cpp
. Chúng ta được rút gọn tốn đa so với những gì chúng ta đã từng thực hiện nhằm tránh những xử lý lan man không cần thiết.
#ifndef DEF_CUASO #define DEF_CUASO #include <QApplication> #include <QWidget> #include <QPushButton> #include <QMessageBox> class CuaSo : public QWidget{ Q_OBJECT public: CuaSo(); public slots: void moHopThoai(); private: QPushButton *m_nutBam; }; #endif
#include "CuaSo.h" CuaSo::CuaSo() : QWidget(){ setFixedSize(230, 120); m_nutBam = new QPushButton("Mở hộp thoại", this); m_nutBam->move(40, 50); QObject::connect(m_nutBam, SIGNAL(clicked()), this, SLOT(moHopThoai())); } void CuaSo::moHopThoai(){ // Ma de mo hop thoai }
Khá đơn giản. Chúng ta sẽ tạo ra trong cửa sổ 1 nút bấm liên kết với slot moHopThoai()
. Trong mã xử lý của slot này là nơi chúng ta mở hộp thoại.
Trong trường hợp 1 số bạn muốn biết, tệp main.cpp
không có thay đổi gì cả.
#include <QApplication> #include "CuaSo.h" int main(int argc, char *argv[]){ QApplication app(argc, argv); CuaSo cuaSo; cuaSo.show(); return app.exec(); }
Lớp QMessageBox cũng cho phép tạo ra các đối tượng QMessageBox như hầu hết các lớp khác. Tuy nhiên ở đây thì chúng ta sẽ sử dụng các phương thức tĩnh vì đơn giản hơn nhiều. Chúng ta sẽ bắt đầu tìm hiểu những phương thức tĩnh của lớp này. Nhắc lại là chúng chả khác gì những phương thức thông thường trừ việc các bạn không cần thực thể hóa đối tượng để có thể sử dụng chúng.
QMessageBox::information
Phương thức information()
cho phép tạo ra 1 hộp thoại chứa sẵn biểu tượng « thông tin ».
Nguyên mẫu của phương thức này như sau :
StandardButton information ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton= NoButton );
Chỉ có 3 tham số đầu là bắt buộc. Dưới đây là ý nghĩa của chúng :
NULL
cho tham số này nếu các bạn không muốn có bất cứ cửa sổ nào bao chứa bên ngoài hộp thoại. Tuy nhiên tôi thấy trường hợp này khá hiếm.Chúng ta sẽ cùng thử trong 1 đoạn mã nhỏ.
void CuaSo::moHopThoai(){ QMessageBox::information(this, "Tiêu đề", "Chào mừng các Tân Binh !"); }
Cách gọi phương thức tĩnh không còn quá xa lạ với chúng ta nữa rồi.
Kết quả của đoạn mã trên là chúng ta đã làm bật ra được 1 thông báo hình dạng trông khá là quen thuộc hay thấy trong các chương trình máy tính.
! Chú ý rằng là khi hộp thoại bật ra thì chúng ta không thể tác động gì lên cửa sổ lúc đầu nữa. Thuật ngữ chuyên môn gọi việc này là hộp thoại trạng thái (modal) : nghĩa là cửa sổ bật ra sẽ chặn tác động của tới các thành phần đồ họa bên dưới để chờ 1 câu trả lời nào đó từ phía người dùng. Trái ngược lại thì có các cửa sổ được gọi là không trạng thái (modaless) cho phép người dùng tiếp tục thao tác với các cửa sổ bên dưới nó như chúng ta hay thấy trong trường hợp các hộp thoại tìm kiếm.
Thậm chí chúng ta có thể dùng đến mã (X)HTML để trình bày thông báo nếu các bạn muốn.
Dưới đây là 1 ví dụ sử dụng đến mã HTML.
QMessageBox::information(this, "Tiêu đề", "Chào mừng các <strong>Tân Binh</strong> !");
QMessageBox::warning
Nếu hộp thoại “thông báo” dùng để đưa ra 1 lời thông báo thì hộp thoại warning dùng để đưa ra 1 lời cảnh báo cho người dùng. Chúng ta rất hay bắt gặp hộp thoại này bật ra kèm theo 1 tiếng “ding” khá rõ.
Phương thức này được sử dụng tương tự như QMessageBox::information()
, chỉ khác là biểu tượng đi kèm lần này có chút khác biệt.
QMessageBox::warning(this, "Tiêu đề", "Chú ý, bạn có thể là 1 Tân Binh !");
QMessageBox::critical
Khi một chuyện xấu xảy ra và máy tính cần phải báo lỗi, chúng ta sẽ nhờ đến phương thức critical()
.
QMessageBox::critical(this, "Tiêu đề", "Bạn không phải 1 Tân Binh ! Mời đi cho !");
QMessageBox::question
Nếu bạn cần phải đặt ra 1 câu hỏi cho người dùng, chúng ta có thể sử dụng hộp thoại sau.
QMessageBox::question(this, "Tiêu đề", "Tôi hỏi thật chứ bạn là 1 Tân Binh à ?");
Trong đa số trường hợp, mặc định thì nút OK là nút luôn luôn xuất hiện. Tuy nhiên, trong 1 số trường hợp chúng ta cần phải có thể thêm các nút lựa chọn khác thì hộp thoại của chúng ta mới có ý nghĩa.
Để thay đổi các nút bấm của hộp thoại, chúng ta sẽ cần sử dụng đến tham số thứ 4 của các phương thức tĩnh. Tham số này bản thân nó là sự kết hợp của các giá trị được định nghĩa trước, liên kết với nhau bởi phép toán “hoặc”. Các giá trị này được gọi là các biến cờ (flag).
! Dành cho 1 số bạn tò mò thì 2 tham số cuối cùng của các phương thức trên dùng để xác định xem nút bấm nào sẽ là nút bấm được chọn mặc định. Chúng ta hiếm khi thay đổi giá trị của các tham số này do Qt khá thông minh trong việc chọn ra giá trị nút bấm mặc định phù hợp nhất.
Danh sách các biến cờ có sẵn được ghi lại bên trong tài liệu của Qt. Như các bạn có thể thấy, chúng ta có rất nhiều lựa chọn. Nếu chúng ta muốn sử dụng các nút bấm “Có”, “Không” và “Bỏ qua”, chỉ cần sử dụng QMessageBox::Yes
, QMessageBox::No
và QMessageBox::Ignore
.
QMessageBox::question(this, "Tiêu đề", "Tôi hỏi thật chứ bạn là 1 Tân Binh à ?" , QMessageBox::Yes | QMessageBox::No | QMessageBox::Ignore);
Các nút bấm sẽ xuất hiện như hình sau.
? Nhãn của các nút bấm đều được viết bằng tiếng Anh, tôi phải xử lý thế nào đây ?
Đúng vậy, các nút bấm của Qt được thiết kế ra sử dụng tiếng Anh. Tuy nhiên các ứng dụng Qt có thể dễ dàng được dịch ra nếu bạn muốn.
Tôi sẽ không đi quá sâu vào đề tài này. Dành cho các bạn muốn tìm hiểu thì dưới đây là mã nguồn tệp main.cpp
mà chúng ta sẽ sử dụng. Cho những ai không quan tâm thì cứ sử dụng máy móc đoạn mã dưới là được.
#include <QApplication> #include <QTranslator> #include <QLocale> #include <QLibraryInfo> #include "CuaSo.h" int main(int argc, char *argv[]){ QApplication app(argc, argv); QString locale = QLocale::system().name().section('_', 0, 0); QTranslator translator; translator.load(QString("qt_") + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&translator); CuaSo cuaSo; cuaSo.show(); return app.exec(); }
Thêm vào 1 vài dòng mã và 1 số lớp được bao gồm và chúng ta sẽ nhận được kết quả là nhãn của những chiếc nút đã được dịch ra từ tiếng Anh.
! Các máy tính ở Việt Nam đa phần sử dụng giao diện tiếng Anh nên những xử lý bên trên có phần vô dụng vì thực ra chúng ta đang cố dịch từ tiếng Anh sang tiếng… Anh!
Xong rồi, chỉ còn thêm 1 việc cần phải nhắc đến nữa, đó là làm sao xác định được người dùng đã chọn ấn nút nào !
Các phương thức tĩnh mà chúng ta sử dụng bên trên trả về là một giá trị kiểu int
. Chúng ta có thể dễ dàng kiểm tra để tìm ra trong các giá trị cho trước trong Qt giá trị nút bấm tương ứng đã được người dùng chọn.
void CuaSo::moHopThoai(){ int traLoi = QMessageBox::question(this, "Câu hỏi", "Bạn có phải là 1 Tân Binh không ?"); if (traLoi == QMessageBox::Yes){ QMessageBox::information(this, "Trả lời", "Chào mừng về nhà, Tân Binh!"); }else if (traLoi == QMessageBox::No){ QMessageBox::critical(this, "Cảnh báo", "Chúng tôi sẽ không bị lừa đâu !"); } }
Và chúng ta sẽ có 1 sơ đồ nhỏ.
! Thật ra mà nói thì giá trị trả về của hộp thoại cũng không phải kiểu int mà là kiểu QMessageBox::StandardButton. Đó là 1 kiểu dữ liệu liệt kê. Cách viết này giúp đoạn mã của chúng ta dễ đọc cũng như dễ hiểu hơn.
Các hộp thoại được sử dụng bên trên có đôi chút hạn chế ở chỗ ngoài việc cho phép thay đổi các loại nút bấm, chúng không cho phép giao tiếp quá nhiều với người dùng.
Nếu các bạn muốn người dùng nhập vào 1 thông tin nào đó, hoặc chọn ra 1 giá trị trong danh sách, các hộp thoại cho phép nhập thông tin sẽ là 1 sự lựa chọn hoàn hảo. Các hộp thoại này được tạo ra bởi lớp QInputDialog. Vậy nên chúng ta có thể bắt đầu thêm tiêu đề của lớp này vào mã nguồn của chúng ta để sử dụng.
Những hộp thoại cho phép người dùng nhập thông tin có thể được chia làm 4 loại :
QInputDialog::getText()
QInputDialog::getInteger()
QInputDialog::getDouble()
QInputDialog::getItem()
Ở đây thì chúng ta sẽ tập trung vào cách nhập văn bản nhưng các bạn sẽ nhanh chóng phát hiện ra là các thao tác còn lại cũng không chứa nhiều khác biệt lắm.
Phương thức tĩnh QInputDialog::getText()
mở ra 1 hộp thoại cho phép người dùng nhập vào 1 đoạn văn bản.
Đây là nguyên mẫu của nó :
QString QInputDialog::getText ( QWidget * parent, const QString & title, const QString & label, QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0, Qt::WindowFlags f = 0 );
Đầu tiên, các bạn có thể thấy ngay là phương thức này trả về 1 đối tượng QString, nghĩa là 1 chuỗi ký tự trong Qt.
Tiếp đo, hãy chú ý đến các tham số của phương thức :
Thật may là 1 vài trong số chúng có những giá trị mặc định. Điều đó khiến những tham số này trở thành không bắt buộc và giúp cho việc sử dụng phương thức trở nên đơn giản hơn rất nhiều.
Chúng ta sẽ tận dụng đoạn mã lúc trước. Tuy nhiên lần này, thay vì hộp thoại thông báo, chúng ta sẽ mở ra hộp thoại nhập thông tin khi chiếc nút được ấn.
void CuaSo::moHopThoai(){ QString nickname = QInputDialog::getText(this, "Nickname", "Nickname của bạn là gì ?"); }
Chỉ trong 1 dòng lệnh, chúng ta đã tạo ra 1 đối tượng QString và trực tiếp gán cho nó giá trị được trả về từ phương thức getText()
. Nếu các bạn muốn rõ ràng hơn nữa thì cũng có thể tách ra thành 2 dòng lệnh riêng biệt.
Chúng ta sẽ nhận được kết quả như sau :
Chúng ta có thể tiếp tục cải tiến hơn nữa bằng cách xác định xem người dùng đã chọn ấn nút nào. Nếu người dùng ấn nút OK, chúng ta sẽ cho bật ra 1 hộp thoại thông báo QMessageBox trong đấy có chứa nickname của người dùng.
void CuaSo::moHopThoai(){ bool ok = false; QString nickname = QInputDialog::getText(this, "Nickname", "Nickname của bạn là gì ?", QLineEdit::Normal, QString(), &ok); if (ok && !nickname.isEmpty()){ QMessageBox::information(this, "Nickname", "Xin chào " + nickname + " !"); }else{ QMessageBox::critical(this, "Nickname", "Hic hic, bạn không muốn tiết lộ nickname của mình à ?"); } }
Trong đoạn mã trên, chúng ta đã tạo ra 1 biến boolean
để chứa thông tin về việc nút OK được ấn hay không.
Để có thể sử dụng tham số này, chúng ta bắt buộc phải cung cấp tất cả những tham số đứng trước nó dù là muốn hay không. Đây cũng là điểm yếu của tham số không bắt buộc trong C++ : nếu tham số cần sử dụng nằm ở cuối nguyên mẫu, bắt buộc phải cung cấp tất cả các tham số trước nó !
Chính vì vậy tôi đã truyền tất cả các tham số kể cả bắt buộc lẫn không bắt buộc trước "ok", trong đó có cả "mode" và "text".
Vậy là đã có thể lấy được kết quả xem nút OK có được ấn không. Khi có được giá trị này, chúng ta sẽ cần thực hiện 2 kiểm tra trước khi mở ra hộp thoại thông báo :
Nếu 2 điều kiện trên được thỏa mãn, chúng ta sẽ mở ra hộp thoại chào mừng. Trong trường hợp ngược lại, nghĩa là 1 trong 2 điều kiện không được đáp ứng, chúng ta sẽ mở ra 1 hộp thoại loại cảnh báo.
Bài tập : tiếp đây hãy thử cải tiến chương trình bằng cách hiện lên nickname của người dùng trong cửa sổ mẹ của hộp thoại (có thể là trên nhãn của nút hoặc đâu đó).
Chúc các bạn vui vẻ !
Hộp thoại chọn kiểu chữ cũng là 1 trong những hộp thoại khá dễ gặp trong các ứng dụng dùng hằng ngày. Chẳng khó gì để bắt gặp 1 hộp thoại như sau trong chương trình.
Hộp thoại lựa chọn này, trong Qt, được quản lý bởi lớp QFontDialog. Lớp này cung cấp cho chúng ta 1 phương thức tĩnh duy nhất, tuy nhiên lại đi kèm đến mấy phiên bản nạp chồng của phương thức này.
Dưới đây là nguyên mẫu của 1 phương thức nạp chồng thuộc loại phức tạp nhất.
QFont getFont ( bool * ok, const QFont & initial, QWidget * parent, const QString & caption )
Các tham số không mấy khó hiểu.
Ngoài những tham số quen thuộc từ bên trên như "ok", "parent", chúng ta bắt gặp những tham số mới : như "initial" là 1 đối tượng kiểu QFont định nghĩa kiểu chữ mặc định sẽ được sử dụng hay như "caption" là đoạn văn bản sẽ hiện lên ở trên cùng của cửa sổ, vv…
Cuối cùng, phương thức này sẽ trả về 1 đối tượng kiểu QFont tương ứng với kiểu chữ mà người dùng lựa chọn.
Để thử nghiệm, tôi đề nghị chúng ta sẽ cải tiến thêm 1 chút đoạn mã lúc trước, theo đó chúng ta sẽ sử dụng kiểu chữ được chọn từ hộp thoại để hiển thị nhãn của chiếc nút. Chúng ta sẽ dùng đến phương thức setFont()
đã sử dụng qua lúc trước để làm việc này.
void CuaSo::moHopThoai(){ bool ok = false; QFont kieuChu = QFontDialog::getFont(&ok, m_nutBam->font(), this, "Chọn kiểu chữ"); if (ok){ m_nutBam->setFont(kieuChu); } }
Phương thức getFont()
sử dụng kiểu chữ mặc định là kiểu chữ được sử dụng trong nhãn của nút bấm (thông qua phương thức font()
là 1 phương thức lấy trả về kết quả kiểu QFont).
Chúng ta sau đó sẽ kiểm tra xem cuối cùng người dùng có nhấn nút « OK ». Trong trường hợp có nhấn, chúng ta sẽ sử dụng kiểu chữ mới cho nhãn bên trên chiếc nút.
Đây là 1 lợi thế của Qt, đó là các lớp của thư viện này phối hợp với nhau rất nhịp nhàng. Lấy ví dụ như ở đây, phương thức getFont()
trả về kết quả là 1 đối tượng QFont và sẽ được dùng để thay đổi kiểu chữ của nút bấm.
Và đây là kết quả chúng ta sẽ nhận được.
Chú ý là kích thước của chiếc nút sẽ không tự động thay đổi phù hợp với kiểu chữ mới. Vậy nên nếu muốn, các bạn có thể thay đổi kích thước này sau khi đã quyết định kiểu chữ mới.
Cùng nguyên lý với hộp thoại chọn kiểu chữ là hộp thoại dùng để chọn màu sắc.
Ở đây chúng ta sử dụng đến lớp QColorDialog và phượng thức tĩnh của nó là getColor()
.
QColor QColorDialog::getColor ( const QColor & initial = Qt::white, QWidget * parent = 0 );
Kết quả trả về là 1 đối tượng kiểu QColor. Chúng ta có thể chỉ định ra màu sắc mặc định thông qua việc sử dụng các giá trị quy ước từ trước trong tài liệu Qt. Nếu không truyền tham số này thì giá trị được sử dụng mặc định sẽ là màu trắng.
Tiếp tục cải tiến đoạn mã lúc trước để thay đổi màu sắc của chiếc nút khá là lằng nhằng. Vấn đề nằm ở việc các widget không sở hữu phương thức tĩnh setColor()
mà lại chỉ có phương thức setPalette()
dùng để định ra 1 bảng màu. Vậy nên trước hết chúng ta sẽ cần tìm hiểu lớp QPalette.
Đoạn mã nêu ra dưới đây sẽ mở ra 1 hộp thoại, từ đó tạo ra 1 bảng màu trong đó có chứa màu mà chúng ta vừa chọn cho văn bản. Bảng màu này sau đó sẽ được áp dụng cho nhãn của nút bấm.
void CuaSo::moHopThoai(){ QColor mau = QColorDialog::getColor(Qt::white, this); QPalette bangMau; bangMau.setColor(QPalette::ButtonText, mau); m_nutBam->setPalette(bangMau); }
Ở đây, tôi không yêu cầu các bạn hiểu được QPalette hoạt động như thế nào. Thậm chí trong phần còn lại của giáo trình thì tôi cũng sẽ không nhắc gì nhiều đến nó. Nếu có thắc mắc thì cần các bạn tự mình đi tìm hiểu.
Dưới đây là kết quả mà chúng ta có thể đạt được.
Chỉ còn lại nốt hộp thoại để chọn tệp và thư mục là chúng ta sẽ tìm hiểu xong những loại hộp thoại cơ bản thường gặp.
Việc lựa chọn tệp và thư mục được chịu trách nhiệm bởi lớp QFileDialog. Lớp này cũng sở hữu 1 phương thức tĩnh cho chúng ta sử dụng.
Phần này có thể chia làm 3 tình huống cụ thể :
Chúng ta cũng chỉ cần đơn giản gọi sử dụng phương thức tĩnh như sau :
QString thuMuc = QFileDialog::getExistingDirectory(this);
Kết quả trả về là chuỗi ký tự là đường dẫn hoàn chỉnh tới thư mục chỉ định.
Cửa sổ được mở ra sẽ có dạng như sau.
Hộp thoại thông dụng “Chọn tệp” được quản lý bởi getOpenFileName()
.
Không cần có tham số, hộp thoại này có thể mở bất cứ tệp nào.
Nếu các bạn muốn hộp thoại chỉ mở 1 loại tệp nhất định, như các tệp ảnh chẳng hạn, chúng ta có thể sử dụng đến bộ lọc (tham số cuối cùng).
Đoạn mã dưới đây mở ra hộp thoại cho phép chọn các tệp ảnh. Đường dẫn của tệp được chọn sẽ được hiển thị thông qua 1 hộp thoại QMesageBox.
void CuaSo::moHopThoai(){ QString tep = QFileDialog::getOpenFileName(this, "Mở tệp", QString(), "Images (*.png *.gif *.jpg *.jpeg)"); QMessageBox::information(this, "Tệp", "Bạn đã chọn mở tệp :\n" + tep); }
Tham số thứ 3 của getOpenFileName()
là tên của thư mục mặc định sẽ mở ra cho người dùng. Tôi sử dụng giá trị mặc định là QString, tương đương với chuỗi ký tự trống, nên hộp thoại sẽ được mở ra trong thư mục trong đó có chứa chương trình của chúng ta.
Nhờ vào tham số thứ 4, tôi thêm vào bộ lọc để chỉ có các loại tệp hình ảnh hiện ra để người dùng lựa chọn.
Cửa sổ sẽ sở hữu tất cả các tính năng được cung cấp bởi hệ điều hành như hiển thị tệp dưới nhiều kích thước, nhiều mức độ chi tiết khác nhau, vv…
Khi chúng ta quyết định chọn 1 tệp thì đường dẫn sẽ được lưu lại và hiện thị trong 1 hộp thông báo được bật ra.
! Nguyên lý là hộp thoại sẽ chỉ trả về đường dẫn tới tệp chỉ định chứ không mở nó. Nếu các bạn muốn thực sự mở tệp này để đọc thông tin thì chính các bạn phải áp dụng các xử lý cần thiết lên đường dẫn này để có thể mở được nó ra.
Cùng chung nguyên lý với với phương thức trước, chỉ khác là lần này để có thể lưu tệp, người dùng đưa ra đường dẫn của 1 tệp có thể chưa tồn tại.
QString tep = QFileDialog::getSaveFileName(this, "Lưu tệp", QString(), "Hình ảnh (*.png *.gif *.jpg *.jpeg)");