手把手教你用wxPython设计一个可以弹琴的计算器

网友投稿 670 2022-05-29

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() # 事件循环

运行上面的代码,计算器是这样的:

手把手教你用wxPython设计一个可以弹琴的计算器

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小时内删除侵权内容。

上一篇:0x6 Java系列:Java NIO?看这一篇就够了!【二】
下一篇:程序员修炼与低代码平台AppCube
相关文章