赵坤2017282110261
黄亦薇201728210260java
https://github.com/zkself/homework3
PS:建议使用chrome浏览器python
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | |
· Estimate | · 估计这个任务须要多少时间 | 10 | |
Development | 开发 | 500 | |
· Analysis | · 需求分析 (包括学习新技术) | 30 | |
· Design Spec | · 生成设计文档 | 30 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | |
· Design | · 具体设计 | 30 | |
· Coding | · 具体编码 | 400 | |
· Code Review | · 代码复审 | 60 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | |
Reporting | 报告 | 120 | |
· Test Report | · 测试报告 | 150 | |
· Size Measurement | · 计算工做量 | 10 | |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 20 | |
合计 | 640 |
拿到这个问题的时候,咱们俩面临的 第一个问题 就是,她没接触过GUI编程,对于GUI编程思想缺少认识,因此咱们决定页面布局让她来写。选择GUI库的时候咱们考虑过tkinter、pyqt、wxpython。最后选择了wxpython由于它文档全面而且demo较多。而后咱们考虑的 第二个问题 就是,页面上会有不少重复的控件,做用是显示题目、以及让用户输入答案、断定结果。至关于每道题都有3个控件,全部题目都是这样的经过3个控件组成的。因此我就在考虑能不能动态生成控件呢?好比说5道题那么页面上就有1至少5个控件这样会形成代码不简洁,而后就找到了python提供的locals函数,这种动态生成控件(变量)的思想是我在js中学到的,python中正好也提供这种方法。咱们面临的 第三个问题 就是计时器,计时器我发现wxpython没有现成的控件,因此必须得本身写,而咱们用了一个笨办法就是用while循环的方式计时,时间间隔靠sleep函数,虽然比较笨可是实际上来讲,pyhon执行每条代码的时间是很快很快的,基本上是us级别的因此循环一次的时间能够忽略不计。第四个问题 就是如何存储用户的记录,考虑到存储的信息不多且不是那么重要,若是用数据库来存储会增长代码的复杂性,咱们认为没有这个必要,最后采起的方式是直接以txt方式存储在用户当前打开的目录下便可。第五个问题 多语言,这也是中期汇报把不少同窗卡住的地方。说实话之前确实没接触过,最开始咱们也是想实在不行就是用if-else来弄吧,可是个人伙伴说,确定有聪明的办法,否则那么多大公司在开发多语言软件的时候都是这样if-else的?而后咱们就搜了一下资料,发现python有一个模块是专门解决多语言(i18n)问题的,而后这个问题才解决掉。总的来讲,此次开发咱们遇到的主要是这五个问题。c++
针对上面遇到的五个重要问题来详细说明一下实现过程。git
首先咱们知道,一个GUI程序用户体验取决于布局是否合理、美观。而GUI编程自己不是python的强项,而且因为python跨平台的特性,wxpython这个库主要的优势就是能解决跨平台GUI编程问题,其自己不管是开发工具仍是API设计都没法和windows平台下的GUI编程语言所媲美。换句话说就是原生界面很差看。它的编程逻辑是,首先在窗体上添加一个frame空间,而后再选择layout布局。且定位问题只能经过开发者本身去尝试,并不能作到c#那样在任何位置拖放控件。说实话这个布局问题确实让咱们头疼了一段时间,由于layout的选择直接决定了布局的方式、最后咱们选择了最灵活的layout————GirdBagSizer,这种布局能够随意在一行、一列放置任意控件。wxpython的布局逻辑是把frame分红行和列来看的,最后咱们调出了比较简明的布局。github
这个问题就是解决代码的简洁性。由于,试想一下咱们的页面最重要的部件就是一个题目(Label)、提供用户输入(Text)、显示判断结果(Label)由这三种控件组成。若是页面上一次显示5个题目就是15个控件,若是10题目就是30个控件,且若是手写这么多不利于后期的版本升级,意思就是我要从显示5个题改为显示10个题。因此咱们没有选择最笨的办法而是借鉴了Js里面的动态生成变量的方式,用for循环去动态生成变量(控件),这里就不得不说脚本语言有脚本语言的好处,若是换成java/c++这种语言这个问题无解。还有这种方式,解决了变量名能够用变量拼接,好比题目一的label就是lanbel1,题目二的label就是label2 。python提供了一个内置的方法locals来以以字典的方式存储这种动态生成的变量,而后咱们访问这些变量的时候直接个根据键值对的形式就能够取到了。还有就是事件绑定的问题,因为咱们设定的逻辑是用户输入完以后按回车直接出发textbox的回车事件,因此这些变量都是绑定的同一个事件,就是说多个事件源一个事件处理函数。chrome
这里又要感谢wxpython了,它里面没有计时的控件,因此没办法只能本身来写了。咱们的逻辑很简单,一个while循环里面有个变量每次循环自加1,而后判断是否达到60,若是达到60,分钟变量加1,此变量归0,继续判断,若是分钟变量达到60则小时变量加1,分钟、秒变量归0。而后sleep1秒。虽然这个办法很差,可是我测了一下,每条语句的执行时间是us级别的,因此精度基本不怎么受影响。别的方法确定有,可是咱们没想到。只能用这个折中的办法来代替了。数据库
前面分析了咱们为何用存取txt这种方式来解决这个问题。这里我讲一下咱们的逻辑,首先进入程序后判断本目录是否有record.txt这个文件,若是有则读取里面的数据显示在页面上,若是没有则新建一个record.txt。当用户选择关闭程序的时候把本次的统计结果保存到文件中。这样若是用户不当心删掉了文件也不会出现bug问题,无非就是记录丢失了。编程
利用Python提供的gettext模块能够解决这个问题。首先咱们把须要显示不一样语言的文字用_()这种形式作上记号,而后在python目录下的TOOls文件夹下找到i18n文件夹,cmd运行里面的pygettext.py文件会生成一个message.pot文件,用 非记事本 编辑器打开,使用非记事本的缘由是win中文版系统默认编码是gb2312,记事本也是,而且改不了。若是你用记事本打开那么无论你最后保存成什么编码都是gb2312。我用的sublime text打开并编辑的。编辑的内容就是以msgid 和msgstr的这种形式来把你须要翻译的字符串,手动翻译成对应的语言,例如 msgid "hello world" ,msgstr "你好世界"。而后保存成.po文件,接着运行此目录下另一个msgfmt.py文件用刚才保存的文件再生成一个.mo文件。而后把这两个文件移动到项目目录下便可。这样程序运行的时候就能按照你手动翻译的结果把_()标记的字符串转换成对应语言的翻译了。c#
页面布局以及控件循环生成windows
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition, size=wx.Size( 600, 500), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) gbSizer1 = wx.GridBagSizer(10, 10) gbSizer1.SetFlexibleDirection(wx.BOTH) gbSizer1.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) # 生成控件 self.btnStart = wx.Button(self, wx.ID_ANY, _(u"开始答题")) gbSizer1.Add(self.btnStart, span=(1, 1), pos=(1, 6)) self.labTime = wx.StaticText(self, wx.ID_ANY, _(u"时间:")) gbSizer1.Add(self.labTime, span=(1, 1), pos=( 2, 0), flag=wx.EXPAND, border=3) self.labTime1 = wx.StaticText(self, wx.ID_ANY, u"0:0:0") gbSizer1.Add(self.labTime1, span=(1, 1), pos=(2, 1), flag=wx.EXPAND, border=3) self.labRatio = wx.StaticText(self, wx.ID_ANY, u"") gbSizer1.Add(self.labRatio, span=(1, 1), pos=(2, 3), flag=wx.EXPAND, border=3) for i in range(1, 6):#循环生成控件 self.creatvar['self.labQues' + str(i)] = wx.StaticText(self, wx.ID_ANY, u"") gbSizer1.Add(self.creatvar['self.labQues' + str(i)], span=(1, 5), pos=(i + 2, 0), flag=wx.ALIGN_CENTER_VERTICAL, border=3) self.creatvar['self.texAns' + str(i)] = wx.TextCtrl(self, i - 1, wx.EmptyString) gbSizer1.Add(self.creatvar['self.texAns' + str(i)], pos=(i + 2, 6), flag=wx.ALIGN_CENTER_VERTICAL) self.creatvar['self.texAns' + str(i)].Disable() self.creatvar['self.labCor' + str(i)] = wx.StaticText(self, i + 4, u"") gbSizer1.Add(self.creatvar['self.labCor' + str(i)], pos=(i + 2, 7), flag=wx.ALIGN_CENTER_VERTICAL) self.btnNext = wx.Button(self, wx.ID_ANY, _(u"再来五题")) gbSizer1.Add(self.btnNext, span=(1, 1), pos=(9, 3)) self.btnPause = wx.Button(self, wx.ID_ANY, _(u"暂停")) gbSizer1.Add(self.btnPause, span=(1, 1), pos=(9, 6)) self.btnEnd = wx.Button(self, wx.ID_ANY, _(u"结束答题")) gbSizer1.Add(self.btnEnd, span=(1, 1), pos=(9, 8)) ################################################################# self.SetSizer(gbSizer1) self.Layout()
事件绑定
self.btnStart.Bind(wx.EVT_BUTTON, self.btnStartOnButtonClick) for i in range(1, 6):#动态控件绑定事件 self.creatvar['self.texAns' + str(i)].Bind(wx.EVT_TEXT_ENTER, self.texAnsOnTextEnter) self.btnNext.Bind(wx.EVT_BUTTON, self.btnNextOnButtonClick) self.btnPause.Bind(wx.EVT_BUTTON, self.btnPauseOnButtonClick) self.btnEnd.Bind(wx.EVT_BUTTON, self.btnEndOnButtonClick)
事件处理函数
def btnStartOnButtonClick(self, event): for i in range(1, 6): self.creatvar['self.texAns' + str(i)].Enable() tmp = initFix() self.creatvar['self.labQues' + str(i)].SetLabel("".join(printFix(tmp))) PostfixExp = changeToPostfix(tmp) self.res.append(PostfixExp) self.eve.set() t = threading.Thread(target=self.stopwatch, name='timer') t.start() self.btnStart.Disable() def texAnsOnTextEnter(self, event): tex = event.GetEventObject() value = tex.GetValue() index = tex.GetId() cor = CalculatePostfix(self.res[index]) if value == str(cor): self.creatvar['self.labCor' + str(index + 1)].SetLabel(_(u'正确')) self.ratio[0] += 1 self.ratio[1] += 1 self.labRatio.SetLabel( _(u'正确|总数') + ':' + str(self.ratio[0]) + '|' + str(self.ratio[1])) else: self.creatvar['self.labCor' + str(index + 1)].SetLabel(_(u'错误,正确答案是') + str(cor)) self.ratio[1] += 1 self.labRatio.SetLabel( _(u'正确|总数') + ':' + str(self.ratio[0]) + '|' + str(self.ratio[1])) tex.Disable() def btnNextOnButtonClick(self, event): self.res = [] for i in range(1, 6): tmp = initFix() self.creatvar['self.labQues' + str(i)].SetLabel("".join(printFix(tmp))) PostfixExp = changeToPostfix(tmp) self.res.append(PostfixExp) self.creatvar['self.texAns' + str(i)].Enable() self.creatvar['self.labCor' + str(i)].SetLabel('') self.creatvar['self.texAns' + str(i)].SetValue('') def btnPauseOnButtonClick(self, event): if self.btnPause.GetLabel() == _(u'暂停'): self.eve.clear() self.btnPause.SetLabel(_(u'开始')) else: self.eve.set() self.btnPause.SetLabel(_(u'暂停')) def btnEndOnButtonClick(self, event): self.eve.clear() writeFile(regularizeData(self.ratio)) dlg = wx.MessageDialog(None, _(u"您的记录已保存"), _(u"感谢使用"), wx.OK | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_OK: self.Close(True) dlg.Destroy()
计时器函数
def stopwatch(self): # 计时器 second = 0 minute = 0 hour = 0 while 1: if not self.eve.isSet():#判断eve变量是否为假 continue second += 1 time.sleep(1) if second == 60: minute += 1 second = 0 if minute == 60: hour += 1 minute = 0 second = 0 ntime = "%d:%d:%d" % (hour, minute, second) self.labTime1.SetLabel(ntime)
用户记录读写
# 初始化记录 content = readFile() if content: self.labRatio.SetLabel(_(u'正确|总数') + ': ' + content) else: self.labRatio.SetLabel(_(u'当前无记录'))
#写用户记录 def btnEndOnButtonClick(self, event): self.eve.clear() writeFile(regularizeData(self.ratio)) dlg = wx.MessageDialog(None, _(u"您的记录已保存"), _(u"感谢使用"), wx.OK | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_OK: self.Close(True) dlg.Destroy()
def regularizeData(ratio):#把数据变成想要的格式 recoard = str(ratio[0]) + '|' + str(ratio[1]) return recoard def readFile():#读文件,若是没有文件则生成新的 cupath = os.getcwd() fpath = cupath + '/recoard.txt' if os.path.exists(fpath): f = open(fpath) content = f.read() f.close() return content else: f = open(fpath, 'w') f.close() return 0 def writeFile(content):#写文件 cupath = os.getcwd() fpath = cupath + '/recoard.txt' f = open(fpath, 'w') f.write(content) f.close()
多语言
gettext.install('lang', './locale/', unicode=False) choices = ['简体中文', '繁體中文', 'English'] dialog = wx.SingleChoiceDialog(None, "语言选择", "请选择语言", choices) if dialog.ShowModal() == wx.ID_OK: langu = dialog.GetStringSelection() if langu == u'English': gettext.translation('lang', './locale/', languages=['en_US']).install(True) if langu == u'繁體中文': gettext.translation('lang', './locale/', languages=['zh_HK']).install(True)
首先选择语言
而后进入系统界面(简体中文)
繁体中文
英文
作题
暂定作题
更换题目
结束答题,存储记录
完善了上次做业的随机运算符个数问题、解决了动态生成重复控件功能、编写了基础的文件读写模块、解决了多语言问题、编写了测试用例
我在任务过程当中比较急躁多亏了黄亦薇能帮我及时整理思路分析问题。女生作事每每比较仔细,有时候代码里的问题她能看出来而我却须要屡次测试才能找到Bug在哪。
构建了基本的界面布局、编写了计时器功能、编写了显示题目处理用户输入并判断结果功能、使用coverage功能测试代码分支覆盖率
个人代码能力不强,赵坤也交给我了不少编码技巧,多语言问题原本他都要放弃了,而后我鼓励他一块儿继续找找资料而后才解决的了这个问题。
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 10 |
· Estimate | · 估计这个任务须要多少时间 | 10 | 10 |
Development | 开发 | 500 | 600 |
· Analysis | · 需求分析 (包括学习新技术) | 30 | 30 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 40 |
· Coding | · 具体编码 | 400 | 500 |
· Code Review | · 代码复审 | 60 | 70 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 120 | 150 |
· Test Report | · 测试报告 | 150 | 180 |
· Size Measurement | · 计算工做量 | 10 | 20 |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 640 | 770 |
我以为此次我主要掌握了多语言问题的处理方法,以及测试用例到底怎么编写。两我的结对编程最大的好处就是能够交换思路,有时候一我的容易钻牛角尖,而且找bug的时候别人看你的代码和本身看是不同的,旁观者清当局者迷。可是咱们仍是没解决括号的问题……由于咱们当时不想大改程序结构,由于后面的GUI也是挺须要时间来搞定的。
此次是赵坤一直在督促我快点写,如今看来要不是他的督促时间根本不够。他对于时间把控方面仍是须要我学习的。此次我学到了好多,GUI编程思想、多线程问题等。能够说大的方向都是他把控的个人工做主要是给他提出建议以及帮他分担任务。感谢赵坤对个人帮助,我会继续努力的。