![]() |
mail us
|
mail this page products | company | support | downloads | isp services | contact us |
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 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 - unlike us. Otherwise.....
We had also, mercifully, forgotten what an anally retentive language C++ is. Sigh.
wxWidgets has evolved its own wxSpeak. Undoubtedly for excellent reasons. No sense of trying to confuse the rest of. 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 (well more like a fellow Scot). However they have a regrettable habit of spelling color as colour (they may say the reverse about North Americans). Some options work with both spellings, other not. If your friendly local compiler tells you its not a class member try the Brit spelling. Or if you have a lot of trouble - add a #define. |
Adding Anything
Adding a new menu
Adding context (popup) menus
Adding tooltips (Static and Dynamic)
Connect an Event at Run-time (dynamically)
Handling Mouse Hover
Events - get the object Reference
Creating XPM Images
Controls/Windows with Multiple Responses
Order of Events
wxWidgets oes a good job at 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
// 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 1a
// there is no main defined - instead the macro
// IMPLEMENT_APP does everything
// It results in a call to a function you provide
// anyname::OnInit() to run start running your code
IMPLEMENT_APP(anyappname)
anyappname::OnInit()
{
// start doing stuff
}
// 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()
};
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()
Every function identified in item 2 will have a correctioning 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
}
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 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.
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.
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 dispaly 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 OnMouseEnterWindow 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.
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 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. </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.
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 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 our target controls. So here is the final code:
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(MyFrame::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.
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
}
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
}
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();
// do something sensible
}
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));
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 testiment 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
}
We frequently get lost in the bushes (we don't even qualify not to see the wood for 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 events are defined for wxWidgets on MS Windows wheter they remain the same across all wxWidgets suported platforms is currently unknown.
// 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
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
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 home
audio stuff
web stuff
dom stuff
css stuff
language stuff
regex stuff
rfc stuff
protocol stuff
cable stuff
lan wiring
rs232 wiring
howto stuff
survival stuff
wireless stuff
ascii codes
data rate stuff
telephony stuff
mechanical stuff
pc stuff
electronic stuff
tech links
open guides
RSS Feed
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 Mozilla
ISO (International)
ANSI (US)
DIN (Germany)
ETSI (EU)
BSI (UK)
AFNOR (France)
TIA (US)
EIA (US)
ITU (International)
IEEE (US)
ETSI (EU)
OFTEL (UK)
|
Copyright © 1994 - 2009 ZyTrax, Inc. All rights reserved. Legal and Privacy |
site by zytrax![]() |
web-master at zytrax Page modified: September 01 2009. |