一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

wxPython:一曲MFC的挽歌,理想主義的絕唱

 copy_left 2022-05-11

概述

MFC是我接觸到的第一個(gè)界面庫,當(dāng)時(shí)的操作系統(tǒng)還是Windows95。在那個(gè)IT技術(shù)日新月異的年代,就像一個(gè)從荒蠻部落闖進(jìn)文明社會(huì)的野人第一眼看見汽車那樣,我對MFC充滿了好奇和迷戀。盡管后來斷斷續(xù)續(xù)接觸了WPF、Qt等GUI庫,卻始終對MFC情有獨(dú)鐘,以至于愛屋及烏,喜歡上了wxWidgets。

wxWidgets和MFC的確太相似了,連命名習(xí)慣和架構(gòu)都高度相似。事實(shí)上,wxWidgets就是跨平臺(tái)的MFC,對各個(gè)平臺(tái)的差異做了抽象,后端還是用各平臺(tái)原生的API實(shí)現(xiàn)。這正是wxWidgets的優(yōu)點(diǎn):編譯出來的程序發(fā)行包比較小,性能也相當(dāng)優(yōu)異。

隨著MFC的日漸式微,Qt異軍突起,目前已成為最強(qiáng)大,最受歡迎的跨平臺(tái)GUI庫之一。在Python生態(tài)圈里,PyQt的用戶群也遠(yuǎn)超wxPython。喜歡Qt的人認(rèn)為這是技術(shù)競爭的結(jié)果,但我覺得這更像是開源理念和商業(yè)化思想的差異造成的。

wxWidgets像是一個(gè)孤獨(dú)的勇士,高舉開源的大旗,試圖以一己之力構(gòu)建一個(gè)相互承認(rèn)、相互尊重的理想社會(huì);而Qt則更像是一個(gè)在商業(yè)資本驅(qū)使下不斷擴(kuò)張的帝國,它不滿足于封裝不同平臺(tái)的API,而是要?jiǎng)?chuàng)造出自己的API和框架,它不僅僅是UI,而是囊括了APP開發(fā)用到的所有東西,包括網(wǎng)絡(luò)、數(shù)據(jù)庫、多媒體、藍(lán)牙、NFC、腳本引擎等。

缺少或拒絕商業(yè)化運(yùn)作的支持,wxWidgets的悲情結(jié)局早已是命中注定。如果不是因?yàn)镻ython的興盛和wxPython的復(fù)興,wxWidgets也許早已經(jīng)和MFC一樣被遺忘在了角落里。不無夸張地說,wxPython是以MFC為代表的一個(gè)時(shí)代的挽歌,更是一曲理想主義的絕唱。

1.1 組織架構(gòu)

其實(shí),wxPython談不上什么組織架構(gòu),因?yàn)樽烂娉绦蜷_發(fā)所用的類、控件、組件和常量幾乎都被放到了頂級(jí)命名空間wx下面了。這樣做看似雜亂無章,但用起來卻是非常便捷。比如,導(dǎo)入必要的模塊,PyQt通常要這樣寫:

from PyQt6.QtWidgets import QApplication, QWidget, QComboBox, QPushButton, QHBoxLayout, QVBoxLayout, QColorDialogfrom PyQt6.QtGui import QIcon, QPainter, QPen, QColor, QPolygonfrom PyQt6.QtCore import Qt, QPoint

PyQt巨人般的體量限制了使用星號(hào)導(dǎo)入所有的模塊,只能用什么導(dǎo)入什么。而wxPython只需要簡短的一句話:

import wx

再比如一些常量的寫法,wxPython同樣簡潔,PyQt已經(jīng)長到匪夷所思的程度了。比如左對齊和確定取消鍵,wxPython這樣寫:

wx.ALIGN_LEFTwx.OK | wx.CANCEL

PyQt寫出來幾乎要占一整行:

Qt.AlignmentFlag.AlignLeftQMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel

盡管wxPython也與時(shí)俱進(jìn)地增加了一些諸如wx.xml、wx.svg之類地外圍模塊,但除了wx這個(gè)核心模塊之外,我個(gè)人覺得只有wx.aui和wx.grid模塊算是必要的擴(kuò)展。如果想讓界面更花哨點(diǎn),那就要了解以下wx.adv、wx.ribbon這兩個(gè)模塊,純python構(gòu)建的控件庫wx.lib也絕對值得一試??傊驹谖业膽?yīng)用領(lǐng)域看,wxPython的組織架構(gòu)如下圖所示。根據(jù)使用頻率的高低,我給各個(gè)模塊標(biāo)注了紅黃綠藍(lán)四種顏色。

文章圖片2

1.2 安裝

截至本文寫作時(shí),wxPython的最新版本是4.1.1。Windows用戶和macOS用戶可以直接使用下面的命令安裝。

pip install -U wxPython

由于Linux平臺(tái)存在發(fā)行版之間的差異,必須使用相應(yīng)的包管理器進(jìn)行下載和安裝。例如,在Ubuntu系統(tǒng)上可以嘗試下面的安裝命令。

sudo apt-get install python3-wxgtk4.0 python3-wxgtk-webview4.0 python3-wxgtk-media4.0

文章圖片3

快速體驗(yàn)

2.1 桌面應(yīng)用程序開發(fā)的一般流程

用wxPython寫一個(gè)桌面應(yīng)用程序,通常分為6個(gè)步驟:

  • 第1步:導(dǎo)入模塊

  • 第2步:創(chuàng)建一個(gè)應(yīng)用程序

  • 第3步:創(chuàng)建主窗口

  • 第4步:在主窗口上實(shí)現(xiàn)業(yè)務(wù)邏輯

  • 第5步:顯示窗主口

  • 第6步:應(yīng)用程序進(jìn)入事件處理主循環(huán)

除第4步之外的其它步驟,基本都是一行代碼就可以完成,第4步的復(fù)雜程度取決于功能需求的多寡和業(yè)務(wù)邏輯的復(fù)雜度。下面這段代碼就是這個(gè)一般流程的體現(xiàn)。

# 第1步:導(dǎo)入模塊import wx
# 第2步:創(chuàng)建一個(gè)應(yīng)用程序app = wx.App()
# 第3步:創(chuàng)建主窗口frame = wx.Frame(None)
# 第4步:在主窗口上實(shí)現(xiàn)業(yè)務(wù)邏輯st = wx.StaticText(frame, -1, 'Hello World')
# 第5步:顯示窗主口frame.Show()
# 第6步:應(yīng)用程序進(jìn)入事件處理主循環(huán)app.MainLoop()

2.2 Hello World

實(shí)際應(yīng)用wxPython開發(fā)桌面應(yīng)用程序的的時(shí)候,上面這樣的寫法難以實(shí)現(xiàn)和管控復(fù)雜的業(yè)務(wù)邏輯,因而都是采用面向?qū)ο蟮膽?yīng)用方式。下面的代碼演示了以O(shè)OP的方式使用wxPython,并且為窗口增加了標(biāo)題和圖標(biāo),設(shè)置了窗口尺寸和背景色,同時(shí)也給靜態(tài)文本控件StaticText設(shè)置了字體字號(hào)。

import wx
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, -1,style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('最簡的的應(yīng)用程序') self.SetIcon(wx.Icon('res/wx.ico')) # 設(shè)置圖標(biāo) self.SetBackgroundColour((217, 228, 0)) # 設(shè)置窗口背景色 self.SetSize((300, 80)) # 設(shè)置窗口大小 self.Center() # 窗口在屏幕上居中 st = wx.StaticText(self, -1, 'Hello World', style=wx.ALIGN_CENTER) # 生成靜態(tài)文本控件,水平居中 st.SetFont(wx.Font(20, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, 'Arial')) # 設(shè)置字體字號(hào)
if __name__ == '__main__': app = wx.App() # 創(chuàng)建一個(gè)應(yīng)用程序 frame = MainFrame(None) # 創(chuàng)建主窗口 frame.Show() # 顯示窗主口 app.MainLoop() # 應(yīng)用程序進(jìn)入事件處理主循環(huán)

代碼中用到了一個(gè).png格式的圖像文件文件,想要運(yùn)行這段代碼的話,請先替換成本地文件。至于文件格式,SetIcon方法沒有限定,常見的包括.ico和.jpg在內(nèi)的圖像格式都支持。代碼運(yùn)行界面如下圖所示。

文章圖片4

2.3 常用控件介紹

盡管wxPython的核心模塊和擴(kuò)展模塊提供了數(shù)以百計(jì)的各式控件和組件,但真正常用且必不可少的控件只有為數(shù)不多的幾個(gè):

  • wx.Frame - 窗口

  • wx.Panel - 面板

  • wx.StaticText - 靜態(tài)文本

  • StaticBitmap - 靜態(tài)圖片

  • wx.TextCtrl - 單行或多行文本輸入框

  • wx.Button - 按鈕

  • wx.RadioButton - 單選按鈕

  • wx.CheckBox - 復(fù)選按鈕

  • wx.Choice - 下拉選擇框

所有的wxPython控件都有一個(gè)不可或缺的parent參數(shù)和若干關(guān)鍵字參數(shù),通常,關(guān)鍵字參數(shù)都有缺省默認(rèn)值。

  • parent - 父級(jí)對象

  • id - 控件的唯一標(biāo)識(shí)符,缺省或-1表示自動(dòng)生成

  • pos - 控件左上角在其父級(jí)對象上的絕對位置

  • size - 控件的寬和高

  • name - 用戶定義的控件名

  • style - 控件風(fēng)格

wxPython的控件在使用風(fēng)格上保持著高度的一致性,一方面因?yàn)樗鼈儚囊粋€(gè)共同的基類派生而來,更重要的一點(diǎn),wxPython不像PyQt那樣充斥著隨處可見的重載函數(shù)。比如,PyQt的菜單欄QMenuBar增加菜單,就有addMenu(QMenu)、addMenu(str)和addMenu(QIcon, str)等三種不同的重載形式。方法重載固然帶來了很多便利,但也會(huì)增加使用難度,讓用戶無所適從。

下面的代碼演示了上述常用控件的使用方法。

import wx
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' # 調(diào)用父類的構(gòu)造函數(shù),從默認(rèn)風(fēng)格中去除改變窗口大小 wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER) self.SetTitle('wxPython控件演示') self.SetIcon(wx.Icon('res/wx.ico')) self.SetSize((860, 450)) self.Center() # 創(chuàng)建一個(gè)面板,用于放置控件 panel = wx.Panel(self, -1)
# 在x=20,y=20的位置,創(chuàng)建靜態(tài)文本控件 st = wx.StaticText(panel, -1, '我是靜態(tài)文本控件', pos=(20, 20))
# 在x=300,y=20的位置,創(chuàng)建靜態(tài)圖片 bmp = wx.Bitmap('res/forever.png') sb = wx.StaticBitmap(panel, -1, bmp, pos=(280, 10))
# 在x=20, y=50的位置,創(chuàng)建文本輸入框,指定輸入框的寬度為260像素,高度默認(rèn) tc1 = wx.TextCtrl(panel, -1, value='我是文本輸入框', pos=(20, 50), size=(260, -1))
# 在x=20, y=90的位置,創(chuàng)建文本輸入框,指定樣式為密碼 tc2 = wx.TextCtrl(panel, -1, value='我是密碼', pos=(20, 90), style=wx.TE_PASSWORD)
# 在x=20, y=130的位置,創(chuàng)建單選按鈕,成組的單選按鈕,第一個(gè)需要指定樣式wx.RB_GROUP rb1 = wx.RadioButton(panel, -1, '單選按鈕1', pos=(20, 130), style=wx.RB_GROUP, name='rb1')
# 在x=100, y=130的位置,創(chuàng)建單選按鈕,不再需要指定樣式wx.RB_GROUP rb2 = wx.RadioButton(panel, -1, '單選按鈕2', pos=(100, 130), name='rb2')
# 在x=180, y=130的位置,創(chuàng)建單選按鈕,不再需要指定樣式wx.RB_GROUP rb3 = wx.RadioButton(panel, -1, '單選按鈕3', pos=(180, 130), name='rb3')
# 在x=20, y=160的位置,創(chuàng)建復(fù)選按鈕 cb1 = wx.CheckBox(panel, -1, '復(fù)選按鈕', pos=(20, 160))
# 在x=100, y=160的位置,創(chuàng)建復(fù)選按鈕,指定其樣式為wx.ALIGN_RIGHT cb2 = wx.CheckBox(panel, -1, '文字在左側(cè)的復(fù)選按鈕', pos=(100, 160), style=wx.ALIGN_RIGHT)
# 在x=20,y=190的位置,創(chuàng)建按鈕 ch = wx.Choice(panel, -1, choices=['wxPython', 'PyQt', 'Tkinter'], pos=(20, 190), size=(100, -1)) ch.SetSelection(0) # 在x=120,y=190的位置,創(chuàng)建按鈕 btn = wx.Button(panel, -1, '按鈕', pos=(150, 190))
# 在x=20,y=230的位置,創(chuàng)建文本框,指定大小為260*150,并指定其樣式為多行和只讀 tc3 = wx.TextCtrl(panel, -1, value='我是多行文本輸入框', pos=(20, 230), size=(260, 150), style=wx.TE_MULTILINE | wx.CB_READONLY)
if __name__ == '__main__': app = wx.App() # 創(chuàng)建一個(gè)應(yīng)用程序 frame = MainFrame(None) # 創(chuàng)建主窗口 frame.Show() # 顯示窗主口 app.MainLoop() # 應(yīng)用程序進(jìn)入事件處理主循環(huán)

代碼運(yùn)行界面如下圖所示。

文章圖片5

文章圖片6

控件布局

3.1. 分區(qū)布局

上面的例子里,輸入框、按鈕等控件的位置由其pos參數(shù)確定,即絕對定位。絕對定位這種布局方式非常直觀,但不能自動(dòng)適應(yīng)窗口的大小變化。更普遍的方式是使用被稱為布局管理器的wx.Sizer來實(shí)現(xiàn)分區(qū)布局。所謂分區(qū)布局,就是將一個(gè)矩形區(qū)域沿水平或垂直方向分割成多個(gè)矩形區(qū)域,并可嵌套分區(qū)布局管理器wx.Sizer的派生類有很多種,最常用到是wx.BoxSizer和wx.StaticBoxSizer。

和一般的控件不同,布局管理器就像是一個(gè)魔法口袋:它是無形的,但可以裝進(jìn)不限數(shù)量的任意種類的控件——包括其他的布局管理器。當(dāng)然,魔法口袋也不是萬能的,它有一個(gè)限制條件:裝到里面的東西,要么是水平排列的,要么是垂直排列的,不能排成方陣。好在程序員可以不受限制地使用魔法口袋,當(dāng)我們需要排成方陣時(shí),可以先每一行使用一個(gè)魔法口袋,然后再把所有的行裝到一個(gè)魔法口袋中。

創(chuàng)建一個(gè)魔法口袋,裝進(jìn)幾樣?xùn)|西,然后在窗口中顯示的偽代碼是這樣的:

魔法口袋 = wx.BoxSizer() # 默認(rèn)是水平的,想要垂直放東西,需要加上 wx.VERTICAL 這個(gè)參數(shù)魔法口袋.add(確認(rèn)按鈕, 0, wx.ALL, 0) # 裝入確認(rèn)按鈕魔法口袋.add(取消按鈕, 0, wx.ALL, 0) # 裝入取消按鈕
窗口.SetSizer(魔法口袋) # 把魔法口袋放到窗口上窗口.Layout() # 窗口重新布局

魔法口袋的 add() 方法總共有4個(gè)參數(shù):第1個(gè)參數(shù)很容易理解,就是要裝進(jìn)口袋的物品;第2個(gè)參數(shù)和所有 add() 方法的第2個(gè)參數(shù)之和的比,表示裝進(jìn)口袋的物品占用空間的比例,0表示物品多大就占多大地兒,不額外占用空間;第3個(gè)參數(shù)相對復(fù)雜些,除了約定裝進(jìn)口袋的物品在其占用的空間里面水平垂直方向的對齊方式外,還可以指定上下左右四個(gè)方向中的一個(gè)或多個(gè)方向的留白(padding);第4個(gè)參數(shù)就是留白像素?cái)?shù)。

下面是一個(gè)完整的例子。

import wx
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('分區(qū)布局') self.SetIcon(wx.Icon('res/wx.ico')) self.SetSize((640, 320)) # 設(shè)置窗口大小 self._init_ui() # 初始化界面 self.Center() # 窗口在屏幕上居中 def _init_ui(self): '''初始化界面''' # 創(chuàng)建容器面板 panel = wx.Panel(self, -1) # 生成黑色背景的預(yù)覽面板 view = wx.Panel(panel, -1, style=wx.SUNKEN_BORDER) view.SetBackgroundColour(wx.Colour(0, 0, 0)) # 生成按鈕和多行文本控件 btn_capture = wx.Button(panel, -1, '拍照', size=(100, -1)) btn_up = wx.Button(panel, -1, '↑', size=(30, 30)) btn_down = wx.Button(panel, -1, '↓', size=(30, 30)) btn_left = wx.Button(panel, -1, '←', size=(30, 30)) btn_right = wx.Button(panel, -1, '→', size=(30, 30)) tc = wx.TextCtrl(panel, -1, '', style=wx.TE_MULTILINE) # 左右按鈕裝入一個(gè)水平布局管理器 sizer_arrow_mid = wx.BoxSizer() sizer_arrow_mid.Add(btn_left, 0, wx.RIGHT, 16) sizer_arrow_mid.Add(btn_right, 0, wx.LEFT, 16) # 生成帶標(biāo)簽的垂直布局管理器 sizer_arrow = wx.StaticBoxSizer(wx.StaticBox(panel, -1, '方向鍵'), wx.VERTICAL) sizer_arrow.Add(btn_up, 0, wx.ALIGN_CENTER|wx.ALL, 0) # 裝入上按鈕 sizer_arrow.Add(sizer_arrow_mid, 0, wx.TOP|wx.BOTTOM, 1) # 裝入左右按鈕 sizer_arrow.Add(btn_down, 0, wx.ALIGN_CENTER|wx.ALL, 0) # 裝入下按鈕 # 生成垂直布局管理器 sizer_right = wx.BoxSizer(wx.VERTICAL) sizer_right.Add(btn_capture, 0, wx.ALL, 20) # 裝入拍照按鈕 sizer_right.Add(sizer_arrow, 0, wx.ALIGN_CENTER|wx.ALL, 0) # 裝入方向鍵 sizer_right.Add(tc, 1, wx.ALL, 10) # 裝入多行文本控件 # 生成水平布局管理器 sizer_max = wx.BoxSizer() sizer_max.Add(view, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.BOTTOM, 5) # 裝入左側(cè)的預(yù)覽面板 sizer_max.Add(sizer_right, 0, wx.EXPAND|wx.ALL, 0) # 裝入右側(cè)的操作區(qū) # 為容器面板指定布局管理器,并調(diào)用布局方法完成界面布局 panel.SetSizer(sizer_max) panel.Layout()
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片7

3.2. 柵格布局

顧名思義,柵格布局就是將布局空間劃分成網(wǎng)格,將控件放置到不同的網(wǎng)格內(nèi)。柵格布局比較簡單,用起來非常方便。柵格布局布局管理器也有很多種,GridBagSizer是最常用的一種。下面是一個(gè)使用GridBagSizer實(shí)現(xiàn)柵格布局的例子。

import wx
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('柵格布局') self.SetIcon(wx.Icon('res/wx.ico')) self.SetSize((800, 440)) # 設(shè)置窗口大小 self._init_ui() # 初始化界面 self.Center() # 窗口在屏幕上居中 def _init_ui(self): '''初始化界面''' panel = wx.Panel(self, -1) # 創(chuàng)建容器面板 sizer = wx.GridBagSizer(10, 10)# 每個(gè)控件之間橫縱間隔10像素 st = wx.StaticText(panel, -1, '用戶名') sizer.Add(st, (0, 0), flag=wx.TOP | wx.ALIGN_RIGHT, border=20) # 在第00列,距離上邊緣20像素,右對齊
userName = wx.TextCtrl(panel, -1) sizer.Add(userName, (0, 1), (1, 3), flag=wx.EXPAND | wx.TOP, border=20) # 在第01列,跨3列,距離上邊緣20像素
sb = wx.StaticBitmap(panel, -1, wx.Bitmap('res/python.jpg')) sizer.Add(sb, (0, 5), (7, 1), flag=wx.TOP | wx.RIGHT, border=20) # 在第04列,跨7行,距離上右邊緣20像素
st = wx.StaticText(panel, -1, '密碼') sizer.Add(st, (1, 0), flag=wx.ALIGN_RIGHT) # 在第10列,右對齊
password = wx.TextCtrl(panel, -1, style=wx.TE_PASSWORD) sizer.Add(password, (1, 1), (1, 3), flag=wx.EXPAND) # 在第11列,跨3
st = wx.StaticText(panel, -1, '學(xué)歷') sizer.Add(st, (2, 0), flag=wx.ALIGN_RIGHT) # 在第20列,右對齊
level1 = wx.RadioButton(panel, -1, '???) sizer.Add(level1, (2, 1)) # 在第21
level2 = wx.RadioButton(panel, -1, '本科') sizer.Add(level2, (2, 2)) # 在第21
level3 = wx.RadioButton(panel, -1, '研究生及以上') sizer.Add(level3, (2, 3)) # 在第21
st = wx.StaticText(panel, -1, '職業(yè)') sizer.Add(st, (3, 0), flag=wx.ALIGN_RIGHT) # 在第30列,右對齊
professional = wx.Choice(panel, -1, choices=['學(xué)生', '程序員', '軟件工程師', '系統(tǒng)架構(gòu)師']) professional.SetSelection(0) sizer.Add(professional, (3, 1), (1, 3), flag=wx.EXPAND) # 在第31列,跨3
# 語言技能 st = wx.StaticText(panel, -1, '語言技能') sizer.Add(st, (4, 0), flag=wx.ALIGN_RIGHT | wx.LEFT, border=20) # 在第40列,距離左邊緣20像素,右對齊
choices = ['C', 'C++', 'Java', 'Python', 'Lua', 'JavaScript', 'TypeScript', 'Go', 'Rust'] language = wx.ListBox(panel, -1, choices=choices, style=wx.LB_EXTENDED) sizer.Add(language, (4, 1), (1, 3), flag=wx.EXPAND) # 在第41列,跨3
isJoin = wx.CheckBox(panel, -1, '已加入QQ群', style=wx.ALIGN_RIGHT) sizer.Add(isJoin, (5, 0), (1, 4), flag=wx.ALIGN_CENTER) # 在第50列,跨4列, 居中
btn = wx.Button(panel, -1, '提交') sizer.Add(btn, (6, 0), (1, 4), flag=wx.ALIGN_CENTER | wx.BOTTOM, border=20) # 在第60列,跨4列, 居中
sizer.AddGrowableRow(4) # 設(shè)置第4行可增長 sizer.AddGrowableCol(3) # 設(shè)置第3列可增長 panel.SetSizer(sizer) panel.Layout()
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片8

文章圖片9

事件驅(qū)動(dòng)

一個(gè)桌面程序不單是控件的羅列,更重要的是對外部的刺激——包括用戶的操作做出反應(yīng)。如果把窗體和控件比作是桌面程序的軀體,那么響應(yīng)外部刺激就是它的靈魂。wxPython的靈魂是事件驅(qū)動(dòng)機(jī)制:當(dāng)某事件發(fā)生時(shí),程序就會(huì)自動(dòng)執(zhí)行預(yù)先設(shè)定的動(dòng)作。

4.1 事件

所謂事件,就是我們的程序在運(yùn)行中發(fā)生的事兒。事件可以是低級(jí)的用戶動(dòng)作,如鼠標(biāo)移動(dòng)或按鍵按下,也可以是高級(jí)的用戶動(dòng)作(定義在wxPython的窗口部件中的),如單擊按鈕或菜單選擇。事件可以產(chǎn)生自系統(tǒng),如關(guān)機(jī),,也可以由用戶自定義事件。

除了用戶自定義事件,在wxPython中我習(xí)慣把事件分為4類:

  • 鼠標(biāo)事件:鼠標(biāo)左右中鍵和滾輪動(dòng)作,以及鼠標(biāo)移動(dòng)等事件

  • 鍵盤事件:用戶敲擊鍵盤產(chǎn)生的事件

  • 控件事件:發(fā)生在控件上的事件,比如按鈕被按下、輸入框內(nèi)容改變等

  • 系統(tǒng)事件:關(guān)閉窗口、改變窗口大小、重繪、定時(shí)器等事件

事實(shí)上,這個(gè)分類方法不夠嚴(yán)謹(jǐn)。比如,wx.Frame作為一個(gè)控件,關(guān)閉和改變大小也是控件事件,不過這一類事件通常都由系統(tǒng)綁定了行為?;诖耍梢灾匦露x所謂的控件事件,是指發(fā)生在控件上的、系統(tǒng)并未預(yù)定義行為的事件。

常用的鼠標(biāo)事件包括:

  • wx.EVT_LEFT_DOWN - 左鍵按下

  • wx.EVT_LEFT_UP - 左鍵彈起

  • wx.EVT_LEFT_DCLICK - 左鍵雙擊

  • wx.EVT_RIGHT_DOWN - 右鍵按下

  • wx.EVT_RIGHT_UP - 右鍵彈起

  • wx.EVT_RIGHT_DCLICK - 右鍵雙擊

  • wx.EVT_MOTION - 鼠標(biāo)移動(dòng)

  • wx.EVT_MOUSEWHEEL - 滾輪滾動(dòng)

  • wx.EVT_MOUSE_EVENTS - 所有的鼠標(biāo)事件

常用的鍵盤事件有:

  • wx.EVT_KEY_DOWN - 按鍵按下

  • wx.EVT_KEY_UP - 按鍵彈起

常用的系統(tǒng)事件包括:

  • wx.EVT_CLOSE - 關(guān)閉

  • wx.EVT_SIZE - 改變大小

  • wx.EVT_TIMER - 定時(shí)器事件

  • wx.EVT_PAINT - 重繪

  • wx.EVT_ERASE_BACKGROUND -背景擦除

常用的控件事件包括:

  • wx.EVT_BUTTON - 點(diǎn)擊按鈕

  • wx.EVT_CHOICE - 下拉框改變選擇

  • wx.EVT_TEXT - 輸入框內(nèi)容改變

  • wx.EVT_TEXT_ENTER - 輸入框回車

  • wx.EVT_RADIOBOX - 單選框改變選擇

  • wx.EVT_CHECKBOX - 點(diǎn)擊復(fù)選框

4.2 事件綁定

事件驅(qū)動(dòng)機(jī)制有三個(gè)要素:事件、事件函數(shù)和事件綁定。比如,當(dāng)一個(gè)按鈕被點(diǎn)擊時(shí),就會(huì)觸發(fā)按鈕點(diǎn)擊事件,該事件如果綁定了事件函數(shù),事件函數(shù)就會(huì)被調(diào)用。所有的事件函數(shù)都以事件對象為參數(shù),事件對象提供了事件的詳細(xì)信息,比如鍵盤按下事件的事件對象就包含了被按下的鍵的信息。

下面這個(gè)例子演示了如何定義事件函數(shù),以及綁定事件和事件函數(shù)之間的關(guān)聯(lián)關(guān)系。

import wx
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('事件和事件函數(shù)的綁定') self.SetIcon(wx.Icon('res/wx.ico')) self.SetBackgroundColour((224, 224, 224)) # 設(shè)置窗口背景色 self.SetSize((520, 220)) self._init_ui() self.Center() def _init_ui(self): '''初始化界面''' wx.StaticText(self, -1, '第一行輸入框:', pos=(40, 50), size=(100, -1), style=wx.ALIGN_RIGHT) wx.StaticText(self, -1, '第二行輸入框:', pos=(40, 80), size=(100, -1), style=wx.ALIGN_RIGHT) self.tip = wx.StaticText(self, -1, u'', pos=(145, 110), size=(150, -1), style=wx.ST_NO_AUTORESIZE) self.tc1 = wx.TextCtrl(self, -1, '', pos=(145, 50), size=(150, -1), name='TC01', style=wx.TE_CENTER) self.tc2 = wx.TextCtrl(self, -1, '', pos=(145, 80), size=(150, -1), name='TC02', style=wx.TE_PASSWORD|wx.ALIGN_RIGHT) btn_mea = wx.Button(self, -1, '鼠標(biāo)左鍵事件', pos=(350, 50), size=(100, 25)) btn_meb = wx.Button(self, -1, '鼠標(biāo)所有事件', pos=(350, 80), size=(100, 25)) btn_close = wx.Button(self, -1, '關(guān)閉窗口', pos=(350, 110), size=(100, 25)) self.tc1.Bind(wx.EVT_TEXT, self.on_text) # 綁定文本內(nèi)容改變事件 self.tc2.Bind(wx.EVT_TEXT, self.on_text) # 綁定文本內(nèi)容改變事件 btn_close.Bind(wx.EVT_BUTTON, self.on_close, btn_close) # 綁定按鍵事件 btn_close.Bind(wx.EVT_MOUSEWHEEL, self.on_wheel) # 綁定鼠標(biāo)滾輪事件 btn_mea.Bind(wx.EVT_LEFT_DOWN, self.on_left_down) # 綁定鼠標(biāo)左鍵按下 btn_mea.Bind(wx.EVT_LEFT_UP, self.on_left_up) # 綁定鼠標(biāo)左鍵彈起 btn_meb.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse) # 綁定所有鼠標(biāo)事件 self.Bind(wx.EVT_CLOSE, self.on_close) # 綁定窗口關(guān)閉事件 self.Bind(wx.EVT_SIZE, self.on_size) # 綁定改變窗口大小事件 self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) # 綁定鍵盤事件 def on_text(self, evt): '''輸入框事件函數(shù)''' obj = evt.GetEventObject() objName = obj.GetName() text = evt.GetString() if objName == 'TC01': self.tc2.SetValue(text) elif objName == 'TC02': self.tc1.SetValue(text) def on_size(self, evt): '''改變窗口大小事件函數(shù)''' print('你想改變窗口,但是事件被Skip了,所以沒有任何改變') evt.Skip() # 注釋掉此行(事件繼續(xù)傳遞),窗口大小才會(huì)被改變 def on_close(self, evt): '''關(guān)閉窗口事件函數(shù)''' dlg = wx.MessageDialog(None, '確定要關(guān)閉本窗口?', '操作提示', wx.YES_NO | wx.ICON_QUESTION) if(dlg.ShowModal() == wx.ID_YES): self.Destroy() def on_left_down(self, evt): '''左鍵按下事件函數(shù)''' self.tip.SetLabel('左鍵按下') def on_left_up(self, evt): '''左鍵彈起事件函數(shù)''' self.tip.SetLabel('左鍵彈起') def on_wheel(self, evt): '''鼠標(biāo)滾輪事件函數(shù)''' vector = evt.GetWheelRotation() self.tip.SetLabel(str(vector)) def on_mouse(self, evt): '''鼠標(biāo)事件函數(shù)''' self.tip.SetLabel(str(evt.EventType)) def on_key_down(self, evt): '''鍵盤事件函數(shù)''' key = evt.GetKeyCode() self.tip.SetLabel(str(key))
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片10

兩個(gè)輸入框,一個(gè)明文居中,一個(gè)密寫右齊,但內(nèi)容始終保持同步。輸入焦點(diǎn)不在輸入框的時(shí)候,敲擊鍵盤,界面顯示對應(yīng)的鍵值。最上面的按鈕響應(yīng)鼠標(biāo)左鍵的按下和彈起事件,中間的按鈕響應(yīng)所有的鼠標(biāo)事件,下面的按鈕響應(yīng)滾輪事件和按鈕按下的事件。另外,程序還綁定了窗口關(guān)閉事件,重新定義了關(guān)閉函數(shù),增加了確認(rèn)選擇。

文章圖片11

程序框架

5.1 菜單欄、工具欄和狀態(tài)欄

通常,一個(gè)完整的窗口程序一般都有菜單欄、工具欄和狀態(tài)欄。下面的代碼演示了如何創(chuàng)建菜單欄、工具欄和狀態(tài)欄,順便演示了類的靜態(tài)屬性的定義和用法。不過,說實(shí)話,wx的工具欄有點(diǎn)丑,幸好,wx還有一個(gè) AUI 的工具欄比較漂亮,我會(huì)在后面的例子里演示它的用法。

import wx
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' id_open = wx.NewIdRef() id_save = wx.NewIdRef() id_quit = wx.NewIdRef() id_help = wx.NewIdRef() id_about = wx.NewIdRef() def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('菜單、工具欄、狀態(tài)欄') self.SetIcon(wx.Icon('res/wx.ico')) self.SetBackgroundColour((224, 224, 224)) # 設(shè)置窗口背景色 self.SetSize((360, 180)) self._create_menubar() # 菜單欄 self._create_toolbar() # 工具欄 self._create_statusbar() # 狀態(tài)欄 self.Center() def _create_menubar(self): '''創(chuàng)建菜單欄''' self.mb = wx.MenuBar() # 文件菜單 m = wx.Menu() m.Append(self.id_open, '打開文件') m.Append(self.id_save, '保存文件') m.AppendSeparator() m.Append(self.id_quit, '退出系統(tǒng)') self.mb.Append(m, '文件') self.Bind(wx.EVT_MENU, self.on_open, id=self.id_open) self.Bind(wx.EVT_MENU, self.on_save, id=self.id_save) self.Bind(wx.EVT_MENU, self.on_quit, id=self.id_quit) # 幫助菜單 m = wx.Menu() m.Append(self.id_help, '幫助主題') m.Append(self.id_about, '關(guān)于...') self.mb.Append(m, '幫助') self.Bind(wx.EVT_MENU, self.on_help,id=self.id_help) self.Bind(wx.EVT_MENU, self.on_about,id=self.id_about) self.SetMenuBar(self.mb) def _create_toolbar(self): '''創(chuàng)建工具欄''' bmp_open = wx.Bitmap('res/open_mso.png', wx.BITMAP_TYPE_ANY) # 請自備按鈕圖片 bmp_save = wx.Bitmap('res/save_mso.png', wx.BITMAP_TYPE_ANY) # 請自備按鈕圖片 bmp_help = wx.Bitmap('res/help_mso.png', wx.BITMAP_TYPE_ANY) # 請自備按鈕圖片 bmp_about = wx.Bitmap('res/info_mso.png', wx.BITMAP_TYPE_ANY) # 請自備按鈕圖片 self.tb = wx.ToolBar(self) self.tb.SetToolBitmapSize((16,16)) self.tb.AddTool(self.id_open, '打開文件', bmp_open, shortHelp='打開', kind=wx.ITEM_NORMAL) self.tb.AddTool(self.id_save, '保存文件', bmp_save, shortHelp='保存', kind=wx.ITEM_NORMAL) self.tb.AddSeparator() self.tb.AddTool(self.id_help, '幫助', bmp_help, shortHelp='幫助', kind=wx.ITEM_NORMAL) self.tb.AddTool(self.id_about, '關(guān)于', bmp_about, shortHelp='關(guān)于', kind=wx.ITEM_NORMAL) self.tb.Realize() def _create_statusbar(self): '''創(chuàng)建狀態(tài)欄''' self.sb = self.CreateStatusBar() self.sb.SetFieldsCount(3) self.sb.SetStatusWidths([-2, -1, -1]) self.sb.SetStatusStyles([wx.SB_RAISED, wx.SB_RAISED, wx.SB_RAISED]) self.sb.SetStatusText('狀態(tài)信息0', 0) self.sb.SetStatusText('', 1) self.sb.SetStatusText('狀態(tài)信息2', 2) def on_open(self, evt): '''打開文件''' self.sb.SetStatusText(u'打開文件', 1) def on_save(self, evt): '''保存文件''' self.sb.SetStatusText(u'保存文件', 1) def on_quit(self, evt): '''退出系統(tǒng)''' self.sb.SetStatusText(u'退出系統(tǒng)', 1) self.Destroy() def on_help(self, evt): '''幫助''' self.sb.SetStatusText(u'幫助', 1) def on_about(self, evt): '''關(guān)于''' self.sb.SetStatusText(u'關(guān)于', 1)
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼里面用到了4個(gè)16x16的工具按鈕,請自備4個(gè)圖片文件,保存路徑請查看代碼中的注釋。代碼運(yùn)行界面如下圖所示。

文章圖片12

5.2 Aui框架

Advanced User Interface,簡稱AUI,是wxPython的子模塊,使用AUI可以方便地開發(fā)出美觀、易用的用戶界面。從2.8.9.2版本之后,wxPython增加了一個(gè)高級(jí)通用部件庫Advanced Generic Widgets,簡稱AGW庫, AGW庫也提供了AUI模塊 wx.lib.agw.aui,而 wx.aui也依然保留著。相比較而言,我更喜歡使用wx.lib.agw的AUI框架。

使用AUI框架可以概括為以下四步:

  1. 創(chuàng)建一個(gè)布局管理器:mgr = aui.AuiManager()

  2. 告訴主窗口由mgr來管理界面:mgr.SetManagedWindow()

  3. 添加界面上的各個(gè)區(qū)域:mgr.AddPane()

  4. 更新界面顯示:mgr.Update()

下面的代碼演示了如何使用AUI布局管理器創(chuàng)建和管理窗口界面。

import wximport wx.lib.agw.aui as aui
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' id_open = wx.NewIdRef() id_save = wx.NewIdRef() id_quit = wx.NewIdRef() id_help = wx.NewIdRef() id_about = wx.NewIdRef() def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('菜單、工具欄、狀態(tài)欄') self.SetIcon(wx.Icon('res/wx.ico')) self.SetBackgroundColour((224, 224, 224)) # 設(shè)置窗口背景色 self.SetSize((640, 480)) self._init_ui() self.Center() def _init_ui(self): '''初始化界面''' self.tb1 = self._create_toolbar() self.tb2 = self._create_toolbar() self.tbv = self._create_toolbar('V') p_left = wx.Panel(self, -1) p_center0 = wx.Panel(self, -1) p_center1 = wx.Panel(self, -1) p_bottom = wx.Panel(self, -1) btn = wx.Button(p_left, -1, '切換', pos=(30,200), size=(100, -1)) btn.Bind(wx.EVT_BUTTON, self.on_switch) text0 = wx.StaticText(p_center0, -1, '我是第1頁', pos=(40, 100), size=(200, -1), style=wx.ALIGN_LEFT) text1 = wx.StaticText(p_center1, -1, '我是第2頁', pos=(40, 100), size=(200, -1), style=wx.ALIGN_LEFT) self._mgr = aui.AuiManager() self._mgr.SetManagedWindow(self) self._mgr.AddPane(self.tb1, aui.AuiPaneInfo().Name('ToolBar1').Caption('工具條').ToolbarPane().Top().Row(0).Position(0).Floatable(False) ) self._mgr.AddPane(self.tb2, aui.AuiPaneInfo().Name('ToolBar2').Caption('工具條').ToolbarPane().Top().Row(0).Position(1).Floatable(True) ) self._mgr.AddPane(self.tbv, aui.AuiPaneInfo().Name('ToolBarV').Caption('工具條').ToolbarPane().Right().Floatable(True) ) self._mgr.AddPane(p_left, aui.AuiPaneInfo().Name('LeftPanel').Left().Layer(1).MinSize((200,-1)).Caption('操作區(qū)').MinimizeButton(True).MaximizeButton(True).CloseButton(True) ) self._mgr.AddPane(p_center0, aui.AuiPaneInfo().Name('CenterPanel0').CenterPane().Show() ) self._mgr.AddPane(p_center1, aui.AuiPaneInfo().Name('CenterPanel1').CenterPane().Hide() ) self._mgr.AddPane(p_bottom, aui.AuiPaneInfo().Name('BottomPanel').Bottom().MinSize((-1,100)).Caption('消息區(qū)').CaptionVisible(False).Resizable(True) ) self._mgr.Update() def _create_toolbar(self, d='H'): '''創(chuàng)建工具欄''' bmp_open = wx.Bitmap('res/open_mso.png', wx.BITMAP_TYPE_ANY) bmp_save = wx.Bitmap('res/save_mso.png', wx.BITMAP_TYPE_ANY) bmp_help = wx.Bitmap('res/help_mso.png', wx.BITMAP_TYPE_ANY) bmp_about = wx.Bitmap('res/info_mso.png', wx.BITMAP_TYPE_ANY) if d.upper() in ['V', 'VERTICAL']: tb = aui.AuiToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, agwStyle=aui.AUI_TB_TEXT|aui.AUI_TB_VERTICAL) else: tb = aui.AuiToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, agwStyle=aui.AUI_TB_TEXT) tb.SetToolBitmapSize(wx.Size(16, 16)) tb.AddSimpleTool(self.id_open, '打開', bmp_open, '打開文件') tb.AddSimpleTool(self.id_save, '保存', bmp_save, '保存文件') tb.AddSeparator() tb.AddSimpleTool(self.id_help, '幫助', bmp_help, '幫助') tb.AddSimpleTool(self.id_about, '關(guān)于', bmp_about, '關(guān)于') tb.Realize() return tb def on_switch(self, evt): '''切換信息顯示窗口''' p0 = self._mgr.GetPane('CenterPanel0') p1 = self._mgr.GetPane('CenterPanel1') p0.Show(not p0.IsShown()) p1.Show(not p1.IsShown()) self._mgr.Update()
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片13


文章圖片14

示例和技巧


6.1. 相冊

前文的例子中已經(jīng)展示了wx.StaticBitmap控件作為圖像容器的例子,下面的例子用它制作了一個(gè)相冊,點(diǎn)擊前翻后翻按鈕可在多張照片之間循環(huán)切換。

import wx
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('相冊') self.SetIcon(wx.Icon('res/wx.ico')) self.SetBackgroundColour((224, 224, 224)) # 設(shè)置窗口背景色 self.SetSize((980, 680)) self._init_ui() self.Center() def _init_ui(self): '''初始化界面''' self.curr = 0 self.photos = ('res/DSC03363.jpg', 'res/DSC03394.jpg', 'res/DSC03402.jpg') bmp = wx.Bitmap(self.photos[self.curr]) self.album = wx.StaticBitmap(self, -1, bmp, pos=(280, 10)) btn_1 = wx.Button(self, -1, '<', size=(80, 30), name='prev') btn_2 = wx.Button(self, -1, '>', size=(80, 30), name='next') btn_1.Bind(wx.EVT_BUTTON, self.on_btn) btn_2.Bind(wx.EVT_BUTTON, self.on_btn) sizer_btn = wx.BoxSizer() sizer_btn.Add(btn_1, 0, wx.RIGHT, 20) sizer_btn.Add(btn_2, 0, wx.LEFT, 20) sizer_max = wx.BoxSizer(wx.VERTICAL) sizer_max.Add(self.album, 1, wx.EXPAND | wx.ALL, 10) sizer_max.Add(sizer_btn, 0, wx. ALIGN_CENTER | wx.BOTTOM, 20) self.SetSizer(sizer_max) self.Layout() def on_btn(self, evt): '''響應(yīng)按鍵''' name = evt.GetEventObject().GetName() if name == '<': self.curr = (self.curr-1)%len(self.photos) else: self.curr = (self.curr+1)%len(self.photos) self.album.SetBitmap(wx.Bitmap(self.photos[self.curr]))
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片15

6.2. 會(huì)彈琴的計(jì)算器

幾乎所有的GUI課程都會(huì)用計(jì)算器作為例子,wxPython怎能缺席呢?下面這個(gè)計(jì)算器除了常規(guī)的計(jì)算外,按下每個(gè)鍵都會(huì)發(fā)出不同的音調(diào),粗通樂理就可以彈奏出樂曲。此外,代碼中使用了wx.lib控件庫的按鍵,略帶3D風(fēng)格。

import wximport wx.lib.buttons as wxbtnimport winsound
class MainFrame(wx.Frame): '''桌面程序主窗口類''' def __init__(self): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent=None, style=wx.CAPTION|wx.SYSTEM_MENU|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SIMPLE_BORDER) self.SetTitle('會(huì)彈琴的計(jì)算器') self.SetIcon(wx.Icon('res/wx.ico', wx.BITMAP_TYPE_ICO)) self.SetBackgroundColour((217, 228, 241)) self.SetSize((287, 283)) self._init_ui() self.Center() def _init_ui(self): '''初始化界面''' # 定義按鍵排列順序和名稱 keys = [ ['(', ')', 'Back', 'Clear'], ['7', '8', '9', '/'], ['4', '5', '6', '*'], ['1', '2', '3', '-'], ['0', '.', '=', '+'] ] # 指定每個(gè)按鍵聲音的頻率,523赫茲就是C調(diào)中音 self.keySound = { '(':392, ')': 440, '0':494, '1':523, '2':587, '3':659, '4':698, '5':784, '6':880, '7':988, '8':1047, '9':1175, '.':1318, '+':523, '-':587, '*':659, '/':698, 'Clear':784, 'Back':880, '=':2000 } # 用輸入框控件作為計(jì)算器屏幕,設(shè)置為只讀(wx.TE_READONLY)和右齊(wx.ALIGN_RIGHT) self.screen = wx.TextCtrl(self, -1, '', pos=(10,10), size=(252,45), style=wx.TE_READONLY|wx.ALIGN_RIGHT) self.screen.SetFont(wx.Font(20, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, '微軟雅黑')) # 設(shè)置字體字號(hào) self.screen.SetBackgroundColour((0, 0, 0)) # 設(shè)置屏幕背景色 self.screen.SetForegroundColour((0, 255, 0)) # 設(shè)置屏幕前景色 # 按鍵布局參數(shù) btn_size = (60, 30) # 定義按鍵的尺寸,便于統(tǒng)一修改 x0, y0 = (10, 65) # 定義按鍵區(qū)域的相對位置 dx, dy = (64, 34) # 定義水平步長和垂直步長 # 生成所有按鍵 for i in range(len(keys)): for j in range(len(keys[i])): key = keys[i][j] btn = wxbtn.GenButton(self, -1, key, pos=(x0+j*dx, y0+i*dy), size=btn_size, name=key) if key in ['0','1','2','3','4','5','6','7','8','9','.']: btn.SetBezelWidth(1) # 設(shè)置3D效果 btn.SetBackgroundColour(wx.Colour(217, 228, 241)) # 定義按鍵的背景色 elif key in ['(',')','Back','Clear']: btn.SetBezelWidth(2) btn.SetBackgroundColour(wx.Colour(217, 220, 235)) btn.SetForegroundColour(wx.Colour(224, 60, 60)) elif key in ['+','-','*','/']: btn.SetBezelWidth(2) btn.SetBackgroundColour(wx.Colour(246, 225, 208)) btn.SetForegroundColour(wx.Colour(60, 60, 224)) else: btn.SetBezelWidth(2) btn.SetBackgroundColour(wx.Colour(245, 227, 129)) btn.SetForegroundColour(wx.Colour(60, 60, 224)) btn.SetToolTip(u'顯示計(jì)算結(jié)果') self.Bind(wx.EVT_BUTTON, self.on_button) # 將按鈕事件綁定在所有按鈕上 def on_button(self, evt): '''響應(yīng)鼠標(biāo)左鍵按下''' obj = evt.GetEventObject() # 獲取事件對象(哪個(gè)按鈕被按) key = obj.GetName() # 獲取事件對象的名字 self.PlayKeySound(key) # 播放按鍵對應(yīng)頻率的聲音 if self.screen.GetValue == 'Error': self.screen.SetValue('') if key == 'Clear': # 按下了清除鍵,清空屏幕 self.screen.SetValue('') elif key == 'Back': # 按下了回退鍵,去掉最后一個(gè)輸入字符 content = self.screen.GetValue() if content: self.screen.SetValue(content[:-1]) elif key == '=': # 按下了等號(hào)鍵,則計(jì)算 try: result = str(eval(self.screen.GetValue())) except: result = 'Error' self.screen.SetValue(result) else: # 按下了其他鍵,追加到顯示屏上 self.screen.AppendText(key) def PlayKeySound(self, key, Dur=100): '''播放按鍵聲音''' winsound.Beep(self.keySound[key], Dur)
if __name__ == '__main__': app = wx.App() frame = MainFrame() frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片16

6.3. 定時(shí)器和線程

在一個(gè)桌面程序中,GUI線程是主線程,其他線程若要更新顯示內(nèi)容,Tkinter使用的是類型對象,PyQt使用的信號(hào)和槽機(jī)制,wxPython則相對原始:它允許子線程更新GUI,但需要借助于wx.CallAfter()函數(shù)。

這個(gè)例子里面設(shè)計(jì)了一個(gè)數(shù)字式鐘表,一個(gè)秒表,秒表顯示精度十分之一毫秒。從代碼設(shè)計(jì)上來說沒有任何難度,實(shí)現(xiàn)的方法有很多種,可想要達(dá)到一個(gè)較好的顯示效果,卻不是一件容易的事情。請注意體會(huì) wx.CallAfter() 的使用條件。

import wximport timeimport threading
class MainFrame(wx.Frame): '''桌面程序主窗口類''' def __init__(self): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent=None, style=wx.CAPTION|wx.SYSTEM_MENU|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SIMPLE_BORDER) self.SetTitle('定時(shí)器和線程') self.SetIcon(wx.Icon('res/wx.ico', wx.BITMAP_TYPE_ICO)) self.SetBackgroundColour((224, 224, 224)) self.SetSize((320, 300)) self._init_ui() self.Center() def _init_ui(self): '''初始化界面''' font = wx.Font(30, wx.DECORATIVE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Monaco') self.clock = wx.StaticText(self, -1, '08:00:00', pos=(50,50), size=(200,50), style=wx.TE_CENTER|wx.SUNKEN_BORDER) self.clock.SetForegroundColour(wx.Colour(0, 224, 32)) self.clock.SetBackgroundColour(wx.Colour(0, 0, 0)) self.clock.SetFont(font) self.stopwatch = wx.StaticText(self, -1, '0:00:00.00', pos=(50,150), size=(200,50), style=wx.TE_CENTER|wx.SUNKEN_BORDER) self.stopwatch.SetForegroundColour(wx.Colour(0, 224, 32)) self.stopwatch.SetBackgroundColour(wx.Colour(0, 0, 0)) self.stopwatch.SetFont(font) self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.on_timer, self.timer) self.timer.Start(50) self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) self.sec_last = None self.is_start = False self.t_start = None thread_sw = threading.Thread(target=self.StopWatchThread) thread_sw.setDaemon(True) thread_sw.start() def on_timer(self, evt): '''定時(shí)器函數(shù)''' t = time.localtime() if t.tm_sec != self.sec_last: self.clock.SetLabel('%02d:%02d:%02d'%(t.tm_hour, t.tm_min, t.tm_sec)) self.sec_last = t.tm_sec def on_key_down(self, evt): '''鍵盤事件函數(shù)''' if evt.GetKeyCode() == wx.WXK_SPACE: self.is_start = not self.is_start self.t_start= time.time() elif evt.GetKeyCode() == wx.WXK_ESCAPE: self.is_start = False self.stopwatch.SetLabel('0:00:00.00') def StopWatchThread(self): '''線程函數(shù)''' while True: if self.is_start: t = time.time() - self.t_start ti = int(t) wx.CallAfter(self.stopwatch.SetLabel, '%d:%02d:%02d.%.02d'%(ti//3600, ti//60, ti%60, int((t-ti)*100))) time.sleep(0.02)
if __name__ == '__main__': app = wx.App() frame = MainFrame() frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。界面上方的時(shí)鐘一直再跑,下方的秒表則是按鍵啟動(dòng)或停止。

文章圖片17

6.4. DC繪圖

DC 是 Device Context 的縮寫,字面意思是設(shè)備上下文——我一直不能正確理解DC這個(gè)中文名字,也找不到更合適的說法,所以,我堅(jiān)持使用DC而不是設(shè)備上下文。DC可以在屏幕上繪制點(diǎn)線面,當(dāng)然也可以繪制文本和圖像。事實(shí)上,在底層所有控件都是以位圖形式繪制在屏幕上的,這意味著,我們一旦掌握了DC這個(gè)工具,就可以自己創(chuàng)造我們想要的控件了

DC有很多種,PaintDC,ClientDC,MemoryDC等。通常,我們可以使用 ClientDC 和 MemoryDC,PaintDC 是發(fā)生重繪事件(wx.EVT_PAINT)時(shí)系統(tǒng)使用的。使用 ClientDC 繪圖時(shí),需要記錄繪制的每一步工作,不然,系統(tǒng)重繪時(shí)會(huì)令我們前功盡棄——這是使用DC最容易犯的錯(cuò)誤。

import wx
class MainFrame(wx.Frame): '''桌面程序主窗口類''' def __init__(self): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent=None, style=wx.CAPTION|wx.SYSTEM_MENU|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SIMPLE_BORDER) self.SetTitle('使用DC繪圖') self.SetIcon(wx.Icon('res/wx.ico', wx.BITMAP_TYPE_ICO)) self.SetBackgroundColour((224, 224, 224)) self.SetSize((800, 480)) self._init_ui() self.Center() def _init_ui(self): '''初始化界面''' self.palette = wx.Panel(self, -1, style=wx.SUNKEN_BORDER) self.palette.SetBackgroundColour(wx.Colour(0, 0, 0)) btn_base = wx.Button(self, -1, '文字和圖片', size=(100, -1)) sizer_max = wx.BoxSizer() sizer_max.Add(self.palette, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.BOTTOM, 5) sizer_max.Add(btn_base, 0, wx.ALL, 20) self.SetAutoLayout(True) self.SetSizer(sizer_max) self.Layout() btn_base.Bind(wx.EVT_BUTTON, self.on_base) self.palette.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse) self.palette.Bind(wx.EVT_PAINT, self.on_paint) self.xy = None self.lines = list() self.img = wx.Bitmap('res/forever.png', wx.BITMAP_TYPE_ANY) self.update_palette() def on_mouse(self, evt): '''移動(dòng)鼠標(biāo)畫線''' if evt.EventType == 10030: #左鍵按下 self.xy = (evt.x, evt.y) elif evt.EventType == 10031: #左鍵彈起 self.xy = None elif evt.EventType == 10036: #鼠標(biāo)移動(dòng) if self.xy: dc = wx.ClientDC(self.palette) dc.SetPen(wx.Pen(wx.Colour(0,224,0), 2)) dc.DrawLine(self.xy[0], self.xy[1], evt.x, evt.y) self.lines.append((self.xy[0], self.xy[1], evt.x, evt.y)) self.xy = (evt.x, evt.y) def on_base(self, evt): '''DC基本方法演示''' img = wx.Bitmap('res/forever.png', wx.BITMAP_TYPE_ANY) w, h = self.palette.GetSize() dc = wx.ClientDC(self.palette) dc.SetPen(wx.Pen(wx.Colour(224,0,0), 1)) dc.SetBrush(wx.Brush(wx.Colour(0,80,80) )) dc.DrawRectangle(10,10,w-22,h-22) dc.DrawLine(10,h/2,w-12,h/2) dc.DrawBitmap(img, 10, 10) dc.SetTextForeground(wx.Colour(224,224,224)) dc.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Comic Sans MS')) dc.DrawText('霜重閑愁起', 100, 360) dc.DrawRotatedText('春深風(fēng)也疾', 400, 360, 30) def on_paint(self, evt): '''響應(yīng)重繪事件''' dc = wx.PaintDC(self.palette) self.paint(dc) def update_palette(self): '''刷新畫板''' dc = wx.ClientDC(self.palette) self.paint(dc) def paint(self, dc): '''繪圖''' dc.Clear() dc.SetPen(wx.Pen(wx.Colour(0,224,0), 2)) for line in self.lines: dc.DrawLine(line[0],line[1],line[2],line[3])
if __name__ == '__main__': app = wx.App() frame = MainFrame() frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片18

6.5. 內(nèi)嵌瀏覽器

wx.html2是wxPython擴(kuò)展模塊中封裝得最干凈漂亮的模塊之一,它被設(shè)計(jì)為允許為每個(gè)端口創(chuàng)建多個(gè)后端,盡管目前只有一個(gè)可用。它與wx.html.HtmlWindow的不同之處在于,每個(gè)后端實(shí)際上都是一個(gè)完整的渲染引擎,MSW上是Trident, macOS和GTK上是Webkit。wx.html2渲染web文檔,對于HTML、CSS和javascript都可以有很好的支持。

import wximport wx.html2 as webview
class MainFrame(wx.Frame): '''桌面程序主窗口類''' def __init__(self): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent=None) self.SetTitle('內(nèi)嵌瀏覽器') self.SetIcon(wx.Icon('res/wx.ico', wx.BITMAP_TYPE_ICO)) self.SetBackgroundColour((224, 224, 224)) self.SetSize((800, 480)) self.Center() wv = webview.WebView.New(self) wv.LoadURL('https://cn.bing.com')
if __name__ == '__main__': app = wx.App() frame = MainFrame() frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片19

文章圖片20

集成應(yīng)用

7.1. 集成Matplotlib

Matplotlib的后端子模塊backends幾乎支持所有的GUI庫,wxPyton當(dāng)然也不例外,backend_wxagg是專門為wxPyton生成canvas的類,只要傳一個(gè)matplotlib.Figure實(shí)例即可。剩下的就是水到渠成了。

import numpy as npimport matplotlibfrom matplotlib.backends import backend_wxagg from matplotlib.figure import Figure import wx
matplotlib.use('TkAgg')matplotlib.rcParams['font.sans-serif'] = ['FangSong']matplotlib.rcParams['axes.unicode_minus'] = False
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.SetTitle('集成Matplotlib') self.SetIcon(wx.Icon('res/wx.ico')) self.SetBackgroundColour((224, 224, 224)) # 設(shè)置窗口背景色 self.SetSize((800, 600)) self._init_ui() self.Center() def _init_ui(self): '''初始化界面''' self.fig = Figure() self.canvas = backend_wxagg.FigureCanvasWxAgg(self, -1, self.fig) btn_1 = wx.Button(self, -1, '散點(diǎn)圖', size=(80, 30)) btn_2 = wx.Button(self, -1, '等值線圖', size=(80, 30)) btn_1.Bind(wx.EVT_BUTTON, self.on_scatter) btn_2.Bind(wx.EVT_BUTTON, self.on_contour) sizer_btn = wx.BoxSizer() sizer_btn.Add(btn_1, 0, wx.RIGHT, 20) sizer_btn.Add(btn_2, 0, wx.LEFT, 20) sizer_max = wx.BoxSizer(wx.VERTICAL) sizer_max.Add(self.canvas, 1, wx.EXPAND | wx.ALL, 10) sizer_max.Add(sizer_btn, 0, wx. ALIGN_CENTER | wx.BOTTOM, 20) self.SetSizer(sizer_max) self.Layout() def on_scatter(self, evt): '''散點(diǎn)圖''' x = np.random.randn(50) # 隨機(jī)生成50個(gè)符合標(biāo)準(zhǔn)正態(tài)分布的點(diǎn)(x坐標(biāo)) y = np.random.randn(50) # 隨機(jī)生成50個(gè)符合標(biāo)準(zhǔn)正態(tài)分布的點(diǎn)(y坐標(biāo)) color = 10 * np.random.rand(50) # 隨即數(shù),用于映射顏色 area = np.square(30*np.random.rand(50)) # 隨機(jī)數(shù)表示點(diǎn)的面積 self.fig.clear() ax = self.fig.add_subplot(111) ax.scatter(x, y, c=color, s=area, cmap='hsv', marker='o', edgecolor='r', alpha=0.5) self.canvas.draw() def on_contour(self, evt): '''等值線圖''' y, x = np.mgrid[-3:3:60j, -4:4:80j] z = (1-y**5+x**5)*np.exp(-x**2-y**2) self.fig.clear() ax = self.fig.add_subplot(111) ax.set_title('有填充的等值線圖') c = ax.contourf(x, y, z, levels=8, cmap='jet') self.fig.colorbar(c, ax=ax) self.canvas.draw()
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片21

7.2. 集成OpenGL

wx.glcanvas.GLCanvas是wxPython為顯示OpenGL提供的類,顧名思義,可以將其理解為OpenGL的畫板。有了這個(gè)畫板,我們就可以使用OpenGL提供的各種工具在上面繪制各種三維模型了。下面的代碼僅是一個(gè)demo,并未構(gòu)建投影系統(tǒng)和視點(diǎn)系統(tǒng)。

import numpy as npfrom OpenGL.GL import *
import wxfrom wx import glcanvas
class MainFrame(wx.Frame): '''從wx.Frame派生主窗口類''' def __init__(self, parent): '''構(gòu)造函數(shù)''' wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE) self.canvas = glcanvas.GLCanvas(self, style=glcanvas.WX_GL_RGBA|glcanvas.WX_GL_DOUBLEBUFFER|glcanvas.WX_GL_DEPTH_SIZE) self.context = glcanvas.GLContext(self.canvas) self.csize = self.canvas.GetClientSize() self.SetTitle('集成OpenGL') self.SetIcon(wx.Icon('res/wx.ico')) self.SetBackgroundColour((224, 224, 224)) # 設(shè)置窗口背景色 self.SetSize((800, 600)) self.Center() sizer_max = wx.BoxSizer() sizer_max.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer_max) self.Layout() self.Bind(wx.EVT_SIZE, self.on_resize) self.canvas.SetCurrent(self.context) glClearColor(0,0,0,1) # 設(shè)置畫布背景色 self.draw() def on_resize(self, evt): '''窗口改變事件函數(shù)''' self.canvas.SetCurrent(self.context) self.csize = self.GetClientSize() self.draw() evt.Skip() def draw(self): '''繪制''' # 清除屏幕及深度緩存 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # --------------------------------------------------------------- glBegin(GL_LINES) # 開始繪制線段(坐標(biāo)軸) # 以紅色繪制x軸 glColor4f(1.0, 0.0, 0.0, 1.0) # 設(shè)置當(dāng)前顏色為紅色不透明 glVertex3f(-0.8, 0.0, 0.0) # 設(shè)置x軸頂點(diǎn)(x軸負(fù)方向) glVertex3f(0.8, 0.0, 0.0) # 設(shè)置x軸頂點(diǎn)(x軸正方向) # 以綠色繪制y軸 glColor4f(0.0, 1.0, 0.0, 1.0) # 設(shè)置當(dāng)前顏色為綠色不透明 glVertex3f(0.0, -0.8, 0.0) # 設(shè)置y軸頂點(diǎn)(y軸負(fù)方向) glVertex3f(0.0, 0.8, 0.0) # 設(shè)置y軸頂點(diǎn)(y軸正方向) # 以藍(lán)色繪制z軸 glColor4f(0.0, 0.0, 1.0, 1.0) # 設(shè)置當(dāng)前顏色為藍(lán)色不透明 glVertex3f(0.0, 0.0, -0.8) # 設(shè)置z軸頂點(diǎn)(z軸負(fù)方向) glVertex3f(0.0, 0.0, 0.8) # 設(shè)置z軸頂點(diǎn)(z軸正方向) glEnd() # 結(jié)束繪制線段 # --------------------------------------------------------------- glBegin(GL_TRIANGLES) # 開始繪制三角形(z軸負(fù)半?yún)^(qū)) glColor4f(1.0, 0.0, 0.0, 1.0) # 設(shè)置當(dāng)前顏色為紅色不透明 glVertex3f(-0.5, -0.366, -0.5) # 設(shè)置三角形頂點(diǎn) glColor4f(0.0, 1.0, 0.0, 1.0) # 設(shè)置當(dāng)前顏色為綠色不透明 glVertex3f(0.5, -0.366, -0.5) # 設(shè)置三角形頂點(diǎn) glColor4f(0.0, 0.0, 1.0, 1.0) # 設(shè)置當(dāng)前顏色為藍(lán)色不透明 glVertex3f(0.0, 0.5, -0.5) # 設(shè)置三角形頂點(diǎn) glEnd() # 結(jié)束繪制三角形 # --------------------------------------------------------------- glBegin(GL_TRIANGLES) # 開始繪制三角形(z軸正半?yún)^(qū)) glColor4f(1.0, 0.0, 0.0, 1.0) # 設(shè)置當(dāng)前顏色為紅色不透明 glVertex3f(-0.5, 0.5, 0.5) # 設(shè)置三角形頂點(diǎn) glColor4f(0.0, 1.0, 0.0, 1.0) # 設(shè)置當(dāng)前顏色為綠色不透明 glVertex3f(0.5, 0.5, 0.5) # 設(shè)置三角形頂點(diǎn) glColor4f(0.0, 0.0, 1.0, 1.0) # 設(shè)置當(dāng)前顏色為藍(lán)色不透明 glVertex3f(0.0, -0.366, 0.5) # 設(shè)置三角形頂點(diǎn) glEnd() # 結(jié)束繪制三角形 # 交換緩沖區(qū) self.canvas.SwapBuffers()
if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()

代碼運(yùn)行界面如下圖所示。

文章圖片22

原文鏈接:https://blog.csdn.net/xufive/article/details/124548040

文章圖片23

END


成就一億技術(shù)人

文章圖片24

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多

    欧洲亚洲精品自拍偷拍| 91香蕉国产观看免费人人| 狠色婷婷久久一区二区三区| 亚洲清纯一区二区三区| 欧美日韩国产免费看黄片| 国产高清三级视频在线观看| 好吊日视频这里都是精品| 日韩一级免费中文字幕视频| 成人精品视频一区二区在线观看| 丝袜视频日本成人午夜视频| 欧美一二三区高清不卡| 国产精品亚洲一级av第二区| 亚洲最新的黄色录像在线| 欧洲日韩精品一区二区三区| 欧美亚洲综合另类色妞| 欧美日不卡无在线一区| 国产成人精品国内自产拍| 人体偷拍一区二区三区| 中文字幕精品一区二区三| 久久婷婷综合色拍亚洲| 国产欧美日韩视频91| 国产视频在线一区二区| 国产视频在线一区二区| 精品al亚洲麻豆一区| 人妻人妻人人妻人人澡| 亚洲超碰成人天堂涩涩| 国产日本欧美特黄在线观看| 久久黄片免费播放大全 | 亚洲欧美精品伊人久久| 日韩av亚洲一区二区三区| 国产视频在线一区二区| 国产精品熟女在线视频| 国产大屁股喷水在线观看视频| 日韩性生活视频免费在线观看| 日韩性生活片免费观看| av在线免费观看在线免费观看| 午夜福利直播在线视频| 久久国产精品熟女一区二区三区| 国产熟女一区二区精品视频| 欧美有码黄片免费在线视频| 国产免费观看一区二区|