手把手教你用wxPython設計一個可以彈琴的計算器
1. 前言
用 Python 設計桌面程序,首先得選擇一個GUI庫。至于有哪些庫可選,各個庫又有什么特點,請參考我的博客《wxPython:python首選的GUI庫》。有很多網友對這篇博客的觀點,以及引用的材料,提出了不同的看法,甚至是批評。對此,我都一一回應,并對明顯的謬誤做了修正,對不同的觀點也做了追記。蘿卜青菜,各有所愛。我喜歡 wxPython,自然會向各位大力推薦,但一定盡可能保持客觀中立的立場,絕不厚此而薄彼。
本文詳細介紹了如何使用 wxPython 設計一個帶按鍵提示音的計算器,用這個計算器還可以彈奏簡單的樂曲。為了讓讀者能夠從零基礎上手 wxPython,我將設計過程,拆成了5個階段,形成了5個腳本文件,并附上了詳盡的代碼注釋。本文最后,使用 pyinstall 將最終的腳本打包成 .exe 文件,成為真正的桌面程序。
2. 桌面程序設計的通用框架
下面是一個實用的窗口程序框架,任何一個窗口程序的開發都可以在這個基礎之上展開。請注意,代碼里面用到了一個圖標文件 calculator.ico,和腳本文件在同一級目錄下。如果你要運行這段代碼,請先準好icon文件。如果沒有,也不要緊,只是會彈出一個警告信息。
pyCalculator_1.py
#-*- coding: utf-8 -*- import wx """這是一個通用的窗口程序框架,所有的桌面應用程序都可以從它開始構建""" APP_TITLE = '計算器' # 桌面程序的標題 APP_ICON = 'calculator.ico' # 桌面程序圖標 class mainFrame(wx.Frame): """桌面程序主窗口類,繼承自wx.Frame類""" def __init__(self): """構造函數""" # 顯式調用父類wx.Frame的構造函數__init__(),生成主窗口 # 主窗口通常沒有父級窗口,因此parent參數為None # id = -1,表示窗口id自動生成;title為窗口標題,可以通過wx.Frame.SetTitle()修改 wx.Frame.__init__(self, parent=None, id=-1, title=APP_TITLE) # ----------------------------------------------------- # 窗口設置wx.Frame有很多方法,下面演示了三個最常用的方法 self.SetBackgroundColour((240, 240, 240)) # 設置窗口背景色。顏色的標準寫法是wx.Colour(240, 240, 240) self.SetSize((640, 480)) # 設置窗口大小 self.Center() # 設置窗口屏幕居中 self.SetIcon(wx.Icon(APP_ICON, wx.BITMAP_TYPE_ICO) ) # 設置圖標(沒有圖標文件的話,會彈出警告信息) # ----------------------------------------------------- # 以下添加各類控件、綁定事件和時間函數。這就是桌面程序設計的主要工作 # wx是基于事件驅動的,每個事件都綁定了事件函數。一旦有事件發生,則觸發對應的事件函數執行 # 昨天群里有朋友說,原本打算寫個圖形界面,一看布局全靠想象力,最終重演了一遍從入門到放棄 # 我想說,即使你用鉛筆在紙上畫一個最簡單的圖案,不也全靠想象力嗎?如果連這一點想象力都沒有的話,那就干脆放棄吧 # 同樣的,一個優秀的前端工程師,也應該僅憑想象力就能直接寫出漂亮的html代碼 pass # 暫未添加任何控件,也沒有綁定任何事件 class mainApp(wx.App): # 通常,mainApp類無需任何修改,看不明白的話,照抄即可 def OnInit(self): self.SetAppName(APP_TITLE) self.Frame = mainFrame() self.Frame.Show() return True #---------------------------------------------------------------------- if __name__ == "__main__": app = mainApp() # 創建應用程序 app.MainLoop() # 事件循環
3. 了解事件驅動,探索鼠標事件及其綁定
wx是基于事件驅動的,每個事件都需要綁定事件函數。一旦有事件發生,則觸發對應的事件函數執行。下面的代碼演示了常用鼠標事件的綁定和對應事件函數的寫法。
pyCalculator_2.py
#-*- coding: utf-8 -*- import wx """學習wx.Button和wx.StaticText控件,探索鼠標事件以及綁定事件函數""" APP_TITLE = '計算器' # 桌面程序的標題 APP_ICON = 'calculator.ico' # 桌面程序圖標 class mainFrame(wx.Frame): """桌面程序主窗口類,繼承自wx.Frame類""" def __init__(self): """構造函數""" style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.MINIMIZE_BOX | wx.SIMPLE_BORDER wx.Frame.__init__(self, parent=None, id=-1, title=APP_TITLE, style=style) self.SetBackgroundColour((240, 240, 240)) # 設置窗口背景色 self.SetSize((640, 480)) # 設置窗口大小 self.Center() # 設置窗口屏幕居中 self.SetIcon(wx.Icon(APP_ICON, wx.BITMAP_TYPE_ICO) ) # 設置圖標(沒有圖標文件的話,會彈出警告信息) btn = wx.Button(self, -1, '按鈕', pos=(100,100), size=(80,40)) # 添加按鈕 self.st = wx.StaticText(self, -1, '', pos=(200,110), size=(200, -1)) # 添加靜態文本控件,用于顯示信息 btn.Bind(wx.EVT_LEFT_DOWN, self.onLeftDown) # 綁定鼠標左鍵按下事件,事件發生時,調用self.onLeftDown() btn.Bind(wx.EVT_LEFT_UP, self.onLeftUp) # 綁定鼠標左鍵彈起事件,事件發生時,調用self.onLeftUp() self.Bind(wx.EVT_MOUSE_EVENTS, self.onMouse) # 在窗口上綁定所有的鼠標事件 def onLeftDown(self, evt): """響應鼠標左鍵按下""" self.st.SetLabel('左鍵在按鈕上按下了') def onLeftUp(self, evt): """響應鼠標左鍵彈起""" self.st.SetLabel('左鍵從按鈕上彈起了') def onMouse(self, evt): """響應所有的鼠標事件""" if evt.EventType == 10036: tip = '鼠標移動: (%d, %d)'%(evt.x, evt.y) elif evt.EventType == 10030: tip = '左鍵按下' elif evt.EventType == 10031: tip = '左鍵彈起' elif evt.EventType == 10034: tip = '右鍵按下' elif evt.EventType == 10035: tip = '右鍵彈起' elif evt.EventType == 10045: vector = evt.GetWheelRotation() tip = '滾輪滾動: %d'%vector else: tip = '其他鼠標事件: %d'%evt.EventType self.st.SetLabel(tip) class mainApp(wx.App): def OnInit(self): self.SetAppName(APP_TITLE) self.Frame = mainFrame() self.Frame.Show() return True #---------------------------------------------------------------------- if __name__ == "__main__": app = mainApp() # 創建應用程序 app.MainLoop() # 事件循環
4. 最原始的計算器
有了上面的鋪墊,我們就可以著手設計計算器了。除了0到0、小數點、等號、加減乘除等常規按鍵外,我們還打算加上括號、退格、清屏等,共計20個按鍵,排成5行4列。為了便于調整布局,我們定義了按鍵區域的左上角的起始位置(x0, y0),列間距和行間距(dx, dy),以及按鍵大小btn_size,并將按鍵名依照布局順序放在二維列表 allKeys 中,最后使用 for 循環依次生成20個按鍵。顯示屏幕使用文本輸入框控件wx.TextCtrl,設置為只讀(wx.TE_READONLY)和右齊(wx.ALIGN_RIGHT)。除退格、清屏和等號鍵,按其他按鍵會會將對應字符追加到顯示屏幕上。當按下等號鍵時,使用python內置函數 eval() 計算當前表達式的值。如果表達式可以計算,則顯示計算結果;否則,捕獲異常,顯示“算式不符合規則”。按等號鍵之后,無論結果是否正確,下次按鍵會自動清屏,無需手動——請仔細體會主窗口屬性 over 是如何實現這個功能的。
pyCalculator_3.py
#-*- coding: utf-8 -*- import wx """最原始的計算器""" APP_TITLE = '計算器' # 桌面程序的標題 APP_ICON = 'calculator.ico' # 桌面程序圖標 class mainFrame(wx.Frame): """桌面程序主窗口類,繼承自wx.Frame類""" def __init__(self): """構造函數""" style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.MINIMIZE_BOX | wx.SIMPLE_BORDER wx.Frame.__init__(self, parent=None, id=-1, title=APP_TITLE, style=style) self.SetBackgroundColour((217, 228, 241)) # 設置窗口背景色 self.SetSize((287, 283)) # 設置窗口大小 self.Center() # 設置窗口屏幕居中 self.SetIcon(wx.Icon(APP_ICON, wx.BITMAP_TYPE_ICO) ) # 設置圖標(沒有圖標文件的話,會彈出警告信息) # 用輸入框控件作為計算器屏幕,設置為只讀(wx.TE_READONLY)和右齊(wx.ALIGN_RIGHT) self.screen = wx.TextCtrl(self, -1, '', pos=(10,10), size=(252,50), style=wx.TE_READONLY|wx.ALIGN_RIGHT) # 定義計算結束的標志:按等號鍵之后,無論結果是否正確,下次按鍵會自動清屏,無需手動 self.over = False # 按鍵布局參數 btn_size = (60, 30) # 定義按鍵的尺寸,便于統一修改 x0, y0 = (10, 65) # 定義按鍵區域的相對位置 dx, dy = (64, 34) # 定義水平步長和垂直步長 # 定義按鍵排列順序和名稱 allKeys = [ ['(', ')', 'Back', 'Clear'], ['7', '8', '9', '/'], ['4', '5', '6', '*'], ['1', '2', '3', '-'], ['0', '.', '=', '+'] ] # 生成所有按鍵 for i in range(len(allKeys)): for j in range(len(allKeys[i])): # 添加按鈕。請注意屬性name的用法 btn_ = wx.Button(self, -1, allKeys[i][j], pos=(x0+j*dx, y0+i*dy), size=btn_size, name=allKeys[i][j]) # 綁定按鈕事件(請注意:既非彈起,也不是按下,是按鈕被點擊) self.Bind(wx.EVT_BUTTON, self.onButton) # 將按鈕事件綁定在所有按鈕上 def onButton(self, evt): """響應鼠標左鍵按下""" obj = evt.GetEventObject() # 獲取事件對象(哪個按鈕被按) name = obj.GetName() # 獲取事件對象的名字 if self.over: self.screen.SetValue('') self.over = False if name == 'Clear': # 按下了清除鍵,清空屏幕 self.screen.SetValue('') elif name == 'Back': # 按下了回退鍵,去掉最后一個輸入字符 content = self.screen.GetValue() if content: self.screen.SetValue(content[:-1]) elif name == '=': # 按下了等號鍵,則計算 try: result = str(eval(self.screen.GetValue())) except: result = '算式不符合規則' self.screen.SetValue(result) self.over = True else: # 按下了其他鍵,追加到顯示屏上 self.screen.AppendText(name) class mainApp(wx.App): def OnInit(self): self.SetAppName(APP_TITLE) self.Frame = mainFrame() self.Frame.Show() return True #---------------------------------------------------------------------- if __name__ == "__main__": app = mainApp() # 創建應用程序 app.MainLoop() # 事件循環
運行上面的代碼,計算器是這樣的:
5. 更漂亮的計算器
這一節,我們來對原始的計算器做一些美化。首先,我們通過設置文本輸入框的背景前景顏色、字體字號等,使之看上去更像一個屏幕;其次,更換按鈕控件,放棄 wx.Button,改用 wx.lib.buttons。這個按鈕控件提供了背景、3D效果等更多的控制項。
pyCalculator_4.py
#-*- coding: utf-8 -*- import wx import wx.lib.buttons as wxbtn """更漂亮的計算器""" APP_TITLE = '計算器' # 桌面程序的標題 APP_ICON = 'calculator.ico' # 桌面程序圖標 class mainFrame(wx.Frame): """桌面程序主窗口類,繼承自wx.Frame類""" def __init__(self): """構造函數""" style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.MINIMIZE_BOX | wx.SIMPLE_BORDER wx.Frame.__init__(self, parent=None, id=-1, title=APP_TITLE, style=style) self.SetBackgroundColour((217, 228, 241)) # 設置窗口背景色 self.SetSize((287, 283)) # 設置窗口大小 self.Center() # 設置窗口屏幕居中 self.SetIcon(wx.Icon(APP_ICON, wx.BITMAP_TYPE_ICO)) # 設置圖標 # 用輸入框控件作為計算器屏幕,設置為只讀(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, '微軟雅黑')) # 設置字體字號 self.screen.SetBackgroundColour((0, 0, 0)) # 設置屏幕背景色 self.screen.SetForegroundColour((0, 255, 0)) # 設置屏幕前景色 # 定義計算結束的標志:按等號鍵之后,無論結果是否正確,下次按鍵會自動清屏,無需手動 self.over = False # 按鍵布局參數 btn_size = (60, 30) # 定義按鍵的尺寸,便于統一修改 x0, y0 = (10, 65) # 定義按鍵區域的相對位置 dx, dy = (64, 34) # 定義水平步長和垂直步長 # 定義按鍵排列順序和名稱 allKeys = [ ['(', ')', 'Back', 'Clear'], ['7', '8', '9', '/'], ['4', '5', '6', '*'], ['1', '2', '3', '-'], ['0', '.', '=', '+'] ] # 生成所有按鍵 for i in range(len(allKeys)): for j in range(len(allKeys[i])): key = allKeys[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) # 設置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"顯示計算結果") # 綁定按鈕事件(請注意:既非彈起,也不是按下,是按鈕被點擊) self.Bind(wx.EVT_BUTTON, self.onButton) # 將按鈕事件綁定在所有按鈕上 def onButton(self, evt): """響應鼠標左鍵按下""" obj = evt.GetEventObject() # 獲取事件對象(哪個按鈕被按) name = obj.GetName() # 獲取事件對象的名字 if self.over: self.screen.SetValue('') self.over = False if name == 'Clear': # 按下了清除鍵,清空屏幕 self.screen.SetValue('') elif name == 'Back': # 按下了回退鍵,去掉最后一個輸入字符 content = self.screen.GetValue() if content: self.screen.SetValue(content[:-1]) elif name == '=': # 按下了等號鍵,則計算 try: result = str(eval(self.screen.GetValue())) except: result = '算式錯誤,請Clear' self.screen.SetValue(result) self.over = True else: # 按下了其他鍵,追加到顯示屏上 self.screen.AppendText(name) class mainApp(wx.App): def OnInit(self): self.SetAppName(APP_TITLE) self.Frame = mainFrame() self.Frame.Show() return True #---------------------------------------------------------------------- if __name__ == "__main__": app = mainApp() # 創建應用程序 app.MainLoop() # 事件循環
這次的計算器,屏幕和按鍵不但顏色有了變化,還多少增加了立體感。
6. 給漂亮的計算器加上聲音
播放聲音,有很多方案。為了不干擾前進的方向,我們使用 Python 標準庫 winsound 模塊的 Beep() 函數播放按鍵音。 winsound.Beep()需要兩個參數,一是頻率,整型,單位赫茲,二是時長,浮點型,單位秒。為了彈奏出音樂,我們需要學一點音樂知識,了解音樂和頻率的關系:鋼琴鍵盤中央C,對應簡譜C調的1,其頻率為523赫茲,2對應587赫茲…字典 keySound 定義了按鍵和頻率的關系。
pyCalculator_5.py
#-*- coding: utf-8 -*- #-*- coding: utf-8 -*- import wx import wx.lib.buttons as wxbtn import winsound """給漂亮的計算器加上聲音""" APP_TITLE = '計算器' # 桌面程序的標題 APP_ICON = 'calculator.ico' # 桌面程序圖標 class mainFrame(wx.Frame): """桌面程序主窗口類,繼承自wx.Frame類""" def __init__(self): """構造函數""" style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.MINIMIZE_BOX | wx.SIMPLE_BORDER wx.Frame.__init__(self, parent=None, id=-1, title=APP_TITLE, style=style) self.SetBackgroundColour((217, 228, 241)) # 設置窗口背景色 self.SetSize((287, 283)) # 設置窗口大小 self.Center() # 設置窗口屏幕居中 self.SetIcon(wx.Icon(APP_ICON, wx.BITMAP_TYPE_ICO)) # 設置圖標 # 用輸入框控件作為計算器屏幕,設置為只讀(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, '微軟雅黑')) # 設置字體字號 self.screen.SetBackgroundColour((0, 0, 0)) # 設置屏幕背景色 self.screen.SetForegroundColour((0, 255, 0)) # 設置屏幕前景色 # 定義計算結束的標志:按等號鍵之后,無論結果是否正確,下次按鍵會自動清屏,無需手動 self.over = False # 按鍵布局參數 btn_size = (60, 30) # 定義按鍵的尺寸,便于統一修改 x0, y0 = (10, 65) # 定義按鍵區域的相對位置 dx, dy = (64, 34) # 定義水平步長和垂直步長 # 定義按鍵排列順序和名稱 allKeys = [ ['(', ')', 'Back', 'Clear'], ['7', '8', '9', '/'], ['4', '5', '6', '*'], ['1', '2', '3', '-'], ['0', '.', '=', '+'] ] # 指定每個按鍵聲音的頻率,523赫茲就是C調中音 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 } # 生成所有按鍵 for i in range(len(allKeys)): for j in range(len(allKeys[i])): key = allKeys[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) # 設置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"顯示計算結果") # 綁定按鈕事件(請注意:既非彈起,也不是按下,是按鈕被點擊) self.Bind(wx.EVT_BUTTON, self.onButton) # 將按鈕事件綁定在所有按鈕上 def onButton(self, evt): """響應鼠標左鍵按下""" obj = evt.GetEventObject() # 獲取事件對象(哪個按鈕被按) key = obj.GetName() # 獲取事件對象的名字 self.PlayKeySound(key) # 播放按鍵對應頻率的聲音 if self.over: self.screen.SetValue('') self.over = False if key == 'Clear': # 按下了清除鍵,清空屏幕 self.screen.SetValue('') elif key == 'Back': # 按下了回退鍵,去掉最后一個輸入字符 content = self.screen.GetValue() if content: self.screen.SetValue(content[:-1]) elif key == '=': # 按下了等號鍵,則計算 try: result = str(eval(self.screen.GetValue())) except: result = '算式錯誤,請Clear' self.screen.SetValue(result) self.over = True else: # 按下了其他鍵,追加到顯示屏上 self.screen.AppendText(key) def PlayKeySound(self, key, Dur=100): """播放按鍵聲音""" winsound.Beep(self.keySound[key], Dur) class mainApp(wx.App): def OnInit(self): self.SetAppName(APP_TITLE) self.Frame = mainFrame() self.Frame.Show() return True #---------------------------------------------------------------------- if __name__ == "__main__": app = mainApp() # 創建應用程序 app.MainLoop() # 事件循環
現在可以嘗試彈一些簡單的曲子了。
7. 打包成.exe文件
pyInstaller 是一個十分有用的第三方庫,可以用來打包 python 應用程序,打包完的程序就可以在沒有安裝 Python 解釋器的機器上運行了。pyInstaller 可以運行在 Windows、Linux、 Mac OS X 等操作平臺上。
參數解釋
在腳本文件的目錄下,運行:
pyinstaller -F pyCalculator_5.py -i calculator.ico -w
運行成功的話,將會生成兩個文件夾:build 和 dist,打包生成的.exe文件就存放在dist目錄中。拷貝圖標文件到dist,就可以分發、運行我們的計算器程序了。
Python
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。