Extending wxPython with C++ or Wrapping wxWidgets C++ Classes with wxPython
Either way you look at it, it's the same thing, you have some c++ code that you want to be made available to your wxPython programs.
This example uses the wxPython build mechanism and includes your sources. For examples of stand-alone build methods, using Visual C++ projects instead of distutils, see the VisualStudioExtensions page, and the same thing using Scons on Mac and Linux on the SconsExtensions page.
Let's look at a ridiculously simple example:
We have a class written in c++ that is just a button with the word "foo" on it. We'll call it FooButton (because we're very creative).
The c++ portion of this class consists of a single .cpp and a single .h file.
foobutton.h:
#ifndef FOOBUTTON_H #define FOOBUTTON_H class FooButton : public wxButton { public: FooButton(wxWindow *parent, wxWindowID id, const wxString& label, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0, const wxValidator& validator = wxDefaultValidator, const wxString& name = "foobutton"); ~FooButton(); private: DECLARE_CLASS(FooButton); }; #endif
foobutton.cpp:
#include <wx/wx.h> #include "foobuton.h" IMPLEMENT_CLASS(FooButton, wxButton); FooButton::FooButton(wxWindow *parent, wxWindowID id, const wxString& label, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) : wxButton(parent, id, label, pos, size, style, validator, name) { SetLabel("foo"); } FooButton::~FooButton() { };
Now what we need to do is create a swig wrapper for the class so that we can let swig (wxswig actually) handle the boring details of writing all the interface code. This will be pretty simple. For the most part, it is going to be a copy of the .h file with some swig directives to pull in all the wxPython code. So here's what foobutton_.i will look like (named foobutton_ because we don't want swig to create another file named foobutton.cpp and clobber our code).
foobutton_.i
%module foobutton_ %{ #include "wxPython.h" #include "foobutton.h" %} //--------------------------------------------------------------------------- %include typemaps.i %include my_typemaps.i %extern wx.i %extern _defs.i %extern controls.i %pragma(python) code = "import wx" //---------------------------------------------------------------------- %{ // Put some wx default wxChar* values into wxStrings. static const wxString wxPyFooButtonStr(wxT("FooButton")); static const wxString wxPyEmptyString(wxT("")); %} //--------------------------------------------------------------------------- class FooButton : public wxButton { public: FooButton(wxWindow *parent, wxWindowID id, const wxString& label, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0, const wxValidator& validator = wxDefaultValidator, const wxString& name = wxPyFooButtonStr); %pragma(python) addtomethod = "__init__:self._setOORInfo(self)" }; //--------------------------------------------------------------------------- %init %{ wxClassInfo::CleanUpClasses(); wxClassInfo::InitializeClasses(); %}
- The first section tells swig that our python module will need to include the wxPython and foobutton header files.
The second section pulls in some swig macros and definitions, notice that we included controls.i, this is because that is where the wxButton class is definedand swig needs to be able to get the info on all the parent classes of FooButton.
- The third section defines some string constants. One of which we use for the default value of "name" in the constructor.
The fourth section is an almost verbatim copy of foobutton.h. Notice the %pragma line under the constructor. This tells swig to add the command in quotes to the init method of the FooButton class. This will be necessary for any class you wrap, without it, your wxPython and c++ objects will not be "linked." Also, don't put the destructor in the .i file. That would cause SWIG to generate code to destroy the C++ object when the Python object is destroyed. Since the C++ object belongs to it's parent window you can't do that.
The fifth section updates the wxRTTI class information so that wxWidgets knows about our new class. The reason this needs to be done is because our module is getting loaded _after_ the initial call to InitializeClasses.
Now that we have the necessary source files in place, you will also want to acquire wxSWIG. Instructions on that already exist at http://www.wxpython.org/download.php#build just pick your OS and follow the steps.
The next step is modify setup.py to tell it to build foobutton
We'll put the line:
1 BUILD_FOOBUTTON = 1
near the top of the file with all the other BUILD_XXX = X lines
and then add this section right above the "if BUILD_XRC:" line
1 if BUILD_FOOBUTTON:
2 msg('Preparing FooButton...')
3 location = 'contrib/foobutton'
4 swig_files = ['foobutton_.i']
5 other_sources = ['contrib/foobutton/foobutton.cpp']
6
7 other_includes = [ ] # if you had some other include dirs
8 # they would go here
9
10 other_libs = [ ] # additional libs would go here
11
12 swig_sources = run_swig(swig_files, location, "", PKGDIR,
13 USE_SWIG, swig_force, swig_args, swig_deps)
14
15 ext = Extension('foobutton_c',
16 swig_sources + other_sources,
17
18 include_dirs = includes + other_includes,
19 define_macros = defines,
20
21 library_dirs = libdirs,
22 libraries = libs + other_libs,
23
24 extra_compile_args = cflags,
25 extra_link_args = lflags,
26 )
27
28 wxpExtensions.append(ext)
swig_files of course contains the list of swig files, in our case only the one. other_sources contains the relative path to foobutton.cpp. If you had libs or include paths that needed to be passed to the compiler, you could put them in other_includes and other_libs
Now we can run setup.py like so: ./setup.py USE_SWIG=1 build
and then, I usually install into a temporary path for testing like so: ./setup.py install --root=/home/mrroach/tmp/
now we can test all that effort with the following, very simple wxPython script:
1 from wxPython.wx import *
2 from wxPython.foobutton_ import FooButton
3
4 class TestFrame(wxFrame):
5 def __init__(self):
6 wxFrame.__init__(self, None, -1, "FooButton Test",
7 wxDefaultPosition,
8 style=wxDEFAULT_FRAME_STYLE)
9
10 self.fb = FooButton(self, -1, "something other than foo...")
11
12 EVT_CLOSE(self, self.OnCloseWindow)
13
14 def OnCloseWindow(self, evt):
15 self.Destroy()
16
17 app = wxPySimpleApp()
18 frame = TestFrame()
19 frame.Show(True)
20 app.MainLoop()
Which will give you an amazingly unimpressive window with a button that, as promised, says "foo"
A new attempt with wxPython 2.8.4.2
The code above contains several constructs that appear to be out of date.
The code in the following attachment is a fairly minimal attempt to wrap a wxWindow subclass:
Update: 2nd try scrollwindow_extension2.zip
When it's working, directions will go here.