Khi phát triển phần mềm điều khiển thiết bị (HMI/SCADA) với PyQt, việc tổ chức mã nguồn một cách khoa học là yếu tố then chốt đảm bảo hiệu năng, khả năng bảo trì và mở rộng. Dưới đây là những vấn đề thực tế thường gặp và giải pháp kỹ thuật tương ứng.
1. Tách biệt lớp giao diện và lớp xử lý nghiệp vụ
Việc trộn lẫn code hiển thị (UI) và logic nghiệp vụ trong cùng một luồng sẽ dẫn đến hiện tượng giao diện treo (unresponsive UI), đặc biệt khi xử lý tác vụ nặng như giao tiếp serial, đọc dữ liệu cảm biến hoặc tính toán thời gian thực. Giải pháp đúng là áp dụng mô hình MVVM hoặc Presenter-based separation: UI chỉ chịu trách nhiệm render và phản hồi sự kiện; toàn bộ xử lý được chuyển sang các đối tượng độc lập chạy trên QThread, QRunnable, hoặc sử dụng asyncio kết hợp QEventLoop.
2. Chuyển đổi tập tin .ui thành mã Python ổn định
Với PyQt5, công cụ chuẩn để biên dịch file thiết kế từ Qt Designer là pyuic5, không phải pyside2-uic — vì hai thư viện này không tương thích nhị phân. Câu lệnh đúng:
pyuic5 -x main_window.ui -o ui_main_window.py
Nếu dùng PyCharm, có thể cấu hình External Tools để tự động sinh mã mỗi khi lưu file .ui, tránh sai sót do chạy lệnh thủ công. Ngoài ra, nên thêm tùy chọn -x để tạo template có hàm setupUi() đầy đủ và hỗ trợ preview trực tiếp.
3. Thứ tự khai báo phương thức __init__ trong lớp
Python yêu cầu phương thức khởi tạo __init__ phải được định nghĩa trước khi bất kỳ đoạn mã nào trong lớp tham chiếu đến thuộc tính của nó. Nếu viết __init__ ở cuối lớp mà lại gọi self.data_buffer trong một method khác được định nghĩa trước đó, trình thông dịch sẽ ném ngoại lệ AttributeError. Đây là lỗi cú pháp ngầm — không phải lỗi runtime — do thứ tự khai báo ảnh hưởng đến phạm vi (scope resolution). Luôn đặt __init__ ngay sau khai báo lớp và trước các phương thức khác.
4. Triển khai điều hướng đa trang linh hoạt
Thay vì hiển thị/ẩn từng widget bằng show()/hide(), nên sử dụng QStackedWidget để quản lý các trang — giúp giảm độ phức tạp, tránh xung đột hiển thị và tối ưu hóa tài nguyên. Ví dụ cải tiến:
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QListWidget,
QStackedWidget, QLabel, QVBoxLayout, QWidget
)
class ControlPanel(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Hệ thống điều khiển thiết bị")
# Khởi tạo ngăn điều hướng
self.nav = QListWidget()
self.nav.setFixedWidth(160)
self.nav.addItems(["Trang giám sát", "Cài đặt hệ thống", "Lịch sử dữ liệu"])
self.nav.currentRowChanged.connect(self._on_nav_change)
# Quản lý nội dung trang
self.pages = QStackedWidget()
self.pages.addWidget(QLabel("📊 Bảng điều khiển thời gian thực"))
self.pages.addWidget(QLabel("⚙️ Cấu hình cổng truyền thông"))
self.pages.addWidget(QLabel("📜 Nhật ký hoạt động (24h gần nhất)"))
# Tổ chức layout chính
container = QWidget()
main_layout = QVBoxLayout(container)
main_layout.addWidget(self.nav)
main_layout.addWidget(self.pages)
main_layout.setContentsMargins(0, 0, 0, 0)
self.setCentralWidget(container)
def _on_nav_change(self, index):
self.pages.setCurrentIndex(index)
if __name__ == "__main__":
app = QApplication([])
panel = ControlPanel()
panel.resize(960, 600)
panel.show()
app.exec_()