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