Ứng Dụng Reactive Programming với RxCpp và RxQt trong Giao Diện Qt

Mục tiêu thiết kế

  • Giao diện gồm ba ô nhập liệu (QLineEdit) và một nhãn hiển thị kết quả (QLabel).
  • Nhãn cập nhật tự động tổng giá trị số từ ba ô nhập — ngay khi người dùng gõ.
  • Khi nội dung ô nhập không phải số hợp lệ, giá trị được coi là 0.
  • Giá trị khởi tạo cho ba ô lần lượt là "1", "2", "3".

Cài đặt thư viện cần thiết

Tải mã nguồn RxCpp và phần mở rộng RxQt bằng lệnh Git:

$ git clone --recursive https://github.com/ReactiveX/RxCpp.git
$ git clone --recursive https://github.com/tetsurom/rxqt.git

Tạo dự án Qt Widgets

  1. Mở Qt Creator → File > New File or Project...
  2. Chọn Application > Qt Widgets Application.
  3. Đặt tên dự án là RxSumApp, chọn thư mục lưu trữ phù hợp.
  4. Chọn bộ công cụ Desktop Qt và hoàn tất.

Cấu hình tệp dự án (.pro)

Mở tệp RxSumApp.pro và cập nhật các dòng sau:

CONFIG += c++17
INCLUDEPATH += $$PWD/../RxCpp/Rx/v2/src \
    $$PWD/../rxqt/include

Lưu ý: Thay $$PWD/../ bằng đường dẫn tuyệt đối hoặc tương đối chính xác tới vị trí bạn đã clone hai kho lưu trữ.

Thiết kế giao diện trong Qt Designer

  1. Mở mainwindow.ui, kéo một Form Layout vào trung tâm.
  2. Thêm ba Line Edit vào layout, đặt tên lần lượt là:
    • inputA → thiết lập thuộc tính text = "1", alignment = AlignRight
    • inputBtext = "2", alignment = AlignRight
    • inputCtext = "3", alignment = AlignRight
  3. Thêm một Label, đặt tên là sumLabel, căn lề phải.

Triển khai không dùng Reactive Extensions

Để so sánh, ta có thể xử lý thủ công qua signal-slot truyền thống:

  1. Trong Qt Designer, nhấp chuột phải từng QLineEdit → Go to slot... → chọn textChanged(const QString&).
  2. Qt Creator sẽ sinh ba hàm thành viên trong MainWindow:
// mainwindow.h
private slots:
    void on_inputA_textChanged(const QString &text);
    void on_inputB_textChanged(const QString &text);
    void on_inputC_textChanged(const QString &text);

// mainwindow.cpp
void MainWindow::on_inputA_textChanged(const QString &text) { updateSum(); }
void MainWindow::on_inputB_textChanged(const QString &text) { updateSum(); }
void MainWindow::on_inputC_textChanged(const QString &text) { updateSum(); }

void MainWindow::updateSum() {
    const int a = ui->inputA->text().toInt(&_); // _ bỏ qua lỗi chuyển đổi
    const int b = ui->inputB->text().toInt(&_);
    const int c = ui->inputC->text().toInt(&_);
    ui->sumLabel->setNum(a + b + c);
}

Và gọi updateSum() trong constructor để khởi tạo nhãn.

Triển khai theo phong cách Reactive

Thay vì gắn từng slot riêng lẻ, ta sử dụng luồng quan sát (Observable) để kết hợp dữ liệu theo thời gian thực:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <rxqt.hpp>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);

    // Tạo luồng quan sát từ mỗi QLineEdit, phát giá trị ban đầu trước khi thay đổi
    auto makeStream = [](QLineEdit* edit) {
        return rxqt::from_signal(edit, &QLineEdit::textChanged)
            .start_with(edit->text());
    };

    // Kết hợp ba luồng: lấy giá trị mới nhất từ mỗi luồng, tính tổng, cập nhật giao diện
    rxcpp::observable<>::combine_latest(
        makeStream(ui->inputA),
        makeStream(ui->inputB),
        makeStream(ui->inputC),
        [](const QString& a, const QString& b, const QString& c) {
            return a.toInt() + b.toInt() + c.toInt();
        }
    )
    .subscribe([this](int total) {
        ui->sumLabel->setNum(total);
    });
}

MainWindow::~MainWindow() {
    delete ui;
}

Giải thích ngắn gọn:

  • from_signal biến tín hiệu Qt thành Observable.
  • start_with đảm bảo luồng luôn bắt đầu bằng giá trị hiện tại của ô nhập.
  • combine_latest phát ra kết quả mỗi khi bất kỳ luồng nào cập nhật — phù hợp với yêu cầu "cập nhật tức thì".
  • subscribe nhận kết quả và cập nhật QLabel mà không cần quản lý trạng thái thủ công.

Thẻ: RxCpp RxQt Qt5 ReactiveX C++17

Đăng vào ngày 14 tháng 6 lúc 22:41