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.
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)
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.
Is it possible to bind keyboard shortcuts (like Ctrl+z to Undo, Ctrl+Shift+z to Redo, Ctrl+c, Ctrl+v, etc.) to GLCanvas in wxpython? If so, can someone provide a minimal example, maybe something like drawing a sphere on single-click and then Undo and Redo with a shortcut?
import wx
from wx import glcanvas
class myFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='test')
self.gl = MyCanvasBase(self)
self.Show()
class MyCanvasBase(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, style=wx.WANTS_CHARS)
self.Bind(wx.EVT_CHAR, self.OnKeyDown) # for wx.WANTS_CHARS
# self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
def OnKeyDown(self, evt):
keycode = evt.GetKeyCode()
print keycode
if __name__ == '__main__':
app = wx.App(0)
Frame = myFrame()
app.MainLoop()
app.Destroy()
(edit) control+c etc can be captured by setting window style to wx.WANTS_CHARS. I made changes to use that style.
This is my attempt to make a minimum example of capturing key down events but without cube.
Have you looked at wxphython demo? That is one of the best learning sources of wxpython. If you install wxPython2.8-win32-docs-demos-2.8.12.1.exe, you will find an OpenGL sample code with a cube that you can rotate with mouse.
I am attempting to start a wxPython application but I want a banner to be displayed before it is started.
One way to do this is to start a wxPython application which in turn starts another wxPython application, the reason for doing it this way is since the App part of the second wxPython application needs to do some processing before starting and may take some time.
The issue is how do I start the other application and know that it has started?
Currently I do this which blocks for the entire GUI session:
subprocess.check_output(["python", "src/gui.py"], stderr=subprocess.STDOUT, shell=True)
I have attempted to do the following but the frame of the first application does not seem to close:
loadCompleted, EVT_LOAD_COMPLETED = wx.lib.newevent.NewEvent()
class MyRegion(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="My Region")
self.label = wx.StaticText(self, label="Hello, World!")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.label, 0, wx.ALL, 5)
self.SetSizer(sizer)
self.startThread = Thread(target=self.Start)
self.startThread.start()
self.Bind(EVT_LOAD_COMPLETED, self.OnClose)
def OnClose(self, result):
self.Close()
def Start(self):
try:
subprocess.check_output(["python", "src/gui.py"], stderr=subprocess.STDOUT, shell=True)
except:
pass
wx.PostEvent(self, loadCompleted(result=(None)))
if __name__ == "__main__":
app = wx.App()
frame = MyRegion(None)
frame.Show()
app.MainLoop()
I don't think starting a second wxPython application is the way to go. Instead, I would just load the banner inside your frame's __init__ method, then do your processing. When the processing finishes, you can destroy the banner and show your main app. Here's some psuedo-code:
#
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Test")
banner = MyBanner()
# do a bunch of stuff
banner.Destroy() # or banner.Close()
self.Show()
Now if the processing takes a really long time, you can put that into a thread and have the thread send a message back to the UI that tells it that the thread is finished. When the app receives the message, it can close the banner in the handler and show the App at that point. Please note that you need to use a thread-safe method, such as wx.CallAfter or wx.PostEvent.
Check out the following articles for ideas:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
http://www.blog.pythonlibrary.org/2013/09/05/wxpython-2-9-and-the-newer-pubsub-api-a-simple-tutorial/
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.