A start of a small collection of very useful to know tidbits, which you can find yourself by reading a lot of sample code and component code... or by looking here.
How do I pass an argument to a child window?
When you create a child window, you are not able to pass arguments via the model.ChildWindow() method, and you can't call the child's init method either.
But, you can pass arguments to a child window, here's how.
You have a class, Foo, that creates a child window Bar when Button1 is clicked.
1 from PythonCard import model, dialog
2 import bar
3 import dao
4 class Foo(model.Background):
5
6 def on_initialize(self, event):
7 self.databaseName = 'mydb.db'
8
9 def on_Button1_mouseClick(self, event):
10 self.childWin = model.ChildWindow(self, bar.Bar)
Now, the trick is to pass the database name to bar.Bar so that Bar can access the database on initialization... Which, looks like this in Foo -
1 from PythonCard import model, dialog
2 import bar
3 class Foo(model.Background):
4
5 def on_initialize(self, event):
6 self.databaseName = 'mydb.db'
7
8 def on_Button1_mouseClick(self, event):
9 self.childWin = model.ChildWindow(self, bar.Bar)
10 self.childWin.dbNamePassedFromParent = self.databaseName
and in Bar -
1 from PythonCard import model, dialog
2 import sqlite
3 class Bar(model.Background):
4
5 def on_initialize(self, event):
6 self.dbName = self.dbNamePassedFromParent
7 self.dbCon = self.connectDB(self.dbName)
8 def connectDB(self, dbName):
9 con = sqlite.connect(dbName)
10 return con
...wait, how did that work? How do you make sure that Bar.dbName has been set before it executes connectDB?
Time for a brief explanation of the event queue in PythonCard
Disclaimer: I may be talking rubbish, but this is my basic understanding
OK, so as I visualise it, there's a queue for events, so when you click Button1, the method fires, and creates the child window Bar. Now, and here's the important bit -
Bar's on_initialize method won't execute until all the code in Foo's on_Button1 method has finished.
That's right, Bar is just sitting there, it's initialize event waiting quietly in line for Foo's method to finish. Which means, you have all the time in the world to pass whatever arguments into Bar you like. Of course, this can be a two edged sword, if Foo's on_button1 method takes a long time to finish, then Bar will appear to have hung, and any repaint events will also wait in line, so keep that in mind with time intensive operations such as mail retrieval, or database queries. If it gets too heavy, you may have to look at threads, or use timers.
A simpler solution may just be using the wx.CallAfter method to execute any additional code you want to run. Just put the code you want to execute after your event is finished AND the new windows on_initialize has finished in another method which you call using wx.CallAfter. Use the findfiles sample for examples in the samples and tools directories.
Make sense? Remember, it's a wiki, so if it didn't, make it make sense! You can also retrieve values from the parent via a couple of simple method in your child's initialize event. An alternative setup for the above could be -
1 from PythonCard import model, dialog
2 import bar
3 class Foo(model.Background):
4
5 def on_initialize(self, event):
6 self.databaseName = 'mydb.db'
7
8 def on_Button1_mouseClick(self, event):
9 self.childWin = model.ChildWindow(self, bar.Bar)
and in Bar -
1 from PythonCard import model, dialog
2 import sqlite
3 class Bar(model.Background):
4
5 def on_initialize(self, event):
6 self.parent = self.GetParent()
7 self.dbName = self.parent.databaseName
8 self.dbCon = self.connectDB(self.dbName)
9 def connectDB(self, dbName):
10 con = sqlite.connect(dbName)
11 return con
Each particular way has it's uses.
Wouldn't this example be easier if you put the database junk in a separate module then import it from the Pythoncard stuff?
How do I use one window's menubar in another window?
So, you want class Diana to use it's parent class Agatha's menubar, because you're going for that OSX global menubar style, or because it's the same menubar throughout the application and you hate copy and paste coding. Well, it's like this.
This is the basic child Diana -
1 from PythonCard import model, dialog
2 class Diana(model.Background):
3 def on_initialize(self, event):
4 self.doStuff()
5 self.doOtherStuff()
This is child Diana, but using the parent's menubar -
1 from PythonCard import model, dialog, menu
2 class Diana(model.Background):
3 def on_initialize(self, event):
4 #The menubar could've been passed via either method outlined previously
5 #but for now, Diana is retrieving it.
6 self.parent = self.GetParent()
7 self.menuBar = menu.MenuBar(self, self.parent.menuBar)
8 self.doStuff()
9 self.doOtherStuff()
That simple. A example setup, two windows, each with a resource file, is attached here - testMenu.rar
How do I make my application and its components resize nicely?
You need to get your hands a little dirty with sizers. I recently went through this pain and wanted to do my part to try and help any others struggling here. I got most of my help from staring at the code for the the PythonCard Samples browser, reading the sizers section of the wxPython Getting Started document, following tips 8, 9, and 10 of UsingSizers, and drawing pictures. Here's the brief rundown of what I learned:
Use box sizers. Nest them. Horizontal box sizers line things up side by side, vertical box sizers line things up one on top of another. The third argument to sizer.Add ORs together a bunch of unrelated parameters, even though they all look related. The size of everything you've laid out with the resourceEditor becomes the minimum size, so make it all a little smaller, the user can alway expand the window. Here's the simple layout for my application, read the comments (here is a screenshot):
1 def setupSizers( self ):
2 base_sizer = wx.BoxSizer( wx.HORIZONTAL )
3 right_side_sizer = wx.BoxSizer( wx.VERTICAL )
4 comp = self.components
5 # If the second parameter to Add is 0 the component won't
6 # grow, otherwise it is used as a size ratio for the
7 # component. The last parameter to Add is the padding that is
8 # applied to wx.ALL sides of the item added to the sizer.
9 # I don't want the choice component growing:
10 right_side_sizer.Add( comp.categoryChoice, 0, wx.ALL | wx.ALIGN_LEFT, 5 )
11 right_side_sizer.Add( comp.refinementList, 1, wx.ALL | wx.EXPAND, 5 )
12 right_side_sizer.Add( comp.mainList, 1, wx.ALL | wx.EXPAND, 5 )
13 # I want the stuff on the right side about three times as wide
14 # as the tree on the left:
15 base_sizer.Add( comp.groupsTree, 1, wx.ALL | wx.EXPAND, 5 )
16 base_sizer.Add( right_side_sizer, 3, wx.ALL | wx.EXPAND, 5 )
17 base_sizer.Fit( self )
18 base_sizer.SetSizeHints( self )
19 self.panel.SetSizer( base_sizer )
20 self.panel.Layout()
21 self.visible = True
22
23 def on_initialize(self, event):
24 # if you have any initialization
25 # including sizer setup, do it here
26 self.setupSizers()
Instead of padding all sides of something with wx.ALL, you can specify wx.TOP, wx.BOTTOM, wx.LEFT, and/or wx.RIGHT. Again, these have nothing to do with specifying the alignment withwx.ALIGN_*, or whether it will resize with the window with wx.EXPAND, even though they all get ORed together as one parameter.
Also, don't forget the mysterious final 5 lines of my setUpSizers method. I have no idea what they do, but they seem important.
Coming soon - Multicolumnlist, some quirks, some tips.
How do I make my program go fullscreen?
Put a call to wxTopLevelWindow.ShowFullScreen(True) in the 'on_initialize' method of your program. This is possible because model.Background's parent is wxFrame, whose parent is wxTopLevelWindow.
This is not the best way but it seems to work after minimal testing.
from PythonCard import model class Minimal(model.Background): def on_initialize(self, event): self.ShowFullScreen(True) if __name__ == '__main__': app = model.Application(Minimal) app.MainLoop()
The slideshow sample included with PythonCard makes use of this method to toggle fullscreen on and off based on the user selecting a menu item.
How can I use 'open with' to open pythoncard files on windows xp?
Note from JH: A better method is described in [http://wiki.wxpython.org/PythonCardEditor]
In the future hopefully pythoncard will modify Windows on install so that it will have an 'edit with pythoncard' option whenever you right click a pythoncard filename. But for now make two files:
codeedit.bat:
C:\Python24\pythonw.exe C:\Python24\Lib\site-packages\PythonCard\tools\codeEditor\codeEditor.py %1
resedit.bat:
C:\Python24\pythonw.exe C:\Python24\Lib\site-packages\PythonCard\tools\resourceEditor\resourceEditor.py %1
Now right-click a pythoncard file, do 'open with', then tell it codeedit.bat. Then open a resource file, and tell it resedit.bat. From then on you can right-click a pythoncard file, hit 'open with', and it will give you the option of codeedit or resedit.
How do I run pythoncard files from an external Python script?
Suppose you have a normal foo.py and foo.rsrc.py, with class Foo(model.Background) inside foo.py. If another Python file ends with the following code, it will load and run the Foo class:
from PythonCard import model import foo r_file = model.resource.ResourceFile('foo.rsrc.py')) app = model.Application(foo.Foo, rsrc=r_file.getResource()) app.MainLoop()
How do I launch a pure WX window from a PythonCard app?
(I wrote all this in a bit of a rush, if there's a problem email wrybread @ gmail dot you know what and I'll fix it).
In your PythonCard app:
def on_launchWinddow_command(self, event): try: # try to show it self.externalwindow.Show() except: self.testing = "just a test" # a variable for testing purposes later # Probably doesn't exist yet, so create it. import externalwindow app = wx.App(0) self.externalwindowHandle = externalwindow.Main(self, -1, 'My External Window') app.MainLoop()
That could create this window, in this case from a file named externalwindow.py which looks like this:
import wx class Main(wx.Frame): def __init__(self, parent, id, title, size=(444, 355)): wx.Frame.__init__(self, parent, id, title, size=(444, 355)) panel = wx.Panel(self, -1) self.something = "some test variable" self.parent = parent # so we can refer to parent elsewhere # Instructions self.instructions = wx.StaticText(panel, -1, 'What would you like to do?', (18, 90)) # Radiogroup self.rb1 = wx.RadioButton(panel, -1, 'Import Settings', (18, 110), style=wx.RB_GROUP) self.rb2 = wx.RadioButton(panel, -1, 'Export Settings', (18, 130)) self.Bind(wx.EVT_RADIOBUTTON, self.SetVal, id=self.rb1.GetId()) self.Bind(wx.EVT_RADIOBUTTON, self.SetVal, id=self.rb2.GetId()) # Buttons self.back = wx.Button(panel, -1, '< Back', (184, 289)) self.next = wx.Button(panel, -1, 'Next >', (269, 289)) self.cancel = wx.Button(panel, -1, 'Cancel', (353, 289)) self.Bind(wx.EVT_BUTTON, self.OnBack, id=self.back.GetId()) self.Bind(wx.EVT_BUTTON, self.OnNext, id=self.next.GetId()) self.Bind(wx.EVT_BUTTON, self.OnCancel, id=self.cancel.GetId()) self.SetVal(True) self.Centre() self.Show(True) def SetVal(self, event): pass def OnBack(self, event): pass def OnNext(self, event): pass def OnCancel(self, event): pass
In your main PythonCard app you can refer to your child WX window like this:
print self.externalwindowHandle.something
self.externalwindowHandle.Show()
self.externalwindowHandle.Hide()
And your child window can refer to your main PythonCard app like this.
print self.parent.testing
How do I make my PythonCard app minimize to the system tray?
(As with the previous entry, this is chopped from one of my scripts and untested in this form, if it has a problem please email me wrybread @ gmail dot you know what and I'll fix it). I'm not sure how Windows-specific the system tray is, but this works nicely in XP.
In the initialization section of your PythonCard app:
# I copied this list of imports from somewhere, # it could be culled waaay down, please update this # page if you figure out what doesn't need to be here. from wxPython.wx import wxMessageDialog,wxFrame,wxMenu, wxBitmap,wxOK,wxID_ANY,wxID_EXIT,wxID_ABOUT, wxBITMAP_TYPE_ICO,wxBITMAP_TYPE_PNG,wxMenuItem,wxPySimpleApp,wxTaskBarIcon,wxIcon,EVT_TASKBAR_RIGHT_UP,EVT_MENU from wx import TaskBarIcon, EVT_TASKBAR_LEFT_DCLICK, EVT_TASKBAR_RIGHT_UP # Create the system tray icon itself self.tbIcon = wxTaskBarIcon() icon = wxIcon("icon.ico", wxBITMAP_TYPE_ICO) self.tbIcon.SetIcon(icon, "My Program Name") # Bind some events to it wx.EVT_TASKBAR_LEFT_DCLICK(self.tbIcon, self.ToggleShow) # left click wx.EVT_TASKBAR_LEFT_UP(self.tbIcon, self.ToggleShow) # double left click wx.EVT_TASKBAR_RIGHT_UP(self.tbIcon, self.ShowMenu) # single left click wx.EVT_CLOSE(self,self.on_close) # triggered when the app is closed, which deletes the icon from tray # build the menu that we'll show when someone right-clicks self.menu = wx.Menu() # the menu object self.menu.Append(101, 'Show It') # Resume EVT_MENU(self, 101, self.onResume) # Bind a function to it self.menu.AppendSeparator() # Separator self.menu.Append(102, 'Close It') # Close EVT_MENU(self, 102, self.on_close) # Bind a function to it self.Bind(wx.EVT_ICONIZE, self.onMinimize) # binding for minimizing
Then some event handlers:
def onMinimize(self, e): self.Hide() # this removes it from the taskbar so it only appears in the system tray def ToggleShow(self, event): if self.IsShown(): self.Hide() else: self.Show() self.Restore() # take it out of the taskbar (otherwise it'll be shown but still minimized, which is awkward) def ShowMenu(self, event): self.PopupMenu(self.menu) # show the popup menu self.Show() def onResume(self, event): # Show the window self.Show() self.Restore() # unminimize it def on_close(self, event): self.tbIcon.RemoveIcon() # remove the systemtray icon when the program closes os._exit(1)
update to: How do I make my PythonCard app minimize to the system tray?
I came across this very useful tip but it's slightly outdated. The following is a tested, updated version. In addition, I created a super-class and any PythonCard can simply sub-class from this and be able to be minimized to the system tray.
First, the superclass:
import wx import os from PythonCard import model class Minimizable(model.Background): def on_initialize(self, event, icon, name=None): if name==None: name=self.name # Create the system tray icon itself self.tbIcon = wx.TaskBarIcon() icon = wx.Icon(icon, wx.BITMAP_TYPE_ICO) self.tbIcon.SetIcon(icon, name) # Bind some events to it wx.EVT_TASKBAR_LEFT_DCLICK(self.tbIcon, self.ToggleShow) # left click wx.EVT_TASKBAR_LEFT_UP(self.tbIcon, self.ToggleShow) # double left click wx.EVT_TASKBAR_RIGHT_UP(self.tbIcon, self.ShowMenu) # single left click wx.EVT_CLOSE(self,self.on_close) # triggered when the app is closed, which deletes the icon from tray # build the menu that we'll show when someone right-clicks self.menu = wx.Menu() # the menu object self.menu.Append(101, 'Show It') # Resume wx.EVT_MENU(self, 101, self.onResume) # Bind a function to it self.menu.AppendSeparator() # Separator self.menu.Append(102, 'Close It') # Close wx.EVT_MENU(self, 102, self.on_close) # Bind a function to it self.Bind(wx.EVT_ICONIZE, self.onMinimize) # binding for minimizing def onMinimize(self, e): self.Hide() # this removes it from the taskbar so it only appears in the system tray def ToggleShow(self, event): if self.IsShown(): self.Hide() else: self.Show() self.Restore() # take it out of the taskbar (otherwise it'll be shown but still minimized, which is awkward) def ShowMenu(self, event): self.PopupMenu(self.menu) # show the popup menu self.Show() def onResume(self, event): # Show the window self.Show() self.Restore() # unminimize it def on_close(self, event): self.tbIcon.RemoveIcon() # remove the systemtray icon when the program closes os._exit(1)
Now, a minimum Pythoncard application:
""" __version__ = "$Revision: 1.10 $" __date__ = "$Date: 2004/04/24 22:13:31 $" """ from PythonCard import model from minimizable import Minimizable class Minimal(Minimizable): def on_initialize(self, event): Minimizable.on_initialize(self, event, icon="py.ico") # This ico file needs to be there if __name__ == '__main__': app = model.Application(Minimal) app.MainLoop()
and the associated resource file:
{ 'application':{ 'type':'Application', 'name':'Minimal', 'backgrounds': [ { 'type':'Background', 'name':'bgMin', 'title':'Minimal PythonCard Application', 'size':( 200, 100 ), 'menubar': { 'type':'MenuBar', 'menus': [ { 'type':'Menu', 'name':'menuFile', 'label':'&File', 'items': [ { 'type':'MenuItem', 'name':'menuFileExit', 'label':'E&xit\tAlt+X', 'command':'exit' } ] } ] }, 'components': [ { 'type':'TextField', 'name':'field1', 'position':(5, 5), 'size':(150, -1), 'text':'Hello PythonCard' }, ] } ] } }