➣ Reading Time: 12 minutes

看完這篇文章你會得到的成果圖

今天我們要延伸昨天的概念,開始來講解我們如何設計 controller.py

此篇文章的範例程式碼 github

https://github.com/howarder3/ironman2021_PyQt5_photoshop/tree/main/day05_pyqt_template

先複習一下 PyQt 的程式邏輯

細節我們在昨天已經有提過了,這個是我們設計「視覺化應用的精神」

接下來我們會以「程式的角度」,來說明怎麼樣寫這樣的程式

以「程式角度」,來說明如何建立 PyQt 的系統

我們設計完 UI.py 後,再來我們要設計 controller.py,
我們可以先看下圖,先有個架構理解我們在做什麼:

  • 用「controller.py」去 import「UI.py」來使用,
  • 用「start.py」去 import「controller.py」來使用,

之前在網路上看到的程式碼,很多都會將程式進入點也寫在這裡
並將 controller.py 命名為 start.py 或 strat_UI.py 之類的
這邊讀者只要有一隻程式會有包含「UI.py」、「controller.py」的概念,
相信就能對網路上大部分的範例程式碼能夠進行初步的解讀,
(之前我就是卡在 UI 與 control 的概念整個混在同一支程式碼中,所以學得非常混亂XD)

先來看 controller.py 範例,將程式進入點 (start.py) 整在一起

這個範例是我看來不少文章後,做給自己的特製版

  • 第二行的「from UI import Ui_MainWindow」:UI 的部分,請改成你儲存的 UI.py 檔名

Ui_MainWindow,是 Qt dedigner 預設產生出 .py 檔就會取名的 class 封裝後的名稱,建議沒必要不需要特別去更改

from PyQt5 import QtWidgets, QtGui, QtCore
from UI import Ui_MainWindow

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        # in python3, super(Class, self).xxx = super().xxx
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setup_control()

    def setup_control(self):
        # TODO
        self.ui.textEdit.setText('Happy World!')


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

說明

上面的範例程式,有調整過適合套用在往後要設計的各種 UI 程式裡面,
我們接下來只需要改動的地方都在「setup_control()」之中,

例如像上面範例中的

def setup_control(self):
    # TODO
    self.ui.textEdit.setText('Happy World!')

我們將我們設計的「UI程式」,封裝後儲存在「self.ui」中,
而 self.ui 裡面的 textEdit,就是我們這次修改內容的目標。

我們使用 .setText 修改成 ‘Happy World!’,就能得到以下的結果。

我們使用 Day2 製作出來的 「UI.py」

程式碼是一樣的,為了不讓讀者還需要特別回去翻,
我們這邊在附上範例一次,請將他存成「UI.py」,
或自行去修改上面提到的「from UI import Ui_MainWindow」的「UI」檔名。

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(270, 290, 104, 71))
        self.textEdit.setObjectName("textEdit")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.textEdit.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'PMingLiU\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Hello World!</p></body></html>"))

執行

將「controller.py」、「UI.py」準備好後,
執行我們的程式,目前這個 「controller.py」也包含著我們程式的進入點。

python controller.py

結果

  • 我們可以注意到,中間的內文從「Hello World!」變成了「Happy World!」

最後,為了讓我們的邏輯在更漂亮一些,我們讓 start 與 controller 分離。

為了符合當初我們想的架構,如下圖,
我們讓 controller.py 獨立出來,專心做控制的動作就好,
這樣也更符合 Design Pattern 的 「單一職責原則 (SRP)」

真正的實現我們上述所說的:

  • 用「controller.py」去 import「UI.py」來使用
  • 用「start.py」去 import「controller.py」來使用

  • 另外,在 python3 當中,super(MainWindow_controller, self).init() 的寫法,
    可以簡寫為 super().init()

因此,我們將 controller.py 簡化為

from PyQt5 import QtWidgets, QtGui, QtCore

from UI import Ui_MainWindow

class MainWindow_controller(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__() # in python3, super(Class, self).xxx = super().xxx
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setup_control()

    def setup_control(self):
        # TODO
        self.ui.textEdit.setText('Happy World!')

並新增一個 start.py,作為之後程式的入口

from PyQt5 import QtWidgets

from controller import MainWindow_controller

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow_controller()
    window.show()
    sys.exit(app.exec_())

小結

今天我們完成了一支 Qt 程式大致的模板,
之後我們都可以照著這個模板進行後續的開發!

⭐Python PyQt5 相關文章整理⭐:
⭐基礎知識與架構篇⭐:
1.【PyQt5】Day 1 – 安裝 PyQt,建立自己的第一支 PyQt5 程式
2.【PyQt5】Day 2 – 利用 Qt designer 建立第一支有自己介面的 PyQt5 程式
3.【PyQt5】Day 4 – 重要的 Qt 程式邏輯觀念,務必先有此觀念後面才會懂自己在幹嘛
4.【PyQt5】Day 5 – 開始來設計我們的 controller.py,改以「程式角度」來說明如何建立 PyQt 的系統
⭐基本介面控制篇⭐:
1.【PyQt5】Day 6 – 我們的第一個 output 手段 – Qlabel
2.【PyQt5】Day 7 – 我們的第一個 input 手段 – QPushButton
3.【PyQt5】Day 8 – 我們的第二個 input 手段 – QLineEdit
4.【PyQt5】Day 9 – 以 QLineEdit, QTextEdit, QPlainTextEdit 作為文字的輸入
5.【PyQt5】Day 10 – 以 QFileDialog 讀取系統的檔案、資料夾
⭐影像處理篇⭐:
1.【PyQt5】Day 11 – 以 Qlabel 在 PyQt 中顯示圖片 (基於 QImage 使用 OpenCV)
2.【PyQt5】Day 12 – 建立一個可以縮放圖片大小的顯示器 (基於 QImage 使用 OpenCV)
3.【PyQt5】Day 13 – 使用 QVBoxLayout, QscrollArea 製作出捲軸,以高解析度檢視圖片 (基於 QImage 使用 OpenCV)
⭐打包程式篇⭐:
1.【PyQt5】Day 3 – 用 pyinstaller 將 python 程式打包,把每天的成果分享給你的親朋好友
⭐【喜歡我的文章嗎? 歡迎幫我按讚~ 讓基金會請創作者喝一杯咖啡!
如果喜歡我的文章,請幫我在下方【按五下Like】 (Google, Facebook 免註冊),會由 「LikeCoin」 贊助作者鼓勵繼續創作,讀者們「只需幫忙按讚,完全不用出錢」哦!

likecoin-steps