
PyQt & GUI
By 小黑
目錄
- 專有名詞介紹
- GUI 介紹 & 簡史
- Qt & PyQt 介紹 & 安裝
- 第一個 PyQt 程式
- OOP
- GUI 基本概念
- Signals & Slots
- 常見 widgets
講師介紹
小黑(吳習之)
- 建中電研 44th 網管

- 寒訓一小隊輔 & 暑訓三小隊輔
- Linux & Python 廚
- 愛玩買夠梗但實際上沒追買夠
- 成發保重
- 不是同性戀或雙性戀
- 精神狀況堪憂
一些名詞定義
OS (Operating System)
- 作業系統
- 就你現在在用的 Windows or macOS
- 負責控制電腦硬體,是使用者跟電腦的橋樑
Library
- 指一堆寫好的 functions 跟 classes
- 如同字面意思,就像一個有不少書(functions, classess) 的圖書館
- 上堂課的 React 單獨來看就算 Library
Framework
- Framework = 一堆 Libaries + 一些工具
- 也如同字面意思,就像建築物的框架(鷹架),有了它只要填上建材(程式)就行
- 上上堂課教的 Flask 就是
- React + 一堆東西也算 Framework

Library vs. Framework
- 假設你今天要寫作文
- Framework 就相當於論說文模板
- 寫論說文必須遵從固定規則(Framework 的限制)
- Library 則是修辭、文章架構或詞彙
- 你可以隨意用排比、譬喻這些修辭(Library 中的 functions)

GUI
電腦界面發展簡史
1950s - 打孔卡


打孔卡
打孔機

1960s~Now - CLI

DOS

Unix
1970~80s - Personal Computer

Apple II

IBM PC
1970s - PARC & GUI

Xerox PARC

(這機構有人在 1969 時說未來會有平板電腦)
研究中心早期構想
Xerox Alto

1980s~Now - Apple

Apple Lisa

Macintosh 128K


1980s~Now - Microsoft

Windows 1.0

Windows 95

應用程式是怎樣煉成的
電腦上的 App 是怎麼寫的?
- CLI 程式 可以直接輸出結果,但 GUI 顯然沒辦法就這樣幹
- 所以為了要開發 GUI App,作業系統的設計者(比如微軟跟蘋果)會設計整套 Library,專門給開發者寫 App
- 開發者只要調用這些 Library,就能開發出 GUI App
- 而每個 OS 的 Library 都長得不一樣,所以不同 OS 間的 Library 沒辦法通用
怎麼辦!?
寫 App 的 Framework
- 每次換一個 OS,理論上 App 就要完全重寫,而這顯然不太現實
- 於是有人搞出了 GUI Framework,可以只寫一次 App 就能在不同 OS 跑
- Framework 會根據 OS 來調用對應 Library 中的 functions,達到不同 OS 間幾乎一樣的效果
常見 Framework
Flutter (Dart)
- iOS/Android/Desktop/Web
- 背後爸爸是 Google
- 性能不差
- 但 Dart 偏難寫(?

React Native (JavaScript)
- iOS/Android/Desktop/Web
- 背後爸爸是 Meta
- 相對好寫(如果你平常用 React)
- 性能也算過得去
- 如果你上完 React 覺得沒問題的話…

Electron (JavaScript)
- Desktop
- 背後爸爸是 GitHub
- 基本上就是一般網頁(code 幾乎不用改)
- VSCode、Slack 就是拿 Electron 包的
- 原理是開一個瀏覽器,直接跑網頁
- 所以超肥


還有這門課的主角 Qt!
Qt & PyQt 介紹 & 安裝
Qt 介紹

- 這一三北資學術兼文書 Qt 學姐
- 今年暑假幹訓應該會看到她(?
- 所以要來幹訓!!!
不是這位
Qt 介紹
- 由同名公司開發的一套 GUI Framework
- 最初設計給 C++,後來被移植到其他語言
- 不少桌面 App 都用 Qt 開發,比如 PhotoShop、Telegram

PyQt 介紹
- 由 Riverbank Computing 搞出來的 Qt For Python
- Anki 桌面版就是用 PyQt 寫的

(PyQt 沒 logo,所以…嗯)
為什麼使用 PyQt
- PyQt 是 Python 唯一成熟的 Framework
- 其他語言也能用 Qt,所以學會 PyQt 就能轉移到其他語言
- 生態豐富、有大公司支援

(PyQt 沒 logo,所以…嗯)
PyQt vs. PySide
- PyQt 比 PySide 更早出來,生態更完整、資料更多
- PySide 是由 Qt 官方開發的,理論上相容性會更好
- PyQt 商用要付費(或必須把程式碼公開)
- PySide 可以直接商用
- 還有一部分 class & function 的名稱不同
- 除此之外沒差,一個程式要改用另一者只要改 module name 就行
PyQt 安裝
先去裝 Python ㄅ
接著打開終端機,輸入以下指令
- Windows 使用者開 cmd.exe
- Mac/Linux 使用者開 Terminal
pip install pyqt6 --break-system-packages
(如果報錯誤再加)
PyQt 架構
PyQt 可分成好幾個 modules
- PyQt 底下有十幾個 modules,而常用的有四個
- QtCore: 一些非 GUI 類的 class,包含日期處理、檔案操作、多線程
- QtGui: 各種圖形相關的 class,包含圖片、字體
- QtWidgets: 各個 widgets
- QtMultimedia: 音訊、影片相關
第一個 PyQt 程式
打開編輯器,輸入以下程式
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 300, 300);
win.setWindowTitle("CKEFGISC Winter!")
win.show()
sys.exit(app.exec())
window()
存檔並執行
- 把檔案存到剛剛創的 ckefgisc-pyqt 資料夾下
python3 app.py

- 檔名取 app.py (其他也行,你爽就好
- 執行
- 你應該會看到這個畫面
逐行解釋
- 前三行引入需要的 libraries
- 第一行從 PyQt6 引入 QtWidgets 這個 class
- 第二行從 PyQt6.QtWidgets 引入 QApplication、QMainWindow 這兩個 class
- 第三行引入 Python 內建的 sys (處理系統相關的指令的 module)
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
逐行解釋
- 定義 function window
- 備註:不用寫成 function 也行,但這樣接下來會好改很多
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 300, 300);
win.setWindowTitle("CKEFGISC Winter!")
win.show()
sys.exit(app.exec())
逐行解釋
- 建立一個 QApplication 的 instance app,並傳入 sys.argv
- QApplication 是 Qt 程式的起始點,一定得寫(不然沒法用)
- sys.argv 就是執行程式時傳入的命令行參數(不過等下課程用不到)
app = QApplication(sys.argv)
逐行解釋
- 建立一個 QMainWindow 的 instance win
- 這是程式的(唯一)Window(寫 GUI App 一定需要)
win = QMainWindow()
win.setGeometry(200, 200, 300, 300)
- 將 win 移動到桌面上 x=200、y=200 ,並把大小設定為 300x300
逐行解釋
- 把 win 的 title 設為 "CKEFGISC Winter!" (沒有雙引號)
win.setWindowTitle("CKEFGISC Winter!")
win.show()
- 讓 win 顯示出來(一定要加,不然不會顯示)
sys.exit(app.exec())
- 在 app 執行完後結束程式(一定要加,不然也不會顯示)
來加點文字ㄅ!
加點文字
- 這行會建立一個新的 QLabel 、把這個 Label 的文字設定成「赤坂明我恨你」,並把它建立在 win 上
- 在 win.show() 前一行加上這行並存檔執行
- 此時螢幕上應該會出現以下畫面
label = QtWidgets.QLabel("赤坂明我恨你", win)

要怎麼改 label 位置
- 可以用 label 的 move method 移動 label
- 把下面這行加到 label 跟 win.show() 之間重新執行
- 應該會看到類似這樣的畫面:
label.move(50, 50)

OOP
假設你在寫整理動漫的程式



mygo_name = "BanG Dream! It's MyGO!!!!!"
mygo_year = "2023"
mygo_type = ["Horror", "Love"]
kaguya_name = "輝夜姬想讓人告白"
kaguya_year = "2015"
kaguya_type = ["Love", "Comedy"]
heroines_name = "敗北女角太多了"
heroines_year = "2021"
heroines_type = ["Love", "Comedy"]
一個個列短期還好…
但你是個選化被當(絕對不是講師)的動漫廚,你一年會看好幾部動漫(這也不是講師)。




這部很棒
這部很棒
這部很棒
這部很棒
這部很棒
這部是他媽垃圾
但長期下來很麻煩…
那既然這幾部都是「動漫」,有沒有辦法設計類似模板的東西呢?
有!
class Anime:
def __init__(self, name, year, type):
self.name = name
self.year = year
self.type = type
mygo = Anime("BanG Dream! It's MyGO!!!!!", 2023, ["Horror", "Love"])
kaguya = Anime("輝夜姬想讓人告白", 2015, ["Love", "Comedy"])
heroines = Anime("敗北女角太多了", 2021, ["Love", "Comedy"])
print(mygo.name) # BanG Dream! It's MyGO!!!!!
print(kaguya.year) # 2015
print(heroines.type) # ["Love", "Comedy"]
水啦!
Class 就像是食譜
mygo = Anime("BanG Dream! It's MyGO!!!!!",
2023,
["Horror", "Love"])
動漫
- 名稱
- 年份
- 類型


kaguya = Anime("輝夜姬想讓人告白",
2015,
["Love", "Comedy"])

heroines = Anime("敗北女角太多了",
2021,
["Love", "Comedy"])
食譜
做出來的料理
而這些做出來的料理就是 Object
Class & Object
- 如同前面提到,Class 類似食譜或藍圖
- 利用 Class 建立出來的實體即為 Object
- Instance 指特定 Object,比如上面的 mygo 或 kaguya
- __init__ 這個酷 function 每次建立新 Object 都會被呼叫一次
又假設你想寫農場模擬遊戲
你定義好了「狗」這個類別
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
你想讓「狗」能叫怎麼辦?
簡單!
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("在嗎?我寄去的禮物收到了嗎?希望你會喜歡。\n",
"天氣又變冷了,記得多穿點衣服。\n",
"你在忙吧,不用回我沒關係。\n",
"忙完早點休息吧,晚安。")
dog = Dog("Aaron", 17)
dog.bark()
method
- 這些 class 內的 function 被稱為 method
- method 至少要有一個參數 self
- 不同 class 可以有相同的 method 名稱,但同一個 class 內不行(在 Python)
又双叒叕你想寫一個 Minecraft
你設計出了 Block 這個 class,並拿它去設計出其他方塊。
但你忘了 Minecraft 很不科學
一個方塊可能不受重力影響、不被岩漿燒、會被安德拿走、發光、有彈性!
class Block:
def __init__(self, name, shade, id):
self.name = name
self.shade = shade
self.id = id
那要怎解?
將全部方塊定義一遍?
可是很多方塊都有重複的 attribute ㄟ
而且你是個不用 auto complete 的 Vim 廚,每次都會 key attribute 到瘋掉
那能不能將方塊共同的 attribute 獨立成一個 class Block
任何方塊繼承 Block 屬性後再調整
還真的可以ㄟ
class Block:
def __init__(self, name, id, hardness):
self.name = name
self.id = id
self.hardness = hardness
class Grass(Block):
def __init__(self, name, id, hardness):
super().__init__(name, id, hardness)
class Log(Block):
def __init__(self, name, id, hardness):
super().__init__(name, id, hardness)
self.is_craved = False


Inheritance
- Inheritance 指一個 class 繼承另一個 class 的 attributes & methods
- 比如上面 Grass 跟 Log 繼承自 Block
- Block 即為 parent class,Grass 和 Log 即為 child class
- super().__init__(name, id hardness) 調用 parent class (Block) 的 __init__
小統整
- Class 就像食譜,可以用此做出 Object (料理)
- Object 有不同 attribute
- Object 有類似 function 的 methods
- Object 可以繼承自其他 Object
小練習 #1
- 定義一個 class People
- People 有 name, friends 兩個 attributes
- People 裡的 print_friends method 可印出 friends
小練習 #2
- 把剛剛第二個 PyQt 程式的 code 用 OOP 的方式重寫
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 300, 300);
win.setWindowTitle("CKEFGISC Winter!")
label = QtWidgets.QLabel("赤坂明我恨你", win)
win.show()
sys.exit(app.exec())
window()
寫 GUI 要有的基本概念
Window
- 萬物始於 Window
- 每個 GUI App 只會有一個主 Window
- Window 可以有 menu bar、tool bar
Widget
- 一個 Widget 就是一個小組件
- Button、Label 這些都是 Widget
- Widget 必須放到一個 Window 上,不然跑不了
座標
- 每個 window 的左上角座標是(0, 0)
- 左上角向右 +X,向下 +Y
Event Loop
- 可以把一個 GUI App想成 Minecraft 中的偵測器
- 有玩 Minecraft 就知道,偵測器偵測到方塊變更就會發送紅石信號
- 「方塊變更」就是一個 event,對應到 GUI App 裡就是滑鼠移動、點擊、文字輸入
- 「發送紅石信號」就是觸發的 function(handler),GUI App 可以視不同 event 做不同事
- 偵測器會一直偵測,直到消失為止;同理,GUI App 也會一直偵測 event 到退出為止
這整套機制就叫 Event Loop

Signals & Slots
QPushButton
QPushButton
- 作用:建立一個可以按的按鈕
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
button = QtWidgets.QPushButton(win)
button.setText("Hit me")
win.setGeometry(200, 200, 600, 600);
win.show()
sys.exit(app.exec())
window()

Signals & Slots 介紹
Signals & Slots
- 當一個 widget 被操作(比如點擊、被輸入),widget 會發送 Signal
- Signal 可以連接到 function
- 每當 Signal 產生,Signal 所連接的 function 就會執行
- 這些被呼叫的 functions 即為 Slots
- 對應到 Event Loop,Signals 為 event,Slots 為 handler
建立 Signals & Slots 的語法
- 假設我們有個按鈕
- 還有一個 fucntion world_hello
- 如果我們希望讓按鈕被點擊後觸發 world_hello,可以這樣寫
button = QtWidgets.QPushButton("Hit me")
button.resize(200, 100)
def world_hello():
print("Hello, it's me")
button.clicked.connect(world_hello)
Slot
Signal
來個範例
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QApplication, QPushButton, QMainWindow
import sys
def print_hello():
print("Hello, world")
def window():
app = QApplication(sys.argv)
win = QMainWindow()
button = QPushButton("Hit Me", win)
button.clicked.connect(print_hello)
win.show()
app.exec()
window()
執行結果

小練習
- 試根據以下 GIF 撰寫出相似程式

練習解答
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(0, 0, 500, 500)
self.count = 0
self.label = QtWidgets.QLabel(f"Count: {self.count}", self)
self.label.adjustSize()
self.btn = QtWidgets.QPushButton("Hit me", self)
self.btn.adjustSize()
self.btn.move(200, 200)
self.btn.clicked.connect(self.change_counter)
def change_counter(self):
self.count += 1
self.label.setText(f"Count: {self.count}")
self.label.adjustSize()
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
常用 Widgets
QLabel
- 作用:建立可以顯示圖片或文字的標籤
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 300, 300);
label = QtWidgets.QLabel(win)
label.setText("大括號要換行") # 設定 label 要顯示的文字
label.resize(200, 200)
win.show()
sys.exit(app.exec())
window()

範例:設定字體樣式
from PyQt6 import QtWidgets
from PyQt6 import QtGui
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 300, 300);
label = QtWidgets.QLabel(win)
label.setText("To be, or not to be.")
font = QtGui.QFont() # 利用 QtGui 中的 QFont 方法建立一個新字體
font.setFamily("JetBrains Mono Nerd Font") # 更改字體樣式
font.setBold(True) # 字體粗細
font.setItalic(True) # 斜體
font.setUnderline(True) # 底線
font.setStrikeOut(True) # 刪除線
font.setPointSize(20) # 字體大小
label.setFont(font) # 將 label 的字體設定為 font
label.adjustSize() # 讓 label 的尺寸根據文字長度調整
win.show()
sys.exit(app.exec())
window()

範例:顯示圖片
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 600, 600);
label = QtWidgets.QLabel(win)
img = QtGui.QImage("./3/老八.JPG")
label.resize(342, 512)
label.setScaledContents(True)
label.setPixmap(QtGui.QPixmap.fromImage(img))
win.show()
sys.exit(app.exec())
window()

QTextEdit
- 作用:建立一個文字輸入框
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
box = QtWidgets.QTextEdit(win)
win.setGeometry(200, 200, 600, 600);
win.show()
sys.exit(app.exec())
window()

範例:獲取使用者輸入的文字
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def printText():
print(box.toPlainText())
app = QApplication(sys.argv)
win = QMainWindow()
box = QtWidgets.QTextEdit(win)
win.setGeometry(200, 200, 600, 600);
btn = QtWidgets.QPushButton(win)
btn.move(100, 100)
btn.clicked.connect(printText)
win.show()
print(box.toPlainText())
sys.exit(app.exec())
範例:設定預設文字
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def printText():
print(box.toPlainText())
app = QApplication(sys.argv)
win = QMainWindow()
box = QtWidgets.QTextEdit(win)
win.setGeometry(200, 200, 600, 600);
btn = QtWidgets.QPushButton(win)
btn.move(100, 100)
btn.clicked.connect(printText)
win.show()
print(box.toPlainText())
sys.exit(app.exec())
QCheckBox
- 作用:建立一個勾選框
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 600, 600);
checkbox = QtWidgets.QCheckBox(win)
checkbox.setText("這是個勾選框")
checkbox.adjustSize()
win.show()
sys.exit(app.exec())
window()

QComboBox
- 作用:建立一個下拉選單
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(200, 200, 600, 600);
combobox = QtWidgets.QComboBox(win)
combobox.addItems(["有馬加奈", "黑川赤音", "星野露比", "星野愛"])
win.show()
sys.exit(app.exec())
window()

實作
BMI App
- 寫一個 BMI 計算機

2025 建北電資聯合寒訓「資己資彼,百 Code 不殆」課程——PyQt
By Aaron Wu
2025 建北電資聯合寒訓「資己資彼,百 Code 不殆」課程——PyQt
- 193