PageRenderTime 41ms CodeModel.GetById 8ms RepoModel.GetById 1ms app.codeStats 0ms

/PythonCard-0.8.2/docs/html/walkthrough3.html

#
HTML | 252 lines | 248 code | 4 blank | 0 comment | 0 complexity | fb30da2b19e3363c28a76eed4cc2f7d5 MD5 | raw file
Possible License(s): GPL-2.0
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html>
  3. <head>
  4. <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
  5. <meta name="author" content="David Primmer" />
  6. <link rel="stylesheet" href="PythonCard.css" type="text/css" />
  7. <title>How to Add a Child Window to a PythonCard Application</title>
  8. </head>
  9. <body>
  10. <div id="banner">
  11. <h1>PythonCard Documentation</h1>
  12. </div>
  13. <?php include "sidebar.php" ?>
  14. <div id="content">
  15. <h1>How to add a child window (or non-modal dialog)</h1>
  16. <h4>by David Primmer</h4>
  17. <p>This is the third walkthrough in a series of tutorial-style walkthroughs
  18. to help newcomers get started using PythonCard.</p>
  19. <h2>Overview, Scope and Purpose</h2>
  20. <p>This walkthrough covers PythonCard Version 0.8.</p>
  21. <p>The first two PythonCard walkthrough tutorials (which can be found, like
  22. this one, in the docs/html directory of your PythonCard installation)
  23. have created single-window applications. It is, of course, often necessary
  24. to create applications with multiple windows. Adding another background as
  25. a child window will allow you to modularize your application. You can allow
  26. users to hide and show windows (by using the visible attribute), and you can clean up your
  27. user interface and split widgets off into logical groups.</p>
  28. <p>In this walkthrough, we will extend our simple Counter application (the
  29. subject of walkthrough2) to add a child window which launches when the
  30. application opens, interacts with it, and exhibits some demonstrative
  31. behavior when the child window is closed. This walkthrough will make more
  32. sense if you've worked through walkthrough2.</p>
  33. <h2>Modal vs. Non-modal</h2>
  34. <p>There are two types of child windows: those that require the user to stop
  35. any other activity and pay attention only to the current window (modal) and
  36. windows that allow simultaneous interaction with the main window (non-modal
  37. or modeless). The first thing to decide when creating a child window is
  38. whether you require the window to be dismissed before the user can continue
  39. to work with the main application window. With a modal window, users can
  40. choose to cancel, they can respond to a message, or they can set parameters
  41. and assign values to content in the active application. An example of a
  42. simple modal window is a message box or alert.</p>
  43. <p>You may want your window to stick around while the user is working in
  44. another window. The radioclient sample, for example, has a child window that
  45. displays a view of the current document as it would be rendered in a Web
  46. browser. It's important to decide between modal and non-modal before you
  47. begin creating your child window because the standard window type in
  48. PythonCard, the background, cannot be modal.</p>
  49. <h2>model.Background</h2>
  50. <p>This is the standard class definition for a PythonCard app:</p>
  51. <pre>
  52. class Minimal(model.Background):
  53. </pre>
  54. <p>One of the most important concepts when dealing with child windows in
  55. PythonCard is the background. The background is unique to PythonCard and it
  56. basically encapsulates a wxFrame and a wxPanel. Each PythonCard application
  57. is a class that derives from model.Background. You can't make a class derived
  58. from model.Background modal. That is a wxWidgets limitation, not PythonCard.
  59. If you want a modal dialog then your class has to be derived from
  60. model.CustomDialog. They are similar, but different and the resource format
  61. is slightly different as well. dbBrowser, resourceEditor, textEditor, and
  62. textRouter all use custom modal dialogs.</p>
  63. <h2>Overview of Designing a Child Window</h2>
  64. <p>Making a non-modal child window using resourceEditor is simple, following
  65. these steps:</p>
  66. <ol>
  67. <li>Create a window with resourceEditor. In this walkthrough, we'll do so
  68. by starting with the minimal sample and adding features.</li>
  69. <li>In your main application, import the module you created at step 1 and
  70. add code to the main application's startup routine to create an instance of
  71. your imported child window class.</li>
  72. <li>Modify the attributes of the new object, calling its methods and using
  73. its components. It is now part of the main application.</li>
  74. </ol>
  75. <h3>Create a stand-alone PythonCard application</h3>
  76. <p>Each PythonCard .py file contains a class definition derived from
  77. PythonCard's model.Background as well as a stub to instantiate that class. A
  78. child window is simply an instantiation of a class from within another
  79. application. Tutorials in walkthrough1 and walkthrough2 cover how to create a
  80. stand-alone PythonCard application so we won't cover that here.</p>
  81. <p>It is possible that your child window will be interacting with the data
  82. in your main application, so there may be limits to how much design can be
  83. done independently of your main application. But it is still a good idea to
  84. try to separate their functions as much as possible to allow code re-use and
  85. to simplify debugging. In the current example, we will use the minimal sample
  86. as the basis of our child window while using the counter sample as our main
  87. application.</p>
  88. <p>Minimal.py has one control, a text field called 'field1'. We will add a
  89. button to minimal.py to reset the value of the field in the main counter
  90. application to 0 when it is pressed. We will also connect the buttons in the
  91. main counter application to update field1's contents in the child window
  92. (minimal.py) to match those of the field in counter's window.</p>
  93. <p>Even though these are relatively trivial interactions, they serve to
  94. demonstrate the techniques involved in getting two (or more) windows
  95. communicating with one another in a PythonCard application.</p>
  96. <p>Start by creating a new folder to hold your two resource files and your two
  97. PythonCard script files. Copy minimal.py, minimal.rsrc.py, counter.py, and
  98. counter.rsrc.py from their respective folders in the samples directory into
  99. this new folder. You can leave their names the same, though in practice you
  100. will generally change the names of files to match the application you are
  101. constructing.</p>
  102. <p>Open minimal.rsrc.py in the PythonCard resourceEditor and add a button to
  103. it as shown in Figure 1.</p>
  104. <p class="imageCaption"><img src="images/wt3fig1.png" alt="button added to minimal application" width="200" height="100" /><br />
  105. Figure 1. Button Added to minimal Sample Window</p>
  106. <p>Label the button &quot;Clear Counter&quot; and name it btnReset. Save the
  107. resource file.</p>
  108. <p>We'll get back to scripting this button shortly.</p>
  109. <h2>Launching the Child Window From the Parent Application</h2>
  110. <p>Opening the child window in our main application's code is simply a matter
  111. of importing the class and creating an object of that class, attached to the
  112. current background.</p>
  113. <p>In addition to the standard imports for Counter, we'll import minimal:</p>
  114. <pre>
  115. from PythonCard import model
  116. import minimal
  117. </pre>
  118. <p>Next we'll add an event handler to be executed when the Counter application
  119. is started. This handler acts something like autoexec.bat on a PC or .login
  120. in a Unix shell. Place an on_initialize handler right below the class
  121. definition. (Placement isn't important but following this convention will
  122. make it easier for you to work through and maintain multi-window
  123. applications.) Here is the class declaration of our Counter application with
  124. on_initialize added:</p>
  125. <pre>
  126. class Counter(model.Background):
  127. def on_initialize(self, event):
  128. </pre>
  129. <p>and here is the code that we will add to the on_initialize method:</p>
  130. <pre>
  131. self.minimalWindow = model.childWindow(self, minimal.Minimal)
  132. </pre>
  133. <p>We create a minimal window object uisng the <span class="code">childWindow
  134. </span> function and give it the name minimalWindow by passing two parameters
  135. to the function: the parent window (<span class="code">self</span>), and the
  136. background class (<span class="code">minimal.Minimal</span>) we want to use.</p>
  137. <p>That is all is needed to create a minimal window object, but at this point,
  138. it is still hidden and not much good to us.</p>
  139. <h2>Communicating With Your Child Window</h2>
  140. <p>Continuing in the on_initialize handler, we make calls to set the
  141. position and visibility of the new window:</p>
  142. <pre>
  143. # override resource position
  144. self.minimalWindow.position = (200, 5)
  145. self.minimalWindow.visible = True
  146. </pre>
  147. <p>We now have a window that is an attribute of our main background, just like
  148. any of the menus or buttons that are already a part of Counter.</p>
  149. <p>As constructed before we began this project, the increment and decrement
  150. buttons in Counter modify the value of the text field in Counter. To cause the
  151. Counter application's buttons to update the text value in the minimal child
  152. window minimalWindow, we simply add one more call to update the control in
  153. that window as well (the new lines are in bold type):</p>
  154. <pre>
  155. def on_incrBtn_mouseClick(self, event):
  156. startValue = int(self.components.field1.text)
  157. endValue = startValue + 1
  158. self.components.field1.text = str(endValue)
  159. <strong>self.minimalWindow.components.field1.text = str(endValue)</strong>
  160. def on_decrBtn_mouseClick(self, event):
  161. startValue = int(self.components.field1.text)
  162. endValue = startValue - 1
  163. self.components.field1.text = str(endValue)
  164. <strong>self.minimalWindow.components.field1.text = str(endValue)</strong>
  165. </pre>
  166. <p>Notice that we reference components in the child window by a collection of
  167. objects starting with the main application (self) and then pointing first to
  168. the child window, then to its components property, then to the specific
  169. component, then to the property of that component we wish to change. If we
  170. wanted to execute a method of that component or the background, we would use
  171. a similar construct.</p>
  172. <p>This is obviously very simplistic, (not to mention somewhat redundant
  173. coding). Many times, you will be using a child window to modify components or
  174. data associated with the parent window. PythonCard is not passing events
  175. between windows in this release, but in many cases you can simply call the
  176. event handler directly. If you are interested in passing an arbitrary event
  177. such as a mouseClick, that will require creating the event and then posting
  178. it using wx.PostEvent. Custom events are beyond the scope of this tutorial.
  179. However, the child window is able to access the parent window directly by
  180. traversing up the stack of windows.</p>
  181. <p>For example we can place a control on our child window that updates a
  182. control on our main background. Let's connect the Reset Counter button we
  183. added to the minimal application above. Add the following code to
  184. minimal.py</p>
  185. <pre>
  186. def on_initialize(self, event):
  187. self.parent = self.getParent()
  188. def on_btnReset_mouseClick(self, event):
  189. self.parent.components.field1.text = "0"
  190. </pre>
  191. <p>When our child window it initialized, it calls getParent() to get a
  192. reference to its parent window, and then stores that reference. We place the
  193. code that handles this task in the on_initialize handler so that the
  194. reference is available to all handlers in the application.</p>
  195. <p>You'll notice that the text field on the Counter background is reset to
  196. zero but the text field on the Minimal background is not. (This might be a
  197. little confusing because both fields are called 'field1'. In a real
  198. application, the fields should be named something more descriptive.) At this
  199. point, our minimal sample is no longer a stand-alone. If you run Minimal by
  200. itself, self.parent is set to None. If you were using the child window in
  201. other roles or wanted to make it multi-purpose, you could place the
  202. self.getParent() call in a try...except block.</p>
  203. <h2>Closing the Child Window</h2>
  204. <p>Your main application window will clean up the child window when your app
  205. is closed. If the user has the ability to close the child window before the
  206. main window closes (by using the the child's File-&gt;Exit menu or by
  207. clicking the close box on the window) it's a good idea to just hide the
  208. window instead of destroying it. This will allow you to unhide the window
  209. without re-initializing it and also permits you to communicate with the
  210. window while it is hidden. In the process, you avoid runtime errors that
  211. could result from the child window being non-existent as far as the
  212. application is concerned.</p>
  213. <p>We do this by overriding the on_close event handler. on_close would
  214. normally destroy the window but we change it so it hides the window by
  215. setting the visible attribute to False, hiding the window. Just to
  216. make things a little more interesting, we've also added a custom doExit
  217. function that sets the counter's field1 to an arbitrary value just to confirm
  218. the connection between the two windows visibly:</p>
  219. <pre>
  220. def doExit(self):
  221. self.parent.components.field1.text = "99"
  222. def on_close(self, event):
  223. self.doExit()
  224. self.visible = False
  225. def on_exit_command(self, event):
  226. self.close()
  227. </pre>
  228. <p>The resource file (minimal.rsrc.py) defines an exit command for the File-&gt;Exit menu
  229. so we use a command handler to override the default behavior.
  230. As the above code shows, the File-&gt;Exit menu item just calls the
  231. close() method to close the window. That is the same as clicking the
  232. close box on the window and triggers the close window event, so that on_close
  233. is called. We placed the work to be done when the document is closing in the
  234. doExit method. In this case it just sets the counter field in the parent to
  235. "99".</p>
  236. <p>In addition, in doExit() you could modify some properties of the parent
  237. window to keep track of the state of your child window. For example, assuming
  238. you have a View menu with an item that hides or unhides your child window, you
  239. could use doExit() to check or uncheck the 'View Minimal Window' menu item on
  240. the parent. The code would look something like this:</p>
  241. <pre>
  242. self.parent.menuBar.setChecked('menuViewMinimalWindow', False)
  243. </pre>
  244. <?php include "footer.php" ?>
  245. <p>$Revision: 1.11 $ : $Author: kasplat $ : Last update $Date: 2005/12/30 06:29:36 $</p>
  246. </div> <!-- end of content -->
  247. </body>
  248. </html>