零基础:100个小案例玩转Python软件开发!第六节:英语教学软件

欢迎回到我们的 《零基础:100个小案例玩转Python软件开发!》 系列!在前面我们已经学了五节课,大家是否对PyMe开发软件有一定了解和掌握了呢?

在本节课,我将教大家如何开发一款英语教学软件。在这一节课中,涉及到导航控件、语音播放和打包MSI安装包的知识哦!

一、界面设计

1.1 启动界面

我们先在PyMe中创建一个空工程EnglishStudy,然后将当前窗口界面Form_1的属性布局方式设置为"自动适应"(旧版本称为"打包排布"),然后从左边的控件工具条拖动一个导航控件Navigation和一个容器控件Frame到Form_1中。

选中Navigation_1,设置其布局方式也为"自动适应",居上停靠,横向填充,高度60像素,这时导航条就会在60像素高度空间横向占据顶部空间。

导航条Navigation是一种用于页面导航的按钮栏,我们经常在各类网站上看到它。

在PyMe中,可以通过右边的属性栏设置导航条的方向、对齐方式和每个选项的背景色、文字颜色、背景图等,感兴趣的小伙伴可以尝试设置观察不同的效果。

用鼠标右键在导航条Navigation_1单击,从弹出菜单里选择"标题选项编辑",这时会弹出导航栏编辑区对话框,我们创建两个选项:

  • "听力模式":根据英语语音选择单词选项,选项值0。

  • "翻译模式":根据英语单词选择中文翻译,选项值1。

Frame控件是容器控件,也就是用来放置其它控件的控件,使用Frame可以方便的将属于同一部分功能或区域的控件放在一起,方便我们管控。选中Frame_1,同样设置使用"自动适应"布局方式,但设为居下停靠,四周填充,这样设置后Frame控件将填充Form_1中余下的整个空间。

这样界面就做好了,我们稍后再来处理逻辑部分。

1.2 开始学习界面

在下方"文件和资源栏"用鼠标右键单击,新建一个Home界面,在生成的新界面里摆放一些图文和按钮,作为学习英语的开始界面。右上角放置了一个用于设置任务量的Frame控件,并在其中加入了设置输入框SpinBox和完成设置的按钮,并设置Frame_1的显示属性为"隐藏",我们让它开始状态看不见,点击按钮"设置任务量"时再显示。

1.3 听音练习界面

在下方"文件和资源栏"用鼠标右键单击,新建一个ListenMode界面,放置一个标题Label、一个播放声音的按钮和一个跳过当前单词的按钮。并在中间再放置用于点选的A、B、C、D四个选项的按钮和下方的学习进度条。

二、功能逻辑实现

2.1 启动界面逻辑

双击启动界面文件,进入到界面设计器,这个界面需要做两件事:

  1. 为导航栏设置功能。

  2. 加载开始学习界面。

我们可以在导航栏上用鼠标右键单击,在弹出菜单中选择"事件响应"菜单项,然后在弹出对话框中左边选择ItemSelect事件,右边点击"编辑函数代码"来绑定选项变化时的回调函数。

在Navigation_1_onItemSelect函数中,我们可以通过参数itemValue来获取设置的值0或1,然后根据这个值来做判断以加载对应界面。但在当前项目中,我们并没有为每个选项创建一个单独的界面,而是统一使用Home界面,只是记录一下选项值到全局变量中。

python 复制代码
#Form 'Form_1's Load Event :
def Form_1_onLoad(uiName):
    #学习模式
    Fun.G_UserVarDict['mode']="0"
    #将学习单词的配置信息存入PyMe的Fun函数库中的用户字典中
    #learn字段代表总的单词数量
    #do代表学完的单词数量,也就是进度
    Fun.G_UserVarDict['wordConfig']={"learn":3,"do":0}
    Fun.LoadUIDialog(uiName,'Frame_1','Home',None,ignoreSameUI=False)
#Navigation 'Navigation_1's ItemSelect Event :
def Navigation_1_onItemSelect(uiName,widgetName,itemText,itemValue):
    Fun.G_UserVarDict['mode']=itemValue
    Fun.LoadUIDialog(uiName,'Frame_1','Home',None,ignoreSameUI=False)

2.2 开始学习界面

在开始界面用鼠标右键单击"学习"按钮,在弹出菜单里选择"事件响应"菜单项,在弹出的对话框中选择单击事件Command,并在右边选择"控件中加载界面",然后选择将ListenMode界面加载到Form_1上,这样可以实现换界面的效果。

然后就是为"设置任务量"按钮和"完成设置"按钮绑定点击事件函数,在"设置任务量"按钮的单击事件函数中,将Frame_1面板设置显示。在"完成设置"按钮的单击事件函数中,获取SpinBox_1的值并记录和刷新,具体代码如下:

python 复制代码
#显示当前任务信息
def show_task():
    #当前要学习的单词数量
    Fun.SetText(uiName,'Label_6',str(Fun.G_UserVarDict['wordConfig']["learn"]))
    #当前进度
    Fun.SetText(uiName,'Label_7',str(Fun.G_UserVarDict['wordConfig']["do"]))

#Form 'Form_1's Event :Load
def Form_1_onLoad(uiName):
    #如果有进度,按钮显示为"继续学习"
    if Fun.G_UserVarDict['wordConfig']["do"]>0:
        Fun.SetText(uiName,'Button_1',"继续学习")
    else:
        Fun.SetText(uiName,'Button_1',"学习") 
    show_task()
#Button 'Button_1' 'sCommand Event :
def Button_1_onCommand(uiName,widgetName):
    #在Form_1上嵌入其它界面,等于将当前页面更换为ListenMode
    Fun.LoadUIDialog(uiName,"Form_1","ListenMode",ignoreSameUI=False)
#Button 'Button_set' 's Command Event :
def Button_set_onCommand(uiName,widgetName):
    #打开设置面板
    Fun.SetElementVisible(uiName,'Frame_1',True)
#Button 'Button_3' 's Command Event :
def Button_3_onCommand(uiName,widgetName):
    #获取任务量并记录到Fun函数库中的自定义变量单词配置信息中
    Fun.G_UserVarDict['wordConfig']["learn"]= int(Fun.GetText(uiName,'SpinBox_1'))
    #刷新任务
    show_task()
    #隐藏设置面板Frame_1
    Fun.SetElementVisible(uiName,'Frame_1',False)

2.3单词处理与语音播放

在听音练习界面,要实现单词语音播放和界面显示与选中的逻辑。首先我们需要创建一个单词的EXCEL表,在A、B两列放置单词和对应的中文解释:

有了这个EXCEL表,我们封装一个用于处理单词读取与语音播放的模块文件vocabularyUtil.py来读取和播放语音以方便使用:

python 复制代码
import configparser
from openpyxl import load_workbook
from datetime import datetime
import random
#单词工具类
class WordUtil:
    def __init__(self):
        # 配置文件
        self.local_ini = configparser.ConfigParser()
        self.local_ini.read('local.ini', encoding='utf-8')
        # 把单词和翻译从EXCEL表V.xlsx中读取
        workbook_V = load_workbook(filename='V.xlsx')
        # 取得第一个Sheet
        self.sheet_V = workbook_V.worksheets[0]
        self.max_row = 893
        self.V_position = self.local_ini['Last_read']['position']
       
    def getWord(self,index):
        #取得对应索引的单词
        count_next = str(int(self.V_position) + index)
        cur_v = self.sheet_V['A' + count_next].value
        cur_n=self.sheet_V['B' + count_next].value
        return cur_v,cur_n
    def getWordSalt(self,index):
        #取得三个错误的单词信息
        cur_v,cur_n=self.getWord(index)
        salt_indices = self.salt(index)
        # TODO:返回json格式{"word":"","options":[{"word_zh":"","word_en":"",""},{}]}
        # 获取随机单词及其翻译
        options = []
        for idx in salt_indices:
            word_en, word_zh= self.getWord(idx)
            options.append({"word_zh": word_zh, "word_en": word_en})
        # 构造结果字典
        result = {
            "cur_n": cur_n,
            "cur_v": cur_v,
            "options": options
        }
        # 返回 JSON 格式的字符串
        return json.dumps(result, ensure_ascii=False)
    #保存记录
    def saveRecord(self,count):
        self.local_ini.set('Last_read', 'time', str(datetime.now()))
        self.local_ini.set('Last_read', 'position', str(int(self.V_position) + count))
        self.local_ini.write(open('local.ini', 'w'))
    #清除记录
    def clearRecord(self):
        self.local_ini.set("Last_read", "time", str(datetime.now()))
        self.local_ini.set("Last_read", "position", "1")
        self.local_ini.write(open("local.ini", "w"))
        print("clear record")
    #从集合中随机选择 count 个不包含 exclude_index 的索引
    def salt(self, exclude_index, count=3):
        indices = list(range(1,exclude_index) )+ list(range(exclude_index+1,self.max_row-int(self.V_position)+1))
        selected_indices = list(set(random.sample(indices, count)))
        return selected_indices
    
    #根据模式取得选项信息:当前单词、对应的四个选项值、正确选项
    def getOption(self,mode, index):
        word_en_list = []
        word_zh_list = []
        cur_v,cur_n=self.getWord(index)
        salt_indices = self.salt(index)
        for idx in salt_indices:
            word_en, word_zh= self.getWord(idx)
            word_en_list.append(word_en)
            word_zh_list.append(word_zh)
        # 在 word_en_list 的一个随机位置插入新元素
        answer_index = random.randint(0, len(word_zh_list))  # 生成一个随机索引
        word_en_list.insert(answer_index, cur_v)
        word_zh_list.insert(answer_index, cur_n)
        if int(mode)==0:
            return cur_v, word_en_list, answer_index
        else:
            return cur_v, word_zh_list, answer_index

单词语音播放的功能,我们也可以写成一个函数放置在这个文件中。这里用到pyttsx3。pyttsx3 是一个 Python 文本转语音(TTS)库,支持离线运行和跨平台使用(Windows/macOS/Linux),通过调用系统原生语音引擎实现高效语音合成。可以通过pip install pyttsx3进行安装。

python 复制代码
import pyttsx3
import threading
# 创建一个全局的语音引擎实例
engine = pyttsx3.init()
# 创建一个线程锁
engine_lock = threading.Lock()
def run_in_thread(word):
    with engine_lock:
        #语音引擎调用say将单词进入播放队列
        engine.say(word)
        #语音引擎调用runAndWait来启动播放
        engine.runAndWait()
def listen(word):
    # 创建并启动新线程播放声音
    threading.Thread(target=run_in_thread, args=(word,)).start()

完成这个文件后,我们就可以完成听音练习了。

2.3 听音练习界面

返回听音练习界面,引入WordUtil和listen函数,在Form_1_onLoad函数中创建WordUtil对象,并显示选项,在显示选项函数show_option函数中,根据模式来获取选项信息并设置到按钮选项上。

python 复制代码
#coding=utf-8
import sys
import os
from   os.path import abspath, dirname
sys.path.insert(0,abspath(dirname(__file__)))
import tkinter
from   tkinter import *
import Fun
uiName="ListenMode"
ElementBGArray={}
ElementBGArray_Resize={}
ElementBGArray_IM={}
from vocabularyUtil import WordUtil,listen
import threading
#显示选项
def show_options(index):
    global cur_v,answer_index
    #如果是听音模式
    if Fun.G_UserVarDict['mode'] =='0':
        Fun.SetElementVisible(uiName,'Label_v',False)
        Fun.SetElementEnable(uiName,'Label_v',False)
        Fun.SetElementVisible(uiName,'Button_play',False)
        Fun.SetElementEnable(uiName,'Button_play',False)
    else:
        #翻译模式
        Fun.SetElementVisible(uiName,'Label_v',True)
        Fun.SetElementEnable(uiName,'Label_v',True)
        Fun.SetElementVisible(uiName,'Button_play',True)
        Fun.SetElementEnable(uiName,'Button_play',True)
    #取得四个选项信息并设置显示
    cur_v,word_list,answer_index=wordUtil.getOption(Fun.G_UserVarDict['mode'],index)
    Fun.SetText(uiName,'Label_v',cur_v)
    
    Fun.SetText(uiName,'Button_A',word_list[0])
    Fun.SetText(uiName,'Button_B',word_list[1])
    Fun.SetText(uiName,'Button_C',word_list[2])
    Fun.SetText(uiName,'Button_D',word_list[3])
    
    Fun.SetTKAttrib(uiName,'Button_A',"bg","#FFFFFF")
    Fun.SetTKAttrib(uiName,'Button_B',"bg","#FFFFFF")
    Fun.SetTKAttrib(uiName,'Button_C',"bg","#FFFFFF")
    Fun.SetTKAttrib(uiName,'Button_D',"bg","#FFFFFF")
    
    
#显示当前选中按钮的结果
def show_result(index,widgetName):
    if answer_index != index:
        #如果选项不对
        Fun.SetTKAttrib(uiName,widgetName,"bg","#F56C6C")
    else:
        #如果选项正确
        Fun.SetTKAttrib(uiName,widgetName,"bg","#67C23A")
        #更新进度条
        updateProgressBar()
        #显示下一个按钮
        Fun.SetElementVisible(uiName,'Button_6',True)
        Fun.SetElementEnable(uiName,'Button_6',True)
        Fun.SetImage(uiName,'Button_6',r"Resources\右箭头.png",True)
#更新进度条
def updateProgressBar():
    Fun.SetCurrentValue(uiName,'Progress_1', Fun.GetCurrentValue(uiName,'Progress_1')+1)
    text=str(Fun.GetCurrentValue(uiName,'Progress_1'))+"/"+str(Fun.G_UserVarDict['wordConfig']["learn"])
    Fun.SetText(uiName,'Label_2',text)    
#跳转到下一个单词 
def getNext():
    #隐藏下一个按钮
    Fun.SetElementVisible(uiName,'Button_6',False)
    Fun.SetElementEnable(uiName,'Button_6',False)
    global count
    count=count+1
    if count < Fun.G_UserVarDict['wordConfig']["learn"]:
        show_options(count)
        listen(cur_v)
    else:
        Fun.LoadUIDialog("EnglishStudyApp",'Frame_1','Success',None,ignoreSameUI=False)
        Fun.G_UserVarDict['wordConfig']["do"] += count
        wordUtil.saveRecord(count)
#Form 'Form_1's Event :Load
def Form_1_onLoad(uiName):
    #单词处理对象
    global wordUtil
    #当前单词和正确选项索引
    global cur_v,answer_index
    #当前练习过的单词数
    global count
    Fun.SetProgress(uiName,'Progress_1',Fun.G_UserVarDict['wordConfig']["learn"],0)
    text="0/"+str(Fun.G_UserVarDict['wordConfig']["learn"])
    Fun.SetText(uiName,'Label_2',text)
     
    count=0
    wordUtil=WordUtil()
    show_options(count)
    listen(cur_v)

写完这个界面代码,项目就完成啦!

运行的效果还是挺不错的!

三、打包发布

练习软件开发完成后,我们如果想让它发布给朋友们使用,最好还是做成完装包显得更加专业和友好,在PyMe中新加入了cx_Freeze打包模式,选择cx_Freeze后,勾选"打包成MSI安装包",即可一键生成MSI安装程序。

打包成MSI文件后,就可以进行安装使用啦!~

结语

在这一节里,我们学习了导航栏工具条的使用,也掌握了如何使用pyttsx3进行语音播放和打包MSI。学会这些之后,你将可以制作一些语音类小工具。

相关推荐
2401_841495642 小时前
深度卷积生成对抗网络(DCGAN)
人工智能·python·深度学习·神经网络·机器学习·生成对抗网络·深度卷积生成对抗网络
忧郁的橙子.2 小时前
26期_01_Pyhton函数进阶
python
充值修改昵称2 小时前
数据结构基础:B+树如何优化数据库性能
数据结构·b树·python·算法
AI殉道师2 小时前
FastScheduler:让 Python 定时任务变得优雅简单
开发语言·python
花间相见2 小时前
【JAVA开发】—— HTTP常见请求方法
java·开发语言·http
楼田莉子2 小时前
Linux系统小项目——“主从设计模式”进程池
linux·服务器·开发语言·c++·vscode·学习
小二·2 小时前
Python Web 开发进阶实战:AI 伦理审计平台 —— 在 Flask + Vue 中构建算法偏见检测与公平性评估系统
前端·人工智能·python
走粥2 小时前
选项式API与组合式API的区别
开发语言·前端·javascript·vue.js·前端框架
从此不归路2 小时前
Qt5 进阶【7】网络请求与 REST API 实战:QNetworkAccessManager 深度应用
开发语言·c++·qt