Browse in : |
All
> Topics
> Programming
All > Journals > CVu > 165 Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Wx - A Live Port
Author: Administrator
Date: 03 October 2004 13:16:08 +01:00 or Sun, 03 October 2004 13:16:08 +01:00
Summary:
This is a collection of notes I have made while porting an application from MFC to wxWidgets. It is intended partly as a tutorial and partly to document some of the roadblocks I met on the way.
Body:
Moving beyond MFC and opening new horizons with wxWidgets.
This is a collection of notes I have made while porting an application from MFC to wxWidgets. It is intended partly as a tutorial and partly to document some of the roadblocks I met on the way. Right now it has gaping holes in it that are gradually being filled. When I upgraded from DOS to Windows 3.1 and purchased Microsoft Visual C++ version 1, a whole new world opened up. The massive framework allowed me to be writing fully fledged GUI applications in a few short weeks and a world of creative opportunity was opened up.
The software was exceptional value coming with a formidable array of manuals that I would leaf through in my spare time. Over the years I and my software company built up a large software library of applications related to marine weather, communication and navigation, and our products sold well on the world market. With recent events in the software world and a global shifting towards Linux as a viable alternative there is a growing need for code that is portable.
As this trend seemed to gather momentum, I started to refine my search and start to look more actively for avenues to port my software. With the launch of Visual Studio .NET I had to make a decision. Most of what .NET was about seemed to lack co-ordination and it did not seem to have relevance to ships at sea far away from broadband internet. So I chose to stop with Visual Studio 6 and keep up to date with MSDN subscriptions. Not allowed - my next set of disks had "For Use with Visual Studio .NET only" written on them. And that was the end of that - I was out in the pasture and in need of a new approach to keep 20 man-years of work viable long term.
I looked again at Linux and KDevelop, to mention a few environments. While I am extremely encouraged by the progress Linux is making it is still not up to Windows when it comes to bringing new users in, and hardware installation though robust is still extremely intimidating if you are not an expert. The documentation is excellent but it does need somebody with a leaning towards systems to get into it. This is changing though very rapidly and I am predicting that Linux will overtake Windows in the very near future as the momentum builds up. So this makes a port even more urgent.
I discovered wxWidgets almost by accident. A friend mentioned in passing and I visited the website, downloaded the kit and started to play with it.
I started with version 2.4.0 and compared it to MFC. I was surprised to discover that the framework is 10 years old, very mature and stable. Quite a few hands have contributed over the years. Most important it is all source code, well written and intuitive. Well the first job I had to do was compile it all. Oh dear - here we go I thought, gritting my teeth, and selected
build from the Visual Studio options. I went off to make a cup of tea and almost spilled it over the keyboard when I got back because the whole thing had compiled properly and built all the libraries. A very encouraging start.So what could the kit do?
I ran up the samples and they all compiled. I looked at the code and although the architecture was not the same as I was used to I could see the similarities. I was used to the Stygian macros and confusing comments App Wizard puts in. Not being too concerned with the nitty gritty, I would normally let App Wizard build a template and I would plug in the bits as I learned in my old scribble tutorial many years ago. So it took a little bit of exploring and tracing to see what was actually going on.
At this point I went back on the internet to see if I could find some resources.
I found two excellent articles by Marcus Neifer: Porting MFC Applications to Linux, and also a good wxWidgets primer: Looking Through wxWidgets: An Intro to the Portable C++ and Python GUI Toolkit.
These two articles convinced me that there was something worth pursuing here. So I started my evaluation in earnest to see how the kit could meet our needs. Firstly I looked at the DocView MDI sample that emulates the Scribble sample that all MFC trainees used to start with. The code is a good deal lighter in weight though. I was particularly interested to see if all the little nuances our clients were used to were supported. Tool tips and Help on the status bar ware two I items I went looking for. Menu creation and toolbar interaction with the code were other areas that differed quite a bit from the old MFC way of doing things. I could not find an easy way to make the toolbar dockable but I did not look very hard - that is a bit of a gimmick at least in our apps. There was the option of trying to convert the MFC .rc to the new XML format that wxWidgets is using. I had only very limited success with that route and preferred to go the conservative mothod of putting everything in C++ code, I followed that methodology. In my opinion clearly commented code beats everything else when it comes to reviewing something a few years down the road.
Next the interaction with the users was examined. I latched on to wxDesigner as the tool of choice. The tool uses a completely different concept to the VC++ method of constructing a dialog. It uses what we call sizers to group and encapsulate controls in boxes or groups. Once you get the hang of it, it is a very quick way of creating an attractive looking dialog. The software also generates a wrapper so you can run the dialog as a stand alone program. An excellent way to jump start an application.
However my main focus was to take an existing application and port it to wxWidgets. This required an in-depth view as to how the two architectures differ. It also needs a thorough review of all the underlying capabilities of the GUI, how the tool bars and menus behave, what context help support is around. One of the biggest advantages of Windows is the redundancy of the help system and the embedded context-sensitive actions that are available.
Now we have decided that wxWidgets as the tool of choice, we now have to come to grips with it. In my trainee programming days I was taught to help myself and to find the resources I need in the documentation. In those days this meant poring over voluminous books. Today of course the CD-ROM and HTML have made this all a lot easier. The wxWidgets help file comes in many flavours. My choice is the HTML help version. Now we are going to be reading this a lot. Unlike many programming tomes, I am not proposing to fill out the pages with vast chunks from the manual. You are going to have to go out there yourself and find it.
The first thing I did was to add the wxWidgets help into the MSVC++ tools menu - an area where you can add custom features. In the tools menu:
-
Go to
-
Go to the
tab and add an entry help -
Create a keyboard shortcut SHIFT F1
-
Add the the following command: hh.exe
-
Add as arguments: C:\wxWidgets_2.4.0\docs\htmlhelp\wx.chm
The core of the wxWidgets documentation is the alphabetical class library reference. It is largely complete however some sections may be very terse. All the source code is available and if you are struggling - why not use the MSVC browser and take a look through some of the source code. Unlike some GUI libraries it is very clear and well written.
Some of the tutorials I have mentioned to date can help although to get a basic understanding of the framework components I strongly recommend you take a tour of the samples supplied. They are clear and address a very specific point. As a last resort there is <wx-Users@lists.wxwidgets.org>. Here you will get an answer to a question very quickly. Several of the sticking points I encountered were answered, once within 3 minutes on a Sunday. Try getting commercial support of that quality!
The downside is that you need to be patient and do not expect anything. The open source model is a two-edged sword. The contributors are not paid.
This is the very basis of a nugget of user interface code.
The class you will be using is wxFrame - derived from wxWindow. Like CWnd, wxWidgets has a huge bewildering array of sub functions that drive the application.
Here is the basic code to pop up a single window.
wxPoint pt(20,20); // where the window will // appear wxSize sz (600,400); // the window size WxFrame *FrameWindow = new wxFrame;; WxFrame->Create(frame, -1, "Key", pt, sz, wxDefault, "wxWindow sample");
Both MFC and wxWidgets support the Document/View approach. The concept here is the application launches a Main Frame that contains the primary controls. The Main Frame has Child Frames (in the Multiple Document Interface world) The Child Frame has views and and a class that manipulates the document and supports reading, writing, etc.
The Visual C++ App wizard makes one's life very easy by generating a bare bones application with the following components:
-
Application registration - Associating a file type with the application.
-
Drag and drop support of files onto the application main frame.
-
Context sensitive help
-
Command line processing
-
Print and Print Preview
In wxWidgets all this has to be done yourself. Of course many simple applications do not need all this baggage, however if you are developing a full fledged application that meets the criteria of a modern day system you must include this functionality. We will start by laying out a bare bones framework and build our application from there.
The MFC Document template approach has close parallels. You draw up a document that has a filter for the
and a specific extension. The framework uses this template to manipulate your data via file open, most recently used document etc.In MFC the View is subclassed from the Child Frame, whereas in wxWidgets you need to explicitly create your own Frame. This was the most important difference I found and thus the OnDraw member of the view class had to be invoked from the Sub Frame Window class. I followed the design of the docviewmdi sample and used the MyCanvas class from there. Other little differences were the more modular way the menus work and especially getting the help tips to show on the main frame status bar. I ended up creating a second status bar on the Frame window for the time being for reading the menu help tips. It is a good idea to port the App routine first. Command line processing Drag and Drop, Document template, and Registry profile strings should be sorted out at the very first step. The wxConfig function is very useful here for storing profile data, windows size and other variables you need to store. Non persistent applications where you have to set everything up again when you run the program are a trademark of the amateur.
Since we have the view creating a child window we need to make sure it is closed when the view is closed. I followed the examples and used a scroll window called canvas locally. This code is lifted almost verbatim from the DocViewMDI sample that forms a reasonable basis for starting.
// Clean up windows used for displaying the // view. bool WXWindPlotView::OnClose( bool deleteWindow) { if(!GetDocument()->Close()) return FALSE; // Clear the canvas in case we're in single- // window mode, and the canvas stays canvas->Clear(); canvas->view = (WXWindPlotView *)NULL; canvas = (MyCanvas *)NULL; wxString s(wxTheApp->GetAppName()); if (frame) frame->SetTitle(s); SetFrame((wxFrame*)NULL); Activate(FALSE); if(deleteWindow) { delete frame; return TRUE; } return TRUE; }
Once I had everything in place I could start dropping in chunks of code from the MFC app and let the compiler crank through the errors.
Step 1 was to remove all includes for windows.h and stdafx.h. Now we are are a huge step to throwing off the Microsoft yoke and out there in the real world at last. All those windows functions that are not in the wxWidgets library are going to have to be found in the the ANSI C libraries. The first one I came up against was GlobalLock to allocate memory. Several lines of code were replaced with a simple malloc statement - and a free in the destructor.
The sidebar contains a great little macro to speed up your work.
Most wxWidgets classes are counterparts of MFC and there is normally a 1-to-1 correlation. wXstring and CString are very similar as is DC - wxDC. Others are not quite the same. CTime is replaced by wxDateTime that has slightly different construction and considerably more functionality. MoveTo and LineTo are replaced by one call in wxWidgets - DrawLine. Eventually somebody will write a porting macro. I was tempted to batch some edit commands together but resisted. The process went quite fast and it is better to keep an eye on where the code is changing.
One thing to look out for is making sure you have all the right includes. If a class is not found whizz over to the documentation. Every class has a #include associated with it.
It is certainly not a plug compatibility and where there is a difference you can be sure that wxWidgets is more intuitive and better thought out. Best of all the compiler does all the hard work. I linked the Shift F1 key in Visual Studio to bring up the wxWidgets help file. It is very complete and it is very easy to jump to the relevant section and find out what each class is about. In a very short time I was reaming out whole sections of code and getting them to run. File IO was simplified. try and catch were not supported but then I do not use exceptions much.
Certain things to look out for: wxString::Format has the same syntax as the MFC equivalent but with a difference you need to assign it i.e.
CString str; str.Format("%i",Integer); wxString str; str = str.Format("%i",Integer);
I personally like to expand out my code for readability and thus the wX implementation makes more sense.
Another difference is the drawing functions of wxDC.
Functions like ellipse (DrawEllipse) use starting coordinates and dimension as opposed to starting and ending coordinates. wXDC::DrawText is similar to CDC::TextOut but the text only is drawn making an initial rectangle draw necessary to wipe out the old text and avoid overlaying. This in fact solved a lot of irritating quirks I experienced with MFC and trying to get a mixture of text and graphics to stop interfering with each other.
So you see that there is some work to do here and it is not a straight conversion exercise at all. However the compiler is a great help and guides your work. My methodology was to open up little bits of functionality at a time. Now the first port is behind me I will probably adopt a more confident holistic strategy.
I started creating the toolbar manually from the samples. This is reasonably easy but is time-consuming and takes a bit of care as the coding is quite critical in places. Using wXDesigner automates this process. The menus and tool bars can be very easily laid out .
To generate an app from scratch using existing bitmaps and menu structures from the old program took an hour and a half with 15 toolbar buttons and 8 main menus.
Visual C++ is a bit easier and more intuitive but of course you are only coding for one platform. After a few minutes with wXDesigner you will get the hang of it and you will find it is a very much worth the money you have paid for it.
You need to be aware of the symbolic event Ids - some of them exist already as system events. See Table 1 for these
Name | Value | Name | Value |
---|---|---|---|
wxID_LOWEST | 4999 | wxID_PASTE | 5032 |
wxID_OPEN | 5000 | wxID_CLEAR | 5033 |
wxID_CLOSE | 5001 | wxID_FIND | 5034 |
wxID_NEW | 5002 | wxID_DUPLICATE | 5035 |
wxID_SAVE | 5003 | wxID_SELECTALL | 5036 |
wxID_SAVEAS | 5004 | wxID_FILE1 | 5050 |
wxID_REVERT | 5005 | wxID_FILE2 | 5051 |
wxID_EXIT | 5006 | wxID_FILE3 | 5052 |
wxID_UNDO | 5007 | wxID_FILE4 | 5053 |
wxID_REDO | 5008 | wxID_FILE5 | 5054 |
wxID_HELP | 5009 | wxID_FILE6 | 5055 |
wxID_PRINT | 5010 | ID_FILE7 | 5056 |
wxID_PRINT_SETUP | 5011 | ID_FILE8 | 5057 |
wxID_PREVIEW | 5012 | wxID_FILE9 | 5058 |
wxID_ABOUT | 5013 | wxID_OK | 5100 |
wxID_HELP_CONTENTS | 5014 | wxID_CANCEL | 5101 |
wxID_HELP_COMMANDS | 5015 | wxID_APPLY | 5102 |
wxID_HELP_PROCEDURES | 5016 | wxID_YES | 5103 |
wxID_HELP_CONTEXT | 5017 | wxID_NO | 5104 |
wxID_CUT | 5030 | wxID_STATIC | 5105 |
wxID_COPY | 5031 | wxID_HIGHEST | 5999 |
Table 1. Symbolic Event IDs
You need to add to the file Resource.h to manually code in the ID number where your user interface interacts with the above functions You can put the ID anywhere in a header file, however, I like to use Resource.h.
Otherwise you leave the ID as -1 and wXDesigner will generate the event entries for you. wXDesigner starts from ID=10000. All you need to do is use the symbols in your code.
To generate the CPP code press the C++ button in the wXDesigner and the file nnnn_wdr.cpp is generated, this file contains all the nasty menu generation code you used to have. Our Menu bar was called MainMenuBarFunc. All you need to do to invoke it in your main app is to put the following line in the App::OnInit() section:
m_mainFrame->SetMenuBar(MainMenuBarFunc());
wXDesigner has already created a shell for you but you probably want to use your own template. The class browser in MSVC is very useful. The code generated by wXDesigner does not parse well and all the functions are in one file. The wrapper wXDesigner generates it seems is primarily an example shell rather than a starting point for a GUI application and so far we have been evolving a full scale MDI program. The shell provided though is very useful to explain where all the bits plug in.
On the creation of pop-up menus, WXDesigner forces you to use a menu bar. You can try to override this however I found it easier to dump the generated code right into the canvas class. This code is fairly static and if we regenerate it is will be fairly easy to dump it in again.
This is what wXDesigner generates:
wxMenuBar *PopUpMenuBarFunc() { wxMenuBar *item0 = new wxMenuBar; wxMenu* item1 = new wxMenu(wxMENU_TEAROFF); item1->Append(wxID_NEW, wxT("&New\tCtrl-N"), wxT("New chart")); item1->Append(ID_GRIB, wxT("&Open\tCtrl-O"), wxT("Open a Grib File")); item1->AppendSeparator(); item1->Append(ID_1X, wxT("&1x"), wxT("Zoom 1 times")); item1->Append(ID_2X, wxT("&2x"), wxT("Zoom 2 times")); item1->Append(ID_3X, wxT("&3x"), wxT("Zoom 3 times")); item1->Append(ID_4X, wxT("&4x"), wxT("Zoom 4 times")); item1->Append(ID_5X, wxT("&5x"), wxT("Zoom 5 times")); item0->Append(item1, wxT("")); return item0; }
By stripping off the menubar class and implementing this directly in your code you get:
void MyCanvas::ShowDContextMenu( const wxPoint &pos) { wxMenu* item1 = new wxMenu(wxMENU_TEAROFF); item1->Append(wxID_NEW, wxT("&New\tCtrl-N"), wxT("New chart")); item1->Append(ID_GRIB, wxT("&Open\tCtrl-O"), wxT("Open a Grib File")); item1->AppendSeparator(); item1->Append(ID_1X, wxT("&1x"), wxT("Zoom 1 times")); item1->Append(ID_2X, wxT("&2x"), wxT("Zoom 2 times")); item1->Append(ID_3X, wxT("&3x"), wxT("Zoom 3 times")); item1->Append(ID_4X, wxT("&4x"), wxT("Zoom 4 times")); item1->Append(ID_5X, wxT("&5x"), wxT("Zoom 5 times")); PopupMenu(item1, pos.x, pos.y); // test for destroying items in popup menus #if 0 // doesn't work in wxGTK! menu.Destroy(Menu_Popup_Submenu); PopupMenu(&menu, event.GetX(), event.GetY() ); #endif // 0 }
The above function is invoked via the Canvas class that handles all mouse events.
void MyCanvas::OnMouseEvent( wxMouseEvent& event) { wxClientDC dc(this); PrepareDC(dc); wxPoint pt=event.GetLogicalPosition(dc); // Popup support if(event.RightUp()) ShowDContextMenu(pt); // Standard mouse events if(event.LeftDClick()) view->OnLButtonDblClk(0, pt); if(event.Moving()) view->OnMouseMove(0, pt); if(!view) return; }
Now we have a path where the UI can be altered on the fly and we just recompile. About as simple as using native MFC and Visual C++. Only difference is that you have a lot more control over what is going on.
Easy enough to do: in the Frame all you need to add is:
CreateStatusBar(4);
to create four equally sized panes, and then in your body code you set the text:
SetStatusText(_("Ready"),1);
to put the word Ready in the first pane.
The function GetApp() that returns addressability is accessed by simply putting MyApp.h in the view path of the class you want to allow access to the App. In the case of MyDoc putting MyApp.h in the front of the file and calling GetApp() you can access variables. This is very important when we use profile variables.
Those from the golden days of windows programming will remember the ini file and GetPrivateProfile string and WritePrivateProfileString.
Persistence in wxWidgets is similar in concept except the concept is portable. For Win32 we will use the windows registry and wxRegConfig. For Unix & Linux we will need to use the fileConfig. The underlying philosophy is the same so the definitions will change but the use of the config base will not. The wxWidgets documention explains the concept very well.
In MyApp.h define the config base variable config.
This will be our point of contact for all our persistent variables.
wxRegConfig *config; config = new wxRegConfig("MYKEY");
Now in the application code we can do something like this:
wxGetApp().config->Read("LAT", &LAT, (double)0); wxGetApp().config->Write("LAT", worklat)
wxWidgets: http://www.wxwidgets.org
wxDesigner: http://www.roebling.de/
Another introduction to wxWidgets: http://www.all-the-johnsons.co.uk/accu/ index.html
Porting MFC to wxWidgets: http://www-106.ibm.com/developerworks/linux/library/l-mfc/
Notes:
More fields may be available via dynamicdata ..