Python Forum
Clock freezes - wx.python glib problem
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Clock freezes - wx.python glib problem
#1
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()
Reply
#2
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?
Reply
#3
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.
Reply
#4
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()
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Python clock menator01 2 2,090 May-14-2020, 10:23 PM
Last Post: menator01
  [PyQt] SSH port forwarding and connection to SQL Server in separate threads freezes jan_pips 25 10,791 Dec-03-2019, 04:28 PM
Last Post: Denni
  [Tkinter] Alarm Clock GUI tickandatock_ 1 4,109 Nov-10-2019, 02:52 AM
Last Post: Larz60+
  GUI freezes while executing a callback funtion when a button is pressed abi17124 5 7,485 Jul-10-2019, 12:48 AM
Last Post: FullOfHelp
  tkinter clock Ondrej 5 4,368 Jun-06-2019, 02:09 PM
Last Post: heiner55
  [Tkinter] tkinter freezes by clicking button Zatox11 33 25,717 Apr-10-2018, 09:03 AM
Last Post: Zatox11
  Running telnet loop freezes GUI reedhallen 1 3,444 Jan-27-2018, 10:24 PM
Last Post: j.crater

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020