I have a GUI with buttons 'OK' and 'Cancel' clicking on which creates a child threads which pops up a dialog box saying 'OK/Cancel button is pressed'. Now when i click on OK button, i want child thread calling it must wait for another process(may be another dialogbox saying wait) and then must pop up message 'OK button is pressed'.
I used wx.timer for childthread to wait but could not get it worked.
How to make a child thread pause and continue(like an interrupt) when the process is done?
Below find my trials!
import wx, time
from threading import Thread
ID_RUN = 101
ID_RUN2 = 102
class ChildThread_OK(Thread):
def __init__(self, myframe):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.myframe = myframe
self._want_abort = True
def run(self):
if self._want_abort is True:
self.waitevent()
wx.CallAfter(self.myframe.AfterRun, 'Ok button pressed')
def waitevent(self):
wx.CallAfter(self.myframe.message,"Oops!!there is another process running wait for it to finish. Closing this dialog box in 2s...")
def closeit(self, event):
self.dialogBox.Destroy()
class ChildThread_Cancel(Thread):
def __init__(self, myframe):
Thread.__init__(self)
self.myframe = myframe
def run(self):
wx.CallAfter(self.myframe.AfterRun, "Cancel button pressed")
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title)
panel = wx.Panel(self, -1)
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
mainSizer.Add(wx.Button(panel, ID_RUN, "OK"))
mainSizer.Add(wx.Button(panel, ID_RUN2, "Cancel"))
panel.SetSizer(mainSizer)
mainSizer.Fit(self)
wx.EVT_BUTTON(self, ID_RUN, self.onRun)
wx.EVT_BUTTON(self, ID_RUN2, self.onRun2)
def onRun(self, event):
self.child = ChildThread_OK(myframe=self)
self.child.daemon = True
self.child.start()
def onRun2(self, event):
self.child2 = ChildThread_Cancel(myframe = self)
self.child2.daemon = True
self.child2.start()
def AfterRun(self, msg):
dlg = wx.MessageDialog(self, msg, "Message", wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def message(self,msg):
dlg = wx.BusyInfo(msg)
time.sleep(5)
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, "My GUI")
frame.Show(True)
frame.Centre()
return True
app = MyApp(0)
app.MainLoop()
First: You cannot use timer or message box in a child thread, you can only use them in main thread. So the methods below are not meaningful.
def waitevent(self):
self.timer = wx.Timer(self, 1)
self.Bind(wx.EVT_TIMER, self.closeit, self.timer)
self.timer.Start(2000, wx.TIMER_ONE_SHOT)
wx.CallAfter(self.AfterRun, 'Ok button pressed')
print 'Oops!!there is another process running wait for it to finish. Closing this dialog box in 2s...'
def closeit(self, event):
self.dialogBox.Destroy()
Second: If you use message boxes to display something, main thread is suspended and your code after message box is run after message box is closed. You can use wx.StaticText or wx.TextCtrl on main panel to show the messages or you can create new frames mimicing message boxes and they dont suspend the program and can be easily destroyed OR you can make a custom message dialog waiting for a variable to change (e.g. True/False). There is a custom message dialog example in wxPython demo.
Third: You should learn the events and queues in python's multithreading module to help you control the child threads. Child thread can sleep until an event is set or can wait until there is something in a queue.
Related
I have window A with some graphs and a button "NEXT". When the user presses the button, window B opens with some questions about the graphs on window A. It also has a button "Done". Once "Done" is pressed, window A, and B close.
Problem: I don't get window A to close and no error messages in the console.
Window A:
class Window_A(QDialog):
def __init__(self, parent = None):
super(Window_A, self).__init__(parent,flags=Qt.WindowMinimizeButtonHint|Qt.WindowCloseButtonHint)
self.setWindowTitle("Window A")
self.initUI()
def initUI(self):
# Load the images
[...]
#"NEXT button"
self.b_next = QPushButton("NEXT")
self.b_next.clicked.connect(self.open_next)
#Layout
[...]
def close_win(self):
self.close()
def open_next(self):
self.next= Window_B()
self.next.show()
Window B:
class Window_B(QDialog):
def __init__(self):
super(Window_B, self).__init__()
self.setWindowTitle("Window_B")
self.initUI()
def initUI(self):
#define text labels
[...]
#done button
self.d_button = QPushButton("Done")
self.d_button.clicked.connect(self.on_click)
self.d_button.clicked.connect(lambda:self.close())
#layout for text (questions)
[...]
def on_click(self):
Window_A().close_win()
In your window_B code, you do not know the reference to the correct window_A object.
So your line:
def on_click(self):
Window_A().close_win()
creates a new Window_A object, calls its close function immediately, and this objects is garbage collected asap.
You will need to pass a reference to A in the B object.
For instance:
in window_A.py
def open_next(self):
self.next= Window_B()
self.next.show()
self.next.ref_to_close_father = self
in window_B.py
def on_click(self):
self.ref_to_close_father.close_win()
This should be better (but your example is not reproducible, so there might be another problem).
By the way,
self.d_button.clicked.connect(lambda:self.close())
could simply be
self.d_button.clicked.connect(self.close)
Hi I'm new to python and have started developing a GUI using wxpython. I just want to know the basic threading operation in wxpython. I have a main process which has OK & Cancel Buttons and a child process derived from main with the OK and Cancel Buttons. I want to implement threading such a way that clicking OK button on main process must send a message to child process and child process must look for if any of it's process is running, if so, it has to wait for that process to finish and then receive the message from main process.Similarly the same with the Cancel button in the main process.
Basically i want to see how child process receives message from main process and both work parallel.
I'm trying with wx.CallAfter and wx.PostEvent and i'm confused with the threading concept here. Please someone help me.
Thanks in advance
Multithreading in wxpython is not different than in python. wx.CallAfter and threading example shows how you use both. wx.CallAfter waits for the event to be completed and run the handler in the main thread. Additionally, you can use timers (wx.Timer) to check child processes and send/receive data.
Here is a link for wx.PostEvent showing how to use it. In this example, you create custom event, bind it to a handler in main thread. After that you post the event in a worker thread and attach some data as well. Your event handler receives the event and do some stuff in the main thread.
So, there are important questions arising when you use multithreading
When to start the child threads,
Which communication mechanism is used between threads,
How to stop the threads in a safe way when necessary,
etc.
I would prefer wx.Timer + Queue module. I can regularly check the queue with timers or with user events and send something (i.e. None) through queue to stop threads.
Note: Long running jobs in main thread can make GUI unresponsive.
I got it right. THanks to #ozy
import threading
import wx
from threading import Thread
ID_RUN = 101
ID_RUN2 = 102
class ChildThread(Thread):
def __init__(self, myframe):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.myframe = myframe
def run(self):
wx.CallAfter(self.myframe.AfterRun, 'Ok button pressed')
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title)
panel = wx.Panel(self, -1)
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
mainSizer.Add(wx.Button(panel, ID_RUN, "OK"))
mainSizer.Add(wx.Button(panel, ID_RUN2, "Cancel"))
panel.SetSizer(mainSizer)
mainSizer.Fit(self)
wx.EVT_BUTTON(self, ID_RUN, self.onRun)
wx.EVT_BUTTON(self, ID_RUN2, self.onRun2)
def onRun(self, event):
self.child = ChildThread(myframe=self)
self.child.daemon = True
self.child.start()
def onRun2(self, event):
self.child2 = threading.Thread(None, self.__run)
self.child2.daemon = True
self.child2.start()
def __run(self):
wx.CallAfter(self.AfterRun, "Cancel button pressed")
def AfterRun(self, msg):
dlg = wx.MessageDialog(self, msg, "Message", wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, "My GUI")
frame.Show(True)
frame.Centre()
return True
app = MyApp(0)
app.MainLoop()
Hi i am trying to disable a button , so that the command event does not work for some time .How can i make the button disabled for some time and then later reenable it , to get the callback function .
#! /usr/bin/python
from Tkinter import *
import Tkinter as tk
import time
class MyFrame(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.b1 = Button(self, text="Press Me!",command = self.callback)
self.count=0
self.but_flag=0
self.b1.grid()
def callback(self):
self.b1['state'] = DISABLED
for k in range(5):
time.sleep(1)
print k
self.b1['state'] = NORMAL
mainw = Tk()
mainw.f = MyFrame(mainw)
mainw.f.grid()
mainw.mainloop()
The problem is that the sleep in your callback function is blocking the UI from refreshing. Instead of using sleep, you could schedule the re-enabling of the button using after.
def callback(self):
self.b1['state'] = DISABLED
self.after(3000, self.enable)
def enable(self):
self.b1['state'] = NORMAL
But if you do any long-running task in callback, this will still freeze the UI.
Another alternative would be to create a worker thread for doing the actual work. This way, the UI thread is not blocked and the UI will be updated and the button deactivated/activated.
def callback(self):
threading.Thread(target=self.do_actual_work).start()
def do_actual_work(self):
self.b1['state'] = DISABLED
for i in range(5):
print i
time.sleep(1)
self.b1['state'] = NORMAL
Of course, you could also just add self.b1.update() after the disabling line to update the Button widget to its disabled state, but this will still leave the UI frozen until the method is finished.
I developed a GUI with wxGlade and its still working. But to start the GUI - I coded a script with some choices.
So everything works but when I push the red button with the "x" to close the window - the application doesn´t stop.
I made a method, called by a separate exit button, which calls an exit-function in my script. But normally the users are using the close button (red button with X) so my method is not being used to close the window and the window doesn't get closed ultimately.
This is the exit-function.
def stopExport(self, event): # wxGlade: MyFrame.<event_handler>
self.Close() # close the Frame
from ExportManager import Exportmanager # import the exit function
Exportmanager().exit() # call it
How can I use this function with the red button with the "x"?
As per my understanding of your question, your application is not closing when you click on the close button (The red button with X on the right top corner.)
By default when you click the close button your application should close. In your case it seems to me that you have bind the EVT_CLOSE to some method, which has no code in it to close the app window.
For eg. consider the code snippet below, I have intentionally bind the EVT_CLOSE event to a method named as closeWindow(). This method does nothing that is why I have the pass keyword there. Now if you execute the code snippet below you can see that the app window won't close.
Code:
import wx
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 500
screenHeight = 400
screenSize = (screenWidth,screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.Bind(wx.EVT_CLOSE, self.closeWindow) #Bind the EVT_CLOSE event to closeWindow()
def closeWindow(self, event):
pass #This won't let the app to close
if __name__=='__main__':
app = wx.App(False)
frame = GUI(parent=None, id=-1, title="Problem Demo-PSS")
frame.Show()
app.MainLoop()
So, in order to close the app window you need to change the closeWindow(). For eg: Following code snippet will use the Destroy() close the app window when you click on the close button.
import wx
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 500
screenHeight = 400
screenSize = (screenWidth,screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.Bind(wx.EVT_CLOSE, self.closeWindow) #Bind the EVT_CLOSE event to closeWindow()
def closeWindow(self, event):
self.Destroy() #This will close the app window.
if __name__=='__main__':
app = wx.App(False)
frame = GUI(parent=None, id=-1, title="Problem Demo-PSS")
frame.Show()
app.MainLoop()
I hope it was useful.
Is is possible to make a wxFrame object behave like a modal dialog box in that the window creating the wxFrame object stops execution until the wxFrame object exits?
I'm working on a small game and have run into the following problem. I have a main program window that hosts the main application (strategic portion). Occasionally, I need to transfer control to a second window for resolution of part of the game (tactical portion). While in the second window, I want the processing in the first window to stop and wait for completion of the work being done in the second window.
Normally a modal dialog would do the trick but I want the new window to have some functionality that I can't seem to get with a wxDialog, namely a status bar at the bottom and the ability to resize/maximize/minimize the window (this should be possible but doesn't work, see this question How to get the minimize and maximize buttons to appear on a wxDialog object).
As an addition note, I want the second window's functionality needs to stay completely decoupled from the primary window as it will be spun off into a separate program eventually.
Has anyone done this or have any suggestions?
I was also looking from similar solution and have comeup with this solution, create a frame, disable other windows by doing frame.MakeModal() and to stop execution start and event loop after showing frame, and when frame is closed exit the event loop e.g. I here is sample using wxpython but it should be similar in wxwidgets.
import wx
class ModalFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, style=wx.DEFAULT_FRAME_STYLE|wx.STAY_ON_TOP)
btn = wx.Button(self, label="Close me")
btn.Bind(wx.EVT_BUTTON, self.onClose)
self.Bind(wx.EVT_CLOSE, self.onClose) # (Allows main window close to work)
def onClose(self, event):
self.MakeModal(False) # (Re-enables parent window)
self.eventLoop.Exit()
self.Destroy() # (Closes window without recursion errors)
def ShowModal(self):
self.MakeModal(True) # (Explicit call to MakeModal)
self.Show()
# now to stop execution start a event loop
self.eventLoop = wx.EventLoop()
self.eventLoop.Run()
app = wx.PySimpleApp()
frame = wx.Frame(None, title="Test Modal Frame")
btn = wx.Button(frame, label="Open modal frame")
def onclick(event):
modalFrame = ModalFrame(frame, "Modal Frame")
modalFrame.ShowModal()
print "i will get printed after modal close"
btn.Bind(wx.EVT_BUTTON, onclick)
frame.Show()
app.SetTopWindow(frame)
app.MainLoop()
It does not really make sense to "stop execution" of a window, as the window only handles events that are sent to it, like for example mouse, keyboard or paint events, and ignoring them would make the program appear hung. What you should do is disable all controls in your frame, this will gray them out and make the user aware of the fact that they can not be interacted with at this moment.
You can also disable the parent frame completely, instead of disabling all controls on it. Look into the wxWindowDisabler class, the constructor has a parameter to indicate a window that can be interacted with, and all other windows of the application will be disabled.
If you later on want to execute a secondary program, then you could use the wxExecute() function to do it.
This took me an annoying amount of time to figure out but here is a working example that grew out of Anurag's example:
import wx
class ChildFrame(wx.Frame):
''' ChildFrame launched from MainFrame '''
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, -1,
title=self.__class__.__name__,
size=(300,150))
panel = wx.Panel(self, -1)
closeButton = wx.Button(panel, label="Close Me")
self.Bind(wx.EVT_BUTTON, self.__onClose, id=closeButton.GetId())
self.Bind(wx.EVT_CLOSE, self.__onClose) # (Allows frame's title-bar close to work)
self.CenterOnParent()
self.GetParent().Enable(False)
self.Show(True)
self.__eventLoop = wx.EventLoop()
self.__eventLoop.Run()
def __onClose(self, event):
self.GetParent().Enable(True)
self.__eventLoop.Exit()
self.Destroy()
class MainFrame(wx.Frame):
''' Launches ChildFrame when button is clicked. '''
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id,
title=self.__class__.__name__,
size=(400, 300))
panel = wx.Panel(self, -1)
launchButton = wx.Button(panel, label="launch modal window")
self.Bind(wx.EVT_BUTTON, self.__onClick, id=launchButton.GetId())
self.Centre()
self.Show(True)
def __onClick(self, event):
dialog = ChildFrame(self, -1)
print "I am printed by MainFrame and get printed after ChildFrame is closed"
if __name__ == '__main__':
app = wx.App()
frame = MainFrame(None, -1)
frame.Show()
app.MainLoop()
Not sure this is a great answer but it worked.
bool WinApp1::OnInit()
{
if (!wxApp::OnInit())
return false;
SettingsDialog dialog(m_settingsData);
dialog.ShowModal();
return false;
}
SettingsDialog::SettingsDialog(SettingsData& settingsData)
: m_settingsData(settingsData)
{
SetExtraStyle(wxDIALOG_EX_CONTEXTHELP);
wxWindow* parent = nullptr;
Create(parent, wxID_ANY, "Preferences", wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
The WinApp1 window is never given a wxFrame and never paints.