PythonCardTricks

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.TOPwx.BOTTOMwx.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' },
   ]
  }
 ]
 }
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值