Journal Articles

CVu Journal Vol 17, #1 - Feb 2005 + Programming Topics
Browse in : All > Journals > CVu > 171 (9)
All > Topics > Programming (877)
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 - Part 3

Author: Administrator

Date: 03 February 2005 13:16:10 +00:00 or Thu, 03 February 2005 13:16:10 +00:00

Summary: 

In this, the final part of the series, Jon rounds off the port of an application from MFC to wxWidgets.

Body: 

In this, the final part of the series, Jon rounds off the port of an application from MFC to wxWidgets.

Internet Access

The wxSocket class provides a very simple interface over the Internet. You will need to have a phone connection or DSL/ISDN network connection established.

To get a web page is simplicity in itself.

The following is from the samples - Sockets - Client.

// define the URL
wxString urlname = "your.url.com"
wxURL url(urlname);
// Check to see url is valid
if(url.GetError() != wxURL_NOERR) {
  m_text->AppendText(("Error: couldn't parse URL\n"));
  m_text->AppendText(("=== URL test ends ===\n"));
  return;
}
// Get the data
wxInputStream *data = url.GetInputStream();

Read up in wxStringBase and wxStringBuffer for data manipulation.

This would take about a page in MFC.

For more sophisticated operations like perhaps talking to a mail server on port 25 you have to establish a socket connection directly using wxSocketClient. This is derived from wxSocketBase which handles both Server and Client connections.

Use wxIPV4address to store the address.

wxIPV4address sockAddr;
sockAddr.Hostname(pszHostAddress);
sockAddr.Service(nPort);

The host address can be a server name or a resolved IP Address.

Now we connect:

m_hSocket.Connect(lpSockAddr, TRUE);

Check we are OK

wxASSERT(m_hSocket.Error());

TRUE has the instruction wait for the connection to complete. The command returns true if a connection was made.

To avoid long timeouts you may want to set this flag to FALSE and use WaitForConnect after the connect. This will allow you to specify your own timeout. Input and output is handled by wxSocketBase.

To read use the following
if(m_hSocket.WaitForRead(-1))
  m_hSocket.Read(pszRecvBuffer,256)
To write
wxString Buffer = "DATA";
  m_hSocket.Write(Buffer, Buffer.Length());
To close the connection:
if(!m_hSocket.Error()) {
  m_hSocket.Close();

For more advanced use of both client and server tools please take a look at the Sockets sample in the wxWidgets samples. The comments are very helpful.

Context Sensitive Help

wxWidgets supports winHelp, Microsoft HTMLHelp and wxHTMLHelp. The latter being a subset and useful for cross platform operation. There are also a lot of internal hooks to put in fast context help for dialogs.

Every wxWindow object can have some text associated with it. In practice this type of help seems to have been the most popular. Most users want a quick hint to get them on their way rather than a huge tome presented when they press the F1 button. The old Microsoft way was to pop up a document with the relevant passage. With a quick hint this would consume a huge amount of real estate on the screen and the many keystrokes required to get out of it detracted from the experience and generally soured the user on the F1 button.

So in this section we will design a simple window based help structure where every item has help of some kind and the F1 key can be used on the fly. Lastly we will create a hot link to the documentation file via Shift F1 or a menu item that will allow us to display the help.

If we have been using wxDesigner to its full extent the object functionality of the tool bar and menu bars will be already in place. You should see context sensitive help on the status bar as you pass the cursor over the object and on the toolbar, a tool tip should pop up. If this is not enough for our poor user, then they will have to start reading the documentation.

To provide a clearing house for a single point in the application to handle all help oriented commands (F1, context help), we use in our mainframe an implementation of wxHelpProvider that allows a great deal of flexibility here:

In the App Class header create:

wxHelpControllerHelpProvider* provider; 

In the mainframe class create as private:

private:
  wxHelpController m_help;

and then create an inline function to return the controller

wxHelpController& GetHelpController() {return m_help;}

Now in the App inititialization before and after the Mainframe creation insert the help implementation:

>> provider = new wxHelpControllerHelpProvider;
>> wxHelpProvider::Set(provider);

Here we substantiate and set the controller class.

// create the main application window
m_mainFrame = new WXWindPlotFrame(m_docManager,
                    (wxFrame*) NULL, "WindPlot "+rs,
                    wxPoint(0, 0), wxSize(640, 480),
                    wxDEFAULT_FRAME_STYLE);
>> provider->SetHelpController(
                & m_mainFrame->GetHelpController());

Here we link the controller with the main frame. We can now use the SetHelpText function of the wxWindow class for any object derived from wxWindow - views, dialogs etc. Just add the following text to the constructor for example

WXWindPlotFrame::WXWindPlotFrame(
          wxDocManager *manager, wxFrame *frame,
          const wxString& title, const wxPoint& pos,
          const wxSize& size, long type)
      : wxDocMDIParentFrame(manager, frame, -1,
               title, pos, size, type, "myFrame") {
  SetHelpText(("To review toolbar functions, rest
         mouse over the toolbar button and read the
         description on the bottom Status
         bar.\nSelect help menu Contents for
         detailed help"));

When you are in a window that has control and you press F1 you will see this text pop up in a neat compact frame window. It will disappear on a single mouse click.

To use the traditional Context Help cursor (the ? And pointer) we need to issue a context help command.

This is accomplished by associating a menu entry or toolbar button with wxID_HELP_CONTEXT.

When the context message is received do the following:

BEGIN_EVENT_TABLE(WXWindPlotFrame,
                  wxDocMDIParentFrame)
  EVT_MENU(wxID_HELP_CONTEXT,
           WXWindPlotFrame::OnCHelp)
END_EVENT_TABLE()

void WXWindPlotFrame::OnCHelp() {
  wxContextHelp chp(this, TRUE);
}

The help controller will handle the rest for you. The Cursor will change and you can take it where you will. Left Click to see the help text

The First Linux Compile

The main weapon of choice here is the preprocessor. While we are trying to make our code as portable as we can, some things will have to be done a little differently.

Using the preprocessor command we can eliminate Unix code from Windows compiles:

#ifndef WXMS_
-- Unix specific commands
#endif

Now comes the moment of truth. Prepare to be humbled and pay for all those sloppy little habits you picked up when using MSVC++.

We are moving over to Linux which though not entirely unfriendly is less forgiving in many respects. Firstly wxWidgets needs to be installed.

On RedHat 9 this went without a problem - I downloaded the GTK+ tarball and unzipped it into a working directory.

Following the instructions: ./config, make and then a make install (the last one as root), we were up and running

wxWidgets sets up library and include paths. The command ldconfig -v sets up the linker paths and you should see libwx_gtk somewhere in there.

We need to be sensitive to shared libraries. I first tried to compile and link everything statically. With GTK this is not possible. Most of the high level widgets need to be loaded dynamically at run time. We can link in all the wx libraries though so when you configure use the command:

./configure -with-gtk -disable-shared

Now we need to get familiar with the development environment. I use kdevelop as this has a lot of similar functionality to MSVC. I am a GUI junkie and am more productive when I have a tool to relate messages to code.

A good move now is to try to run up one of the samples.

Follow These Steps

Navigate to the samples directory of the wx distribution and look for a project. DocViewMDI is a good one. Rename the file makefile.unx to Makefile and we have a make environment ready to go.

Now run up kdevelop and from the project menu generate a project file in same directory. You should now be able to press the cogwheel toolbar button to compile and run the program and verify that your wx build environment is ready to go.

Now on to the Port

MSVC is a lot better when it comes to managing projects and files and as a result my project was spread over several folders. I was re-using source code in several projects and had a common source folder. This is OK if you are on your own but in a commercial environment with multiple programmers it is intolerably sloppy. Common modules should be statically linked into a library and then included on the link path. Include files should be in a common include directory. Then all you need to do is put all your C++ code in a working directory and all the wdr generated includes in the .wdr/ folder.

The construct of the makefile becomes very simple in this case and here it is:

# File: Generic Makefile for wxWidgets under GTK
CXX = $(shell wx-config -cxx)
PROGRAM = WxWindplot
#OBJECTS =  WXWindPlotApp.o *.o
OBJECTS =  $(patsubst %.cpp, %.o, $(SOURCES))
SOURCES = $(wildcard *.cpp)
# implementation
.SUFFIXES:      .o .cpp
.cpp.o :
  $(CXX) -c 'wx-config -cxxflags' -o $@ $<
### Uncomment next line if you need debugging
### information
###     $(CXX) -c 'wx-config -cxxflags' -g -o $@ $<
all:    $(PROGRAM)
$(PROGRAM):     $(OBJECTS)
  $(CXX) -o $(PROGRAM) $(OBJECTS) 'wx-config -libs
                                           --static'
clean: 
  rm -f *.o $(PROGRAM)

This makefile compiles all .cpp programs in the same directory and links them. If you uncomment the debugging line and comment out the line above you will get debugging information.

So Off We Go

Most of the errors you get will be due to include files not properly resolved. There will also be a few MS specific library calls and you will need to find ANSI equivalents or look in the wxWidgets documentation. Specific examples will be platform issues life file name resolution, variable persistence etc.

Remember Unix/Linux file names are case sensitive. Very soon all your source files will be lower case. Microsoft does not care and allows mixing of cases.

One thing to be aware of on the port. I have been quite sloppy about defining variables on the fly. MSVC has a slightly different visibility rule on dynamic variables. It is good practice to follow the old C rule of declaring local variables at the beginning of the subroutine to avoid porting problems. If you declare variable i within a for loop

for(int i=0 ; i<20 ; i++)

and you reference i again you will get an error.

int i;
for(i=0;i<20;i++)

The above is a better way of doing things.

Referencing the contents of wxString - always use wxString::GetData() and wxString::GetChar(i) as opposed to referencing the data directly.

Put on your flying goggles and go to work. For my 5,000 line windplot project over 10 cpp files we had clean compile in 4 hours. It would have been faster had I done this before. Running the program - Wow it ran first time. Not perfect but we were cooking. More important I was able to single step with kdevelop and get an idea where the problems lay.

Where classes have integral data types - wxString wxLonLong - you need to use the access functions rather than the classes themselves wxLongLong you need to pull the long value via wxLongLong::ToLong. Very important if you are using time variables where everything is LongLong (takes us over the next rollover event in 2034).

Regrettably a lot of the sexier features of MS Windows are not all implemented on GTK and especially in things like MDI windows there are some things lacking. The next step will be to look at these deficiencies and see how we can address them.

The Tuneup: Basic Debugging

When you get to work with Linux you experience a true multi threaded multitasking environment. Compared to Windows where your primary thread will hang on a dialog until you respond. Be prepared for some very fast lock ups if you put a modal dialog in a the OnDraw loop for instance.

I was having a dreadful time trying to sort out Windows Sockets and and my breakpoints seemed to be activated at random until I got the hang of what was going on. From then on it was plain sailing and kdevelop was a big help. Looking inside classes was not possible with the way I was using kdevelop and so I have to put in debugging statements to pull values out of wxStrings to see them. For those who are used to an IDE, kdevelop worked very well. Above all the price is right.

The problems I encountered on my first run:

File visibility:

This is very important and you need to agree on where your working files will be based.

Month 0 based:

July was August until I put in a conditional compile statement to decrement the month.

Internet Hang-up: Serial Port Access

Could not open a COM port (/dev/cua0). Using setserial -G /dev/cua0 I verified the port was valid and then logged on as root and gave read/write access to all. After that everything worked. [The name of the ports will vary - the serial ports on my linux box are all ttySx - Ed]

Back to MSVC

The last job was to do a release build and put the windows version on the web. A nasty shock, every function crashed horribly. In debug it was perfect. What to do ?

Finally after looking through the support base, I recompiled wxWidgets specifically disabling optimizations and after the libraries were re-built the code worked perfectly. All that is needed is to go into the Project menu and in the settings go to the C++ tab and set optimizations to Disable (Debug). I had visions of a huge rewrite but fortunately a simple fix was all that was required.

The most important thing I found in all this was that the underlying thought process was similar so re-training in wxWidgets from MFC is a very easy and refreshing process. The Class Wizard and App Wizard that MFC prides itself on is in fact a great snare and a delusion. The work it saves you is quite trivial. I am convinced that this toolkit will be with us for a long time. As open source, its future is assured. The modified GPL license it is released under means that it will be attractive to commercial operations and it could be one of the levers that finally puts Linux in the forefront where it belongs.

Resources

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 ..