- 專有名詞介紹
- GUI 介紹 & 簡史
- Qt & PyQt 介紹 & 安裝
- 第一個 PyQt 程式
- OOP
- GUI 基本概念
- Signals & Slots
- 常見 widgets
- 建中電研 44th 網管
- 寒訓一小隊輔 & 暑訓三小隊輔
- Linux & Python 廚
- 愛玩買夠梗但實際上沒追買夠
- 成發保重
- 不是同性戀或雙性戀
- 精神狀況堪憂
- 作業系統
- 就你現在在用的 Windows or macOS
- 負責控制電腦硬體,是使用者跟電腦的橋樑
- 指一堆寫好的 functions 跟 classes
- 如同字面意思,就像一個有不少書(functions, classess) 的圖書館
- 上堂課的 React 單獨來看就算 Library
- Framework = 一堆 Libaries + 一些工具
- 也如同字面意思,就像建築物的框架(鷹架),有了它只要填上建材(程式)就行
- 上上堂課教的 Flask 就是
- React + 一堆東西也算 Framework
- 假設你今天要寫作文
- Framework 就相當於論說文模板
- 寫論說文必須遵從固定規則(Framework 的限制)
- Library 則是修辭、文章架構或詞彙
- 你可以隨意用排比、譬喻這些修辭(Library 中的 functions)
打孔卡
打孔機
DOS
Unix
Apple II
IBM PC
Xerox PARC
(這機構有人在 1969 時說未來會有平板電腦)
研究中心早期構想
Xerox Alto
Apple Lisa
Macintosh 128K
Windows 1.0
Windows 95
- CLI 程式 可以直接輸出結果,但 GUI 顯然沒辦法就這樣幹
- 所以為了要開發 GUI App,作業系統的設計者(比如微軟跟蘋果)會設計整套 Library,專門給開發者寫 App
- 開發者只要調用這些 Library,就能開發出 GUI App
- 而每個 OS 的 Library 都長得不一樣,所以不同 OS 間的 Library 沒辦法通用
- 每次換一個 OS,理論上 App 就要完全重寫,而這顯然不太現實
- 於是有人搞出了 GUI Framework,可以只寫一次 App 就能在不同 OS 跑
- Framework 會根據 OS 來調用對應 Library 中的 functions,達到不同 OS 間幾乎一樣的效果
- iOS/Android/Desktop/Web
- 背後爸爸是 Google
- 性能不差
- 但 Dart 偏難寫(?
- iOS/Android/Desktop/Web
- 背後爸爸是 Meta
- 相對好寫(如果你平常用 React)
- 性能也算過得去
- 如果你上完 React 覺得沒問題的話…
- Desktop
- 背後爸爸是 GitHub
- 基本上就是一般網頁(code 幾乎不用改)
- VSCode、Slack 就是拿 Electron 包的
- 原理是開一個瀏覽器,直接跑網頁
- 所以超肥
- 這一三北資學術兼文書 Qt 學姐
- 今年暑假幹訓應該會看到她(?
- 所以要來幹訓!!!
- 由同名公司開發的一套 GUI Framework
- 最初設計給 C++,後來被移植到其他語言
- 不少桌面 App 都用 Qt 開發,比如 PhotoShop、Telegram
- 由 Riverbank Computing 搞出來的 Qt For Python
- Anki 桌面版就是用 PyQt 寫的
(PyQt 沒 logo,所以…嗯)
- PyQt 是 Python 唯一成熟的 Framework
- 其他語言也能用 Qt,所以學會 PyQt 就能轉移到其他語言
- 生態豐富、有大公司支援
(PyQt 沒 logo,所以…嗯)
- PyQt 比 PySide 更早出來,生態更完整、資料更多
- PySide 是由 Qt 官方開發的,理論上相容性會更好
- PyQt 商用要付費(或必須把程式碼公開)
- PySide 可以直接商用
- 還有一部分 class & function 的名稱不同
- 除此之外沒差,一個程式要改用另一者只要改 module name 就行
- Windows 使用者開 cmd.exe
- Mac/Linux 使用者開 Terminal
pip install pyqt6 --break-system-packages
(如果報錯誤再加)
- PyQt 底下有十幾個 modules,而常用的有四個
- QtCore: 一些非 GUI 類的 class,包含日期處理、檔案操作、多線程
- QtGui: 各種圖形相關的 class,包含圖片、字體
- QtWidgets: 各個 widgets
- QtMultimedia: 音訊、影片相關
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 的 move method 移動 label
- 把下面這行加到 label 跟 win.show() 之間重新執行
- 應該會看到類似這樣的畫面:
label.move(50, 50)
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"]
mygo = Anime("BanG Dream! It's MyGO!!!!!",
2023,
["Horror", "Love"])
動漫
- 名稱
- 年份
- 類型
kaguya = Anime("輝夜姬想讓人告白",
2015,
["Love", "Comedy"])
heroines = Anime("敗北女角太多了",
2021,
["Love", "Comedy"])
食譜
做出來的料理
- 如同前面提到,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()
- 這些 class 內的 function 被稱為 method
- method 至少要有一個參數 self
- 不同 class 可以有相同的 method 名稱,但同一個 class 內不行(在 Python)
你設計出了 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 到瘋掉
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 指一個 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
- 定義一個 class People
- People 有 name, friends 兩個 attributes
- People 裡的 print_friends method 可印出 friends
- 把剛剛第二個 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()
- 萬物始於 Window
- 每個 GUI App 只會有一個主 Window
- Window 可以有 menu bar、tool bar
- 一個 Widget 就是一個小組件
- Button、Label 這些都是 Widget
- Widget 必須放到一個 Window 上,不然跑不了
- 每個 window 的左上角座標是(0, 0)
- 左上角向右 +X,向下 +Y
- 可以把一個 GUI App想成 Minecraft 中的偵測器
- 有玩 Minecraft 就知道,偵測器偵測到方塊變更就會發送紅石信號
- 「方塊變更」就是一個 event,對應到 GUI App 裡就是滑鼠移動、點擊、文字輸入
- 「發送紅石信號」就是觸發的 function(handler),GUI App 可以視不同 event 做不同事
- 偵測器會一直偵測,直到消失為止;同理,GUI App 也會一直偵測 event 到退出為止
這整套機制就叫 Event Loop
- 作用:建立一個可以按的按鈕
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()
- 當一個 widget 被操作(比如點擊、被輸入),widget 會發送 Signal
- Signal 可以連接到 function
- 每當 Signal 產生,Signal 所連接的 function 就會執行
- 這些被呼叫的 functions 即為 Slots
- 對應到 Event Loop,Signals 為 event,Slots 為 handler
- 假設我們有個按鈕
- 還有一個 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())
- 作用:建立可以顯示圖片或文字的標籤
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()
- 作用:建立一個文字輸入框
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())
- 作用:建立一個勾選框
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()
- 作用:建立一個下拉選單
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 計算機