3.8. Điểm danh những widget thông dụng

Kể từ khi chúng ta bắt đầu làm quen với Qt, tôi đã từng gián tiếp hoặc trực tiếp nhắc đến 1 số widget với các bạn. Thế nhưng chúng ta lại chưa từng dành thời gian ra để điểm qua mặt những widget mà Qt cung cấp sẵn cho chúng ta để sử dụng.

Nhân tiện hôm nay là 1 ngày đẹp trời, tôi xin phép được tạm dừng việc nói đến các kỹ thuật lập trình và dành thời gian để giới thiệu với các bạn về những widget thông dụng nhất. Nói vậy nhưng chúng ta cũng không thể nào điểm danh hết toàn bộ các widget của Qt bởi chúng thật sự quá nhiều và 1 số trong số chúng cũng rất hiếm khi được sử dụng. 1 số widget khác thì khá là phức tạp và tôi thích dành riêng cả 1 bài học riêng cho chúng.

Vậy là chỉ còn sót lại trong danh sách giới thiệu những widget thông dụng với độ phức tạp không cao. Thế nhưng các bạn cũng không cần quá lo lắng, thế là đủ để tạo nên gần hết các loại cửa sổ mà các bạn có thể nghĩ ra rồi.

Cửa sổ

Trong Qt, mọi thành phần của cửa sổ đều là 1 widget và chính bản thân cửa sổ cũng vậy.

Các widget, trực tiếp hoặc gián tiếp, đều được kế thừa từ QWidget. Đây là 1 lớp cơ bản khá quan trọng nên rồi các bạn sẽ tự thấy là mình thường xuyên phải tham khảo tài liệu của lớp này.

Gợi nhắc về cách tạo cửa sổ

Trong vòng vài bài học trở lại đây, chúng ta đã tạo ra các cửa sổ trong chương trình của chúng ta nhờ lớp QWidget. Vậy có phải "QWidget = Cửa sổ" không ?

Thực ra là không phải thế. Nói chính xác hơn, 1 widget mà không bị chứa bên trong widget nào thì được coi là 1 cửa sổ.

Vậy nên khi chúng ta chạy thử đoạn mã đơn giản dưới đây :

#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]){
    QApplication app(argc, argv);
    QWidget cuaSo;
    cuaSo.show();

    return app.exec();
}

… thì kết quả nhận được sẽ là 1 cửa sổ trống.

Đó là cách Qt thao tác với các widget. Lúc ban đầu, có thể khiến các bạn hơi thất vọng nhưng dần dần các bạn sẽ thấy may mắn là mọi thứ đã được xây dựng như thế.

? Vậy nếu hiểu đúng thì có nghĩa là không tồn tại lớp QWindow hay cái gì đấy đại loại như thế ?

Đúng vậy, trong Qt không có tồn tại 1 lớp chuyên biệt kiểu như QWindow hay QCuaSo gì cả bởi vì tất cả các widget đều có thể được coi như cửa sổ. Như đã nói bên trên, widget nào mà không nằm trong widget khác sẽ được coi là cửa sổ. Nói thế nghĩa là, dù là QPushButton hay QLineEdit, thì cũng đều có thể được coi là cửa sổ nếu chúng không được chứa bên trong widget nào khác.

Tuy vậy, vẫn có 2 loại widget mà tôi muốn đề cập nhiều hơn :

  • QMainWindow: widget đặc biệt chuyên dùng để tạo cửa sổ chính của ứng dụng. 1 cửa sổ chính thường là nơi chứa thanh công cụ, thanh trạng thái hoặc mục lục, vv…
  • QDialog: là lớp hay được sử dụng để tạo nên các hộp thoại mà chúng ta thảo luận lúc trước. Chúng ta cũng dựa vào chúng để tạo ra các hộp thoại tùy chỉnh.

Lớp cửa sổ chính QMainWindow xứng đáng có 1 bài học cho riêng nó. Chúng ta sẽ tạm thời gác lại chưa nói đến nó.

Lớp QDialog được sử dụng để tạo ra các hộp thoại, từ loại mặc định đến những hộp thoại tùy chỉnh tỉ mỉ. 1 hộp thoại về cơ bản chính là 1 cửa sổ nhưng kích thước nhỏ và trong đó chứa khá ít thông tin.

QDialog kế thừa từ QWidget như tất cả các widget khác. Nhưng nó không thêm vào nhiều tính năng so với lớp QWidget cơ bản. 1 trong những tính năng quan trọng nhất mà nó thêm vào là cửa sổ modal (nghĩa là cửa sổ đè trên 1 cửa sổ khác và sẽ không biến mất nếu người dùng chưa thực hiện 1 hành động nào đó).

Trong bài này chúng ta sẽ cùng thảo luận về lớp cơ bản QWidget với những tính năng cho phép tạo ra hầu hết các loại cửa sổ. Tiếp đấy sẽ là 1 phần nhỏ về những gì có thể làm với QDialog. Về phần QMainWindow, như đã nói, sẽ có riêng 1 bài học khác dành trọn cho nó.

Tạo cửa sổ với QWidget

Để bắt đầu, tôi đề nghị chúng ta mở tài liệu của lớp QWidget bên cạnh để tiện theo dõi theo tiến trình bài học.

Tài liệu QWidget

Các bạn hẳn đã nhận ra đây là lớp mẹ của hàng ti tỉ lớp khác. Nó sở hữu rất nhiều thuộc tính cũng như phương thức khác nhau. Nhờ vậy các lớp khác kế thừa nó cũng đồng thời có những phương thức và thuộc tính này.

Chúng ta đại khái có thể chia các thuộc tính ra thành 2 loại :

  • Các thuộc tính dành cho tất cả các widget và cửa sổ
  • Các thuộc tính chỉ có ý nghĩa với cac cửa sổ

Hãy chỉ chú ý vào những thuộc tính có ích nhất. Nếu bạn nào cần danh sách đầy đủ, vậy hãy dùng tài liệu của Qt ngay bên cạnh. Chúng ta sẽ không mất công để nhắc lại toàn bộ chúng.

Các thuộc tính dành cho mọi widget

Dưới đây là 1 danh sách ngắn tôi đã tạo nhanh cho các bạn, trong đó có chứa những thuộc tính hấp dẫn nhất. Để hiểu cách sử dụng chúng, giống như tôi đã nói bên trên, các bạn có tài liệu chính thức của Qt ở ngay bên cạnh.

! Đừng quên là trong Qt, chúng ta luôn có 1 phương thức đặt tương ứng với mỗi thuộc tính. Tên của phương thức này là tên của thuộc tính với “set” ở ngay đằng trước. Ví dụ như với thuộc tính cursor, chúng ta có phương thức setCursor().

  • cursor : hình dáng con trỏ chuột khi chỉ lên widget. Phương thức setCursor() cần tham số thuộc kiểu QCursor. 1 vài hình thái con trỏ cơ bản đã được định nghĩa sẵn trong Qt. Trong tài liệu Qt có đường dẫn tới danh sách liệt kê này.
  • enable : chỉ ra xem widget có được kích hoạt không và chúng ta liệu có thể thay đổi nó không. 1 widget không kích hoạt thường hiển thị mờ hơn và không tương tác được. Chúng ta có thể setEnable(false) cho bất kỳ widget nào mà chúng ta muốn tránh tương tác với người dùng.
  • height : chiều cao của cửa sổ
  • size : kích thước cửa sổ. Cần chỉ rõ chiều cao và chiều ngang.
  • visible : cửa số có thể được nhìn thấy hoặc không.
  • width : chiều rộng cửa sổ

Tất cả đều có phương thức đặt của riêng mình. Ví dụ như :

cuaSo.setWidth(200);

Đây là những thuộc tính mà chúng ta có thể tìm thấy ở mọi widget. Có thể hiểu đơn giản là nếu sử dụng hàm setWidth trên 1 chiếc nút thì sẽ thay đổi chiều ngang của chiếc nút, còn nếu sử dụng trên 1 cửa sổ thì sẽ thay đổi chiều ngang của cửa sổ đó.

Các thuộc tính dành riêng cho cửa sổ

Rất dễ để nhận ra các thuộc tính này bởi chúng đều bắt đầu bằng window. Các thuộc tính này chỉ có ý nghĩa khi sử dụng chúng trên cửa sổ. Dưới đây là các loại thuộc tính dạng này.

  • Các windowFlags : là hệ thống các cờ tùy chọn cho phép tùy chỉnh đặc tính của cửa sổ. Chúng ta sẽ cần tra cứu bảng liệt kê Qt::WindowType để biết tất cả những tùy chọn có thể. Hãy lấy ví dụ 1 cửa sổ thuộc kiểu “công cụ” với nút “x” nhỏ ở góc và không thể thay đổi kích thước.
cuaSo.setWindowFlags(Qt::Tool);
  • windowIcon : dùng để thiết lập biểu tượng cho cửa sổ. Thuộc tính này nhận giá trị là 1 đối tượng QIcon mà chúng ta có thể tạo ra từ các tệp ảnh. Dưới đây là mã nguồn tạo ra biểu tượng của cửa sổ từ tệp ảnh trong cùng thư mục.
cuaSo.setWindowIcon(QIcon("icon.png"));
  • windowTitle dùng để đặt tiêu đề cho cửa sổ. Tiêu đề này sẽ hiện lên ở góc trên bên trái của cửa sổ.
Tạo cửa sổ với QDialog

QDialog là 1 widget đặc biệt cho phép chúng ta tạo ra các cửa sổ kiểu hộp thoại.

? Chúng có gì khác biệt so với những cửa sổ tạo ra bởi QWidget ?

Về cơ bản thì QDialog tạo ra các cửa sổ phụ nhỏ hơn, thường gặp là những cửa sổ đưa ra câu hỏi với nhiều lựa chọn.

QDialog hiếm khi được sử dụng để tạo ra các cửa sổ chính. Như đã nói thì để tạo cửa sổ chính, chúng ta sẽ trực tiếp dùng QMainWindow hoặc ít nhất cũng là QWidget.

Có 2 loại cửa sổ QDialog :

  • Hộp thoại trạng thái : là loại hộp thoại ngăn người dùng tương tác với các cửa sổ khác khi nó mở ra. Ở trên tôi từng nhắc tới modal, chính là trạng thái của loại hộp thoại này.
  • Hộp thoại không trạng thái : vẫn cho phép tương tác với các cửa sổ khác khi cửa sổ này mở.

Mặc định thì các cửa sổ tạo ra bởi QDialog là hộp thoại trạng thái.

Các hộp thoại này sở hữu 1 phương thức là exec() dùng để mở ra hộp thoại trong trạng thái modal. Phương thức này cũng đồng thời là 1 slot nên sẽ rất thuận tiện nếu các bạn muốn tạo ra các liên kết trao đổi giữa các cửa sổ.

Dưới đây, chúng ta sẽ thử 1 ví dụ cho phép kiểm nghiệm tất cả những kiến thức trên. Chúng ta sẽ tạo 1 cửa sổ chính bằng QWidget, trong đó có chứa 1 chiếc nút mà khi ấn vào thì sẽ bật ra 1 cửa sổ kiểu QDialog. Cửa sổ phụ kiểu QDialog đơn giản là chứa 1 hình ảnh là đủ.

#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[]){
    QApplication app(argc, argv);

    QWidget cuaSo;
        QPushButton *nutBam = new QPushButton("Mở cửa sổ", &cuaSo);

    QDialog cuaSoPhu (&cuaSo);
        QVBoxLayout *lop = new QVBoxLayout;
        QLabel *hinhAnh = new QLabel(&cuaSoPhu);
        hinhAnh->setPixmap(QPixmap("icon.png"));
        lop->addWidget(hinhAnh);
        cuaSoPhu.setLayout(lop);

    QWidget::connect(nutBam, SIGNAL(clicked()), &cuaSoPhu, SLOT(exec()));
        
        cuaSo.show();
        return app.exec();
}

! 1 số người tinh ý sẽ nhận ra là đoạn mã ví dụ thò ra thụt vào 1 cách hơi bị bất bình thường. Tuy nhiên tôi thấy như vậy thì dễ nhìn hơn. Chúng ta nhận ra ngay đâu là câu lệnh tạo ra các cửa sổ cũng như những đối tượng mà chúng ta thêm vào cửa sổ đó. Trong ví dụ này thì tất cả mã nguồn nằm trong hàm main. Thế nhưng trong thực tế, mỗi cửa sổ thường được viết trong tệp .cpp của riêng nó.

Và đây là kết quả của đoạn mã ví dụ.

Khi chúng ta bấm nút thì cửa sổ phụ hiện ra và chúng ta không thể thao tác với cửa sổ chính bên dưới được nữa.

Trên đây chỉ là ví dụ đơn giản nhất. Nếu muốn biết thêm gì về lớp QDialog, các bạn hẳn biết là phải làm gì rồi nhỉ J, tài liệu của Qt hướng dẫn rất đầy đủ đấy.

Nút bấm

Phần tiếp theo sẽ dành để thảo luận về các widget thể loại “nút bấm”. Chúng ta sẽ cùng nhau điểm danh qua 1 số loại nút bấm sau đây :

  • QPushButton : loại nút bấm cơ bản mà chúng ta đã cùng nhau theo tác từ 1 vài bài học trước.
  • QRadioButton : nút bấm cho phép thực hiện chọn phương án trắc nghiệm 1 kết quả.
  • QCheckBox : nút bấm cho phép thực hiện chọn phương án trắc nghiệm nhiều kết quả.

Tất cả những widget trên đều kế thừa lớp QAbstractButton, vốn là 1 lớp con của QWidget. Và như mọi người đều biết QWidget thì lại là lớp con của QObject.

Có thể nhận ra ngay từ tên lớp, lớp QAbstractButton là 1 lớp trừu tượng. Những ai đã nắm vững khái niệm lập trình hướng đối tượng chắc hẳn đều sẽ hiểu nghĩa là chúng ta không thể thực thể hóa đối tượng của lớp này.

Vậy là chúng ta không thể nào tạo ra đối tượng kiểu QAbstractButton mà chỉ có thể sử dụng các lớp con của nó. Lớp này chỉ đóng vai trò nền tảng để xây dựng các lớp con chuyên biệt.

QPushButton : nút bấm thường

QPushButton có thể coi là 1 trong những thành phần cơ bản và thường thấy nhất trong tất cả các cửa sổ. Chắc là không cần tôi nhắc lại nút bấm dùng để làm gì đâu nhỉ ? Đương nhiên là để …bấm rồi.

Hãy bắt đầu bằng 1 thông tin quan trọng : QPushButton kế thừa từ QAbstractButton. Tại sao lại nói là thông tin này quan trọng ? Bởi vì các bạn sẽ nhanh chóng nhận ra là QPushButton không có mấy phương thức và thuộc tính thuộc về riêng nó mà đã phần là đến từ lớp mẹ QAbstractButton.

! Vậy là chúng ta sẽ cần xem cả tài liệu của QAbstractButton và thậm chí là cả QWidget hay QObject nếu muốn biết tất cả những tính năng mà QPushButton có thể cung cấp cho chúng ta. Ví dụ như setEnable(), là phương thức định nghĩa trong QWidget, có thể được sử dụng để kích hoạt hay vô hiệu hóa nút bấm.

1 nút bấm sẽ gửi đi tín hiệu clicked() khi được ấn. Đây là tín hiệu thường hay được sử dụng nhất.

Chúng cũng sở hữu những tín hiệu khác như pressed() (nhấn và giữ nút) et released() (nhả nút) những hiếm khi được sử dụng hơn.

QCheckBox : ô chọn

1 ô chọn QCheckBox thường đi kèm với đoạn văn bản làm nhãn như hình dưới đây.

Nhãn này được định nghĩa khi chúng ta gọi phương thức tạo ra ô chọn.

#include <QApplication>
#include <QWidget>
#include <QCheckBox>
int main(int argc, char *argv[]){
    QApplication app(argc, argv);
    QWidget cuaSo;
    QCheckBox *oChon = new QCheckBox("Tôi chọn bạn", &cuaSo);
    cuaSo.show();
    return app.exec();
}

Ô chọn sẽ gửi đi tín hiệu stateChanged(bool) khi trạng thái chọn bị thay đổi. Tham số boolean được gửi đi cho biết xem ô chọn có được đánh dấu hay không.

Ở bất cứ thời điểm nào, chúng ta đều có thể kiểm tra trạng thái 1 ô chọn có được đánh dấu không bằng cách sử dụng phương thức isChecked().

Chúng ta cũng có thể tùy chỉnh để ô chọn mang 3 trạng thái. Ngoài 2 trạng thái thường là được đánh dấu hoặc không, trạng thái thứ 3 là trạng thái bị vô hiệu hóa, tức là không thể bị tác động. Nếu bạn nào muốn tìm hiểu thì có thể nghiên cứu thuộc tính triaste của lớp này. Đây là 1 trạng thái khá hiếm khi được sử dụng.

Cuối cùng, khi có nhiều ô chọn và chúng ta muốn nhóm chúng lại với nhau, chúng ta có thể sử dụng lớp QGroupBox.

QRadioButton : ô chọn radio

Ô chọn radio là 1 loại ô chọn đặc biệt : trong 1 danh sách thì chỉ có thể lựa chọn 1 phương án, hay dùng cho các trắc nghiệm với 1 phương án duy nhất.

Những ô chọn radio trong cùng 1 nhóm có liên hệ với nhau nên khi chúng ta chọn 1 ô trong số chúng thì tất cả các ô còn lại sẽ tự động bỏ chọn.

Chúng ta cũng sử dụng QGroupBox để nhóm các ô chọn radio với nhau. Nếu các bạn không muốn 2 ô chọn radio liên hệ với nhau, đơn giản chỉ cần đưa chúng vào 2 nhóm khác nhau là được.

Dưới đây là 1 ví dụ ngắn gọn sử dụng loại widget này.

#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[]){
    QApplication app(argc, argv);
    QWidget cuaSo;
    QGroupBox *monNgon = new QGroupBox("Hãy chọn món ăn mà bạn thích ăn nhất !", &cuaSo);
    QRadioButton *bunCha = new QRadioButton("Bún chả");
    QRadioButton *banhXeo = new QRadioButton("Bánh xèo");
    QRadioButton *huTieu = new QRadioButton("Hủ tiếu bò viên");
    bunCha ->setChecked(true);
    QVBoxLayout *lop = new QVBoxLayout;

    lop ->addWidget(bunCha);
    lop ->addWidget(banhXeo);
    lop ->addWidget(huTieu);

    monNgon->setLayout(lop);
    monNgon ->move(5, 5);
    cuaSo.show();
    return app.exec();
}

! Nhắc lại chút kiến thức. Khi chúng ta thêm QtWidgets vào trong tiêu đề thì nó sẽ tự động thêm tất cả gói các loại widget vào để chúng ta có thể sử dụng được.

Các ô chọn được xếp vào 1 lớp và lớp này được đặt trong 1 đối tượng QGroupBox rồi đối tượng này được đặt trong 1 cửa sổ. Khái niệm widget trong widget được thể hiện rõ ràng trong ví dụ trên.

Các widget hiển thị

Trong số các widget dùng để hiển thị, chúng ta có thể điểm ra vài loại quan trọng :

  • QLabel : một trong những widget quan trọng nhất, dùng để hiển thị văn bản hoặc hình ảnh.
  • QProgressBar : hiển thị thanh tiến trình
QLabel : hiển thị văn bản hoặc hình ảnh

Đây chính là widget mà chúng ta đã nhiều lần sử dụng bên trong cửa sổ để hiển thị văn bản. Chúng ta cũng từng gián tiếp sử dụng chúng trong các widget ô chọn hay bản mẫu.

! QLabel kế thừa QFrame vốn là 1 widget dùng để hiển thị 1 khoảng trống có giới hạn bởi đường viền. Hãy tham khảo tài liệu của QFrame để biết thêm về cách thao tác với widget này. Về phần QLabel, mặc định thì widget này không có đường viền.

1 đối tượng QLabel có thể hiển thị nhiều kiểu thành phần khác nhau, có thể là văn bản hoặc hình ảnh.

Hiển thị văn bản đơn giản

Không có gì có thể đơn giản hơn nữa, chúng ta chỉ cần sử dụng phương thức setText() :

nhan->setText("Xin chào !");

Ngoài ra chúng ta cũng có thể tạo ngay giá trị văn bản muốn hiển thị từ khi gọi hàm khởi tạo.

QLabel *nhan = new QLabel("Xin chào !", &cuaSo);

Nếu các bạn muốn thực hiện căn lề văn bản, vậy thì có thể nghía qua 1 chút thuộc tính tên là alignment.

! Chú ý là chúng ta cũng có thể viết văn bản xen lẫn trong mã HTML để thực hiện 1 số thay đổi nhỏ kiểu cách văn bản.

Hiển thị hình ảnh

Bên cạnh hiển thị văn bản, chúng ta cũng có thể dùng QLabel để hiển thị hình ảnh.

Bởi vì lớp này không có phương thức khởi tạo nào nhận tham số là hình ảnh, vậy nên chúng ta sẽ tạo ra 1 nhãn thông thường với tham số chỉ là cửa sổ mẹ sẽ chứa nó. Tiếp đấy chúng ta mới sử dụng phương thức setPixmap() để cho biết hình ảnh chúng ta muốn hiển thị trong nhãn. Phương thức này nhẫn vào tham số là 1 đối tượng QPixmap, có thể được tạo ra khi chúng ta biết đường dẫn tới 1 tệp hình ảnh. Làm sao tôi lại biết cách để tạo ra đối tượng QPixmap ư ? Đương nhiên là do đọc tài liệu về lớp này rồi.

QProgressBar : thanh tiến trình

Các thanh tiến trình, cho phép người dùng biết được tiến độ của 1 xử lý, được quản lý bởi lớp QProgressBar.

Dưới đây là 1 vài thuộc tính của thanh chạy tiến trình.

  • maximum : giá trị lớn nhất của thanh chạy
  • minimum : giá trị nhỏ nhất của thanh chạy
  • value : giá trị hiện tại của thanh chạy

Chúng ta dùng phương thức setValue() để thay đổi giá trị hiển thị hiện tại của thanh chạy. Mặc định thì giá trị này sẽ nằm giữa 0% và 100%.

! Qt không thể nào tự mình biết được tiến độ của 1 xử lý bất kỳ. Chính các lập trình viên là người thực hiện tính toán để cho ra % tiến độ đã hoàn thành. QProgressBar chỉ đơn giản là để hiển thị kết quả đó.

QProgressBar sẽ gửi đi 1 tín hiêu valueChanged() khi giá trị của nó thay đổi.

Các trường thông tin

Phần dưới đây chúng ta sẽ nói về các widget dùng trong việc nhập dữ liệu. Đây là kiểu widget quan trọng nhất theo ý kiến cá nhân tôi.

Thêm 1 lần nữa, chúng ta sẽ không trình bày tất cả mà chỉ nhắc đến những widget quan trọng nhất.

  • QLineEdit : trường để nhập 1 dòng văn bản
  • QTextEdit : trường để nhập nhiều dòng văn bản
  • QSpinBox : trường thích hợp cho việc nhập số nguyên
  • QDoubleSpinBox : trường thích hợp để nhập số thực
  • QSlider : thanh chạy con trỏ cho phép chọn giá trị
  • QComboBox : hộp danh sách giá trị
QLineEdit : trường để nhập 1 dòng văn bản

Chúng ta đã từng thấy qua widget này khi lấy ví dụ làm mẫu cách sử dụng tài liệu Qt.

QLineEdit là trường cho phép nhập 1 dòng văn bản.

Ứng dụng của nó trong đa số trường hợp là vô cùng đơn giản. Một vài thuộc tính mà chúng ta cần để mắt đến :

  • text : cho phép lấy ra hoặc thay đổi giá trị chứa trong trường
  • alignment : căn lề văn bản bên trong trường
  • echoMode : kiểu hiển thị của văn bản. Chúng ta cần phải xem danh sách liệt kê EchoMode để biết về các kiểu hiển thị dựng sẵn. Mặc định hiển thị thì văn bản nhập trong trường sẽ được che đi như các bạn hay thấy trong trường hợp nhập mật khẩu.
vanBanNgan->setEchoMode(QLineEdit::Password);
  • inputMask : cho phép định ra 1 khuôn khổ định dạng (mask) để bắt người dùng phải nhập dữ liệu theo một kiểu mẫu, tuân thủ những quy tắc nhất định (ví dụ số điện thoại chỉ được có toàn chữ số, vv…). Nếu bạn nào tò mò thì có thể xem thêm về các lớp xác nhận (validator) là 1 cách khác để kiểm soát nội dung nhập vào bới người dùng.
  • maxlength : số ký tự tối đa có thể nhập
  • readOnly : cho biết nội dung của trường có thể thay đổi hay không. Thuộc tính này gần giống với thuộc tính enable được định nghĩa trong QWidget. Khác biệt duy nhất là với readOnly thì chúng ta vẫn có thể sao chép nội dung từ trong trường ra bên ngoài chứ không phải hoàn toàn không tác động được như trong trường hợp của enable.

Lớp này cũng cung cấp khá nhiều slot khác nhau thức hiện những thao tác như cắt, sao chép, dán, vv… văn bản trong trường.

Cuối cùng, lớp cũng có 1 vài tín hiệu như returnPressed() (tín hiệu gửi đi khi ấn nút Enter) ou textChanged() (tín hiệu gửi đi khi văn bản thay đổi) khá hữu dụng trong 1 vài trường hợp.

QTextEdit : trường để nhập nhiều dòng văn bản

Widget này khá giống với widget mà chúng ta vừa thảo luận bên trên, chỉ khác là nó cho phép nhập vào đoạn văn bản nhiều dòng cũng như là có hỗ trợ các kiểu cách chữ khác nhau.

Có khá nhiều thứ để nói về widget QTextEdit và có khả năng khiến nội dung của bài học này trở nên quá dài và đi lệch khỏi mục đích điểm danh nhanh các widget.

Ở đây, chúng ta chỉ cần chú ý là lớp này sở hữu 2 thuộc tính là plainTexthtml lần lượt tương ứng cho phép chúng ta thao tác với văn bản của trường dưới dạng chữ kiểu đơn giản và dạng chữ kiểu cách. Trong đa số trường hợp thì plainText là đủ nhưng không có gì là tuyệt đối, tất cả tùy thuộc vào các thao tác mà chúng ta đã thực hiện với trường.

QSpinBox : trường nhập số nguyên

QSpinBox là 1 trường kiểu QLineEdit nhưng chỉ cho phép nhập vào giá trị số nguyên. Trường này sở hữu 2 nút nhỏ có thể dùng để tăng giảm giá trị của số được ghi trong trường.

QSpingBox kế thừa lớp QAbstractSpinBox (lại thêm 1 lớp trừu tượng nữa). Vậy nên hãy tham khảo thêm cả tài liệu của lớp QAbstractSpinBox để nắm được toàn bộ các tính năng mà QSpinBox hỗ trợ.

Dưới đây là 1 vài thuộc tính cần chú ý :

  • accelerated : cho phép hộp giá trị tăng nhanh giá trị trong trường khi nút ở bên phải trường được nhấn trong thời gian dài.
  • minimum : giá trị tối thiểu có thể điền
  • maximum : giá trị tối đa có thể điền
  • singleStep : bước tăng của giá trị mỗi lần bấm nút, mặc định là 1 đơn vị. Nếu các bạn muốn mỗi lần bấm nút tăng 100 đơn vị thì đây chính là thuộc tính cần dùng đến.
  • value : giá trị chứa trong trường
  • prefix : văn bản hiện trước giá trị số trong trường
  • suffix : văn bản hiện sau số trong trường
QDoubleSpinBox : trường nhập số thực

QDoubleSpinBox về cơ bản là giống như QSpinBox, chỉ khác là nó làm việc với số thực thay vì số nguyên.

Chúng ta có thể tìm thấy hầu như tất cả các thuộc tính mà chúng ta vừa nhắc đến ở phần trước. Thêm vào đó, chúng ta cũng có 1 thuộc tính khác là decimals chịu trách nhiệm thao tác với phần giá trị thập phân của giá trị trong trường.

QSlider : thanh chạy con trỏ cho phép chọn giá trị

QSlider là widget có hình dạng 1 thanh chạy với con trỏ đển chọn giá trị.

QSlider kế thừa từ QAbstractSlider (ặc, lại 1 lớp trừu tượng nữa) là lớp hỗ trợ khá nhiều tính năng cơ bản.

QSlider sở hữu khá nhiều thuộc tính giống như QSpinBox nên tôi không nhắc lại nữa. Ngoài ra, lớp này có thêm thuộc tính orientation cho phép chúng ta định nghĩa phương của thanh chạy (dọc hoặc ngang).

Các bạn còn cần chú ý đến các tín hiệu của lớp này vì có những tín hiệu quan trọng như valueChanged(int) là tín hiệu rất hay được nối với slot của widget khác để xử lý tương tác của người dùng. Nếu các bạn chưa quên thì chúng ta đã từng thao tác với lớp này trong bài học về tín hiệu và slot. Có thể quay về và đọc lại bài học đó nếu các bạn còn có điều gì chưa hiểu về widget này.

QComboBox : hộp danh sách giá trị

QComboBox là widget dưới dạng hộp danh sách kéo thả. Người ta thường thêm các giá trị vào hộp sử dụng phương thức addItem().

QComboBox *danhSach = new QComboBox(&cuaSo);
danhSach ->addItem("Paris");
danhSach ->addItem("London");
danhSach ->addItem("Tokyo");

Chúng ta đương nhiên sở hữu 1 vài thuộc tính để tùy chỉnh hoạt đọng của QComboBox.

  • count : số lượng giá trị của danh sách thả
  • currentIndex : trị số của giá trị đang được chọn. Giá trị của các trị số bắt đầu từ 0, nghĩa là nếu trị số trả về là 2 thì giá trị đang được chọn sẽ là “Tokyo”.
  • currentText : văn bản tương ứng với giá trị đang được chọn. Nếu chúng ta chọn “Paris” thì giá trị thuộc tính này cũng là “Paris”.
  • editable : cho biết liệu widget có chấp nhận việc thêm các giá trị tùy chỉnh không. Mặc định thì là không thể thêm giá trị vào danh sách.

Nếu danh sách cho phép thêm giá trị vào thì nó sẽ đi kèm thêm 1 trường văn bản để người dùng có thể nhập giá trị mà họ muốn thêm vào danh sách. Giá trị sẽ được thêm vào danh sách sau khi người dùng ấn nút Enter. Các giá trị mới thêm vào sẽ nằm ở cuối danh sách.

QComboBox gửi đi tín hiệu currentIndexChanged() để thông báo là người dùng đã chọn giá trị mới và tín hiệu highlighted() để chỉ ra giá trị nào trong danh sách đang được chỉ bởi chuột. 2 tín hiệu này có thể gửi 1 giá trị int cho trị số hoặc đối tượng kiểu QString tùy hoàn cảnh sử dụng.

Các widget chứa

Về cơ bản thì bất cứ widget nào cũng có thể dùng để chứa widget khác.

Tuy nhiên, có 1 số widget được thiết kế đặc biệt để thích hợp với vai trò này.

  • QFrame : khoảng trống bao quanh bởi đường viền
  • QGroupBox : widget thích hợp để nhóm các ô chọn lại với nahu
  • QTabWidget : widget chứa nhiều thẻ

QFrame không quá phức tạp. Các bạn có thể tìm hiểu trực tiếp tài liệu của lớp này. Qua đó, chúng ta sẽ tự lựa chọn ra loại viền thích hợp với yêu cầu sử dụng của từng người. Ngoài ra thì widget này không còn gì khác đặc biệt nữa, chỉ sử dụng để chứa đựng các widget khác.

Về phần QGroupBox, chúng ta đã vừa nói về chúng ở ngay bên trên. Vậy nên hãy quan tâm nhiều hơn đến QTabWidget, widget chia không gian sử dụng thành các thẻ.

Widget này cần thao tác khá tinh tế bới nó là hệ thống quản lý đồng thời nhiều vùng chứa widget thông qua hệ thống thẻ.

Tại sao tôi lại nói là thao tác với widget này cần sự tinh tế ? Bởi thực tế mỗi thẻ của nó chỉ có thể chứa được 1 widget duy nhất.

? Hả ? Chỉ 1 widget trong 1 thẻ thôi á ? Vậy thì có tác dụng quái gì đây ?

Đúng vậy. Thế nhưng đừng quên là 1 widget có thể chứa rất nhiều widget khác bên trong nó. Và khi chúng ta thêm vào các lớp để sắp xếp trình bày các widget thì chúng ta có thể tạo ra những giao diện rất tuyệt vời. Tất cả phụ thuộc vào trí tưởng tượng và cách sử dụng kết hợp các widget của các bạn.

Dựa theo phần giới thiệu trong tài liệu của lớp QTabWidget, chúng ta cần phải thao tác với nó như sau.

  1. Tạo ra đối tượng QTabWidget
  2. Tạo ra QWidget tương ứng với từng thẻ của QTabWidget, nhưng không chỉ ra widget chứa nó
  3. Thêm các widget vào các QWidget mà chúng ta vừa tạo ra và sắp xếp chúng với sự hỗ trợ của các lớp sắp xếp.
  4. Sử dụng phương thức addTab() của QTabWidget để thêm từng QWidget vừa tạo ra vào trong nó.

Hơi lằng nhằng 1 chút nhỉ. Tuy nhiên chúng ta cũng cần chút thử thách trong bài học dễ dàng này chứ.

Nếu các bạn tuân thủ theo từng bước đã nêu trên, các bạn sẽ thấy là chúng ta không gặp phải vấn đề gì lớn. Hãy thử xem đoạn mã dưới đây và tự rút ra các nhận xét cho riêng mình.

#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[]){
    QApplication app(argc, argv);
    QWidget cuaSo;

    // 1 : Tạo ra đối tượng QTabWidget
    QTabWidget *cuaSoThe = new QTabWidget(&cuaSo);
    cuaSoThe->setGeometry(30, 20, 240, 160);

    // 2 : Tao trang ben trong moi the de chua cac widget con
    QWidget *trang1 = new QWidget;
    QWidget *trang2 = new QWidget;
    QLabel *trang3 = new QLabel; // Do QLabel cun ke thua tu QWidget nen cung dung nhu 1 trang duoc

    // 3 : Tao ra noi dung tung trang
        // Trang 1
        QLineEdit *ten = new QLineEdit("Nhập tên bạn:");
        QPushButton *nutBam1 = new QPushButton("Ấn vào đây");
        QPushButton *nutBam2 = new QPushButton("Hoặc đây…");

        QVBoxLayout *lop1 = new QVBoxLayout();
        lop1->addWidget(ten);
        lop1->addWidget(nutBam1);
        lop1->addWidget(nutBam2);
        trang1->setLayout(lop1);

        // Trang 2
        QProgressBar *thanhTienTrinh = new QProgressBar();
        thanhTienTrinh ->setValue(50);
        QSlider *thanhChay = new QSlider(Qt::Horizontal);
        QPushButton *nutBam3 = new QPushButton("Xác nhận");

        QVBoxLayout *lop2 = new QVBoxLayout();
        lop2->addWidget(thanhTienTrinh);
        lop2->addWidget(thanhChay);
        lop2->addWidget(nutBam3);
        trang2->setLayout(lop2);

        // Trang 3
        trang3->setPixmap(QPixmap("icon.png"));
        trang3->setAlignment(Qt::AlignCenter);

    // 4 : Them tung the vao QTabWidget
    cuaSoThe->addTab(trang1, "Thông tin");
    cuaSoThe->addTab(trang2, "Tiến độ");
    cuaSoThe->addTab(trang3, "Hình ảnh");

    cuaSo.show();
    return app.exec();
}  

Các bạn có thể tìm thấy từng bước mà tôi đã nhắc đến bên trên. Duy có chút khác biệt nhỏ là trong thẻ thứ 3 tôi không sử dụng QWidget mà thay bằng QLabel nhưng không có vấn đề gì vì đây cũng là 1 lớp con của QWidget và chúng ta cũng chỉ dùng nó đơn giản để chứa 1 cái ảnh.

Kết quả có thể thấy như hình dưới đây.

Để luyện tập, tôi có ý kiến là các bạn có thể cố gắng tự tạo 1 QWidget của riêng mình. Đây sẽ là cơ hội để tập cách sử dụng của đa số widget được nhắc đến trong bài này.

Tóm tắt bài học :
  • Với Qt, mọi thành phần của cửa sổ đều là 1 widget, từ nút bấm, hình ảnh, vv… đến cả bản thân cửa sổ.
  • Qt cung cấp 1 thư viện widget khá phong phú. Để học cách sử dụng của từng thành phần thì cách tốt nhất là trực tiếp đọc tài liệu về nó. Không cần cố học thuộc long hết tất cả chúng. Đừng ngại tra tài liệu mỗi khi cần thiết.
  • Mọi widget đều có thể chứa widget khác bên trong. 1 số widget được thiết kế chuyên để làm việc này, đó là các widget chứa.
  • 1 widget không bị chứa trong widget khác sẽ hiển thị dưới dạng cửa sổ.