Writing Your Very Own Widgets |
Creating Your Own Widgets.
FOX makes it very easy to build your own widgets. It was designed to do this from the ground up. This is actually one of the prime benefits of FOX vis-a-vis other widget libraries. As FOX is completely written in C++, and most important functions are overloadable, writing your own widget typically entails picking an existing widget class which looks close to the one you want, and then redefining selected aspects of that widget by subclassing it.
We illustrate this process with an example from FOX itself:- the FXProgressBar widget. First, you pick which widget to subclass from; in this case, we subclass of of FXFrame, as the progress bar widget needs to inherit the margins and borders from FXFrame.
In terms of C++ code, this means:
class FXProgressBar: public FXFrame {
FXDECLARE(FXProgressBar)The FXDECLARE() macro establishes some boilerplate member functions that every class derived from FXObject should redefine. Next, we add some member data. In the case, we want to add:
protected:
FXuint progress; // Integer percentage number
FXuint total; // Amount for completion
FXint barsize; // Bar size
FXFont* font; // Font used for the percentage text
FXPixel barBGColor; // Background color of bar
FXPixel barColor; // Bar color
FXPixel textNumColor; // Text color outside of bar
FXPixel textAltColor; // Text color inside of barThere is nothing unusual about this. Standard C++ programming practice! Next, we make the default and copy constructors protected; while this is not required, it prevents an application programmer from accidentally creating an object with these, causing the creation of an improperly initialized object, which will most certainly cause problems later on.
protected:
FXProgressBar(){}
FXProgressBar(const FXProgressBar&){}So far so good. Now comes the meat. The progress bar should of course draw itself to reflect the amount of progress. This should look something like:
Progress Bar
This is done by implementing the onPaint() message. Every time
FOX determines that a widget needs to be repainted, for example, after
moving a window out of the way, it sends the widget a SEL_PAINT message.
This message should be intercepted by your widget so that you can make
it draw in a different manner. If you didn't intercept it, the message
would be intercepted later on by the base class, which will perform its
usual drawing behaviour.
Hence, we declare the message-handling routine (handler):
public:
long onPaint(FXObject*,FXSelector,void*);Note that the message handler should be declared public. The message handler is passed three parameters when invoked: the object that sent it the message, the selector, and a pointer. The sending object is in this case the widget itself; the selector is a combination of the message-type and message-id.
The pointer which is passed to the message handler may refer to different things; for system messages, however, it typically points to a data structure called FXEvent, which contains information about the event that caused this handler to be invoked.
The onPaint() handler may use the information in the FXEvent structure to determine the rectangle that needs to be repainted. Widgets (especially complicated ones!) should try and redraw only the indicated area. This cuts down significantly on expensive drawing commands, as well as reducing visual flickering on the screen.
Next, we define the widget's main constructor. As a first argument, all child-widgets pass in a pointer to the parent widget (Toplevel or shell widgets pass in a pointer to the application object). Next, the layout and other options are passed, followed by the x, y coordinate, followed by the width, and height. All of these have default values, as in many cases the exact widget placement and size is left to a container class such as the Packer or Matrix layout manager.
The options should be passed in as a bit-wise or (|) of
a number of option flags. Care should be taken so as to not conflict
with any option flags which have already been used in base classes of this
widget .
FXProgressBar(FXComposite* p,FXuint opts=(FRAME_SUNKEN|FRAME_THICK),FXint x=0,FXint y=0,FXint w=0,FXint h=0,FXint pl=DEFAULT_PAD,FXint pr=DEFAULT_PAD,FXint pt=DEFAULT_PAD,FXint pb=DEFAULT_PAD);The constructor you write should simply pass the arguments to their corresponding arguments in the base class of your class, then initialize the newly added member data to some sensible values.
In order to interact properly with the layout managers, each child widget
needs to properly compute its dimensions when asked by its parent.
The two functions involved with this are:
virtual FXint getDefaultWidth();
virtual FXint getDefaultHeight();These two functions compute the minimum width and height of the widget, given the current state of the widget. The layout managers use this information to place the widgets properly. In the case of the ProgressBar, for example, getDefaultWidth() computes the widget's size based on the border, left- and right padding, current text font, whether it is horizontal or vertical, and presence of the percentage display.
Next, the create() function needs to be implemented:
void create();The create() function is called in the second stage of the widget instantiation process. In the first stage, the C++ objects have been created, but no windows have been associated yet with those C++ objects. The second stage takes place when the application is instantiating the widgets, and building X-Windows associated with the C++ objects. It does this by recursively travering all widgets, starting from the root window.
After implementing the ordinary C++ member functions for your new widget, you may have to define a destructor:
virtual ~FXProgressBar();The FOX library usually implements a destructor which intentionally thrashes the object; in this case, it sets font to (FXFont*)(-1). Thrashing objects intentionally may cause dangling pointers etc. to be discovered much sooner, and thus ease debugging and increase program correctness.
Note that FOX does not try trash the other member variables:- thrash-values
for the other variables would not be distinguishable from good values anyway.
But a thrash value for a pointer like -1 will most likely cause a SIGSEGV,
when accidentally used!!