Python Forum
Clock freezes - wx.python glib problem - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: Clock freezes - wx.python glib problem (/thread-5439.html)



Clock freezes - wx.python glib problem - strongheart - Oct-03-2017

new to python and wx, I wrote a simple clock program.
It works well for a while but eventually the graphics freeze.
Internally it still runs, but the graphics cease to update.
It unfreezes if I resize the frame and the print buffer releases this error message:

Error:
GLib-CRITICAL **: Source ID 4759 was not found when attempting to remove it
The id keeps increasing.
My guess is that the garbage collector loses track of dc.

What did I do wrong?

xClock.py
# -*- coding: utf-8 -*-
# 
import wx
import threading
import time
from datetime import datetime

#return 2 digit string from int
def fix(n):
	s=repr(n)
	if n<10:
		s='0'+s
	return s

class xClock(wx.Window):
	
	def __init__(self,p):
		super(xClock, self).__init__(p)
		self.W=400
		self.H=100
		self.SetSize(wx.Size(self.W,self.H))
		
		self.R=195
		self.img=None
		self.DRAWN=False
		self.timeText=""
		self.SetForegroundColour(wx.BLUE)
		self.font=self.GetFont()
		w,h,d,e =self.GetFullTextExtent('00:00:00')
		FE=[w,h,d,e]
		print ('GetFullTextExtent '+ repr(FE))
		self.font.SetPointSize(70)
		self.SetFont(self.font)
		w,h,d,e =self.GetFullTextExtent('00:00:00')
		FE=[w,h,d,e]
		print ('GetFullTextExtent '+ repr(FE))
		
		wx.EVT_PAINT(self,self.OnPaint)
		wx.EVT_WINDOW_DESTROY=self.stop()
		
		self.RUNNING=False
		self.ticker=None
		self.start()

	def OnPaint(self, evt):
		if self.img==None:
			self.draw()
			return False
		dc=wx.PaintDC(self)
		dc.BeginDrawing()
		w,h=self.img.GetSize()
		W,H=self.GetSize()
		x=(W-w)//2
		y=(H-h)//2
		dc.DrawBitmap(self.img,x,y)
		dc.EndDrawing()
		return True


	def draw(self):
		if self.img==None:
			self.img=wx.EmptyBitmap(self.W,self.H)
			self.DRAWN=False
		now=datetime.now()
		h=fix(now.hour)
		m=fix(now.minute)
		s=fix(now.second)
		t=h+":"+m+":"+s
		self.timeText=t
		dc = wx.MemoryDC()
		dc.SelectObject(self.img)
		dc.BeginDrawing()
		F=self.GetFont()
		dc.SetFont(F)
		w,h=dc.GetTextExtent(t)
		W,H=self.img.GetSize()
		x0=(self.W-w)//2
		y0=(self.H-h)//2
		dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
		dc.Clear()
		dc.SetTextForeground(wx.BLUE)
		dc.DrawText(t,x0,y0)
		self.DRAWN=True
		dc.EndDrawing()
		self.Refresh()
		

	def start(self):
		pass
		if self.ticker ==None or not self.ticker.is_alive():
			self.ticker=threading.Thread(None, self.run, "Ticker")
			print("start() "+self.ticker.getName())	
			self.ticker.start()

	def stop(self):
		self.RUNNING=False
		time.sleep(2)

	def run(self):
		self.RUNNING=True
		delay=1
		print("RUNNING")
		while self.RUNNING:
			self.draw()
			self.Refresh(False)
			time.sleep(delay)
			#T=threading.currentThread().getName() # just curious
			# print(self.timeText+' '), # prove it runs when frozen
		print(self.timeText)
		self.RUNNING=False
		
__init__.py
#!/usr/bin/python
# -*- coding: utf-8 -*-


import wx
from xClock import xClock

class XCFrame(wx.Frame):
	
	def __init__(self,parent):
		super(XCFrame, self).__init__(parent)
		self.SetTitle("XAOS CLOCK")
		xcp=xClock(self)
		sizer=wx.BoxSizer(wx.VERTICAL)
		sizer.Add(xcp,1,wx.EXPAND,wx.ALL)
		self.SetBackgroundColour(wx.CYAN)
		self.SetSizer(sizer)


class xaosApp(wx.App):
	
	def __init__(self):
		super(xaosApp, self).__init__()
		xcf=XCFrame(None)
		xcf.Show()
		

if __name__=='__main__':
	app=xaosApp()
	app.MainLoop()



RE: Clock freezes - wx.python glib problem - Larz60+ - Oct-03-2017

wx.PaintDC was depreciated with the release of wxpython phoenix.
I was trying to run, but cannot in python 3.6
Since you're new to python, why not use the latest version?


RE: Clock freezes - wx.python glib problem - snippsat - Oct-04-2017

There is own wx.Timer() that handles stuff like clock,
so then is no need to use threading which can freeze GUI if used wrong(interfere with GUI own main loop).
Here a analog clock example rewritten for Phoenix and Python 3.6.
See that it call wx.Timer().
import wx
import time

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
        self.timer.Start(1000)
        self.Show(True)

    def Draw(self, dc):
        t = time.localtime(time.time())
        st = time.strftime("%I:%M:%S", t)
        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()
        dc.SetFont(wx.Font(30, wx.SWISS, wx.NORMAL, wx.NORMAL))
        tw, th = dc.GetTextExtent(st)
        dc.DrawText(st, 20, 20)

    def OnTimer(self, evt):
        dc = wx.BufferedDC(wx.ClientDC(self))
        self.Draw(dc)

    def OnPaint(self, evt):
        dc = wx.BufferedPaintDC(self)
        self.Draw(dc)

if __name__ == '__main__':
    ex = wx.App()
    Example(None)
    ex.MainLoop()
As a note Threading and GUI can be difficult,that why most GUI toolkit has own method to deal with this.
Example for wxPython:
  • wx.PostEvent
  • wx.CallAfter
  • wx.CallLater
  • wx.Timer
wxPython and Threads.


RE: Clock freezes - wx.python glib problem - strongheart - Oct-10-2017

Thank-you folks, I went with the buffered paint DC and everything is smooth and solid.

Just guessing what was going wrong...
A) repainting an image every second and making a new dc probably jammed up the garbage collector ?
B) maybe the system tried to paint the image while it was being redrawn ?

Still a learning project in progress.....

###ClockProps.py
# -*- coding: utf-8 -*-
import wx
import DClockPane # may not need to import



BackgroundColor=wx.BLACK
TextColor=wx.CYAN
Draw_Frame=True #False
frame=wx.Rect(0,0,0,0)
frame_color=wx.GREEN

FontSize=100
FontFamily=wx.FONTFAMILY_DEFAULT

Show_Seconds=True
Show_Blink=False # annoyint, but could be useful as a flag
Time_sep=':'
Time_blink='.' # if Show_Blink then every half second Time_sep='.'

TimeFormat_hour=24 # might never use this, but it should be an option



# optional 'features'
Shadow_Effect=True
Shadow_color=wx.LIGHT_GREY
Shadow_x=3
Shadow_y=3
### DClockPane
# -*- coding: utf-8 -*-
import wx
import time
import ClockProps


def fix(n):
	N=repr(n)
	if n<10:
		N="0"+N
	return N

def ts(c=':'):
	b=" "
	t=time.localtime()
	h=fix(t[3])
	m=fix(t[4])
	s=fix(t[5])
	return b+h+c+m+c+s+b


class DClockPane(wx.Window):

	def __init__(self, p):
		super(DClockPane, self).__init__(p)
		font=self.GetFont()
		font.SetPointSize(ClockProps.FontSize)
		self.SetFont(font)
		self.SetBackgroundColour(ClockProps.BackgroundColor)
		w,h=self.textSize(font)
		print ("text size="+ repr([w,h] ))
		self.SetSize(wx.Size(w,h))
		self.drawFrame=ClockProps.Draw_Frame
		self.Box=None
		if self.drawFrame:
			ClockProps.frame=wx.Rect(0,0,w,h)
			self.Box=ClockProps.frame
		self.showSecs=ClockProps.Show_Seconds
		self.textColor=ClockProps.TextColor
		self.BLINK=ClockProps.Time_blink
		self.DELAY=1000
		if self.BLINK:
			self.DELAY=500
		self.TOCK=False
		self.SHADOW=ClockProps.Shadow_Effect
		self.Bind(wx.EVT_PAINT, self.OnPaint)
		self.timer = wx.Timer(self)
		self.Bind(wx.EVT_TIMER,self.run,self.timer )
		self.timer.Start(self.DELAY)




	def draw(self, dc):
		dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
		dc.Clear()
		#dc.SetFont(wx.Font(30, wx.SWISS, wx.NORMAL, wx.NORMAL))
		c=':'
		if self.BLINK:
			self.TOCK= not self.TOCK
			if self.TOCK:
				c='.'
		st=ts(c)
		tw, th = dc.GetTextExtent(st)
		w,h =self.GetSize()
		x=(w-tw)/2
		y=(h-th)/2
		if self.drawFrame:
			dc.SetPen(wx.Pen( ClockProps.frame_color ))
			dc.DrawRectanglePointSize( wx.Point(x,y), self.Box.GetSize())
		if self.SHADOW:
			dc.SetTextForeground(ClockProps.Shadow_color)
			dc.DrawText(st, x+ClockProps.Shadow_x, y+ClockProps.Shadow_y)

		dc.SetTextForeground(wx.CYAN)  #(wx.Brush(wx.CYAN, wx.SOLID))
		dc.DrawText(st, x, y)


	def run(self,e):
		dc = wx.BufferedDC(wx.ClientDC(self))
		self.draw(dc)


	def OnPaint(self, e):
		""" paint the buffer """
		dc = wx.BufferedPaintDC(self)
		self.draw(dc)

	def textSize(self, font):
		if font==None:
			font=self.GetFont()
		#z=font.GetSize()
		dc=wx.ClientDC(self)
		x,y=dc.GetTextExtent(" 88:88:88 ")
		return wx.Size(x,y)
#__init__.py
import wx
from DClockPane import DClockPane

class ClockFrame(wx.Frame):
	def __init__(self,p):
		super(ClockFrame, self).__init__(None)
		self.SetTitle("Steve's Clock")
		self.clock=DClockPane(self)
		sizer=wx.BoxSizer(wx.VERTICAL)
		sizer.Add(self.clock,1,wx.EXPAND,wx.ALL)
		self.SetBackgroundColour(wx.CYAN)
		self.SetSizer(sizer)



class DClock(wx.App):
	def __init__(self):
		super(DClock, self).__init__()
		cf=ClockFrame(None)
		cf.Show()


if __name__=='__main__':
    app=DClock()
    app.MainLoop()