Qt 5 C++ – Layout

4/5 - (10 votes)

Trong phần này các bạn sẽ được học cách làm việc với các loại layout có trong Qt 5, mình sẽ đề cập đến bốn loại layout là QHBoxLayout, QVBoxLayout, QFormLayout, và QGridLayout.

Nói về layout thì khi bạn viết một ứng dụng, bạn sẽ phải dùng đến các widget (hay control trong Visual Studio) như nút bấm, label… trong Qt 5 bạn có hai cách để thiết kế chúng, một là tự quy định vị trí, kích thước, cái này gọi là Absolute Positioning, hai là dùng các layout, các layout sẽ quản lý widget cho bạn.

Absolute Positioning

Như đã nói ở trên, bạn sẽ tự định vị vị trí cũng như kích thước của từng widget theo tọa độ pixel. Và đây là những vấn đề bạn sẽ gặp nếu thiết kế theo kiểu Absolute Positioning:

  • Kích thước của các widget sẽ không tự động thay đổi khi cửa sổ chính thay đổi.
  • Ứng dụng nhìn sẽ rất khác (thường là xấu hơn) trên từng hệ điều hành.
  • Thay đổi kiểu font cũng sẽ làm hỏng giao diện.
  • Nếu bạn muốn thay đổi giao diện, chẳng hạn thêm/bớt nút bấm, di chuyển textbox lên xuống… bạn sẽ phải làm lại từ đầu, như thế rất tốn thời gian và không chuyên nghiệp.

Tuy sẽ có những trường hợp mà bạn phải làm theo kiểu absolute. Còn lại thì hầu hết đều nên dùng đến layout.

#include <QApplication>
#include <QDesktopWidget>
#include <QTextEdit>

class Absolute : public QWidget {
    
 public:
     Absolute(QWidget *parent = 0);
};

Absolute::Absolute(QWidget *parent)
    : QWidget(parent) {
        
  QTextEdit *ledit = new QTextEdit(this);
  ledit->setGeometry(5, 5, 200, 150);
}

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  Absolute window;

  window.setWindowTitle("Absolute");
  window.show();

  return app.exec();
}

Trong ví dụ trên chúng ta tạo ra widget QTextEdit và thiết kế nó bằng tay. Phương thức setGeometry() quy định vị trí và kích thước của widget.

Capture
Trước khi thay đổi kích thước window
Capture
Sau khi thay đổi kích thước window

QVBoxLayout

Lớp QVBoxLayout sẽ sắp xếp các widget theo hàng dọc. Để thêm widget vào layout thì ta dùng phương thức addWidget().

#pragma once

#include <QWidget>

class VerticalBox : public QWidget {

  public:
    VerticalBox(QWidget *parent = 0);
};
#include "verticalbox.h"
#include <QVBoxLayout>
#include <QPushButton>

VerticalBox::VerticalBox(QWidget *parent)
    : QWidget(parent) {

  QVBoxLayout *vbox = new QVBoxLayout(this);
  vbox->setSpacing(1);
  
  QPushButton *settings = new QPushButton("Settings", this);
  settings->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  QPushButton *accounts = new QPushButton("Accounts", this);
  accounts->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  QPushButton *loans = new QPushButton("Loans", this);
  loans->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  QPushButton *cash = new QPushButton("Cash", this);
  cash->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  QPushButton *debts = new QPushButton("Debts", this);
  debts->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

  vbox->addWidget(settings);
  vbox->addWidget(accounts);
  vbox->addWidget(loans);
  vbox->addWidget(cash);
  vbox->addWidget(debts);

  setLayout(vbox);
}

Trong ví dụ trên, chúng ta có một layout và năm button. Mỗi button sẽ dãn ra theo cả hai chiều ngang và dọc.

QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->setSpacing(1);

Tạo một đối tượng QVBoxLayout, phương thức setSpacing() quy định khoảng cách giữa các widget, trong trường hợp này là 1 pixel.

QPushButton *settings = new QPushButton("Settings", this);
settings->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

Với mỗi button được tạo ra, chúng ta quy định kích thước cho chúng bằng phương thức setSizePolicy(), tham số đầu tiên là kích thước chiều ngang, tham số thứ hai là kích thước chiều dọc. Mặc định trong Qt thì chiều ngang sẽ tự động dãn nở, còn chiều rộng thì không. Hằng số QSizePolicy::Expanding quy định kiểu co dãn là tự động co dãn.

vbox->addWidget(settings);
vbox->addWidget(accounts);
...

Sử dụng phương thức addWidget() để đưa widget vào layout.

setLayout(vbox);

Cuối cùng set layout cho window.

#include "verticalbox.h"
#include <QApplication>

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  VerticalBox window;

  window.resize(240, 230);
  window.setWindowTitle("VerticalBox");
  window.show();

  return app.exec();
}
Capture

Bạn có thể thử kéo dãn cửa sổ window để thấy các button cũng tự co dãn theo.

Buttons

Trong ví dụ dưới đây, chúng ta sẽ hiển thị hai button nằm ở góc dưới bên phải cửa sổ.

#pragma once

#include <QWidget>
#include <QPushButton>

class Buttons : public QWidget {
    
  public:
    Buttons(QWidget *parent = 0);

  private:
    QPushButton *okBtn;
    QPushButton *applyBtn;
};
#include "buttons.h"
#include <QVBoxLayout>
#include <QHBoxLayout>

Buttons::Buttons(QWidget *parent)
    : QWidget(parent) {

  QVBoxLayout *vbox = new QVBoxLayout(this);
  QHBoxLayout *hbox = new QHBoxLayout();

  okBtn = new QPushButton("OK", this);
  applyBtn = new QPushButton("Apply", this);

  hbox->addWidget(okBtn, 1, Qt::AlignRight);
  hbox->addWidget(applyBtn, 0);

  vbox->addStretch(1);
  vbox->addLayout(hbox);
}
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();

Chúng ta tạo hai layout, QVBoxLayoutQHBoxLayout, trong đó QHBoxLayout cũng tương tự như QVBoxLayout nhưng sắp xếp các widget theo chiều ngang.

hbox->addWidget(okBtn, 1, Qt::AlignRight);
hbox->addWidget(applyBtn, 0);

Các button được đặt trong layout nằm ngang, và được canh lề về phía bên phải với phương thức addWidget().Ở đây chúng ta dùng ba tham số cho phương thức này (khác với các phần trước chỉ có một), tham số đầu tiên là biến widget được thêm vào, tham số thứ hai là chỉ số stretch (co dãn), tham số thứ ba là chỉ số align (canh lề).

Về tham số stretch, tham số stretch nhận giá trị là các số nguyên, quy định widget sẽ chiếm bao nhiêu phần trong tổng số chiều ngang (hoặc dọc) của layout. Mặc định tham số này là 0, khi đó chiều ngang (hoặc dọc) sẽ được chia đều cho các widget. Khi có một widget có chỉ số 1, các widget còn lại mang chỉ số 0, thì các widget 0 sẽ nhận chiều ngang vừa đủ bao bọc lấy tiêu đề của nó, còn widget 1 sẽ chiếm toàn bộ chiều ngang còn lại. Khi các widget này có chỉ số khác 0 với 1, thì chúng sẽ được chia đều theo đúng tỉ lệ đó. Ví dụ nút OK có stretch là 3, nút Apply có stretch là 4 chẳng hạn, thì layout sẽ chia chiều ngang theo đúng tỉ lệ 3:4 cho hai nút này.

Về tham số align, tham số này nhận bốn giá trị là Qt::AlignCenter, Qt::AlignRight, Qt::AlignLèt và no alignment (cái này là mặc định nếu bạn ko đưa giá trị nào vào), đối với no alignment thì widget sẽ tự động dãn chiều ngang/dọc của nó cho đủ tỉ lệ đã được layout phân chia. Còn lại ba loại kia thì widget sẽ tự thu nhỏ chiều ngang/dọc của nó cho vừa đủ với tiêu đề của nó rồi tự nằm đúng vị trí bên trái, phải hoặc giữa với khu vực đã được layout phân chia.

Nếu đoạn giải thích trên có vẻ hơi khó hiểu thì bạn có thể tự điều chỉnh các tham số để hiểu thêm về cách hoạt động của nó hoặc comment hỏi ở dưới bài, mình sẽ giải thích rõ hơn.

vbox->addStretch(1);
vbox->addLayout(hbox);

Ở đây mình sử dụng phương thức addStretch(), phương thức này sẽ đưa một khoảng trống vào layout, đối với QVBoxLayout (layout dọc) thì khoảng trống này sẽ được đưa từ trên xuống, với layout ngang là từ trái qua phải, khoảng trống được đưa vào sẽ lấp đầy phần khoảng trống dư thừa do các widget không đủ kích thước để lấp.

#include <QApplication>
#include "buttons.h"

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

  Buttons window;

  window.resize(290, 170);
  window.setWindowTitle("Buttons");
  window.show();
  
  return app.exec();
}
Capture

Kết hợp các layout

Đoạn code ví dụ dưới đây kết hợp các layout lại với nhau thay vì chỉ dùng một-hai layout. Việc kết hợp các layout lại sẽ làm cho giao diện trở nên tinh xảo hơn. Để kết hợp layout thì chúng ta dùng phương thức addLayout().

#pragma once

#include <QWidget>

class Layouts : public QWidget {

  public:
    Layouts(QWidget *parent = 0);
};
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include "nesting.h"

Layouts::Layouts(QWidget *parent)
    : QWidget(parent) {

  QVBoxLayout *vbox = new QVBoxLayout();
  QHBoxLayout *hbox = new QHBoxLayout(this);

  QListWidget *lw = new QListWidget(this);
  lw->addItem("The Omen"); 
  lw->addItem("The Exorcist");
  lw->addItem("Notes on a scandal");
  lw->addItem("Fargo");
  lw->addItem("Capote");

  QPushButton *add = new QPushButton("Add", this);
  QPushButton *rename = new QPushButton("Rename", this);
  QPushButton *remove = new QPushButton("Remove", this);
  QPushButton *removeall = new QPushButton("Remove All", this);

  vbox->setSpacing(3);
  vbox->addStretch(1);
  vbox->addWidget(add);
  vbox->addWidget(rename);
  vbox->addWidget(remove);
  vbox->addWidget(removeall);
  vbox->addStretch(1);

  hbox->addWidget(lw);
  hbox->addSpacing(15);
  hbox->addLayout(vbox);

  setLayout(hbox);
}

Chúng ta tạo ra một window có chứa bốn button và một list widget. Các button nằm trong vertical layout (layout chiều dọc), bản thân layout này được đặt trong một horizontal layout (layout ngang) và được đặt bên phải layout. Ngoài ra bên trái layout ngang này còn chứa list widget. Nếu chúng ta co dãn kích thước window thì kích thước của list widget cũng tự động co dãn theo.

QVBoxLayout *vbox = new QVBoxLayout();

QVBoxLayout để chứa các button.

QHBoxLayout *hbox = new QHBoxLayout(this);

QHBoxLayout là layout chính cho toàn bộ widget.

QListWidget *lw = new QListWidget(this);
lw->addItem("The Omen"); 
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");

Tạo QListWidget và thêm các item cho nó.

QPushButton *add = new QPushButton("Add", this);
QPushButton *rename = new QPushButton("Rename", this);
QPushButton *remove = new QPushButton("Remove", this);
QPushButton *removeall = new QPushButton("Remove All", this);

Tạo bốn button.

vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeall);
vbox->addStretch(1);

Tạo vertical layout, thêm các tùy chỉnh như khoảng cách giữa các widget và chỉ số stretch cho nó.

hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);

Đặt list widget vào trong horizontal layout. Sau đó đưa vertical layout vào horizontal layout bằng phương thức addLayout().

#include <QApplication>
#include "nesting.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  Layouts window;
  
  window.setWindowTitle("Layouts");
  window.show();

  return app.exec();
}
Capture

QFormLayout

QFormLayout là layout trợ giúp chúng ta trong việc tạo các form (biểu mẫu). Layout này chia thiết kế làm hai cột, một bên là tên các trường, một bên là các trường để điền, có thể là các text box hay spin box. Xem ví dụ.

#pragma once

#include <QWidget>

class FormEx : public QWidget {
    
  public:
    FormEx(QWidget *parent = 0);
};
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include "form.h"

FormEx::FormEx(QWidget *parent)
    : QWidget(parent) {
        
  QLineEdit *nameEdit = new QLineEdit(this);
  QLineEdit *addrEdit = new QLineEdit(this);
  QLineEdit *occpEdit = new QLineEdit(this);
  
  QFormLayout *formLayout = new QFormLayout;
  formLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
  formLayout->addRow("Name:", nameEdit);
  formLayout->addRow("Email:", addrEdit);
  formLayout->addRow("Age:", occpEdit);
  
  setLayout(formLayout);
}

Đoạn code trên tạo ra ba label và ba line edit (hay textbox trong visual studio).

QFormLayout *formLayout = new QFormLayout;

Tạo một đối tượng QFormLayout.

formLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);

Căn chỉnh lề cho cột label bằng phương thức setLabelAlignment(), các bạn có thể dùng các tham số khác nhau để thử nghiệm.

formLayout->addRow("Name:", nameEdit);

Phương thức  addRow() sẽ thêm một dòng vào trong form, tham số đầu tiên là label sẽ được hiển thị, tham số thứ hai là biến widget.

#include <QApplication>
#include "form.h"

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

  FormEx window;

  window.setWindowTitle("Form example");
  window.show();

  return app.exec();
}

.Capture

QGridLayout

Lớp QGridLayout thiết kế các widget theo dạng lưới (hay bảng).

#pragma once

#include <QWidget>

class Calculator : public QWidget {

  public:
    Calculator(QWidget *parent = 0);

};
#include <QGridLayout>
#include <QPushButton>
#include "calculator.h"

Calculator::Calculator(QWidget *parent)
    : QWidget(parent) {

  QGridLayout *grid = new QGridLayout(this);
  grid->setSpacing(2);

  QList<QString> values({ "7", "8", "9", "/", 
    "4", "5", "6", "*",
    "1", "2", "3", "-",
    "0", ".", "=", "+"
  });
  
  int pos = 0;
  
  for (int i=0; i<4; i++) {
   for (int j=0; j<4; j++) { QPushButton *btn = new QPushButton(values[pos], this); btn->setFixedSize(40, 40);
     grid->addWidget(btn, i, j);
     pos++;
   }
  }  
  
  setLayout(grid);
}

Chúng ta sẽ thiết kế giao diện của một chương trình máy tính bỏ túi.

QGridLayout *grid = new QGridLayout(this);
grid->setSpacing(2);

Tạo đối tượng QGridLayout và quy định khoảng cách giữa các widget là 2 pixel.

QList<QString> values({ "7", "8", "9", "/", 
  "4", "5", "6", "*",
  "1", "2", "3", "-",
  "0", ".", "=", "+"
});

Tạo danh sách các tên các nút bấm cần đưa vào.

for (int i=0; i<4; i++) {
  for (int j=0; j<4; j++) { QPushButton *btn = new QPushButton(values[pos], this); btn->setFixedSize(40, 40);
      grid->addWidget(btn, i, j);
      pos++;
  }
} 

Cuối cùng đưa 16 widget nút bấm vô layout. Các nút bấm sẽ có kích thước cố định như nhau.

#include <QApplication>
#include "calculator.h"

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

  QApplication app(argc, argv); 

  Calculator window;

  window.move(300, 300);
  window.setWindowTitle("Calculator");
  window.show();

  return app.exec();
}
Capture

Tổng kết

Để kết thúc bài chúng ta sẽ tạo ra một giao diện phức tạp một tí bằng QGridLayout :).

#pragma once

#include <QWidget>

class Review : public QWidget {
    
  public:
    Review(QWidget *parent = 0);
};
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include "review.h"

Review::Review(QWidget *parent)
    : QWidget(parent) {

  QGridLayout *grid = new QGridLayout(this);
  grid->setVerticalSpacing(15);
  grid->setHorizontalSpacing(10);

  QLabel *title = new QLabel("Title:", this);
  grid->addWidget(title, 0, 0, 1, 1);
  title->setAlignment(Qt::AlignRight | Qt::AlignVCenter);

  QLineEdit *edt1 = new QLineEdit(this);
  grid->addWidget(edt1, 0, 1, 1, 1);

  QLabel *author = new QLabel("Author:", this);
  grid->addWidget(author, 1, 0, 1, 1);
  author->setAlignment(Qt::AlignRight | Qt::AlignVCenter);

  QLineEdit *edt2 = new QLineEdit(this);
  grid->addWidget(edt2, 1, 1, 1, 1);

  QLabel *review = new QLabel("Review:", this);
  grid->addWidget(review, 2, 0, 1, 1);
  review->setAlignment(Qt::AlignRight | Qt::AlignTop);

  QTextEdit *te = new QTextEdit(this);
  grid->addWidget(te, 2, 1, 3, 1);

  setLayout(grid);
}

Trong ví dụ trên, ta tạo ra một form thường dùng để đánh giá sách, các trường gồm có tên tác giả, tiêu đề, nội dung đánh giá.

#include <QApplication>
#include "review.h"

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

  Review window;

  window.setWindowTitle("Review");
  window.show();

  return app.exec();
}
Capture
0 0 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments