== Introduction == Currently, wx only provides (as Robin aptly described), 'app-modal' modality; a modal dialog can only block the entire application. In many applications with frames that are very independent, its sometimes desirable for a dialog to pop up a question that blocks those frames-- but leaves the rest of the application puttering along happily. Thus, the 'parent-modal' frame. == Process Overview == In a normal modal dialog, the execution of the parent code is halted at the 'ShowModal' method, but in this mechanism, callbacks are used to return the result of what the parent-modal frame has accomplished. The basic strategy is: * The '''Show''' method on the parent-modal frame takes two arguments, one a callback, and the other a cancelCallback. * When shown, the frame disables the parent, centers itself over the parent, and uses the wx.FRAME_FLOAT_ON_PARENT style to ensure it remains 'on top'. * If the user clicks OK, the frame calls the callback provided earlier and passes it the value it retrieved. * If the user clicks Cancel, the frame calls the cancelCallback provided earlier -- if one was provided at all, of course. * In either case, the parent is enabled, and the frame is then destroyed. == Code Sample == {{{ #!python import wx class ParentModalDialog(wx.Frame): def __init__(self, parent, title="My Dialog"): wx.Frame.__init__(self, parent, -1, title=title, size=(200,100), style=wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.FRAME_TOOL_WINDOW) panel = wx.Panel(self, -1) self.callback = None self.cancelCallback = None mainsizer = wx.BoxSizer(wx.VERTICAL) ctrlsizer = wx.BoxSizer(wx.HORIZONTAL) ctrlsizer.Add(wx.StaticText(panel, -1, "Foo:"), wx.ALL, 5) self.textctrl = wx.TextCtrl(panel, -1) ctrlsizer.Add(self.textctrl, 0, wx.ALL, 5) mainsizer.Add(ctrlsizer, 1, wx.EXPAND) buttonsizer = wx.BoxSizer(wx.HORIZONTAL) okbutton = wx.Button(panel, wx.ID_OK, "OK") cancelbutton = wx.Button(panel, wx.ID_CANCEL, "Cancel") buttonsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) buttonsizer.Add(cancelbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) mainsizer.Add(buttonsizer) panel.SetSizer(mainsizer) panel.Layout() mainsizer.Fit(self) okbutton.SetDefault() okbutton.Bind(wx.EVT_BUTTON, self.OnOK) cancelbutton.Bind(wx.EVT_BUTTON, self.OnCancel) def Show(self, callback=None, cancelCallback=None): self.callback = callback self.cancelCallback = cancelCallback self.CenterOnParent() self.GetParent().Enable(False) wx.Frame.Show(self) self.Raise() def OnOK(self, event): try: if self.callback: self.callback(self.textctrl.GetValue()) finally: self.GetParent().Enable(True) self.Destroy() def OnCancel(self, event): try: if self.cancelCallback: self.cancelCallback() finally: self.GetParent().Enable(True) self.Destroy() class TestFrame(wx.Frame): def __init__(self, parent, **kwargs): wx.Frame.__init__(self, parent, -1, size=(400,300), **kwargs) panel = wx.Panel(self, -1) sizer = wx.BoxSizer(wx.VERTICAL) self.textctrl = wx.TextCtrl(panel, -1) choice = wx.Choice(panel, -1, choices=['Choice-1', 'Choice-2', 'Choice-3'], size=(200,20)) sizer.Add(self.textctrl, 0, wx.EXPAND) sizer.Add(choice) btn = wx.Button(panel, -1, "Block Me") sizer.Add(btn) panel.SetSizer(sizer) panel.Layout() btn.Bind(wx.EVT_BUTTON, self.OnBlock) def OnBlock(self, event): dlg = ParentModalDialog(self) dlg.Show(callback=self._ReceiveAnswer, cancelCallback=self._ReceiveCancellation) def _ReceiveAnswer(self, value): self.textctrl.SetValue(value) def _ReceiveCancellation(self): print "Dialog was cancelled! Oh well!" class TestApp(wx.App): def OnInit(self): wx.InitAllImageHandlers() frame1 = TestFrame(None, title="Testing Frame #1", pos=(20,20)) frame2 = TestFrame(None, title="Testing Frame #2", pos=(200,200)) frame1.Show(True) frame2.Show(True) self.SetTopWindow(frame1) return True def main(*args, **kwargs): app = TestApp(0) app.MainLoop() if __name__ == "__main__": main() }}} === Comments === The sample above is runnable, to get a feel for how it works; you'll notice that even if you click 'Block me' on one frame, you can still click over and operate on the other. ... oh, and this was my first wxPyWiki contribution, so excuse any fudges :) -- StephenHansen <<DateTime(2007-12-04T08:10:55Z)>> ----- I thought I would try using a python generator to keep from separating the launch of the dialog to handling the result. I'm not sure that this is any better but the logic for handling the dialog is now in two methods. Here's the code above as modified. -- MarkErbaugh ... Personally? I can't understand what it's really doing anymore, and you've now tightly coupled the parent-modal frame with the parent itself, instead of being able to use any number of 'parent-modal' dialogs that ''just work'' on top of the parent frame. In my original design, all the parent had to know was what the dialog would return as a result. It's not really modular anymore, nor able to be used with any arbitrary frame-- it has to be used with a frame now specially created for it. To each his own :) -- StephenHansen <<DateTime(2007-12-04T18:44:15Z)>> (... I'm editing back in my vertical whitespace in the above recipe, as the previous edit made it go away and it's so hard to read without it) {{{ #!python import wx class ParentModalDialog(wx.Frame): def __init__(self, parent, title="My Dialog"): wx.Frame.__init__(self, parent, -1, title=title, size=(200,100), style=wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.FRAME_TOOL_WINDOW) panel = wx.Panel(self, -1) self.callback = None mainsizer = wx.BoxSizer(wx.VERTICAL) ctrlsizer = wx.BoxSizer(wx.HORIZONTAL) ctrlsizer.Add(wx.StaticText(panel, -1, "Foo:"), wx.ALL, 5) self.textctrl = wx.TextCtrl(panel, -1) ctrlsizer.Add(self.textctrl, 0, wx.ALL, 5) mainsizer.Add(ctrlsizer, 1, wx.EXPAND) buttonsizer = wx.BoxSizer(wx.HORIZONTAL) okbutton = wx.Button(panel, wx.ID_OK, "OK") cancelbutton = wx.Button(panel, wx.ID_CANCEL, "Cancel") buttonsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) buttonsizer.Add(cancelbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) mainsizer.Add(buttonsizer) panel.SetSizer(mainsizer) panel.Layout() mainsizer.Fit(self) okbutton.SetDefault() okbutton.Bind(wx.EVT_BUTTON, self.OnBtn) cancelbutton.Bind(wx.EVT_BUTTON, self.OnBtn) def OnBtn(self, event): self.result = event.EventObject.GetId() if self.callback <> None: self.callback() class TestFrame(wx.Frame): def __init__(self, parent, **kwargs): wx.Frame.__init__(self, parent, -1, size=(400,300), **kwargs) panel = wx.Panel(self, -1) sizer = wx.BoxSizer(wx.VERTICAL) self.textctrl = wx.TextCtrl(panel, -1) choice = wx.Choice(panel, -1, choices=['Choice-1', 'Choice-2', 'Choice-3'], size=(200,20)) sizer.Add(self.textctrl, 0, wx.EXPAND) sizer.Add(choice) btn = wx.Button(panel, -1, "Block Me") sizer.Add(btn) panel.SetSizer(sizer) panel.Layout() btn.Bind(wx.EVT_BUTTON, self.OnBlock) def ParentModal(self): self.Enable(False) dlg = ParentModalDialog(self) dlg.callback = self.x.next dlg.CenterOnParent() dlg.Show() dlg.Raise() yield None if dlg.result == wx.ID_OK: self.textctrl.SetValue(dlg.textctrl.GetValue()) else: print "Dialog was cancelled! Oh well!" dlg.Destroy() self.Enable(True) yield None def OnBlock(self, event): self.x = self.ParentModal() self.x.next() class TestApp(wx.App): def OnInit(self): wx.InitAllImageHandlers() frame1 = TestFrame(None, title="Testing Frame #1", pos=(20,20)) frame2 = TestFrame(None, title="Testing Frame #2", pos=(200,200)) frame1.Show(True) frame2.Show(True) self.SetTopWindow(frame1) return True def main(*args, **kwargs): app = TestApp(0) app.MainLoop() if __name__ == "__main__": main() }}}