mail us  |  mail this page

contact us
training  | 
tech stuff  | 

Tech Stuff - wxWidgets Survival Guide

wxWidgets are just great. Features by the ton, free and cross platform. Lovely stuff. However, wxWidgets is a massive system. And the problem with massive systems is just how do you give each of a wide variety of users, with wildly varying needs and knowledge levels, what they want. It ain't easy. If wxWidgets is a really big deal for you then invest the time and read the project documentation written by the people who know what they are talking about. If you are an intermittent user, need a quick fix for a wxWidgets problem or want to enhance existing code - these notes may help. There again, they may not.

Being C people, we had also, mercifully, forgotten what an anally retentive language C++ is. Sigh.

Note: All information relates to wxWidgets 2.8.10 and may, or may not, be relevant to later versions.

wxWidgets has evolved its own wxSpeak. Undoubtedly for excellent reasons. No sense of trying to confuse the rest of us non-experts. Here is a translation table that may help:

wxWidgets Rest of Us Notes
Frame (wxFrame) Window The big thing that has a title bar at the top! A frame is also a window.
Window(wxWindow) Control Stuff like buttons, text boxes are all called windows in wxSpeak though they will also have a specific type. Thus a normal clickable button has a class of wxButton, will be called a control in some wxWidgets documents but is a window (has wxWindow properties).
Panel(wxPanel) - You need one of these, which is typically the same size as the Frame, to act as a container for all your windows/controls. So create frame, create panel in frame, add windows (controls) to panel.
colour color The original work on wxWidgets was done by Brits (to be pecise, a fellow Scot). However, Brits (and even Scots) have a regrettable habit of spelling color as colour (they may say the reverse about North Americans). Some options work with both spellings, others not. If your friendly local compiler tells you it's not a class member try the Brit spelling. Or, if you have a lot of trouble - add a #define.

Contents

  1. Install and Build wxWidgets (VC++ 2008 Express)
  2. Adding Anything
  3. Adding a new menu
  4. Adding context (popup) menus
  5. Adding tooltips (Static and Dynamic)
  6. Connect an Event at Run-time (dynamically)
  7. Handling Mouse Hover
  8. Mouse Hover Class and Custom Events
  9. Events - get the object Reference
  10. Creating XPM Images
  11. Controls/Windows with Multiple Responses
  12. Order of Events
  13. Dragging Items in a wxListCtrl

Install and Build wxWidgets (VC++ 2008 Express)

A relatively simple and painless process.

Note: The wxWidgets projects recommends you use VC++ 2008 Express (if you are cheapskates like us). On our bright and shiny new Windows 7 system we tried to use MS VC++ 2010 Express Edition since it was the brightest and shiniest version. Big mistake. The wxWidget files are built for VC 6.0 - a very sensible decision to give lots of options. VC++ 2010 Express will not make the conversion from VC 6.0 files in one step (there may be a work around here). However you have a choice - install VC++ 2008 Express Edition - then use it to open xw.dsw (or xw_dll.dsw as you choose) and let it make the conversions from .dsp -> vcproj and .dsw -> .sln. Then you can either continue to use 2008 or load and run your shiny new 2010 Express Edition. If you made the mistake of actually trying to build the project in VC 2010 in spite of all those conversion message errors (OK, we did) then, when you finally install VC++ 2008, you must either delete all the .ndb and .idb files or force a rebuild.

Follow these steps to painless results on Windows (7 in our case):

  1. Download the latest vxWidgets version

  2. This downloads an xxxx-setup.exe. Install it (Oh, that was the problem)

  3. Load your IDE (VC++ 2008 Express in our case)

  4. Open Solution and navigate to vxWidgets-xxx/build/msw and select either vx.dsw or vx_dll.dsw

  5. Click OK to all the conversion requests

  6. Now you have two choices. You can simply hit Build->Build Solution (which on a modest machine can take a long time)

  7. OR You can go to Project->Properties then click Configuration. By default all possible projects are built. By selecting from the Drop down Configuration box at the top you can unset all the libraries for any thing you don't want (click apply after every page update). Close the window and now select Build->Build solution

  8. Once the libararies have been built you probably won't need to touch the vxWidgets projects again. But if you do, load the vx.sln or vx_dll.sln files (unless you like a lot of clicking)

  9. To include the vxWidgets libraries in projects Tools->Options and select Projects and Solution->VC++ Directories. Select the Include Files from the 'Show Direcctories for' drop down and add both vxwidget-xxx/include and vxwidget-xxx/include/msvc. now select the 'Libary files' drop down and add vxwidget-xxx/lib/vc_lib

Adding Anything

wxWidgets does a good job of trying to makes things approachable. If you need to add anything to an existing system these are the 4 things you need to check:

Item 1: Application Start (Main)

// item 1
// there is no main defined - instead the macro
// IMPLEMENT_APP does everything 
// It results in a call to a function you provide
// x::OnInit() to run start running your code
// where x is the name you choose for your application
// example startup for anyapp:

// class definition for 
class anyapp : public wxApp
{
public:
	virtual bool OnInit();
  ...
};

IMPLEMENT_APP(anyapp)

anyapp::OnInit()
{
 // start doing stuff
}

Item 2: Frame (Class) Definition

// item 2
class anyname : public wxFrame
{	
public:
  // this is the contructor definition
  // calls function anyname::anyname
  anyname(const wxString& title, const wxPoint& pos = wxDefaultPosition,
    const wxSize& size = wxDefaultSize); 
  
  virtual ~anyname();	// destructor for object (wxFrame)
  ...
  // any function which is used by the anyname object appears here
  // format as shown or anyname::OnDoSomething1(..)
  void OnDOSomething1(wxCommandEvent& event);
  void OnDOSomething1(wxCommandEvent& event);
  void OnDOSomething1(wxCommandEvent& event);
  ...
  void MakeMenus();
  ...
  // macro below indicates there is an event table 
  // see item 3
  DECLARE_EVENT_TABLE()
};

Item 3: Event Table

The event table defines the functions that will be called to respond to user defined wxWidgest events. Event tables can be defined for any window but are typically defined only for a frame window:

// item 3
// each window has an event table
// the BEGIN_EVENT_TABLE defines the object name and type

BEGIN_EVENT_TABLE(anyname, wxFrame)
  // where anyname is the users chosen name for the window and
  // wxFrame is the wxWidgets object class
  ...
	// various macros define events types
  // EVT_MENU covers clicked menu items
	
  EVT_MENU(ID_MENU_OPEN,	  anyname::OnMenuOpen)
  
  // ID_MENU_OPEN is a unique ID (nomally defined using enum 
  // to ensure uniqueness) when user clicks the function
  // enum{
  // ...
  // ID_MENU_OPEN
  // ...
  //};
  // anyname::OnMenuOpen is called (prototype defined in item2)
	// macros are control/window type specific
  EVT_SPIN_UP(ID_SPIN_HIGH, anynamee::OnSpinHigh)
  ...
  EVT_CHECKBOX(ID_CHK_YES,  anyname::OnChkYes)
	...
  // END_EVENT_TABLE defines the end of the event table
END_EVENT_TABLE()

Item 4: Functions/Constructor

Every function identified in item 2 will have a corresponding function definition. This is shown for a frame for no particulary good reason:

// item 4
// the following code fragment creates anyframe (and hence calls its constructor)
anyappname::OnInit()
  ...
  anyname *frame = new anyname(text, wxPoint(100,500), wzSize(200,300);
  ...
};

// anyname constructor
anyname::anyname(const wxString& title, const wxPoint& pos, const wxSize& siz)
    : wxFrame((wxWindow *)NULL, -1, title, pos, siz, style)
{
	...
  // make menus and add menu items
  ...
  // add the panel - minimal defaulst to same size and position
  // as wxFrame which is typical) 
  panel = new wxPanel(this)
  // this simply says the current 
  // object (anyframe) is the parent
  ...
  // add controls 
};

To add a menu to an existing frame simply add the construction code to the frame constructor (see item 4 above), define and then add the various menu items to the Event Table (see item 3 above), define and add the prototypes for each function to the class definition (see item 2 above). Add the various functions (see also item 4 above). The process is shown below:


// add menu construction code to frame constructor
anyname::anyname(....)
  ...
  wxMenu nm = new wxMenu;  // create new menu object or
  // add menu items
  nm.Append(ID_MENU_OPENLIST, wxT("What!"));
  ...
	nm.AppendSeparator();
	menu.Append(ID_MENU_LAST,	wxT("Eh?"));
};

// define all menu events
BEGIN_EVENT_TABLE(anyname, wxFrame)
  ...
  EVT_MENU(ID_MENU_OPEN,  anyframe::OnMenuOpen)
  ...
  EVT_MENU(ID_MENU_LAST,  anyframe::OnMenuEh)
  ...
END_EVENT_TABLE()	

// add menu handling prototypes to anyname class
class anyname : public wxFrame
{	
public:
  // this is the contructor definition
  // calls function anyname::anyname
  anyname(const wxString& title, const wxPoint& pos = wxDefaultPosition,
    const wxSize& size = wxDefaultSize); 
 
  // format as shown or anyname::OnDoSomething1(..)
  void OnMenuOpen(wxCommandEvent& event);
  ...
  void OnMenuEh(wxCommandEvent& event);
  ...
  DECLARE_EVENT_TABLE()
};

// code the menu functions
anyname::OnMenuOpen(wxCommandEvent& event)
{
  // something sensible
}

// code the menu functions
anyname::OnMenuEh(wxCommandEvent& event)
{
  // something more sensible
}

Adding Context (Popup) Menus

Context (or right click popup) menus can be fired from the frame event table as shown:

BEGIN_EVENT_TABLE(anyname, wxFrame)
  ...
  EVT_CONTEXT_MENU(        anyname::OnContext)
  // the above event does not have an associated window ID
  // the function OnContext will be called if the user right 
  // clicks anywhere in the frame 
  ...
  EVT_MENU(ID_MENU_OPEN,   anyname::OnMenuOpen)
  // menu events by contrast do take an ID argument so OnMenuOpen
  // will only be called when that menu item is clicked 
  // - not every menu item
  ...
END_EVENT_TABLE()

// function called for right click anywhere in frame
void anyname::OnContext(wxContextMenuEvent& event)
{
  wxMenu menu;
  wxPoint point = event.GetPosition();
  // to get a window location if required use
  int id = wxFindWindowAtPoint(point)
		
  // create menu 
  menu = new wxMenu;
  // add stuff
  menu.AppendRadioItem(ID_XXX, wxT("Get Lost"), wxT(""));
  ...
  // and then display
  PopupMenu(&menu, point.x, point.y);
}

If you want to have context specific menus for individual windows/controls within the frame then the technique described below for Connect() is probably the quickest and most efficient.

Tooltips - Dynamic and Static

Static tooltips are trivial - just use the SetToolTip() method for the object as shown below:

  t = new wxStaticText(panel, ID_TEXT_WHIZZO, wxT("Whizzo"), wxPoint(x,y));
  t->SetToolTip(wxT("Some incredibly compelling text"));
  ...

When the user's mouse hovers over the control/window (static text in the example above but could be any appropriate control) the defined text will be displayed automatically by wxWidgets. No further action is required by the application.

Dynamic Info/Tooltip

If you want/need to display context specific or run-time text, for example to display the values of one of more variables as you mouse over a control/window in a tooltip, then you need to capture various mouse events that indicate when the user is over your target control/window and display the results using TipWindow. However, if you look at the various mouse events none of them take an id value. They operate at the window level (in whose event table they appear) which is typically the frame. Assume a normal frame event table definition (see also Item 3 above):

BEGIN_EVENT_TABLE(anyname, wxFrame)
	...
  // mouse event macros
  EVT_ENTER_WINDOW(                  anyname::OnMouseWelcome)
  EVT_LEAVE_WINDOW(                  anyname::OnMouseBye)
  // none of the above mouse events have an associated window ID
  // the function OnMouseWelcome will be called every time the 
  // the mouse enters every window on the frame - very inefficient 
  ...
  EVT_MENU(ID_MENU_OPENLIST,			   anyname::OnMenuOpenList)
  // menu events by contrast do take an ID argument so OnMenuOpenList
  // will only be called when that menu item clicked - not every menu item
  ...
END_EVENT_TABLE()
...
void anyname::OnMouseWelcome(wxMouseEvent& event)
{
   // this function will get every mouse enter for the frame but 
   // we could do something like this to select the windows we want 
   wxWindow *w = event.GetEventObject();
   int target = w->GetId(){
   // OR
   // wxPoint point = event.GetPosition();
   // int target = wxFindWindowAtPoint(point)
   if(target >= LOW_TARGET_ID && target <= HIGH_TARGET_ID){
     // do something
   }
   event.Skip();  // allow the window to pick up mouse operations as well
}

This will work but it is extremely inefficient - especially if you have tons of controls in the frame - but in any case it offends mightily. The normal solution is to create a new class from the control/window type(s) that you are interested in and then define a EVENT_TABLE for each one. Life is not long enough to either figure out how to do that, or to define all the required EVENT_TABLES if you are simply trying to use this stuff as opposed to being a fully paid-up C++ kinda person.

Connecting Events at Run Time

wxWidgets provides a modest little feature called Connect() which allows event handlers to be attached to specific controls/windows at run time-time, whereas the EVENT_TABLE macros link event handlers statically at compile time. This means that, say, a mouse event (or a context menu event) which as we have seen above cannot be associated with a specific control when defined statically can in fact be associated with a specific control if it is done at run-time. The following code will get the precise events we want, from the controls/windows we want, and no others:

Note: We read in the latest documentation that from WxWidgets Release 2.9.x an alternative Bind() is provided (Connect() remains supported) which does the same, has different paramters but does have additional flexibility. Now if we only had 2.9.x...

<OUCH> In a previous version of this page we omitted the , NULL, this on the Connect() in the fragment which works perfectly if you don't use any Class variables. If the explicit this is omitted, the default this, described in the documentation, is the window to which the Connect() is applied which, in the example case below, is sliders[i]. Obvious, really, when you think about it. Which, of course, we did not.</OUCH>

BEGIN_EVENT_TABLE(anyname, wxFrame)
  // no mouse events defined for tooltip system
	...
END_EVENT_TABLE()
// anyname constructor
anyname::anyname(...........)
                : wxFrame((wxWindow *)NULL, WINDOW_ID, title, pos, siz, style)
{
  ...
  // example - set up an array of sliders (can be for any control/window)
  for(i = 0; i < SOME_NUMBER; i++){
    sliders[i] = new wxSlider(..............);
    ...
    // connect events
    sliders[i]->Connect(wxEVT_ENTER_WINDOW, 
           wxMouseEventHandler(anyname::OnMouseWelcome),NULL,this);  
    sliders[i]->Connect(wxEVT_LEAVE_WINDOW, 
           wxMouseEventHandler(anyname::OnMouseBye),NULL,this);
    // line below if uncommented would enable a window specific context menu
    // sliders[i]->Connect(wxEVT_CONTEXT_MENU, 
           wxContextMenuEventHandler(anyname::OnContext),NULL,this);  
   ...
  }
  ...
}

The functions OnMouseWelcome and OnMouseBye will now only be called when these events occur in the windows/controls that we have issued the ->Connect() for. Now we have only the simple - or, as it turns out, not so simple - matter of figuring when the mouse is actually hovering.

Detecting Mouse Hover

We initially thought triggering dynamic tooltips would be as simple as capturing wxEVT_ENTER_WINDOW and wxEVT_LEAVE_WINDOW events. Shows you how stupid we were. First, there is the classic problem of jitter as the user moves into a window - you tend to wobble a bit on the way in (and on the way out) so you can get all kinds of nasty flickering windows. Second, we found problems getting reliable wxEVT_LEAVE_WINDOW events when a tooltip window is active. So we dropped the wxEVT_LEAVE_WINDOW event entirely and added a de-jitter timer. In our case we used 500ms - you could experiment, perhaps, with smaller values. The timer value determines how long the mouse must be over the control before the TipWindow will be displayed. Finally, we also use the timer to Destroy() the displayed TipWindow - wxWidgets will do this automatically but only if the user clicks outside of the target controls which is jolly helpful but a tad limiting in the real world. So here is the final code:

Note: The following example is a follow on from the previous one and uses the same sliders control simply for illustration and consistency. The general principle illustrated in the mouse handling code holds for detecting a hover operation on any control. Note: We ultimately built a general purpose Hover Class with a Custom Event when we started to use hover more frequently.

BEGIN_EVENT_TABLE(anyname, wxFrame)
  ...
  // no mouse events defined for tooltip system
  ...
  EVT_TIMER(ID_TIMER_TIPWINDOW anyname::OnTimerTip)
  ...
END_EVENT_TABLE()
// anyname constructor
anyname::anyname(const wxString& title, const wxPoint& pos, 
            const wxSize& siz, long style)
            :wxFrame((wxWindow *)NULL, WINDOWEQ, title, pos, siz, style)
{
  ...
  // example - set up an array of sliders
  for(i = 0; i < SOME_NUMBER; i++){
   sliders[i] = new wxSlider(.......);
   ...
   // connect events
   sliders[i]->Connect(wxEVT_ENTER_WINDOW, 
        wxMouseEventHandler(anyname::OnMouseWelcome),NULL,this); 
   ...
  }
  ...
}

// mouse events - connected when slider controls initialized
//
// ToolTip display strategy
//	a. single tooltip at any one time
//	b. Mouse Enter
//		i. if id not same as current id destroy any displayed tipwindow
//		ii. in all cases save new id and fire timer
//	c. Mouse Leave
//		i. not captured
//	d. timer expires
//		  i. get current mouse position and related window id
//		 ii. if id same and there is a tipwindow just refire timer and exit 
//    iii. if id same and no tipwindow - create one
//     iv. if id not same but in our range:
//         - window currently active - Destroy
//         - in all cases display new tipwindow
//      v. if id not in range Destroy tipwindow
void anyname::OnMouseWelcome(wxMouseEvent &event)
{
    wxWindow *w = event.GetEventObject();
    int id = w->GetId();   // get the control id
    if(id != tipid){ // to handle jitter case
      if(tipwin){
         tipwin->Destroy();
         tipwin = NULL;
       }
    }
    tipid = id;  // update to new id
    m_timertipwin->Start(APP_TIMER_TOOLTIP_DELAY, true);
    // allow others to handle mouse event as well
    event.Skip();
}
// timer control
void anyname::OnTimerTip(wxTimerEvent &event)
{
    wxPoint pos;
    int id;
		
    pos = wxGetMousePosition(); // find the location of the mouse
    wxWindow *w = wxFindWindowAtPoint(pos);
    if(w){ // if window valid
        id = w->GetId();
        if(id == tipid){ // current window
            if(!tipwin){ // display tipwindow if not active
                // create data for tipwindow display and then show it
                tipwin = new wxTipWindow(w, data, 100,&tipwin);
            }
            // keep timer running 
            m_timertipwin->Start(APP_TIMER_TOOLTIP_DELAY, true);  
        }else if (id >= LOW_TARGET && id <= HIGH_TARGET){ // new window
            if(tipwin){ // destroy any active tipwindow
                tipwin->Destroy();
                tipwin = NULL;
            }
            tipid = id; // update to new window id
            // create data for tipwindow display and then show it
            tipwin = new wxTipWindow(w, data, 100,&tipwin);
            // keep timer running
            m_timertipwin->Start(APP_TIMER_TOOLTIP_DELAY, true);
        }else{ // not in our target range - kill tipwindow - no timer
            if(tipwin){
                tipwin->Destroy();
                tipwin = NULL;
            }
        }
    }else if(tipwin){ // if tip window active just fire timer
        m_timertipwin->Start(APP_TIMER_TOOLTIP_DELAY, true);
    }
}

Doubtless there are BQF (better, quicker, faster) ways of doing this - but it works. Sigh.

Hover Class and Custom Events

Like many others we constantly have the problem of getting a lot of controls into a small space. Our historic approach to this problem was to make the controls as compact as possible. Which, with hindsight, is really rather pointless and, especially if the control is user manipulated, ulimately self-defeating (if the user needs a magnifying glass to manipulate the control it is probably not too effective). Current trends in UI seem to be moving toward zooming (or popping-up - if such a verb form exists) the control when the user hovers over it. This has two major implications. First, the 'minimal' control need not bear any resemblance to the zoomed control. This is entirely positive and can lead to new and interesting control forms. Second, we need to detect hover on all our new controls. We had previously detected hover in a very simplistic and result specific manner (dynamic tool-tips in our previous case). Now we needed a generic hover process and one whose results we could use in a control specific manner. After much consulting of entrails and other mystical processes we decided that a Hover detection class was called for and a custom event (in our case we called it MOUSE_HOVERING to show how truly creative we are when it comes to naming things). This turned out to be a relatively painless exercise (once we found all the info) because of the trivial insight that only one control can be hovered-over at any one time (OK, so its obvious to you immediately, we needed time - lots of time - to figure it out). Finally, we have no need for an UNHOVER notification since it will always be up to the zoomed control to figure out what to do. So here are the code fragments for a custom event generating Hover class:

The Hover class

// define mouse hovering event MOUSE_HOVERING
// and a EVENT_TABLE type of EVT_MOUSE_HOVERING
// from event sample code - apparently since 2.4.0 the event ID 
// is not used - but a value must be present
BEGIN_DECLARE_EVENT_TYPES()
    DECLARE_LOCAL_EVENT_TYPE(MOUSE_HOVERING, 7790)
END_DECLARE_EVENT_TYPES()

DEFINE_EVENT_TYPE(MOUSE_HOVERING)

// event table macro for MOUSE_HOVERING
#define EVT_MOUSE_HOVERING(id, fn) \
    DECLARE_EVENT_TABLE_ENTRY( \
        MOUSE_HOVERING, id, wxID_ANY, \
        (wxObjectEventFunction)(wxEventFunction) wxStaticCastEvent( wxCommandEventFunction, &fn ), \
        (wxObject *) NULL \
    ),
		
// Hover - class for handling detection of hover in defined windows
// raises EVT_MOUSE_HOVERING /MOUSE_HOVERING
class Hover: public wxTimer
{
public:
  Hover  (wxFrame *f,int interval);
	
  void StartHoverDetect(wxWindow *win);
  void StopHoverDetect(wxWindow *win);
	
private:
  void Notify();
  void OnHello(wxMouseEvent& event);
  int m_i;		  // hover time
  wxFrame *m_p;	// parent frame
  bool m_tr;		// timer running 
  int m_id;		  // current hover window id

}; // Hover
Hover::Hover(wxFrame *f,int interval): wxTimer(this)
{
  m_i = interval;
  m_tr = false; // timer not active
  m_p = f;
}
void Hover::StartHoverDetect(wxWindow *win)
{
  win->Connect(wxEVT_ENTER_WINDOW, (wxObjectEventFunction)(wxEventFunction)(wxMouseEventFunction)
       &Hover::OnHello,NULL,this);
}
void Hover::StopHoverDetect(wxWindow *win)
{
  win->Disconnect(wxEVT_ENTER_WINDOW, 
       (wxObjectEventFunction)(wxEventFunction)(wxMouseEventFunction) &Hover::OnHello);
}
void Hover::OnHello(wxMouseEvent& event)
{
  int id;
  wxWindow *win = (wxWindow *)event.GetEventObject();
  if(win){
    id = win->GetId();
    if(m_tr){
      if(id == m_id){
        return;// wait for timer to expire
      }
      // stop and restart timer
      wxTimer::Stop();
      wxTimer::Start(m_i,true);
      m_id = id; // update to new window
    }else{
      m_id = id;
      wxTimer::Start(m_i,true);
      m_tr = true; 
    }
  }
}
// timer expired
void Hover::Notify()
{
  wxPoint pos = wxGetMousePosition();
  wxWindow *win = wxFindWindowAtPoint(pos);
  int id;
  if(win){
   id = win->GetId();
    if(id = m_id){ // raise event
      wxCommandEvent *e = new wxCommandEvent(MOUSE_HOVERING, m_id);
      m_p->ProcessEvent(*e);
      delete e;
    }
  }
  m_tr = false;
}
// class Hover

// usage of Hover class
// creation of single Hover instance when anyframe created
// parent = this frame, 300ms hover timer detection
  ...
  m_hover = new Hover(parent,300);
  ...
	// create some window
	wxSlider *win = new wxSlider(blah, blah etc.);
	m_hover->StartHoverDetect(win);
  ...

Events - getting an Object Reference

Where a control is unique the event table provides enough information to fully process the event as shown below:

BEGIN_EVENT_TABLE(anyname, wxFrame)
	...
	EVT_MENU(ID_MENU_QUIT,  anyname::OnMenuQuit)
	EVT_MENU(ID_MENU_OPEN,  anyname::OnMenuOpen)
	...
END_EVENT_TABLE()


void anyname::OnMenuOpen(wxCommandEvent& event)
{
   // event can only have come from Menu ID_MENU_OPEN item in anyname
}

Events - Multiple Frame Instances

Assume the case that you want the same code to handle an event that may come from more than one frame instance then you need to obtain an explicit reference to the frame that generated the event as shown below.

BEGIN_EVENT_TABLE(anyname, wxFrame)
	...
	EVT_MENU(ID_MENU_QUIT,    anyname::OnMenuQuit)
	EVT_MENU(ID_MENU_OPEN,    anyname::OnMenuOpen)
	...
END_EVENT_TABLE()
	
	// create frame instance 1
	anyname *an1 = new anyname(......)

	// create frame instance 2
	anyname *an2 = new anyname(......)

// now the event may come from any instance of the frame
void anyname::OnMenuOpen(wxCommandEvent& event)
{
  // event can come from any instance of frame
  // so we need the frame instance id
  int id = this->GetId(); // which can simply be written as GetId();
  int eid = event.GetId();	// returns the id of the menu item if required
}

Events - Drag 'n Drop

When a drop event occurs it delivers the X and Y co-ordinates. To obtain the object reference of the dropped target use the following:


bool DnDFile::OnDrop(wxCoord x, wxCoord y, const wxArrayString& filenames)
{
   wxPoint pos = wxPoint(x,y);
   wxWindow *w = wxFindWindowAtPoint(pos);
   // find the dropped into window - and figure how to handle it
   int id = w->GetId();
   // now do something sensible
}

Creating XPM Images

wxWidgets seems to like XPM format images. Not a lot of common graphic programs support it - especially on the Windows system we were using. The notable exception is, of course, the great Gimp. You can either create the image in Gimp directly or import some other format (if there is an image format the Gimp cannot read and write then we haven't come across it - yet). By default, assuming the file name is button1.xpm, Gimp writes the following character array declaration:

static char * button1_xpm[] = {
"blah",
"blah"};

For use with the C++ used by wxWidgets you will likely need to modify the static to const and if you want, you can change the array name at the same time. In the example below we removed the _xpm simply to show it can be done:

const char * button1[] = {
"blah",
"blah"};

Include the file in the C++ module and then you can reference it directly by its array name as shown:

// include the file containing the xpm image
...
#include "images/button1.xpm"     // button image file
...
// reference the image by its array name
  m_buttom = new wxBitmapButton(MyPanel, ID_BTN, wxBitmap(button1));

Controls/Windows with Multiple Responses

We needed to create a custom control which can generate one of, in this case, three responses (or 5 states) depending where the user left clicks in the control/window. It was part of an effort to reduce screen real estate by combing multiple controls into one. We struggled a little (not unusual and not for the first time) to figure how to do it - and the answer turned out to be trivial, which is perhaps more a testament to the flexibility of wxWidgets than to our vestigal brainpower. We simply generated three appropriate (sensible - to us) events that the user can handle in their frame event table. In addition we also used event.SetInt() (the user can retrieve it with event.GetInt()property) to supply a specific valued integer parameter depending on the event. The documentation is not entirely clear on the use of this method but it seems to work and not upset anything - long may it continue. Thus, for example, we could send positive or negative parameters rather than require separate Up and Down events. The code fragment below shows what we did:

class multiClick : public wxWindow
{
 public:

  multiClick(....);
  ...
  void OnLeftClick(wxMouseEvent &event);
  ...
private:
  DECLARE_EVENT_TABLE()
};

// window/control class event table - left click
BEGIN_EVENT_TABLE(multiClick, wxWindow)
  ...
  // other interesting stuff
  ...
  EVT_LEFT_DOWN(multiClick::OnLeftClick)
  ...
END_EVENT_TABLE()

void multiClick::OnLeftClick(wxMouseEvent &event)
{

  wxPoint p = event.GetPosition(); // get mouse position within window
  WXTYPE  type;
  int mouse;
  int setint;
  ...
  // code for mouse location detection
  if(mouse == LOCATION_ONE){
    type = wxEVT_COMMAND_BUTTON_CLICKED; // generates EVT_BUTTON
    setint = 1;
	}else if(mouse == LOCATION_TWO){
    type = wxEVT_COMMAND_SLIDER_UPDATED; // generates EVT_SLIDER
    setint = 2;	// up event
  }else if(mouse == LOCATION_THREE){
    type = wxEVT_COMMAND_SLIDER_UPDATED; // generates EVT_SLIDER
    setint = -2;	// down event
  }else{
    type = wxEVT_COMMAND_RADIOBOX_SELECTED; // generates EVT_RADIO
    setint = 3;
  }
  wxCommandEvent *e = new wxCommandEvent(type, mID);
	e->SetInt(setint); // parameter for user 
	ProcessEvent(*e);
	delete e;
}

// user frame event table 
BEGIN_EVENT_TABLE(UserFrame, wxWindow)
  ...
  // other interesting stuff
  ...
  // note: it's the control/window id that ties everything together
  EVT_BUTTON(ID_MULTICLICK_CONTROL, UserFrame::OnMultiButton)
  EVT_SLIDER(ID_MULTICLICK_CONTROL, UserFrame::OnMultiSlider)
  EVT_RADIOBOX(ID_MULTICLICK_CONTROL, UserFrame::OnMultiRadio)
  ...
END_EVENT_TABLE()

// example user event handler
void UserFrame::OnMultiSlider(wxCommandEvent &event)
{
  int p = event.GetInt()
  if(p > 0)
    // do stuff
  else 
    // do other stuff
}

Event Order

We frequently get lost in the bushes (we don't even qualify not to see the wood from the trees - we're strictly bushes people). Especially over the order in which stuff happens. We see gobs of code that spends hours creating and carefully drawing an image when the control is initialized only to finally spot the fact that it's a complete waste of time - the EVT_PAINT event is called before the users sees it. You have to do it there anyway - so only do it there. Here is the beginning a time line of events that was of interest to us:

Note: The event order is defined and was verified for wxWidgets (2.8.x) on MS Windows whether they remain the same across all wxWidgets suported platforms remains a mystery we have not yet had cause to solve.


// control/window within Frame
 // user creates Frame
 wxFrame f = new UserFrame(.....)
 wxWidgets->UserFrame->constructor
 
 // in Frame constructor user creates thingie window
 thingie t = new thingie(...)
 
 wxWidgets->thingie->constructor
 
 // constructor causes....
 wxWidgets->wxFrame->EVT_SIZE->User function
 wxWidgets->wxFrame->EVT_MOVE->User function

 // user clicks minimize window/frame
 wwWidgets->wxFrame->EVT_MOVE->users move function 
           wxFrame::IsIconized() = TRUE
 
 // user clicks from taskbar window/frame
 wxWidgets->wxFrame->EVT_MOVE->users move function 
          wxFrame::IsIconized() = FALSE
 wxWidgets->wxFrame->EVT_SIZE->users size function 
          wxFrame::IsIconized() = FALSE
 
 // user maximizes from normal window
 wxWidgets->wxFrame->EVT_MOVE->users move function 
          wxFrame::IsIconized() = FALSE
          wxFrame::IsMaximized() = TRUE
          wxFrame::IsFullScreen() = FALSE
 wxWidgets->wxFrame->EVT_SIZE->users size function 
          wxFrame::IsIconized() = FALSE
          wxFrame::IsMaximized() = TRUE
          wxFrame::IsFullScreen() = FALSE
					
 // user clicks minimize from maximised window/frame
 wwWidgets->wxFrame->EVT_MOVE->users move function 
          wxFrame::IsIconized() = TRUE
          wxFrame::IsMaximized() = FALSE
          wxFrame::IsFullScreen() = FALSE 
	
 // user maximizes from maximized window/frame
 // wxWidgets restores frame to pre-maximise size
 wxWidgets->wxFrame->EVT_MOVE->users move function 
          wxFrame::IsIconized() = FALSE
          wxFrame::IsMaximized() = TRUE
          wxFrame::IsFullScreen() = FALSE
 wxWidgets->wxFrame->EVT_SIZE->users size function 
          wxFrame::IsIconized() = FALSE
          wxFrame::IsMaximized() = TRUE
          wxFrame::IsFullScreen() = FALSE	
					
  // wxListCtrl Event order	
  // user double clicks mouse in wxListCtrl item 
	wxWidgets->wxFrame->wxListCtrl->EVT_LIST_ITEM_FOCUSED->users focus function
	wxWidgets->wxFrame->wxListCtrl->EVT_LIST_ITEM_SELECTED->users selected function
	// events stop after EVT_LIST_ITEM_SELECTED if single click 
	wxWidgets->wxFrame->wxListCtrl->EVT_LIST_ITEM_ACTIVATED->users selected function

Dragging Items in a wxListCtrl

We had a list of items in a classic wxListCtrl and wanted to allow the use to change the order entirely at their discretion. We initially provided a promote/demote feature fired from a context menu which works perfectly for single operations. However, it quickly gets annoying if the user wants to move an item by 3 or 4 places. We needed to allow the user to drag the item to its new position. Two complications. First, we were already using DnDFile to allow the user to drag files or directories into the window. This was going to get nasty - and fast. Second, we would have to provide code for the wxDropSource and wxDropTarget operations. We read the documentation and started to feel ill. All we needed was a list item index when the operation started and the list item index of the location when the user released the left button. Perhaps drag and drop was overkill for in-control (or in-Window) operations. We could get the start of drag operation easily from the EVT_LIST_BEGIN_DRAG event but not the corresponding DROP event (for reasons best known to the wxWidgets folks). Our friendly neigborhood Connect() (and its buddy Disconnect() this time) came bouncing to the rescue - aided and abetted by HitTest which we had been dying to use for ages (great name for a method - though we would prefer if it were called Wackit). We let wxWidgets do the start drag detection (using the normal Event Table EVT_LIST_BEGIN_DRAG) and use that to dynamically Connect() to the wxEVENT_LEFT_UP (end drag) and the wxEVT_LEAVE_WINDOW to (to abort the drag). When either event occurs we Disconnect() the event drivers. So here is a fragment for a poor man's Drag n' Drop in a wxListCtrl:

// static event fires the start of dragging from the Event table
BEGIN_EVENT_TABLE(anyframe, wxFrame)
...
// more interesting stuff
...
  EVT_LIST_BEGIN_DRAG(ID_LIST_CTRL,		anyframe::OnDragStart)
...
END_EVENT_TABLE()
// m_list is an anyframe class variable pointing to wxListCtrl
// m_drag is an anyframe class variable containing the index value on drag start
// OnDragStart is driven from event table when user starts a drag (left button is down)
void anyframe::OnDragStart(wxListEvent &event)
{
  m_drag = event.GetIndex();	// save the start index
  // do some checks here to make sure valid start
  ...
  // trigger when user releases left button (drop)
  m_list->Connect(wxEVT_LEFT_UP, 
    wxMouseEventHandler(anyframe::OnDragEnd), NULL,this);
  // trigger when user leaves window to abort drag
  m_list->Connect(wxEVT_LEAVE_WINDOW, 
    wxMouseEventHandler(anyframe::OnDragQuit), NULL,this);
  // give visual feedback that we are doing something
  m_list->SetCursor(wxCursor(wxCURSOR_HAND));
}

// drop a list item (start row is in m_drag)
void anyframe::OnDragEnd(wxMouseEvent& event)
{
  wxPoint pos = event.GetPosition();  // must reference the event
  int flags = wxLIST_HITTEST_ONITEM;
  long index = m_list->HitTest(pos,flags,NULL); // got to use it at last
  if(index >= 0 && index != m_drag){ // valid and not the same as start
    ...
    // do something to move/copy the list control items
    ...
  }
  // restore cursor
  m_list->SetCursor(wxCursor(*wxSTANDARD_CURSOR));
  // disconnect both functions
  m_list->Disconnect(wxEVT_LEFT_UP, 
    wxMouseEventHandler(anyframe::OnDragEnd));
  m_list->Disconnect(wxEVT_LEAVE_WINDOW, 
    wxMouseEventHandler(anyframe::OnDragQuit));

}
// abort dragging a list item because user has left window
void anyframe::OnDragQuit(wxMouseEvent& event)
{
  // restore cursor and disconnect unconditionally
  m_list->SetCursor(wxCursor(*wxSTANDARD_CURSOR));
  m_list->Disconnect(wxEVT_LEFT_UP, 
    wxMouseEventHandler(anyframe::OnDragEnd));
  m_list->Disconnect(wxEVT_LEAVE_WINDOW, 
    wxMouseEventHandler(anyframe::OnDragQuit));
}

Dragging Items from a wxTreeCtrl

wxTreeCtrl is great when you want to drag stuff within the control it has both EVT_TREE_BEGIN_DRAG and EVT_TREE_END_DRAG (unlike wxListCtrl) which deliver the wxTreeItemId as part of the event. No HitTest required. Lovely stuff. We had this working like a dream for re-organizing the tree control. So then we wanted to both drag items within the control and outside the control. In our case we wanted to drag certain node types to another window that would accept wxFileDropTarget objects using an off-the-shelf DnDFile class (or even into MS Mile Manager/Explorer). To make matters worse the wxTreeCtrl could accept drag and drop from MS File Manager/Explorer into the wxTreeCtrl using a modified DnDFile class (though paradoxically, and unusually for us, this turned out to be positive).

Using the standard in-control drag features of wxTreeCtrl we cannot do what we want. The EVT_TREE_BEGIN_DRAG did not allow us to define data to be associated with the drag operation. You can either issue event.allow() or not. We thought briefly (if the truth be told we rarely think for longer than briefly) about using a context menu item to initiate an external drag but that would not be intuitive for the user. They want to grab the item and drag it. Internally or externally. No matter.

However, wxWidgets provides a lot of functionality and using wxDropSource, a boolean in the frame object and a couple of lines in our modified DnFile object we can now accept internal and external drop operations into the wxTreeCtrl and drag items within the wxTreeCtrl or externally to another wxWidgets app or MS file manager/explorer. There are probably BQF (better, quicker, faster) methods than that shown below but, as usual, we don't know of any.

// anyframe contains wxTreeCtrl
class anyframe : public wxFrame
{
public:
  // normal frame stuff here
  ...
  bool		m_drag;		// true = drag active
  wxTreeCtrlId m_sn; // source node for drag
  ...
private:
	...
  DECLARE_EVENT_TABLE()
}; 

// static event fires the start of dragging from the Event table
BEGIN_EVENT_TABLE(anyframe, wxFrame)
  ...
  // more interesting stuff
  ...
  EVT_TREE_BEGIN_DRAG(ID_LIST_CTRL,		anyframe::OnDragStart)
  // we do NOT define an EVT_TREE_END_DRAG
  ...
END_EVENT_TABLE()
// anyframe constructor
anyframe::anyframe(const wxString& title, int id, const wxPoint& pos, 
          const wxSize& siz, long style)
          : wxFrame((wxWindow *)id, 1, title, pos, siz, style)
{
  ...
  // do useful stuff
  ...
  m_tree = new wxTreeCtrl(blah...blah params);
  m_drag = false;		// drag not active
  SetDropTarget(new anydrop(m_tree,this)); // create our drop target
  // anydrop is a variant of DnDFile which checks m_drag
} // anyframe

// OnDragBegin prepares for both internal and external drops
void anyframe::OnDragBegin(wxTreeEvent &event)
{
  wxFileDataObject file; // drag to file explorer
  wxDropSource ds(this);
  int x;
  // prepare for internal drag and drop
  m_sn = event.GetItem(); // save tree control source item
  // save any other stuff needed for internal drop
  ...
  // now prepare for explorer type drag n' drop to another window
  m_drag = true;	// used by DnDTree
  file.AddFile(filepathstring); // from some internal source
  ds.SetData(file);
  x = ds.DoDragDrop();	// blocked until completion
  switch(x){
   case wxDragCopy:
    ...
   case wxDragMove:
    ...
   default:
     ...
   break;
  }
  m_drag = false;	// clear dragging
  ...
}
	
// drag'n drop class for tree control - source may be internal or external
// trivial change to standard DnDFile
class anydrop: public wxFileDropTarget
{
public:
  anydrop  (wxTreeCtrl *po, anyframe *f) { m_tree = po; m_f = f;}
  virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames);
private:
  wxTreeCtrl *m_tree;
  anyframe *m_f;

};
bool anydrop::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
  ...
  // test if we started the drag in this control
  if(m_f->m_drag){// internal source - so we move entry
    // do internal stuff to move items in tree control
  }else{ // external source - so we add files 
    // do internal stuff to move items in tree control
  }
  return TRUE;


Problems, comments, suggestions, corrections (including broken links) or something to add? Please take the time from a busy life to 'mail us' (at top of screen), the webmaster (below) or info-support at zytrax. You will have a warm inner glow for the rest of the day.

Tech Stuff

If you are happy it's OK - but your browser is giving a less than optimal experience on our site. You could, at no charge, upgrade to a W3C standards compliant browser such as Firefox

Search

web zytrax.com

Share

share page via facebook tweet this page

Page

email us Send to a friend feature print this page Decrease font size Increase font size Display full width page

Standards

ISO (International)
IEC (International)
ANSI (US)
DIN (Germany)
ETSI (EU)
BSI (UK)
AFNOR (France)

Telecom

TIA (US)
ECIA (US)
ITU (International)
IEEE (US)
ETSI (EU)
OFCOM (UK)

Internet

IETF
IETF-RFCs
IANA
ICANN
W3C

Electronics

JEDEC
ECIA (US)

Site

CSS Technology SPF Record Conformant Domain
Copyright © 1994 - 2017 ZyTrax, Inc.
All rights reserved. Legal and Privacy
site by zytrax
Hosted by super.net.sg
web-master at zytrax
Page modified: October 09 2015.