python用的时间不长,一般用来做字符串处理、简单测试的一些小程序。最近工作中需要做一个简单的GUI应用,使用麦克录音并存成wave文件。然后就想拿wxPython练练手。

一、概述

GUI开发采用wxPython,界面编辑工具采用wxGlade,声音采集和播放采用PyAudio,小数据库采用sqlite3,最后使用py2exe打包发布。基本的应用开发流程都包括了。

二、wxGlade

界面编辑工具也是找了几个,比如wxFormBuilder,甚至是收费的DialogBlocks,wxFormBuilder 很漂亮,不过bug好像较多,经常自动退出。wxGlade有经典的Linux GUI界面风格,分立式窗体,了解了基本的原理后用起来很方便。主要是其中的sizer,add slot、insert slot增加空位,然后添加控件。也可以添加自定义的控件,只需要设置自定义控件的Class属性。然后在MainFrame的Extra code for this widget增加from YourModule import YourCLass。因为界面比较简单,我没有采用XRC资源导入的模式,而是直接生成MainFrame的代码。由于界面设计可能会变,在应用中新建一 个MainFrameEx类继承MainFrame,将事件处理放在继承类中完成。这样每次使用wxGlade编辑界面后可以直接覆盖生成的代码。

三、PyAudio

PyAudio是从PortAudio移植的,现在还是alpha版。不过使用起来还真是方便,看看网站上提供的example就可以了。没有什么大问题。需要注意多线程的问题,PyAudio对象尽量复用。注意线程中刷新wxWidget需要使用wx.CallAfter方法。

# -*- coding: UTF-8 -*-

import pyaudio

import wave

import threading

import wx

import datetime

import traceback

import os

import logging

logger = logging.getLogger("root")

class BMRecord(threading.Thread):

CHUNK = 1024

FORMAT = pyaudio.paInt16  # 至少为16位

DEVICE = 1

CHANNELS = 1

RATE = 44100

RECORD_SECONDS = 5

def __init__(self, window, audio, device, prefix):

threading.Thread.__init__(self)

self.window = window

self.audio = audio

self.prefix = prefix  # wave文件命名的前缀

self.DEVICE = device

self.CHANNELS = 2  # 双通道采集

self.RECORD_SECONDS = int(window.params["length"])

self.RATE = int(window.params["rate"])

self.FORMAT = pyaudio.paInt16

def record(self):

self.filename = ""

self.filetime = ""

try :

stream = self.audio.open(format=self.FORMAT,

channels=self.CHANNELS,

rate=self.RATE,

input=True,

input_device_index=self.DEVICE,

frames_per_buffer=self.CHUNK)

frames = []

for i in range(0, int(self.RATE * self.RECORD_SECONDS / self.CHUNK)):

data = stream.read(self.CHUNK)

frames.append(data)

i = i

stream.stop_stream()

stream.close()

now = datetime.datetime.now()

strnow = now.strftime('%Y%m%d%H%M%S')

self.filetime = now.strftime('%Y-%m-%d %H:%M:%S')

savepath = self.checkPath(strnow)

# 双声道存为两个单声道文件

frames1 = []

frames2 = []

wavedata = b''.join(frames)

for i in range(len(wavedata) / 4):

frames1.append(wavedata[i * 4 : i * 4 + 2])

frames2.append(wavedata[i * 4 + 2 : i * 4 + 4])

# 双声道存储

fullpath = savepath + "/" + strnow + self.prefix + "X.wav"

wf = wave.open(fullpath, 'wb')

wf.setnchannels(2)

wf.setsampwidth(self.audio.get_sample_size(self.FORMAT))

wf.setframerate(self.RATE)

wf.writeframes(wavedata)

wf.close()

# 两个单声道存储

filenames = [strnow + self.prefix + "Z.wav", strnow + self.prefix + "Y.wav"]

fullpath = savepath + "/" + filenames[0]

wf = wave.open(fullpath, 'wb')

wf.setnchannels(1)

wf.setsampwidth(self.audio.get_sample_size(self.FORMAT))

wf.setframerate(self.RATE)

wf.writeframes(b''.join(frames1))

wf.close()

fullpath = savepath + "/" + filenames[1]

wf = wave.open(fullpath, 'wb')

wf.setnchannels(1)

wf.setsampwidth(self.audio.get_sample_size(self.FORMAT))

wf.setframerate(self.RATE)

wf.writeframes(b''.join(frames2))

wf.close()

self.message = "录制成功"

self.filenames = filenames

logger.info(filenames[0] + "," + filenames[1] + ", recorded")

return 0

except Exception:

self.message = traceback.format_exc()

logger.error(traceback.format_exc())

return -1

def checkPath(self, pathname):

curpath = os.path.abspath(os.curdir)

strdate = pathname[0:8]

fullpath = curpath + "/data/" + strdate

if  not os.path.exists(fullpath) :

os.makedirs(fullpath)

return fullpath

def run(self):

ret = self.record()

wx.CallAfter(self.window.recordResult, ret, self.filenames, self.filetime, self.message)

四、sqlite

sqlite模块是Python内置的用起来很方便:

import sqlite3

import datetime

class BMDatabase():

def loadData(self, whichDay):

conn = sqlite3.connect("data/bmon.db")

cur = conn.cursor()

start = datetime.datetime.strptime(whichDay, "%Y-%m-%d")

end = start + datetime.timedelta(days=1)

res = cur.execute("select * from CheckRecord where rec_time between ? and ? order by rec_time", (start, end))

return res.fetchall()

def getFile(self, waveFile):

conn = sqlite3.connect("data/bmon.db")

cur = conn.cursor()

res = cur.execute("select * from CheckRecord where rec_file=?", (waveFile,))

row = res.fetchone()

return row

def save(self, filename, filetime, result):

conn = sqlite3.connect("data/bmon.db")

cur = conn.cursor()

record = [(filename, filetime, result)]

cur.executemany('INSERT INTO CheckRecord (rec_file,rec_time,result) VALUES (?,?,?)', record)

conn.commit()

五、自定义wxWidget控件

自绘控件主要是处理EVT_PAINT事件: self.Bind(wx.EVT_PAINT, self.on_paint)

import wx

class WavePane(wx.StaticText):

waveData = None

spectrum = None

def __init__(self, parent, nid=wx.ID_ANY, caption=""):

wx.StaticText.__init__(self, parent)

self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)

self.Bind(wx.EVT_SIZE, self.on_size)

self.Bind(wx.EVT_PAINT, self.on_paint)

def on_size(self, event):

self.Refresh()

event.Skip()

def on_paint(self, event):

w, h = self.GetClientSize()

dc = wx.AutoBufferedPaintDC(self)

brush = wx.Brush(wx.Color(0, 0, 0x80), wx.SOLID)

dc.SetBrush(brush)

dc.Clear()

dc.SetPen(wx.Pen(wx.BLACK, 1))

dc.SetTextForeground(wx.Color(0, 0xFF, 0))

font = dc.GetFont()

font.SetPointSize(8)

dc.SetFont(font)

dc.DrawRectangle(0, 0, w - 1, h - 1)

if self.waveData <> None:

dc.BeginDrawing()

dc.SetPen(wx.Pen(wx.Color(0, 0xFF, 0), 1))

au = self.waveData

step = int(au.nframes / w)

height = au.height  # 或者65536 / 2.0

i = 0

j = 0

while i < au.nframes:

if au.frames[2 * i + 1] >= 0x80:  # 负数

value = au.frames[2 * i] + au.frames[2 * i + 1] * 256 - 65536

else:

value = au.frames[2 * i] + au.frames[2 * i + 1] * 256

dc.DrawLine(j, int(h / 2.0), j, int(h / 2.0 * (1 - value * 1.0 / height)))

i += step

j += 1

if 2 * i + 1 >= au.nframes * 2:

break;

dc.DrawText(str(au.maxValue), 1, 1)

dc.DrawText(str(au.minValue), 1, h - 16)

dc.EndDrawing()

elif self.spectrum <> None :

dc.BeginDrawing()

dc.SetPen(wx.Pen(wx.Color(0, 0xFF, 0), 1))

brush = wx.Brush(wx.Color(0, 0xFF, 0), wx.SOLID)

dc.SetBrush(brush)

dc.SetTextForeground(wx.RED)

barWidth = int(w / 72)

i = 0

j = barWidth

while i < 36:

y = int((1 - self.spectrum[i] * 1.0 / self.maxSpectrum) * h)

dc.DrawRectangle(j, y , barWidth, h - y - 1)

if y < h - 2:

dc.FloodFill(j + 1, y + 1, wx.Color(0, 0xFF, 0), wx.FLOOD_BORDER)

# Bar的编号

if i == 0 or(i + 1) % 5 == 0:

if i < 9:

dc.DrawText(str(i + 1), j + 4, h - 13)

else:

dc.DrawText(str(i + 1), j , h - 13)

i += 1

j += barWidth * 2

dc.SetTextForeground(wx.Color(0, 0xFF, 0))

dc.DrawText(str(self.maxSpectrum), 1, 1)

dc.EndDrawing()

def setWaveData(self, waveData):

self.waveData = waveData

self.Refresh()

def setSpectrum(self, spectrum):

self.spectrum = spectrum

if spectrum <> None:

maxValue = 0

for i in range(0, 36):

if  spectrum[i] > maxValue :

maxValue = spectrum[i]

self.maxSpectrum = maxValue

self.Refresh()

六、日志的使用

使用循环日志

import logging.handlers

logger = logging.getLogger("root")

handler = logging.handlers.RotatingFileHandler(os.path.join(os.getcwd(), 'bmon.log'),

maxBytes=5 * 1024 * 1024, backupCount=5)

formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')

handler.setFormatter(formatter)

logger.addHandler(handler)

logger.setLevel(logging.DEBUG)

七、py2exe

最后一步就是打包成可执行文件。setup.py:

from distutils.core import setup

import py2exe

# setup(console=["hello.py"])

py2exe_options = dict({

"includes":['sip', 'encodings', 'encodings.ascii', 'encodings.utf_8', 'encodings.cp866'],

"dll_excludes":["MSVCP90.dll"]})

setup(version="1.0",

description="Bearing Monitor",

name="bmon",

zipfile=None,

dist_dir="bmon",

windows=["bmon.py"],

options={'py2exe': py2exe_options},

icon_resources=[(1, "check_all.ico")],

data_files=[("", ["check_all.ico"])]

)

然后命令行下执行:python setup.py py2exe,就可以生成dist发布目录

八、小结

这个简单应用涉及的主要模块就这么几个,组合成了一个简单的GUI应用。Python开发还真是很简单,前提是得熟悉各种模块。

wxpython收费吗_使用wxPython开发一个简单GUI应用相关推荐

  1. wxpython问卷调查界面_自己做的一个简单的问卷调查系统

    疫情期间,先来没事做,就简单的用ssm 写了一个问卷调查系统.用于我们学校得青协调查用. 这就是我做得一个首页的页面,首页做的比较简单嘻嘻. 因为刚接触ssm 框架,我首先的思路是先把整体的ssm框架 ...

  2. java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署

    java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署 java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署 本源 ...

  3. [译]使用 Rust 开发一个简单的 Web 应用,第 4 部分 —— CLI 选项解析

    原文地址:A Simple Web App in Rust, Part 4 -- CLI Option Parsing 原文作者:Joel's Journal 译文出自:掘金翻译计划 本文永久链接:g ...

  4. java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署

    java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署 java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署 本源码技 ...

  5. 利用WCF的callback机制开发一个简单的多人游戏模型

    本文介绍了如何利用WCF和callback机制开发一个简单的多人在线游戏模型. 运行过程如下: 当game service 启动之后,若干个客户端便会自动连接到服务器.当某个客户端点击join gam ...

  6. php开发mvc教程,php开发一个简单的MVC

    本文通过实例为大家介绍用php开发一个简单mvc的方法,起到势砖引玉的作用,本文比较适合刚接触mvc的朋友. MVC其实就是三个Model,Contraller,View单词的简称. Model,主要 ...

  7. Nginx开发一个简单的HTTP过滤模块

    本文将学些开发一个简单的HTTP过滤模块,它能够对Content-Type为text/plain的包体前加上前缀字符串prefix. <一> 过滤模块的调用顺序 过滤模块可以叠加,也就是说 ...

  8. python可视化界面编程 pycharm_pycharm开发一个简单界面和通用mvc模板(操作方法图解)...

    文章首先使用pycharm的 PyQt5 Designer 做一个简单的界面,然后引入所谓的"mvc框架". 一.设计登录界面 下面开始第一个话题,使用pycharm的 PyQt5 ...

  9. 开发一个简单的WebPart

    开发一个简单的WebPart,首先我们需要对Visual Studio .NET 2003进行相应功能的扩展,我们可以在微软的网站下载到一个扩展功能包,名字叫:WebPartTemplatesforV ...

最新文章

  1. 打造生物智能和人工智能“双螺旋”,智源研究院发布“人工智能的认知神经基础”重大研究方向...
  2. 借力5G,云VR将推动VR产业迎“第二春”
  3. Maven下载、安装和配置(转)
  4. GitHub 上值得关注的 iOS 开源项目
  5. 机械制图及计算机绘图技能实训,机械制图测绘与CAD技能实训(二)
  6. LeetCode 1654. 到家的最少跳跃次数(BFS)
  7. oracle函数lad,01-查询Oracle中所有用户信息
  8. 使用代理,调用json-server的服务接口
  9. Matlab之字符串的查找(findstr)与替换(strrep)
  10. Python解答蓝桥杯省赛真题之从入门到真题
  11. SAP HANA XS 专栏
  12. 蘑菇战争 2 for Mac游戏介绍
  13. MACBOOK 连接不上wifi的解决办法
  14. 机器学习作业-交通流量预测综述
  15. 编写python 函数,实现冒泡排序算法。
  16. oracle查询遇到关键字
  17. 蓝桥杯真题:数字三角形
  18. AD转换中参考电压的作用
  19. oracle手动挂载crs盘,Oracle rac手动修改crs资源
  20. Ubuntu 10.04下安装TL-WN721N(RT5370)无线网卡驱动

热门文章

  1. 城市系统应用其一-表征城市交通模式
  2. Excel 2010 SQL应用096 聚合函数之标准偏差及标准差
  3. 3D全景模型展示可视化技术演示
  4. 网络和计算机加密解密感叹号,网络连接显示感叹号但是能上网怎么办 网络连接显示感叹号原因【图文】...
  5. mysql报错:1264-Out of range value for column ‘字段‘ at row 1
  6. 空气质量天气质量数据来源整理
  7. 小甲鱼python入门笔记(一)(全)
  8. 【网络编程】TCP 网络应用程序开发
  9. OSEK直接网络管理软件开发
  10. java 高效列转行,java 列转行