Qt 5 C++ – Tùy biến Widget

Rate this post

Trong phần này chúng ta sẽ học cách tùy biến một widget cho riêng mình.

Những IDE giúp phát triển phần mềm như Visual Studio hầu hết đều chỉ cung cấp một tập các widget có sẵn như button, label, slider… Đôi khi chúng ta cần những widget làm các công việc đặc thù riêng. Lúc đó chúng ta có hai cách, một là code lại widget từ đầu, hai là phát triển từ các widget có sẵn trong IDE. Nếu widget mà chúng ta cần không có gì lớn lắm thì chúng ta nên dùng các widget có sẵn để tiết kiệm thời gian.

Burning widget

Để ví dụ cho bài này, chúng ta sẽ tạo widget hiển thị “thanh burn”, cái này các bạn nào thường dùng các phần mềm burn đĩa CD/DVD như Nero sẽ thấy (có lẽ tại thời điểm viết bài này thì chẳng còn mấy ai dùng đĩa nữa).

#pragma once

#include <QWidget>
#include <QSlider>
#include <QFrame>
#include "widget.h"

class Burning : public QFrame {

  Q_OBJECT  

  public:
    Burning(QWidget *parent = 0);
    int getCurrentWidth();
    
  public slots:
    void valueChanged(int);
    
  private:
    QSlider *slider;
    Widget *widget;
    int cur_width;
    
    void initUI();
};
</pre>
<pre class="explanation">[code language="java" title=""]
public:
  Burning(QWidget *parent = 0);
  int getCurrentWidth();

Phương thức getCurrentWidth() dùng để lấy giá trị hiện tại của slider.

private:
  QSlider *slider;
  Widget *widget;
  int cur_width;
    
  void initUI();

Chúng ta sẽ hiển thị 2 widget lên cửa sổ chính, một slider và một widget do chúng ta tự thiết kế. Giá trị max của slider là 750, biến cur_width là biến lưu trữ giá trị hiện tại của slider, mỗi lần biến này thay đổi, chúng ta sẽ thay đổi lớp Widget (do chúng ta tự thiết kế).

#include <QVBoxLayout>
#include <QHBoxLayout>
#include "burning.h"

Burning::Burning(QWidget *parent)
    : QFrame(parent) {
           
  initUI();
}

void Burning::initUI() {
    
  const int MAX_VALUE = 750; 
  cur_width = 0; 
  
  slider = new QSlider(Qt::Horizontal , this); 
  slider->setMaximum(MAX_VALUE);
  slider->setGeometry(50, 50, 130, 30);

  connect(slider, &QSlider::valueChanged, this, &Burning::valueChanged);
  
  QVBoxLayout *vbox = new QVBoxLayout(this);
  QHBoxLayout *hbox = new QHBoxLayout();

  vbox->addStretch(1);

  widget = new Widget(this);  
  hbox->addWidget(widget, 0);

  vbox->addLayout(hbox);

  setLayout(vbox);    
}

void Burning::valueChanged(int val) {
    
  cur_width = val;
  widget->repaint();
}

int Burning::getCurrentWidth() {
    
  return cur_width;
}

Lớp Burning là lớp hiển thị cửa sổ chính.

connect(slider, &QSlider::valueChanged, this, &Burning::valueChanged);

Chúng ta connect signal valueChanged() của slider với slot valueChanged() của lớp Burning, ở đây signal là có sẵn trong Qt, còn slot là do chúng ta viết thêm, mỗi lần thanh trượt trên slider thay đổi, slot valueChanged() của Burning sẽ được gọi.

void Burning::valueChanged(int val) {

  cur_width = val;
  widget->repaint();
}

Mỗi lần gọi phương thức valueChanged(), chúng ta cập nhật giá trị của slider và gọi phương thức repaint() của lớp Widget.

#pragma once

#include <QFrame>

class Burning;

class Widget : public QFrame {

  public:
    Widget(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *e);
    void drawWidget(QPainter &qp);

  private:
    QWidget *m_parent;
    Burning *burn;
    
    static const int DISTANCE = 19;
    static const int LINE_WIDTH = 5;
    static const int DIVISIONS = 10;
    static const float FULL_CAPACITY = 700;
    static const float MAX_CAPACITY = 750;     
};
private:
  QWidget *m_parent;
  Burning *burn;

Ở lớp Widget, chúng ta dùng một con trỏ QWidget để tham chiếu đến widget cha của nó, widget cha ở đây chính là lớp Burning, nhờ vậy chúng ta mới có thể truy xuất được biến cur_width của widget cha từ widget con.

const int DISTANCE = 19;
const int LINE_WIDTH = 5;
const int DIVISIONS = 10;
const float FULL_CAPACITY = 700;
const float MAX_CAPACITY = 750;

Ý nghĩa của các hằng số này như sau:

  • DISTANCE là tọa độ trục tung-y của lớp Widget trên cửa sổ.
  • LINE_WIDTH là chiều dài của các đường kẻ dọc bên trên các chữ số.
  • DIVISIONS là số lượng các phần được chia ra bởi các chữ số.
  • FULL_CAPACITY là khoảng giá trị của phần màu vàng, khi giá trị của slider vượt quá FULL_CAPACITY thì Widget sẽ chuyển sang tô màu đỏ.
  • MAX_CAPACITY là giá trị tối đa của slider.
#include <QtGui>
#include "widget.h"
#include "burning.h"

const int PANEL_HEIGHT = 30;

Widget::Widget(QWidget *parent)
    : QFrame(parent) {
        
  m_parent = parent;
  setMinimumHeight(PANEL_HEIGHT);
}

void Widget::paintEvent(QPaintEvent *e) {
  
  QPainter qp(this);
  drawWidget(qp);
  
  QFrame::paintEvent(e);  
}

void Widget::drawWidget(QPainter &qp) {
    
  QString num[] = { "75", "150", "225", "300", "375", "450", 
    "525", "600", "675" };    
  
  int asize = sizeof(num)/sizeof(num[1]); 
  
  QColor redColor(255, 175, 175);
  QColor yellowColor(255, 255, 184);  
  
  int width = size().width();

  Burning *burn = (Burning *) m_parent;
  int cur_width = burn->getCurrentWidth();

  int step = (int) qRound((double)width / DIVISIONS);
  int till = (int) ((width / MAX_CAPACITY) * cur_width);
  int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY);

  if (cur_width >= FULL_CAPACITY) {
      
    qp.setPen(yellowColor); 
    qp.setBrush(yellowColor);
    qp.drawRect(0, 0, full, 30);
    qp.setPen(redColor);
    qp.setBrush(redColor);
    qp.drawRect(full, 0, till-full, PANEL_HEIGHT);

  } else if (till > 0) {
       
    qp.setPen(yellowColor);
    qp.setBrush(yellowColor);
    qp.drawRect(0, 0, till, PANEL_HEIGHT);
  }

  QColor grayColor(90, 80, 60);
  qp.setPen(grayColor);
  
  for (int i=1; i <=asize; i++) {
      
    qp.drawLine(i*step, 0, i*step, LINE_WIDTH);
    QFont newFont = font();
    newFont.setPointSize(7);
    setFont(newFont);

    QFontMetrics metrics(font());

    int w = metrics.width(num[i-1]);
    qp.drawText(i*step-w/2, DISTANCE, num[i-1]);
  }
}

Chúng ta vẽ Widget bằng cách vẽ hình chữ nhật có tô màu, các đường kẻ dọc và các chữ số.

int width = size().width(); 

Phương thức size().width() sẽ lấy độ lớn của cửa sổ. Bởi vì người dùng có thể resize kích thước cửa sổ, do đó chúng ta lấy kích thước cửa sổ để resize Widget của chúng ta theo cửa sổ chính.

Burning *burn = (Burning *) m_parent;
int cur_width = burn->getCurrentWidth();

Lấy giá trị hiện tại của slider.

int till = (int) ((width / MAX_CAPACITY) * cur_width);
int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY); 

Khi kích thước cửa sổ thay đổi, tọa độ để vẽ phần màu vàng và đỏ cũng phải thay đổi vì chúng ta vẽ theo tọa độ pixel.

qp.setPen(redColor);
qp.setBrush(redColor);
qp.drawRect(full, 0, till-full, PANEL_HEIGHT); 

Vẽ phần hình chữ nhật màu đỏ.

qp.drawLine(i*step, 0, i*step, LINE_WIDTH); 

Vẽ các đường kẻ dọc.

QFontMetrics metrics(font());

int w = metrics.width(num[i-1]);
qp.drawText(i*step-w/2, DISTANCE, num[i-1]);

Vẽ các chữ số, cũng giống như phần màu vàng-đỏ, tọa độ của các chữ số cũng được tính toán lại để phù hợp với thiết kế.

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

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

  window.resize(370, 200);
  window.setWindowTitle("The Burning widget");
  window.show();

  return app.exec();
}
Capture
5 2 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