wxPython:一曲MFC的挽歌,理想主义的绝唱( 九 )


wxPython:一曲MFC的挽歌,理想主义的绝唱
文章图片
wxPython:一曲MFC的挽歌,理想主义的绝唱】6.3.定时器和线程
在一个桌面程序中 , GUI线程是主线程 , 其他线程若要更新显示内容 , Tkinter使用的是类型对象 , PyQt使用的信号和槽机制 , wxPython则相对原始:它允许子线程更新GUI , 但需要借助于wx.CallAfter()函数 。
这个例子里面设计了一个数字式钟表 , 一个秒表 , 秒表显示精度十分之一毫秒 。 从代码设计上来说没有任何难度 , 实现的方法有很多种 , 可想要达到一个较好的显示效果 , 却不是一件容易的事情 。 请注意体会wx.CallAfter()的使用条件 。
importwximporttimeimportthreadingclassMainFrame(wx.Frame):"""桌面程序主窗口类"""def__init__(self):"""构造函数"""wx.Frame.__init__(self,parent=None,style=wx.CAPTION|wx.SYSTEM_MENU|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SIMPLE_BORDER)self.SetTitle('定时器和线程')self.SetIcon(wx.Icon('res/wx.ico',wx.BITMAP_TYPE_ICO))self.SetBackgroundColour((224,224,224))self.SetSize((320,300))self._init_ui()self.Center()def_init_ui(self):"""初始化界面"""font=wx.Font(30,wx.DECORATIVE,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_NORMAL,False,'Monaco')self.clock=wx.StaticText(self,-1,'08:00:00',pos=(50,50),size=(200,50),style=wx.TE_CENTER|wx.SUNKEN_BORDER)self.clock.SetForegroundColour(wx.Colour(0,224,32))self.clock.SetBackgroundColour(wx.Colour(0,0,0))self.clock.SetFont(font)self.stopwatch=wx.StaticText(self,-1,'0:00:00.00',pos=(50,150),size=(200,50),style=wx.TE_CENTER|wx.SUNKEN_BORDER)self.stopwatch.SetForegroundColour(wx.Colour(0,224,32))self.stopwatch.SetBackgroundColour(wx.Colour(0,0,0))self.stopwatch.SetFont(font)self.timer=wx.Timer(self)self.Bind(wx.EVT_TIMER,self.on_timer,self.timer)self.timer.Start(50)self.Bind(wx.EVT_KEY_DOWN,self.on_key_down)self.sec_last=Noneself.is_start=Falseself.t_start=Nonethread_sw=threading.Thread(target=self.StopWatchThread)thread_sw.setDaemon(True)thread_sw.start()defon_timer(self,evt):"""定时器函数"""t=time.localtime()ift.tm_sec!=self.sec_last:self.clock.SetLabel('%02d:%02d:%02d'%(t.tm_hour,t.tm_min,t.tm_sec))self.sec_last=t.tm_secdefon_key_down(self,evt):"""键盘事件函数"""ifevt.GetKeyCode()==wx.WXK_SPACE:self.is_start=notself.is_startself.t_start=time.time()elifevt.GetKeyCode()==wx.WXK_ESCAPE:self.is_start=Falseself.stopwatch.SetLabel('0:00:00.00')defStopWatchThread(self):"""线程函数"""whileTrue:ifself.is_start:t=time.time()-self.t_startti=int(t)wx.CallAfter(self.stopwatch.SetLabel,'%d:%02d:%02d.%.02d'%(ti//3600,ti//60,ti%60,int((t-ti)*100)))time.sleep(0.02)if__name__=='__main__':app=wx.App()frame=MainFrame()frame.Show()app.MainLoop()代码运行界面如下图所示 。 界面上方的时钟一直再跑 , 下方的秒表则是按键启动或停止 。
wxPython:一曲MFC的挽歌,理想主义的绝唱
文章图片
6.4.DC绘图
DC是DeviceContext的缩写 , 字面意思是设备上下文——我一直不能正确理解DC这个中文名字 , 也找不到更合适的说法 , 所以 , 我坚持使用DC而不是设备上下文 。 DC可以在屏幕上绘制点线面 , 当然也可以绘制文本和图像 。 事实上 , 在底层所有控件都是以位图形式绘制在屏幕上的 , 这意味着 , 我们一旦掌握了DC这个工具 , 就可以自己创造我们想要的控件了
DC有很多种 , PaintDC , ClientDC , MemoryDC等 。 通常 , 我们可以使用ClientDC和MemoryDC , PaintDC是发生重绘事件(wx.EVT_PAINT)时系统使用的 。 使用ClientDC绘图时 , 需要记录绘制的每一步工作 , 不然 , 系统重绘时会令我们前功尽弃——这是使用DC最容易犯的错误 。
importwxclassMainFrame(wx.Frame):"""桌面程序主窗口类"""def__init__(self):"""构造函数"""wx.Frame.__init__(self,parent=None,style=wx.CAPTION|wx.SYSTEM_MENU|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SIMPLE_BORDER)self.SetTitle('使用DC绘图')self.SetIcon(wx.Icon('res/wx.ico',wx.BITMAP_TYPE_ICO))self.SetBackgroundColour((224,224,224))self.SetSize((800,480))self._init_ui()self.Center()def_init_ui(self):"""初始化界面"""self.palette=wx.Panel(self,-1,style=wx.SUNKEN_BORDER)self.palette.SetBackgroundColour(wx.Colour(0,0,0))btn_base=wx.Button(self,-1,'文字和图片',size=(100,-1))sizer_max=wx.BoxSizer()sizer_max.Add(self.palette,1,wx.EXPAND|wx.LEFT|wx.TOP|wx.BOTTOM,5)sizer_max.Add(btn_base,0,wx.ALL,20)self.SetAutoLayout(True)self.SetSizer(sizer_max)self.Layout()btn_base.Bind(wx.EVT_BUTTON,self.on_base)self.palette.Bind(wx.EVT_MOUSE_EVENTS,self.on_mouse)self.palette.Bind(wx.EVT_PAINT,self.on_paint)self.xy=Noneself.lines=list()self.img=wx.Bitmap('res/forever.png',wx.BITMAP_TYPE_ANY)self.update_palette()defon_mouse(self,evt):"""移动鼠标画线"""ifevt.EventType==10030:#左键按下self.xy=(evt.x,evt.y)elifevt.EventType==10031:#左键弹起self.xy=Noneelifevt.EventType==10036:#鼠标移动ifself.xy:dc=wx.ClientDC(self.palette)dc.SetPen(wx.Pen(wx.Colour(0,224,0),2))dc.DrawLine(self.xy[0],self.xy[1],evt.x,evt.y)self.lines.append((self.xy[0],self.xy[1],evt.x,evt.y))self.xy=(evt.x,evt.y)defon_base(self,evt):"""DC基本方法演示"""img=wx.Bitmap('res/forever.png',wx.BITMAP_TYPE_ANY)w,h=self.palette.GetSize()dc=wx.ClientDC(self.palette)dc.SetPen(wx.Pen(wx.Colour(224,0,0),1))dc.SetBrush(wx.Brush(wx.Colour(0,80,80)))dc.DrawRectangle(10,10,w-22,h-22)dc.DrawLine(10,h/2,w-12,h/2)dc.DrawBitmap(img,10,10)dc.SetTextForeground(wx.Colour(224,224,224))dc.SetFont(wx.Font(16,wx.FONTFAMILY_DEFAULT,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_NORMAL,False,'ComicSansMS'))dc.DrawText('霜重闲愁起',100,360)dc.DrawRotatedText('春深风也疾',400,360,30)defon_paint(self,evt):"""响应重绘事件"""dc=wx.PaintDC(self.palette)self.paint(dc)defupdate_palette(self):"""刷新画板"""dc=wx.ClientDC(self.palette)self.paint(dc)defpaint(self,dc):"""绘图"""dc.Clear()dc.SetPen(wx.Pen(wx.Colour(0,224,0),2))forlineinself.lines:dc.DrawLine(line[0],line[1],line[2],line[3])if__name__=='__main__':app=wx.App()frame=MainFrame()frame.Show()app.MainLoop()代码运行界面如下图所示 。