Subject: How to create new controls PREFACE: the detail you are about to read should be of no interest to user who wish to create applications (even very complicated ones) by reusing the parts in UICL. This detail is only necessary if you find you must write your own classes from underlying system controls; building your own classes from our classes, again you don't need to know this design detail. Let me see if I can give you something to start with (I hope to complete a magazine article on this topic, so feedback on the example is greatly appreciated): Your goal is to create your own class for a control (in this case, Pen) which fits the basic structure of UICL. The basic steps are: 1) Write a class derived from IControl (typically) whose ctor creates the underlying window system (PM) control, with styles and other state set according to the style bits passed into you. Certain other methods must be implemented as well (see sample code below). 2) Write a class derived from IHandler (typically) to handle any events unique to your new control. You must implement the method dispatchHandlerEvent(evt), and (optionally) a method for each event or related set of events this handler is to process. When you add your handler to a window, the handler will begin to receive events in the form of IEvents. See IEvent.hpp or the doc for how to get all the data therein. The IEvent is first passed to a handler via its ::dispatchHandlerEvent() method. It's up to the handler if it wants to follow our style & call itself at this point with finer-grained methods, or if it just wants to process all the events for that handler in dispatchHandlerEvent(). I advise our style; it makes future reuse easier (and you WILL reuse, or wish you could). Here is some sample code for a control and for a handler. These samples (and if you need it, a subclass of IEvent for special data) should help you to wrapper a control of your own. For now the samples are without warrantee (except for normal forum assistance). Hope this helps you. Again, feedback is more than welcome. Art Jolin, CSet++ UI Class Libraries ============================================ ===================== ctlsampl.hpp ============================== #ifndef _CTLSAMPL_ #define _CTLSAMPL_ /******************************************************************************* * FILE NAME: ctlsampl.hpp * * * * DESCRIPTION: * * Declaration of the class(es): * * SampleControl - This class does neat stuff. * * * *******************************************************************************/ #ifndef _ICONTROL_ #include #endif // Forward declarations for other classes: class IRectangle; class ISize; class IWindowHandle; #ifndef _IRECT_ #include #endif #ifndef _IBITFLAG_ #include #endif /*----------------------------------------------------------------------------*/ /* Align classes on four byte boundary. */ /*----------------------------------------------------------------------------*/ #pragma pack(4) class SampleControl : public IControl { typedef IControl Inherited; /******************************************************************************* * Tell all about SampleControl as a class. ... * *******************************************************************************/ public: /*------------------- Style ----------------------------------------------- The following functions provide a means to set and query sample control styles Style - Nested class that provides static members that define the set of valid sample control styles. These styles can be used in conjunction with the styles defined by the nested classes IWindow::Style and IControl::Style. For example, you could define an instance of the SampleControl::Style class and initialize it like: SampleControl::Style style = SampleControl::ringading | IControl::tabStop; An object of this type is provided when the sample control is created. A customizable default is used if no styles are specified. Once the object is constructed, SampleControl, IWindow and IControl member functions can be used to set or query the object's style. The declaration of the SampleControl::Style nested class is generated by the INESTEDBITFLAGCLASSDEF3 macro. The valid sample control styles are: classDefaultStyle - Original default style for this class, which is IWindow::visible. ringading - Does some alternative neat thing when control is rung on whoopdedo - Does something else when control is rung twice. The following functions provide a means of getting and setting the default style for this class: defaultStyle - Returns the current default style. This is the same as classDefaultStyle unless setDefaultStyle has been called. setDefaultStyle - Sets the default style for all subsequent sample controls. -------------------------------------------------------------------------*/ INESTEDBITFLAGCLASSDEF2(Style, SampleControl, IWindow, IControl); // style class definition static const Style classDefaultStyle, ringading, whoopdedo; static Style defaultStyle(); static void setDefaultStyle(Style style); /*------------------------ Constructors ---------------------------------------- | You can construct instances of this class in the following ways: | | - From a control ID, parent and owner windows, rectangle, and style. | | This creates the specified sample control and an object for it. | | - From the ID of a sample control on a dialog window. This creates | | the object for the specified sample control. | | - From the window handle of an existing sample control. This | | creates the object for the specified sample control. | ------------------------------------------------------------------------------*/ SampleControl(unsigned long id, IWindow* parent, IWindow* owner, const IRectangle& initial= IRectangle(), const Style& style = defaultStyle() ); SampleControl(unsigned long id, IWindow* parentDialog); SampleControl(const IWindowHandle& handle); virtual ~SampleControl(); /*-------------------------------- Style Functions ----------------------------- | These functions provide a means of getting and setting the default style | | attributes of instances of this class: | | . . . | | . . . | | . . . | ------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------ |SampleControl :: method1 | | | | Does some neat thing which a MagicControl is capable of. | ------------------------------------------------------------------------------*/ virtual SampleControl& method1(Boolean turnOn); /*------------------------------------------------------------------------------ |SampleControl :: method2 | | | | Does some other neat thing which a MagicControl is capable of. | ------------------------------------------------------------------------------*/ virtual IBase::Boolean method2(IString aNeatPieceOfData); protected: /*----------------------------- Layout Size ------------------------------------ | calcMinimumSize - Returns the recommended minimum size of this sample | | control. The size is based on the text string length and | | the current font, or figured in some other way. | ------------------------------------------------------------------------------*/ virtual ISize calcMinimumSize() const; private: /*--------------------------------- PRIVATE ----------------------------------*/ SampleControl(const SampleControl&); //copy constructor: should have one SampleControl& operator=(const SampleControl&); //assignment ctor: should hav static Style currentDefaultStyle; }; // class SampleControl INESTEDBITFLAGCLASSFUNCS(Style, SampleControl); // global style functions /*----------------------------------------------------------------------------*/ /* Resume compiler default packing. */ /*----------------------------------------------------------------------------*/ #pragma pack() #endif // _CTLSAMPL_ ===================== end of ctlsampl.hpp ============================== ===================== ctlsampl.cpp ============================== /******************************************************************************* * FILE NAME: ctlsampl.cpp * * * *******************************************************************************/ #ifndef _CTLSAMPL_ #include #endif #ifndef _IEXCEPT_ #include #endif #ifndef _IHANDLE_ #include #endif #ifndef _IPOINT_ #include #endif #ifndef _IRECT_ #include #endif #ifndef _ITRACE_ #include #endif /*------------------------------------------------------------------------------ | Public control styles. | ------------------------------------------------------------------------------*/ const SampleControl::Style SampleControl::ringading(256); const SampleControl::Style SampleControl::whoopdedo(007); /*------------------------------------------------------------------------------ | Default style for new objects (initial value). | ------------------------------------------------------------------------------*/ SampleControl::Style SampleControl::currentDefaultStyle(256); /*------------------------------------------------------------------------------ | SampleControl::SampleControl | | | Constructor to create a sample control on a standard window. | ------------------------------------------------------------------------------*/ SampleControl :: SampleControl(unsigned long ulId, IWindow* pwndParent, IWindow* pwndOwner, const IRectangle& rectInit, const Style& pbsStyle) { IASSERTPARM(pwndParent!=0); . . . . . . . . . IWindowHandle hwnd = WinCreateWindow( parent, WC_MAGICALCONTROLIMWRAPPERING, text, style, initRect.left(), initRect.bottom(), initRect.width(), initRect.height(), owner, behind, id, (void*)ctlData, (void*)presParams ); if (hwnd == 0) ITHROWGUIERROR("Can't Create Magical Control"); //throw an exception // This call actually "attaches" the specified widget to this instance. startHandlingEventsFor(hwnd); //If some of these bits belong to IWindow, call the inherited "setStyle" also. if (pbsStyle != IWindow::noStyle) { IWindow::setStyle (pbsStyle.asUnsignedLong()); } } /*------------------------------------------------------------------------------ | SampleControl::SampleControl | | | | Constructor to instantiate an object for an existing control in a dialog | ------------------------------------------------------------------------------*/ SampleControl :: SampleControl(unsigned long ulId, IWindow* pdlgwndParent) { // assertions on input parms IASSERTPARM(pdlgwndParent!=0); IWindowHandle whMagicalWidget = WinWIndowFromID(pdlgwndParent->handle(), ulId); if (whMagicalWidget == 0) ITHROWGUIERROR("WindowFromID failed"); //or otherwise throw an exception // We don't want to synchronize destruction of this instance and the widget, // since they were created independently. This call turns off synch. setAutoDestroyWindow(false); // This call actually "attaches" the specified control to this instance. startHandlingEventsFor(whMagicalWidget); } /*------------------------------------------------------------------------------ | SampleControl::SampleControl | | | | Constructor to instantiate from an existing control. | ------------------------------------------------------------------------------*/ SampleControl :: SampleControl(const IWindowHandle& whMagicalWidget) { // We don't want to synchronize destruction of this instance and the widget, // since they were created independently. This call turns off synch. setAutoDestroyWindow(false); // This call actually "attaches" the specified control to this instance. startHandlingEventsFor(whMagicalWidget); } /*------------------------------------------------------------------------------ |SampleControl ::~ SampleControl ------------------------------------------------------------------------------*/ SampleControl :: ~SampleControl() { // Often, you don't have to do anything in a dtor. The IWindow dtor takes // care of things like destroying the PM control (if appropriate), cleaning up // stray IHandlers still attached, etc. } /*------------------------------------------------------------------------------ |SampleControl ::defaultStyle ------------------------------------------------------------------------------*/ SampleControl::Style SampleControl :: defaultStyle() { return currentDefaultStyle; } /*------------------------------------------------------------------------------ |SampleControl ::setDefaultStyle ------------------------------------------------------------------------------*/ void SampleControl :: setDefaultStyle(SampleControl::Style pbsStyle) { currentDefaultStyle = pbsStyle; } /*------------------------------------------------------------------------------ |SampleControl :: method1 | | | | Does some neat thing which a MagicControl is capable of. | ------------------------------------------------------------------------------*/ SampleControl& SampleControl :: method1(Boolean turnOn) { return *this; } /*------------------------------------------------------------------------------ |SampleControl :: method2 | | | | Does some other neat thing which a MagicControl is capable of. | ------------------------------------------------------------------------------*/ IBase::Boolean SampleControl :: method2(IString aNeatPieceOfData) { return True; } /*------------------------------------------------------------------------------ | SampleControl::calcMinimumSize | | | | Calculate the minimum screen size needed by the control. The height may be | | based on the font height and width may be based on the average font width | | multiplied by the width of the control text. Or some other scheme may be used. | | | This method is necessary if your control is to fit well on a canvas. | | | ------------------------------------------------------------------------------*/ ISize SampleControl :: calcMinimumSize() const { unsigned long ulWidth = 10; //arbitrary number; this is only a sample afte unsigned long ulHeight = 25; //arbitrary number; this is only a sample afte ISize sizMin(ulWidth, ulHeight); return sizMin; } ===================== end of ctlsampl.cpp ============================== ===================== samplhdr.hpp ============================== #ifndef _SAMPLHDR_ #define _SAMPLHDR_ /******************************************************************************* * FILE NAME: samplhdr.hpp * * * * DESCRIPTION: * * Declaration of the class(es): * * MagicHandler - Process a magic spell event for a control. * * * *******************************************************************************/ #ifndef _IHANDLER_ #include #endif #ifndef _ICTLEVT_ #include //only if you map your events into IControlEvents #endif /*----------------------------------------------------------------------------*/ /* Align classes on four byte boundary. */ /*----------------------------------------------------------------------------*/ #pragma pack(4) // Forward declarations for other classes: class IEvent; class MagicHandler : public IHandler { typedef IHandler Inherited; /******************************************************************************* * The MagicHandler class handles the processing of magic spell events for * * the following controls: * * * * - SomeControl * * - SomeOtherControl * * * * The MagicHandler class is designed to handle events that occur when a * * control has a magic spell cast on it, such as SP_BECOMETOAD or SP_GAINWEALTH * * or user-defined spells. The MagicHandler object should be attached to the * * control that will have the spell cast upon it, or to the owner window * * of the control. You can do this by passing the control or owner * * window on the handleEventsFor function of the MagicHandler object. * * * * When it receives a magic spell event, the MagicHandler object constructs * * an IControlEvent object and routes it to the appropriate virtual function. * * These virtual functions allow you to supply your own specialized processing * * of the event. The return values from the virtual functions specify whether * * the IControlEvent should be passed on for additional processing, as follows: * * * * Value Meaning * * --------------- * * true - The IControlEvent requires no additional processing and should * * not be passed to another handler. * * false - The IControlEvent should be passed to the next handler for * * additional processing, as follows: * * -- If there is another handler for the control or window, the * * IControlEvent is passed on to the next handler. * * -- If this is the last handler for the control, the IControlEvent * * is dispatched to the owner of the control. * * (See IWindow::dispatch.) * * -- If this is the last handler for the owner window, the * * IControlEvent is passed on a call to the owner window's * * defaultProcedure function. (See IWindow::defaultProcedure.) * * * * Example: * * MyWindow::MyWindow() // inherits from IFrame, MagicHandler * * : ... * * { * * ((MagicHandler*)this)->handleEventsFor(this); * * ... * * IEntryField* ef = new IEntryField(ID_EF, this, this, IRectangle(...)); * * IStaticText* stxtHelp = new IStaticText(ID_STXTHELP, this, this, * * IRectangle(...)); * * ... * * show(); * * } * * Boolean MyWindow::becomeToad(IControlEvent& evt) * * { // provide quick help for current owned control * * if (evt.controlId() == ID_EF) * * { // entry field becomes toad * * stxtHelp->setText("Watch out for warts."); * * } * * ... * * else * * { // some other control * * stxtHelp->setText(" "); // no help for this one * * } * * ... * * return false; // let another handle event also * * } * *******************************************************************************/ public: /*-------------------------- Constructor/Destructor ---------------------------- | The only way to construct instances of this class is to use the default | | constructor, which does not take any arguments. | | ----------------------------------------------------------------------------*/ MagicHandler ( ); virtual ~MagicHandler ( ); protected: /*---------------------------- Event Dispatching ------------------------------- | This function evaluates the event to determine if it is appropriate for | | this handler object to process. If it is, this function calls the virtual | | function used to process the event. | | dispatchHandlerEvent - Calls the becomeToad or gainWealth function, as | | appropriate. | ------------------------------------------------------------------------------*/ virtual Boolean dispatchHandlerEvent ( IEvent& event ); /*----------------------------- Event Processing ------------------------------- | These functions should be supplied by a derived class to process a focus | | change event. | | becomeToad - grow a long tongue, turn brown and warty, learn to like flies.| | gainWealth - build a huge house, become insufferably overbearing, dictate | | to your customers, marry an employee and grow a pot belly. | ------------------------------------------------------------------------------*/ virtual Boolean becomeToad ( IControlEvent& event ), gainWealth ( IControlEvent& event ); }; // MagicHandler /*----------------------------------------------------------------------------*/ /* Resume compiler default packing. */ /*----------------------------------------------------------------------------*/ #pragma pack() #endif /* _SAMPLHDR_ */ ===================== end of samplhdr.hpp ============================== ===================== samplhdr.cpp ============================== /**************************************************************/ /* CLASS NAME: MagicHandler */ /* */ /**************************************************************/ #define INCL_WINENTRYFIELDS . . . . . . . . . extern "C" { #include #include } #ifndef _SAMPLHDR_ #include #endif #ifndef _ICTLEVT_ #include #endif #ifndef _IEVENT_ #include #endif #ifndef _IWINDOW_ #include #endif #ifndef _IWCNAME_ #include #endif // Segment definitions #ifdef IC_PAGETUNE #define _SAMPLHDR_CPP_ #include #endif MagicHandler :: MagicHandler ( ) : MagicHandler::Inherited ( ) /*******************************************************************/ /* Default constructor here for page tuning. */ /*******************************************************************/ { ; } MagicHandler :: ~MagicHandler ( ) /*******************************************************************/ /* Empty destructor here for page tuning. */ /*******************************************************************/ { ; } Boolean MagicHandler :: dispatchHandlerEvent ( IEvent& evt ) /**************************************************************/ /* Dispatch command functions for this handler. */ /**************************************************************/ { Boolean bRc = false; if (evt.eventId() == WM_CONTROL) { // Control event IControlEvent ctlevt(evt); IWindow* pwndControl = ctlevt.controlWindow(); /*************************************************************/ /* Figure out what kind of control experienced the event. */ /* This handler is either attached to the control generating */ /* the WM_CONTROL message or to its owner. If */ /* IControlEvent::controlWindow returns 0, this must mean we */ /* have a non-wrappered control with a wrappered owner */ /* window and that the handler is attached to the owner */ /* window. This case is supported only if the owner of the */ /* control is also the control's parent (this is in order */ /* for handleWithId to work). */ /*************************************************************/ unsigned long ulClassName = 0; unsigned long ulMsg = ctlevt.parameter1().number2(); IWindowHandle hwndControl; if (pwndControl) { hwndControl = pwndControl->handle(); } else { hwndControl = IWindow::handleWithId( ctlevt.controlId(), ctlevt.handle() ); } IWindowClassName className( hwndControl ); if (className == WC_ENTRYFIELD) { // entry field case if (ulMsg == SP_BECOMETOAD) { bRc = becomeToad(ctlevt); } else if (ulMsg == SP_GAINWEALTH) { bRc = gainWealth(ctlevt); } } else if (className == WC_SOMEOTHERCONTROL) { . . . . . . . . . } if (bRc) { // processing done evt.setResult(ctlevt.result()); // copy result } } /* endif WM_CONTROL */ return bRc; } Boolean MagicHandler :: becomeToad ( IControlEvent& evt ) { return false; } Boolean MagicHandler :: gainWealth ( IControlEvent& evt ) { return false; } ===================== end of samplhdr.cpp ==============================