超全!Python图形界面框架PyQt5使用指南! . R3 O* |5 a9 c0 s$ O7 U! p" c, m! N; O0 i3 O% t
使用Python开发图形界面的软件其实并不多,相对于GUI界面,可能Web方式的应用更受人欢迎。但对于像我一样对其他编程语言比如C#或WPF并不熟悉的人来说,未必不是一个好的工具。( v7 f0 A. | \& ]4 h5 _' G' T
1 _' E$ v# p4 w3 b8 D常见GUI框架 $ t! B& `% i8 v# K/ h" ~) t$ E3 h9 m: m& n _
PyQt5:Qt是一个跨平台的 C++图形用户界面库。QT一度被诺基亚拥,后出售给芬兰的软件公司Digia Oyj。PyQt5是基于Digia公司Qt5的Python接口,由一组Python模块构成。PyQt5本身拥有超过620个类和6000函数及方法。在可以运行于多个平台,包括:Unix, Windows, and Mac OS。 3 ]0 }& ^7 H5 l + f4 w( F$ X+ W$ |9 j [$ c) [& MPyside6:Pyside是QT公司官方提供的Python包,上一版本为Pyside2,对应的是QT5,最新版命名规则进行了调整,更改为Pyside6,对应的是QT6版本。由于官方出品的比较看好,缺点是发布比较晚,网上的资料没有PyQt5多。1 I: W5 \$ W0 @& H# `9 Z
. X7 |4 f5 e- e* a E. [Tkinter:Python内置的GUI框架,使用TCL实现,Python中内嵌了TCL解释器,使用它的时候不用安装额外的扩展包,直接import,跨平台。不足之处在于UI布局全靠代码实现,只有15种常用部件,显示效果简陋。7 B' H8 }; w" K' C
: w; P# O' k& ^; fPySimpleGUI:PySimpleGUI 是 Tkinter 一层包装。使用 PySimpleGUI 实现自定义 GUI 所需的代码量要比使用 Tkinter 直接编写相同的 GUI 要少得多。 2 d' G' b o+ J) t! ~0 k, V. R m" l) X" A: R, \
WxPython:wxPython是Python语言对流行的wxWidgets跨平台GUI工具库的绑定。用得比较广泛,跨平台,C++编写,文档少,用户可能就需要根据编程内容对不同平台中的GUI代码做一些调整。遇到问题不好解决,代码布局控件,不直观。! ?9 a' i9 F! Q' `6 K4 f7 i
) Q2 d1 T% X" l; }; Q1 J2 V# V+ \
Wax:基于wxPython ,为克服wxPython的问题而制作的一个包。' p; M7 d! C7 P2 E1 `) M! |
! K6 b7 B" }! L9 a
Kivy:主要针对多点触控程序,智能手机平板等,也可以在没有触屏功能的系统上,全平台支持(Windows, Linux, Mac OS X, Android and iOS.)使用Python和cython编写,中文支持差,需要自己下载中文库并且制定路径。 4 R7 A5 \( V, r% q4 X( h- s. M: k4 i
BeeWare:Write once. Deploy everywhere.需要与Kivy配合使用。 , |+ h4 q2 J0 D; n' j, f$ _, T4 g 1 v% S4 Y8 v2 g* [Toga:一个使用Python开发原生APP的GUI工具包。Toga由一个具有共享接口的基础组件库组成,以简化与平台无关的GUI开发。Toga适用于Mac OS、Windows、Linux(GTK)以及Android和iOS等移动平台。1 H) D# q# N! \
$ P* j8 `0 {$ J& k" C
Eel:一个轻量的 Python 库,用于制作简单的类似于 Electron(但是比它更轻量) 的离线 HTML/JS GUI 应用程序,并具有对 Python 功能(capabilities)和库的完全访问权限。 O/ W3 A" `, o) ?* [7 G1 P' h$ s" r. ?3 a$ a1 J+ W1 [+ |
Flexx:一个纯 Python 工具包,用来创建图形化界面应用程序。其使用 Web 技术进行界面的渲染。你可以用 Flexx 来创建桌面应用,同时也可以导出一个应用到独立的 HTML 文档。因为使用纯 Python 开发,所以 Flexx 是跨平台的。只需要有 Python 和浏览器就可以运行。" W6 `' O r/ F3 T7 d
' ]# Q' i- c/ w% `" v2 B: f
pywebview是围绕 webview 组件的轻量型跨平台包装器(wrapper),它允许在其自己的本机 GUI 窗口中显示 HTML 内容。它使您可以在桌面应用程序中使用 Web 技术,同时尽最大可能隐藏使用浏览器构建GUI的事实。4 |) |* @% r0 v
$ f6 u$ I$ P! |/ \3 ^( s6 _enaml:一种能够让你用最小的努力就可以实现高质量GUI界面的的Python框架,也是一种独特的编程语言。enaml将声明性语言与基于约束的布局系统结合在一起,使用户可以轻松地定义灵活布局的UI。enaml应用程序可以在任何支持Python和Qt的平台上运行。 ( b+ N% u0 D4 \6 L Z# Y5 R. Y+ F9 @' m; r6 E0 m
个人想法:太多学不完,先学PyQt5,原因是资料多,学有余力再学pyside6,最后看下PySimpleGUI,看能否解决一些简单问题。 / p% q* L- l7 w: G* Y( B" \ 9 S7 v0 a( e- PPyQt5简介: N2 ^8 s% a/ ]. Y4 C; X
+ N* H( Y$ Y2 {1 y0 p, h1 S6 r
PyQt是Qt框架的Python语言实现,由Riverbank Computing开发,是最强大的GUI库之一。PyQt提供了一个设计良好的窗口控件集合,每一个PyQt控件都对应一个Qt控件,因此PyQt的API接口与Qt的API接口很接近,但PyQt不再使用QMake系统和Q_OBJECT宏。! b) j! ~; k+ [9 K0 F
4 T4 ?+ A* q0 ~) lPyQt5提供GPL版和商业版证书,自由开发者可以使用免费的GPL许可,如果需要将PyQt用于商业应用,则必须购买商业许可。0 w4 ^% g X3 k7 _* b6 E
( v( G6 u. V# u! g( H
PyQt5特性如下: 1 l1 Q- ^ F7 ^6 L1 i- W) { {9 l! V2 Q6 O
基于高性能的Qt的GUI控件集。 - O! M+ I1 p: |, E% V1 d ; h2 G. Y: q: f' R3 S能够跨平台运行在Linux、Window和Mac OS系统上。1 W- ^/ D$ b! H0 d' v' b4 a% F
- D: |# t& j8 ?& a0 `0 D( V8 E
使用信号槽机制进行通信。1 E7 |# G# K6 C1 z5 k9 A
7 S- K% V/ Z! K- p# E安装PyQt5 : D$ J/ P( L1 p8 n+ T 3 F8 k3 N0 `, opip install pyqt55 S2 O0 p6 U. K* \8 a3 Q
7 w* }9 R- @, h* @- @
pip install pyqt5-tools - p9 o8 R4 Y8 Y- l其中pyqt5-tools为Qt Designer拖拽式的界面设计工具。安装过程中可能会报如下错误: + R; W) P! p* ` . E. t( d% r+ X, z8 p+ U: C2 Iqt5-tools 5.15.2.1.2 has requirement click~=7.0, but you'll have click 8.0.1 which is incompatible.; T0 W$ c; l% j, Q0 X% u, N" j$ x
解决方案: @+ H2 H& V& R5 d& \0 _; X
" D# ~3 D3 R# i" a! z
pip install click~=7.0 : f$ `0 H$ v4 m! gQt Designer的配置 3 T( D v& }8 q+ E( o3 j+ l! o : ^7 c, z5 G C) aQt Designer 是通过拖拽的方式放置控件,并实时查看控件效果进行快速UI设计。 / l* g8 U4 E( o4 C# u8 R. B3 w/ _1 Z5 f+ w- N
+ c) T; ~3 a* u. }' g! L# C) {7 q- P% O
整个画面的构成:+ d* q8 j* t# S
! x* b m: l7 j1 i4 |9 J+ s
左侧的“Widget Box”就是各种可以自由拖动的组件' M- g n9 [. B( O: d
# C& f5 B& _3 F, M1 \3 R* k5 x
中间的“MainWindow – untitled”窗体就是画布, n7 g5 U O3 x
6 F/ }3 N+ ?5 ]" ]) b$ K\# self.clearBtn.clicked.connect(Dialog.accept) # a& x3 w* D' X# X0 k' _& w( h6 L- }' g! x7 z; h; W% t# Q; K
\# 修改为: 6 @2 k1 E) L1 f/ s' u. _+ K/ l! O" f
self.queryBtn.clicked.connect(Dialog.queryWeather)% a: Y% k" G& p' Y: t* U5 i1 V* R! C
# C# h; L5 f8 W! V8 U* nself.clearBtn.clicked.connect(Dialog.clearText) " z/ [9 a' D3 i0 f, h; R最终的Weather.py内容如下: ! h$ K; |- H* D% O& o* E$ N/ d) G# w/ a5 m3 i" I$ U
\# -*- coding: utf-8 -*- 7 ^* n9 O4 y1 s6 a6 C $ O* j. v' X) U. _9 j% v/ A! c9 j4 w1 F6 L\# Form implementation generated from reading ui file 'Weather.ui') x' `' s% @/ K* [, p' ^
N9 Z9 }8 P) e( F* D" R\# - u9 R9 k+ @; S3 ]" f1 U8 J1 ~: e M3 Y
\# Created by: PyQt5 UI code generator 5.15.4 5 q7 T( T" w- @" X 7 G* B5 C# f- G" V2 q\# 0 Y R! x' y! ~9 _# R( U8 S/ S- N$ ]1 V. Y' u! f# T% r! u
\# WARNING: Any manual changes made to this file will be lost when pyuic5 is2 c) `0 I/ g3 }1 b
3 ~2 k( s2 |7 H6 I\# run again. Do not edit this file unless you know what you are doing.7 n' P6 w9 q4 O# P- N2 y _7 A
; Y; R7 X4 \, f' W1 ?- z
from PyQt5 import QtCore, QtGui, QtWidgets% ^/ w+ T/ v n0 }2 m4 q: U, c
, S8 }9 h; F3 ^/ iclass Ui_Dialog(object):1 W& N! }* K) D. B8 d3 V7 U
" X' k: _6 k7 O g
def setupUi(self, Dialog): - d; d: i% |$ _$ K& U' s& G& Y# ~! I( h* `: {
Dialog.setObjectName("Dialog") # X' ^3 D# N+ L! w2 R; {/ M, d' |7 `# f, e( Z( t
Dialog.resize(600, 600); O9 u. U9 U) M( p- x2 E, |
+ H+ C! x) n6 ^+ ^ m1 T self.groupBox = QtWidgets.QGroupBox(Dialog) ' E5 b. x- H) b) A+ Y+ @7 ?( S1 @3 z* n! L: m1 M% U. O* G
self.groupBox.setGeometry(QtCore.QRect(30, 20, 551, 511))+ i% z; h4 {) N; L
6 ]! R' R# q$ G/ J, V
self.groupBox.setObjectName("groupBox"). O* v* T7 _# Y+ m0 a# J% ], s
& ]' \+ k+ \) G1 v/ s8 y. b# s
self.label_2 = QtWidgets.QLabel(self.groupBox) 1 ]% D3 U! `6 @; e+ b$ n ; j% V2 W# n* ~( @. @9 ` self.label_2.setGeometry(QtCore.QRect(20, 30, 31, 16))* }, H4 x0 v5 g3 b$ i! J9 ^
* l, D% R! r( U3 Q2 |0 b& r# ^ self.label_2.setObjectName("label_2")8 V& W k3 _# i' J3 T
( h ]/ ?2 p* _5 z, D self.comboBox = QtWidgets.QComboBox(self.groupBox) 9 ?* i5 I8 ]" u* ^ e3 S7 a3 k: g + \3 `. q9 [2 R/ o+ D6 Q2 O self.comboBox.setGeometry(QtCore.QRect(70, 30, 87, 22)) U$ C: A2 J1 n W& f e8 z( W) d- q2 Q6 c+ }4 O$ B
self.comboBox.setObjectName("comboBox") ; i& y# ]: ?8 N) E/ M" w6 V j$ @& t& ~; D1 s9 G7 F
self.comboBox.addItem("") * `) F0 r- E% ~0 p5 k 8 l i& M2 w9 w# J2 O9 M self.comboBox.addItem("")! U, @5 \2 V, S0 N
' s) u9 s Z2 o5 t2 y
self.comboBox.addItem("") $ c5 y V( K9 c( ], o* g q) k$ Z% G7 D1 V. A, o: R9 z/ v) H
self.textEdit = QtWidgets.QTextEdit(self.groupBox) % ^! o% N$ K4 [, n) R 6 F+ B- Q9 f; F# ^- H# t4 o1 H; L self.textEdit.setGeometry(QtCore.QRect(20, 70, 491, 411))) G( X6 A& O( b1 M) z
9 o; Q9 g' Z" q% y |" _8 n
self.textEdit.setObjectName("textEdit") y7 S. Z+ I7 X8 L5 A5 J6 J ! X% F* S$ T, ] self.queryBtn = QtWidgets.QPushButton(Dialog)+ T4 O3 J, I( x) u. t+ i
% t. ~' s& {, w# K6 d9 _2 m5 v4 N- U self.queryBtn.setGeometry(QtCore.QRect(490, 560, 93, 28))) ^$ S7 R- I& ? V& y
* d( u9 U# m) Z0 y
self.queryBtn.setObjectName("queryBtn")* ]9 C' `2 Z+ n2 W* V2 q9 ]
* U+ R5 }9 I4 R3 @7 Y3 O# f1 ^
self.clearBtn = QtWidgets.QPushButton(Dialog) 3 p0 g- C) c1 D+ y0 y3 ?) A* _ 5 Y& L7 h# L4 `# B self.clearBtn.setGeometry(QtCore.QRect(30, 560, 93, 28)) 1 m% Y ^, q1 z+ U 8 {4 A# M2 O3 M0 r/ b3 @0 l self.clearBtn.setObjectName("clearBtn") - `8 U$ \' o/ L8 ^1 }# ?/ h9 f- x
self.retranslateUi(Dialog) & T6 F) ~4 g% R0 b3 v1 s0 m% @# ] ) o& W/ q0 u, H2 {3 ] self.clearBtn.clicked.connect(Dialog.clearText) + c3 M. f+ d' W6 t 8 v# O; W w" z$ R) t self.queryBtn.clicked.connect(Dialog.queryWeather) 4 T2 M/ K5 S/ k" u9 E) o6 b1 x. ]) r ; m; J3 y% L% b* p! P/ v QtCore.QMetaObject.connectSlotsByName(Dialog)! L) v2 K+ x. s% e
6 b% h1 \# B; Z S: C9 [) e( u def retranslateUi(self, Dialog): ( F& K+ Y8 I( m& e ( X3 C+ {2 ^8 T3 w, m+ Q8 K$ J _translate = QtCore.QCoreApplication.translate - `: B( [" _! m' S 6 k, s8 U7 c6 ?* m# I, |5 z Dialog.setWindowTitle(_translate("Dialog", "Dialog")) " n8 p* w8 K* k5 M9 H( w7 K3 J + t' D( G7 ], _8 Y. ~6 i self.groupBox.setTitle(_translate("Dialog", "城市天气预报")) # n( ]( d F7 u. L 6 g$ r8 y! l4 K! F self.label_2.setText(_translate("Dialog", "城市"))! c( _+ k8 D) d/ z$ K; p
8 h% S* ~! K1 ?7 y5 t1 d. m
self.comboBox.setItemText(0, _translate("Dialog", "北京")) " z+ }: R% K0 ]+ P% i. k # C9 S. n8 V0 r7 i8 j/ U1 m self.comboBox.setItemText(1, _translate("Dialog", "苏州")) ( R& a) h- J, k5 x/ x& C7 v1 Y4 i( Y1 o3 R
self.comboBox.setItemText(2, _translate("Dialog", "上海"))1 s2 k; p4 C; H* ^
' g% T" J' A) ]/ ~9 U& \/ [! C8 ` self.queryBtn.setText(_translate("Dialog", "查询")) 6 N) g( b; j5 A+ N3 T9 d' P8 F7 B6 L( y. q- H# ]
self.clearBtn.setText(_translate("Dialog", "清空")) 1 _( e% G3 K6 h* H' V( F4 e' c) H& M' K& K- L
3、调用MainDialog. g3 ^* W4 D( t4 P5 P
3 l/ r7 n' J. A. n9 k
在MainDialog中调用界面类Ui_Dialog,然后在其中中添加查询天气的业务逻辑代码,这样就做到了界面显示和业务逻辑的分离。新增demo.py文件, 在MainDialog类中定义了两个槽函数queryWeather()和clearText(),以便在界面文件Weather.ui中定义的两个按钮(queryBtn 和clearBtn) 触发clicked 信号与这两个槽函数进行绑定。 0 j, j2 z! A& x/ U* @- K5 ~+ o3 U& E; Z# I' N6 l9 Z/ u
完整代码如下:/ R0 `' S' t# s+ F
8 m7 B; G" [4 ^0 U3 b# Wimport sys2 z7 F+ o1 v2 ?# ? Z
1 W2 Z) j! L" n
import Weather 1 b4 b. g8 k a , f& c5 }' E/ W% K/ Gfrom PyQt5.QtWidgets import QApplication, QDialog0 F+ `4 |$ g6 i5 T
. s1 R* a' t% Z9 ^3 bimport requests6 _( c2 [7 P& H6 }# j! [
! ?, X7 [" w* L! ?* G1 Q
class MainDialog(QDialog):& ]: {6 F9 i1 `; H7 k4 Y5 I
/ n( F4 B8 Q$ Q5 u3 _, V) u
def __init__(self, parent=None): / _9 e* x3 o+ g$ ]/ H ) G o/ b! i/ n6 C. f super(QDialog, self).__init__(parent) # p8 Q9 z* ?% t/ w0 j$ j* z h : a/ f1 c! _6 A self.ui = Weather.Ui_Dialog()) d. a4 u6 f+ B n
! w- E) B0 u" h3 o$ \* s& a& p% E3 b
self.ui.setupUi(self) . g0 i# W! F% m# L n! g, g 6 @5 G) j% s2 ?- y" I def queryWeather(self): / f& p6 ?+ ~- P! n3 g5 i2 v& y+ ?: Y7 ~/ P4 e. Y$ ]3 R
cityName = self.ui.comboBox.currentText() " z* L9 Z8 b; C1 O1 H7 B7 x ( X9 s* R" M2 K cityCode = self.getCode(cityName) + ]1 G+ k8 h+ E # L! v r, P/ B; O1 l3 J r = requests.get(1 u }. B$ Z1 Q- Q
0 W0 z6 m2 C2 c+ E
"https://restapi.amap.com/v3/weather/weatherInfo?key=f4fd5b287b6d7d51a3c60fee24e42002&city={}".format( 9 V( }3 T% p! @4 }! @ [% U8 s i. J( F2 T0 Y cityCode)) 2 s; \4 A e+ P) z! L/ Q J$ \% v, n 1 Y8 F' W4 A" k b) B if r.status_code == 200: j$ t( K3 z9 A4 j+ `2 Y: G1 k# v
' M7 f2 i' x6 p/ P
data = r.json()['lives'][0] & w1 K6 n6 \- { 0 j0 m' u8 S {* M M1 x+ D5 w weatherMsg = '城市:{}\n天气:{}\n温度:{}\n风向:{}\n风力:{}\n湿度:{}\n发布时间:{}\n'.format( ; J: I% \6 B( b9 e& w; x3 U) h $ Q# V6 d( H4 @ {1 L data['city'], 6 I) o$ g4 U# Z* I9 J3 G7 a+ m" E" M3 R; T4 i9 @; B
data['weather'], ) T- ]9 E9 d4 S0 s: E3 ? [ & |& t& ^4 b& V- Z data['temperature'], " ^3 A# K8 j a' W: x( m L ; _9 }4 B7 P a1 C* j9 g data['winddirection'], & C3 W5 T6 _, W! ?3 G0 i% X/ b' i- F9 T7 t' q9 o2 b |+ d! U5 _; F
data['windpower'],( p6 |0 ]1 E; z! O: X" a6 F T
. a7 t7 W/ e: k+ `
data['humidity'],$ q9 M6 S! c9 a
$ b1 Y0 @2 z, s! r, z" |+ N data['reporttime'],4 z& z$ k( J2 }6 H$ R% u/ x0 F
, v, ]; R: J8 n# n" X, C
)9 c5 q, q% X* [
$ B' n- F! Q$ u# s7 ]- ] else:2 x$ k3 t3 e9 d- \
# I7 z4 K' O+ b/ F, D# l
weatherMsg = '天气查询失败,请稍后再试!' 8 z2 ?3 J8 A8 }. C5 Z' L( c. h( P9 a4 Y( [3 |8 S2 x
self.ui.textEdit.setText(weatherMsg) 6 D8 O) V$ X, m5 Y& Y2 z3 E. z" ]+ W! y: u( d& P/ _
def getCode(self, cityName):1 { F( E( W0 M5 r
8 k! y! S, ] o cityDict = {"北京": "110000", + S" E# Z/ m3 `' G4 ]* l. k 1 k/ {4 i% }1 k/ M- L F "苏州": "320500",9 b6 p% z1 s% |9 k1 K& b3 K! t