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:

Code Sample

   1 import wx
   2 class ParentModalDialog(wx.Frame):
   3     def __init__(self, parent, title="My Dialog"):
   4         wx.Frame.__init__(self, parent, -1, title=title, size=(200,100), style=wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.FRAME_TOOL_WINDOW)
   5         panel = wx.Panel(self, -1)
   6 
   7         self.callback = None
   8         self.cancelCallback = None
   9 
  10         mainsizer = wx.BoxSizer(wx.VERTICAL)
  11 
  12         ctrlsizer = wx.BoxSizer(wx.HORIZONTAL)
  13         ctrlsizer.Add(wx.StaticText(panel, -1, "Foo:"), wx.ALL, 5)
  14         self.textctrl = wx.TextCtrl(panel, -1)
  15         ctrlsizer.Add(self.textctrl, 0, wx.ALL, 5)
  16 
  17         mainsizer.Add(ctrlsizer, 1, wx.EXPAND)
  18 
  19         buttonsizer = wx.BoxSizer(wx.HORIZONTAL)
  20         okbutton = wx.Button(panel, wx.ID_OK, "OK")
  21         cancelbutton = wx.Button(panel, wx.ID_CANCEL, "Cancel")
  22         buttonsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5)
  23         buttonsizer.Add(cancelbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5)
  24 
  25         mainsizer.Add(buttonsizer)
  26 
  27         panel.SetSizer(mainsizer)
  28         panel.Layout()
  29         mainsizer.Fit(self)
  30         okbutton.SetDefault()
  31         okbutton.Bind(wx.EVT_BUTTON, self.OnOK)
  32         cancelbutton.Bind(wx.EVT_BUTTON, self.OnCancel)
  33 
  34     def Show(self, callback=None, cancelCallback=None):
  35         self.callback = callback
  36         self.cancelCallback = cancelCallback
  37 
  38         self.CenterOnParent()
  39         self.GetParent().Enable(False)
  40         wx.Frame.Show(self)
  41         self.Raise()
  42     def OnOK(self, event):
  43         try:
  44             if self.callback:
  45                 self.callback(self.textctrl.GetValue())
  46         finally:
  47             self.GetParent().Enable(True)
  48 
  49         self.Destroy()
  50 
  51     def OnCancel(self, event):
  52         try:
  53             if self.cancelCallback:
  54                 self.cancelCallback()
  55         finally:
  56             self.GetParent().Enable(True)
  57 
  58         self.Destroy()
  59 class TestFrame(wx.Frame):
  60     def __init__(self, parent, **kwargs):
  61         wx.Frame.__init__(self, parent, -1, size=(400,300), **kwargs)
  62         panel = wx.Panel(self, -1)
  63         sizer = wx.BoxSizer(wx.VERTICAL)
  64 
  65         self.textctrl = wx.TextCtrl(panel, -1)
  66         choice = wx.Choice(panel, -1, choices=['Choice-1', 'Choice-2', 'Choice-3'], size=(200,20))
  67 
  68         sizer.Add(self.textctrl, 0, wx.EXPAND)
  69         sizer.Add(choice)
  70         btn = wx.Button(panel, -1, "Block Me")
  71         sizer.Add(btn)
  72 
  73         panel.SetSizer(sizer)
  74         panel.Layout()
  75 
  76         btn.Bind(wx.EVT_BUTTON, self.OnBlock)
  77 
  78     def OnBlock(self, event):
  79         dlg = ParentModalDialog(self)
  80         dlg.Show(callback=self._ReceiveAnswer, cancelCallback=self._ReceiveCancellation)
  81 
  82     def _ReceiveAnswer(self, value):
  83         self.textctrl.SetValue(value)
  84 
  85     def _ReceiveCancellation(self):
  86         print "Dialog was cancelled! Oh well!"
  87 class TestApp(wx.App):
  88     def OnInit(self):
  89         wx.InitAllImageHandlers()
  90         frame1 = TestFrame(None, title="Testing Frame #1", pos=(20,20))
  91         frame2 = TestFrame(None, title="Testing Frame #2", pos=(200,200))
  92         frame1.Show(True)
  93         frame2.Show(True)
  94 
  95         self.SetTopWindow(frame1)
  96         return True
  97 def main(*args, **kwargs):
  98     app = TestApp(0)
  99     app.MainLoop()
 100 if __name__ == "__main__":
 101     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 2007-12-04 08:10:55


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 2007-12-04 18:44:15 (... 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)

   1 import wx
   2 class ParentModalDialog(wx.Frame):
   3     def __init__(self, parent, title="My Dialog"):
   4         wx.Frame.__init__(self, parent, -1, title=title, size=(200,100), style=wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.FRAME_TOOL_WINDOW)
   5         panel = wx.Panel(self, -1)
   6         self.callback = None
   7         mainsizer = wx.BoxSizer(wx.VERTICAL)
   8         ctrlsizer = wx.BoxSizer(wx.HORIZONTAL)
   9         ctrlsizer.Add(wx.StaticText(panel, -1, "Foo:"), wx.ALL, 5)
  10         self.textctrl = wx.TextCtrl(panel, -1)
  11         ctrlsizer.Add(self.textctrl, 0, wx.ALL, 5)
  12         mainsizer.Add(ctrlsizer, 1, wx.EXPAND)
  13         buttonsizer = wx.BoxSizer(wx.HORIZONTAL)
  14         okbutton = wx.Button(panel, wx.ID_OK, "OK")
  15         cancelbutton = wx.Button(panel, wx.ID_CANCEL, "Cancel")
  16         buttonsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5)
  17         buttonsizer.Add(cancelbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5)
  18         mainsizer.Add(buttonsizer)
  19         panel.SetSizer(mainsizer)
  20         panel.Layout()
  21         mainsizer.Fit(self)
  22         okbutton.SetDefault()
  23         okbutton.Bind(wx.EVT_BUTTON, self.OnBtn)
  24         cancelbutton.Bind(wx.EVT_BUTTON, self.OnBtn)
  25     def OnBtn(self, event):
  26         self.result = event.EventObject.GetId()
  27         if self.callback <> None:
  28                 self.callback()
  29 class TestFrame(wx.Frame):
  30     def __init__(self, parent, **kwargs):
  31         wx.Frame.__init__(self, parent, -1, size=(400,300), **kwargs)
  32         panel = wx.Panel(self, -1)
  33         sizer = wx.BoxSizer(wx.VERTICAL)
  34         self.textctrl = wx.TextCtrl(panel, -1)
  35         choice = wx.Choice(panel, -1, choices=['Choice-1', 'Choice-2', 'Choice-3'], size=(200,20))
  36         sizer.Add(self.textctrl, 0, wx.EXPAND)
  37         sizer.Add(choice)
  38         btn = wx.Button(panel, -1, "Block Me")
  39         sizer.Add(btn)
  40         panel.SetSizer(sizer)
  41         panel.Layout()
  42         btn.Bind(wx.EVT_BUTTON, self.OnBlock)
  43     def ParentModal(self):
  44         self.Enable(False)
  45         dlg = ParentModalDialog(self)
  46         dlg.callback = self.x.next
  47         dlg.CenterOnParent()
  48         dlg.Show()
  49         dlg.Raise()
  50         yield None
  51         if dlg.result == wx.ID_OK:
  52             self.textctrl.SetValue(dlg.textctrl.GetValue())
  53         else:
  54             print "Dialog was cancelled! Oh well!"
  55         dlg.Destroy()
  56         self.Enable(True)
  57         yield None
  58     def OnBlock(self, event):
  59         self.x = self.ParentModal()
  60         self.x.next()
  61 class TestApp(wx.App):
  62     def OnInit(self):
  63         wx.InitAllImageHandlers()
  64         frame1 = TestFrame(None, title="Testing Frame #1", pos=(20,20))
  65         frame2 = TestFrame(None, title="Testing Frame #2", pos=(200,200))
  66         frame1.Show(True)
  67         frame2.Show(True)
  68         self.SetTopWindow(frame1)
  69         return True
  70 def main(*args, **kwargs):
  71     app = TestApp(0)
  72     app.MainLoop()
  73 if __name__ == "__main__":
  74     main()

ParentModalDialog (last edited 2008-03-11 10:50:24 by localhost)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.