I'm trying to create a GUI timer using wxPython. A Start button is clicked, the timer starts and a label displays the timer counting the seconds up to minutes and maybe to hours. A Stop button stops the timer. I don't know how to have the timer constantly displayed in the label. I tried a while True loop but it seems SetLabel() or Time() wants to display once and is waiting for an end to the loop.
import wx
class Timer(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(400, 350))
self.main()
self.Centre()
self.Show()
def main(self):
panel = wx.Panel(self)
sizer = wx.GridBagSizer(5, 0)
self.timer = wx.StopWatch()
self.label = wx.StaticText(panel)
sizer.Add(self.label, pos=(0, 0), flag=wx.ALIGN_CENTER)
button_start = wx.Button(panel, label='Start')
self.Bind(wx.EVT_BUTTON, self.OnStart, button_start)
sizer.Add(button_start, pos=(1, 0), flag=wx.ALIGN_CENTER)
button_stop = wx.Button(panel, label='Stop')
self.Bind(wx.EVT_BUTTON, self.OnStop, button_stop)
sizer.Add(button_stop, pos=(2, 0), flag=wx.ALIGN_CENTER)
sizer.AddGrowableRow(0)
sizer.AddGrowableRow(1)
sizer.AddGrowableRow(2)
sizer.AddGrowableCol(0)
panel.SetSizer(sizer)
def OnStart(self, event):
self.timer.Start()
while True:
self.label.SetLabel(str(self.timer.Time()))
def OnStop(self, event):
self.timer.Pause()
if __name__ == '__main__':
app = wx.App()
Timer(None, 'Timer')
app.MainLoop()
You never return from OnStart, so the event loop is hung waiting for the event handler to finish. All event handlers need to return immediately so that other events can be processed. Use a wx.Timer handler to update the stopwatch label.
It sounds like you want to start from 00:00:00 and count up, right? You should take a look at the LEDNumberCtrl demo in the wxPython demo. The third example in it is very similar to what you're looking for. I think you could get it to work with minimal fiddling.
Related
In my project, I use wx.stc.StyledTextCtrl(). I bind the events of key down and key up. When I want to add a letter into the TextCtrl, I don’t skip the event, in some reasons, I use the method AddText() in order to add a text. When the text is long and the ScrollBar (of the width of the screen) is opened I want that ScrollBar will be at the position where I can see the letter that is added (will move automatically as it should be). currently the ScrollBar always stays at the left side of the screen.
I'm searching for a function that can do that.
when the letters are over the width of the TextCtrl (over pos 300) the ScrollBar is still doesn't move. I want that it will be like the messageTxt in the right side of the frame.
Here is a basic code that present my problem:
import wx
import wx.stc
def on_key_down(event):
pass
def on_key_up(event):
key_code = event.GetKeyCode()
messageTxt.AddText(chr(key_code))
app = wx.App()
frame = wx.Frame(None, -1, title='2', pos=(0, 0), size=(500, 500))
frame.Show(True)
messageTxt = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(0, 0), size=(300, 300),
style=wx.TE_MULTILINE, name="File")
messageTxt.Bind(wx.EVT_KEY_DOWN, on_key_down)
messageTxt.Bind(wx.EVT_KEY_UP, on_key_up)
messageTxt2 = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(320, 0), size=(150, 150),
style=wx.TE_MULTILINE, name="File")
app.SetTopWindow(frame)
app.MainLoop()
Clearly another event is ocurring after the key event, which is being missed.
Use event.Skip() in the functions bound to a key event.
Skip(self, skip=True)
This method can be used inside an event handler to control whether further event handlers bound to this event will be called after the current one returns.
Without Skip (or equivalently if Skip(false) is used), the event will not be processed any more. If Skip(true) is called, the event processing system continues searching for a further handler function for this event, even though it has been processed already in the current handler.
In general, it is recommended to skip all non-command events to allow the default handling to take place. The command events are, however, normally not skipped as usually a single command such as a button click or menu item selection must only be processed by one handler.
import wx
import wx.stc
def on_key_down(event):
event.Skip()
pass
def on_key_up(event):
key_code = event.GetKeyCode()
messageTxt.AddText(chr(key_code))
event.Skip()
app = wx.App()
frame = wx.Frame(None, -1, title='2', pos=(0, 0), size=(500, 500))
frame.Show(True)
messageTxt = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(0, 0), size=(300, 300),
style=wx.TE_MULTILINE, name="File")
messageTxt.Bind(wx.EVT_KEY_DOWN, on_key_down)
messageTxt.Bind(wx.EVT_KEY_UP, on_key_up)
messageTxt2 = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(320, 0), size=(150, 150),
style=wx.TE_MULTILINE, name="File")
app.SetTopWindow(frame)
app.MainLoop()
I've added a different answer because the first issue is still valid, just not for your question, as I interpret it.
I assume this is a mock-up of your problem, if it isn't, let me know and I'll delete it.
The "server updates" are simulated by a timer, the character is added, then we simply move the cursor 1 character to the right.
import wx
import wx.stc
server_sends=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','T','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','T']
class MyFrame(wx.Frame):
def __init__(self, parent, id=wx.ID_ANY, title=""):
super(MyFrame, self).__init__(parent, id, title)
self.SetSize((500,500))
self.panel = wx.Panel(self, -1 , size=(500,500))
self.messageTxt = wx.stc.StyledTextCtrl(self.panel, id=wx.ID_ANY, pos=(0, 0), size=(300, 300),
style=wx.TE_MULTILINE, name="File")
self.messageTxt2 = wx.stc.StyledTextCtrl(self.panel, id=wx.ID_ANY, pos=(320, 0), size=(150, 150), style=wx.TE_MULTILINE, name="File")
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(500)
self.cnt = 0
self.Show()
def OnTimer(self, event):
print (server_sends[self.cnt])
self.Server_Update(server_sends[self.cnt])
self.cnt += 1
if self.cnt > len(server_sends) - 1 :
self.timer.Stop()
def Server_Update(self,char):
self.messageTxt.AddText(char)
self.messageTxt.CharRight()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame(None,title="The Main Frame")
app.MainLoop()
In wxpython, I want to have a window with a picture that changes based on use of toolbar buttons with text controls on top of the picture. When I click the toolbar buttons, I am posting an erase background event, then capturing the erase event, and redrawing the new background from there (base on this).
Mostly works well, except that the text controls cease to be drawn once I redraw the background. They're still there, just not drawn.
Here is a simplified code that demonstrates the problem. If you run this code and click the button to toggle drawing the background image or not, the text controls disappear.:
import wx
import wx.lib.inspection
class PanelWithDrawing(wx.Panel):
def __init__(self, parent):
super(PanelWithDrawing, self).__init__(parent, size=(100, 40))
self.showbmp = False
self.txt = wx.TextCtrl(self, pos=(10, 10))
def onErase(self, dc):
if self.showbmp:
# dc.DrawBitmap(wx.Bitmap('background.png', 0, 0)
dc.DrawRectangle(0, 0, 40, 40) # use a drawing instead so you don't have to find a png
class Toolbar(wx.ToolBar):
def __init__(self, parent):
super(Toolbar, self).__init__(parent, -1)
self.AddLabelTool(wx.ID_SAVE, "Record", wx.Bitmap("picture.png", wx.BITMAP_TYPE_ANY), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title)
self.toolbar = Toolbar(self)
self.SetToolBar(self.toolbar)
self.toolbar.Realize()
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.panel1 = PanelWithDrawing(self.panel)
vbox.Add(self.panel1)
# self.panel2 = PanelWithText(self.panel)
# vbox.Add(self.panel2)
self.panel.SetSizer(vbox)
self.Centre()
self.Show()
self.toolbar.Bind(wx.EVT_TOOL, self.onButton)
self.panel1.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
def onErase(self, evt):
try:
dc = evt.GetDC()
except:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
self.panel1.onErase(dc)
def onButton(self, evt):
self.panel1.showbmp = not self.panel1.showbmp
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND))
if __name__ == '__main__':
app = wx.App()
Example(None, title='Example')
wx.lib.inspection.InspectionTool().Show() # use this for debugging GUI design
app.MainLoop()
How do I tell wxpython to draw all the non-background stuff again? Alternatively, how do I not un-draw it in the first place?
After working on it for a few days, I got it! And the answer is trivially simple (as usual).
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND)) should be replaced with self.Refresh() to refresh the whole frame and not just force a specific (and apparently unsafe) redraw.
I have a medium sized desktop application created with wxPython. I want to implement a session facility in this application. After some amount of time of inactivity, the application should log the user out and show login screen automatically. What will be the best way to accomplish this in wxPython?
The application uses wxPython 2.8.12.1 with Python 2.7 within Windows 8, 7, XP.
EDIT 1
Binding EVT_MOTION to wx.Frame and wx.Panel not working. It is working if I bind EVT_MOTION to all individual objects. Is there any way to get event bubbled to the outermost parent (wx.Frame)?
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Checking EVT_MOTION with Frame")
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(sizer)
sizer.Add(wx.Button(panel, -1, "Button 1"), 1, wx.EXPAND)
sizer.Add(wx.Button(panel, -1, "Button 2"), 1, wx.EXPAND)
sizer.Add(wx.Button(panel, -1, "Button 3"), 1, wx.EXPAND)
self.Bind(event=wx.EVT_MOTION, handler=self.OnMotion)
self.Show()
def OnMotion(self, event):
print "EVT_MOTION: " + str(event.GetEventObject())
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
I would use a wx.Timer. You can restart it every time the user moves their mouse (wx.EVT_MOTION) or presses a key (wx.EVT_KEY_DOWN). I wrote up a tutorial on using wx.Timers that you might find helpful:
http://www.blog.pythonlibrary.org/2009/08/25/wxpython-using-wx-timers/
Here's the one of the examples from that tutorial that implements a super simple wx.Timer:
import wx
import time
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Timer Tutorial 1",
size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
self.toggleBtn = wx.Button(panel, wx.ID_ANY, "Start")
self.toggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
def onToggle(self, event):
btnLabel = self.toggleBtn.GetLabel()
if btnLabel == "Start":
print "starting timer..."
self.timer.Start(1000)
self.toggleBtn.SetLabel("Stop")
else:
print "timer stopped!"
self.timer.Stop()
self.toggleBtn.SetLabel("Start")
def update(self, event):
print "\nupdated: ",
print time.ctime()
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
I'm so troubled by this problem:
I've create a ListCtrl object, a TextCtrl object, and a button. First I fill some data into the ListCtrl object, when I press the button, it will append some strings into TextCtrl object and use SetStringItem to modify ListCtrl object.
As you can see in the button function, I've added time.sleep(2) in each loop. When I've got is when the button is pressed, TextCtrl will be refreshed every time the strings is inserted, but ListCtrl just freeze until the LOOP IS FINISHED, then it will display the correct strings.
I want to know how to refresh the ListCtrl object as soon as SetStringItem is called.
Any help is deeply appreciated.
Here is the code:
import wx
import sys
import time
class Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, size=(450, 450))
self.panel = wx.Panel(self)
self.dl = wx.ListCtrl(self,-1,size=(300,100),style=wx.LC_REPORT)
self.dl.InsertColumn(0, 'File')
self.dl.InsertColumn(1, 'Progress')
self.dl.InsertColumn(2, 'State')
for row in range(3):
labels = [l+str(row) for l in "FILE PERCENT STATE".split()]
# sys.maxint inserts at the end of the list
index = self.dl.InsertStringItem(sys.maxint, labels[0])
self.dl.SetStringItem(index, 1, labels[1])
self.dl.SetStringItem(index, 2, labels[2])
self.Show(True)
button2 = wx.Button(self, label=u"test", pos=(15, 200), size=(60, 25))
self.Bind(wx.EVT_BUTTON, self.test, button2)
self.text = wx.TextCtrl(self, -1, pos=(80, 200), size=(200, 175), style=wx.TE_MULTILINE)
def test(self,event):
for i in range(3):
self.dl.SetStringItem(i,1,"HELLO")
self.text.AppendText("HELLO")
time.sleep(2)
app = wx.App()
Frame(None)
app.MainLoop()
The problem is that time.sleep blocks your GUI, what you will need to do to get the effect that you are trying for is:
On your button press add the first item & start a 2 second wx.Timer with an event handler/
In the event handler add the next string or if there are no more to add cancel the timer.
I've changed my code to this, and it works, thanks steve
import wx
import sys
import time
class Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, size=(450, 450))
self.panel = wx.Panel(self)
self.dl = wx.ListCtrl(self,-1,size=(300,100),style=wx.LC_REPORT)
self.dl.InsertColumn(0, 'File')
self.dl.InsertColumn(1, 'Progress')
self.dl.InsertColumn(2, 'State')
for row in range(3):
labels = [l+str(row) for l in "FILE PERCENT STATE".split()]
# sys.maxint inserts at the end of the list
index = self.dl.InsertStringItem(sys.maxint, labels[0])
self.dl.SetStringItem(index, 1, labels[1])
self.dl.SetStringItem(index, 2, labels[2])
self.Show(True)
self.timer = wx.Timer(self,-1)
#self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.Bind(wx.EVT_TIMER, self.test1, self.timer)
button2 = wx.Button(self, label=u"test", pos=(15, 200), size=(60, 25))
self.Bind(wx.EVT_BUTTON, self.test, button2)
self.text = wx.TextCtrl(self, -1, pos=(80, 200), size=(200, 175), style=wx.TE_MULTILINE)
self.z=0
def test(self,event):
self.timer.Start(3000)
def test1(self,event):
for i in range(1):
self.dl.SetStringItem(self.z,1,"HELLO")
self.text.AppendText("HELLO")
self.z+=1
if self.z >2 :
self.timer.Stop()
app = wx.App()
Frame(None)
app.MainLoop()
I have a frame that exists as a start up screen for the user to make a selection before the main program starts. After the user makes a selection I need the screen to stay up as a sort of splash screen until the main program finishes loading in back.
I've done this by creating an application and starting a thread:
class App(wx.App):
'''
Creates the main frame and displays it
Returns true if successful
'''
def OnInit(self):
try:
'''
Initialization
'''
self.newFile = False
self.fileName = ""
self.splashThread = Splash.SplashThread(logging, self)
self.splashThread.start()
#...More to the class
which launches a frame:
class SplashThread(threading.Thread):
def __init__(self, logger, app):
threading.Thread.__init__(self)
self.logger = logger
self.app = app
def run(self):
frame = Frame(self.logger, self.app)
frame.Show()
The app value is needed as it contains the callback which allows the main program to continue when the user makes their selection. The problem is that the startup screen only flashes for a millisecond then goes away, not allowing the user to make a selection and blocking the rest of start up.
Any ideas? Thanks in advance!
You don't need threads for this. The drawback is that the splash window will block while loading but that is an issue only if you want to update it's contents (animate it) or if you want to be able to drag it. An issue that can be solved by periodically calling wx.SafeYield for example.
import time
import wx
class Loader(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
self.btn1 = wx.Button(self, label="Option 1")
self.btn2 = wx.Button(self, label="Option 2")
sizer.Add(self.btn1, flag=wx.EXPAND)
sizer.Add(self.btn2, flag=wx.EXPAND)
self.btn1.Bind(wx.EVT_BUTTON, self.OnOption1)
self.btn2.Bind(
wx.EVT_BUTTON, lambda e: wx.MessageBox("There is no option 2")
)
def OnOption1(self, event):
self.btn1.Hide()
self.btn2.Hide()
self.Sizer.Add(
wx.StaticText(self, label="Loading Option 1..."),
1, wx.ALL | wx.EXPAND, 15
)
self.Layout()
self.Update()
AppFrame(self).Show()
class AppFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
time.sleep(3)
parent.Hide()
# the top window (Loader) is hidden so the app needs to be told to exit
# when this window is closed
self.Bind(wx.EVT_CLOSE, lambda e: wx.GetApp().ExitMainLoop())
app = wx.PySimpleApp()
app.TopWindow = Loader()
app.TopWindow.Show()
app.MainLoop()