手把手教你用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 等操作平台上。
参数解释
参数 | 含义 |
---|---|
-F,-onefile | 产生单个的可执行文件 |
-D,–onedir | 产生一个目录(包含多个文件)作为可执行程序 |
-d,–debug | 产生 debug 版本的可执行文件 |
-i | 改变生成程序的icon图标 |
-a,–ascii | 不包含 Unicode 字符集支持 |
-w,–windowed,–noconsolc | 使用窗口,无控制台 |
-c,–nowindowed,–console | 使用控制台,无界面(默认) |
-o DIR,–out=DIR | 指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件 |
-n NAME,–name=NAME | 指定项目(产生的spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字 |
在脚本文件的目录下,运行:
pyinstaller -F pyCalculator_5.py -i calculator.ico -w
运行成功的话,将会生成两个文件夹:build 和 dist,打包生成的.exe文件就存放在dist目录中。拷贝图标文件到dist,就可以分发、运行我们的计算器程序了。
- 点赞
- 收藏
- 关注作者
评论(0)