Thread: Question regarding Polymorphism and how to implement it.

  1. #1
    Registered User
    Join Date
    Dec 2009
    Posts
    5

    Question regarding Polymorphism and how to implement it.

    Hello! I'm trying to design a very simple GUI which has the following hierarchy:
    View
    Frame
    Control
    Widgets Timers
    Label
    TextBox
    Button

    Frame has a map which stores pointers to the controls that belong to it. (You can think of frame as a window). It also has a redraw method, that redraws all the widgets that belong to the frame. As you can see in the method implementation below, I am using a switch, that first asks for the kind of widget and then casts a pointer to the correct type, then finally calls the redraw method implementation for that particular object type. The things is that in my very limited knowledge, this seems (to me) to void the concept of polymorphism, because for each new widget that I add to my class, I need to expand the WidgetType enum and create a new case: . Is there another way to do this, so that the type of the object to be redrawn can be determined at run-time ? Thank you very much in advance. Please excuse my poor english.


    METHOD:
    Code:
    void Frame::redraw( SDL_Surface* screen )
    {
        SDL_BlitSurface(backgroundImage, NULL, screen, NULL );
        for( map<string, Control*>::iterator iter = frameControls.begin(); iter != frameControls.end(); iter++ )
            if( CONTROL_WIDGET == ((*iter).second)->getControlType() )
            {
            // (*iter).second is a pointer to a Control object, which is being casted to a Widget*.
               switch( (reinterpret_cast<Widgets*>((*iter).second))->getWidgetType() )
               {
               case WIDGET_LABEL:
                   (reinterpret_cast<Label*>((*iter).second))->getWidgetType();
                   break;
               case WIDGET_TEXTBOX:
                   (reinterpret_cast<Textbox*>((*iter).second))->getWidgetType();
                   break;
               case WIDGET_BUTTON:
                   (reinterpret_cast<Button*>((*iter).second))->getWidgetType();
                   break;
               }
    
            }
    
        return;
    }




    HEADER:
    Code:
    /******************** INCLUDES *******************/
    
    #include <SDL/SDL.h>
    #include <string>
    #include <vector>
    #include <map>
    
    /************** ENUMS *******************/
    
    typedef enum { CONTROL_WIDGET, CONTROL_TIMER } ControlType;
    typedef enum { WIDGET_LABEL, WIDGET_TEXTBOX, WIDGET_BUTTON} WidgetType;
    typedef enum { TIMER_TEXTBOX_POINTER, TIMER_CURRENT_TIME, TIMER_WAIT_FOR_INTERVAL } TimerType;
    
    /***************** CONSTANTS ***********************/
    
    const int SCREEN_WIDTH =    640;
    const int SCREEN_HEIGHT =   480;
    const int SCREEN_BPP =      32;
    
    using namespace std;
    
    /*************************************************************
    *
    *                           Control Class
    *
    **************************************************************/
    
    class Control
    {
    public:
        Control() {}
        ~Control() {}
        inline ControlType getControlType() { return controlType; }
    
    protected:
        ControlType controlType;
    };
    
    /**************************************************************/
    
    /*************************************************************
    *
    *                           Timers Class
    *
    **************************************************************/
    
    class Timers: public Control
    {
    public:
        // Depending on the TimerType, the action performed when the time ends varies.
        Timers( Uint32 interval = 0, TimerType action = TIMER_CURRENT_TIME );
        ~Timers() {}
    
    protected:
        TimerType timerType;
        SDL_TimerID timerID;    //Used to store the info returned by SDL_AddTimer.
        inline Uint32 getTicks() { return SDL_GetTicks();}
    };
    
    /**************************************************************/
    
    /*************************************************************
    *
    *                           Widgets Class
    *
    **************************************************************/
    
    class Widgets: public Control
    {
    public:
        virtual inline int redraw( SDL_Surface* screen) = 0;
    
        inline void setText( string text ) { this->text = text; }
        inline void setImage( string imagePathName ) { }
        inline SDL_Rect getUpLeftCoordinate() { return upLeftCoordinate; }
        inline SDL_Rect getDownRightCoordinate() { return downRightCoordinate; }
        inline WidgetType getWidgetType() { return widgetType; }
    
        void setBackgroundColor( SDL_Color newColor );
        bool wasClicked( SDL_Rect upLeftCoordinate );
    
    protected:
        //Coords to describe the Widget rectangle.
        SDL_Rect upLeftCoordinate;
        SDL_Rect downRightCoordinate;
    
        //Contains the image (which was in a file BMP, JPG, etc) that represents the widget.
        SDL_Surface* image;
    
        // String of text that is shown by the widget.
        string text;
    
        // Optional, in case there is no image.
        Uint32 backgroundColor;
    
        // Used to identify the widget.
        WidgetType widgetType;
    };
    
    /**************************************************************/
    
    /*************************************************************
    *
    *                           Label Class
    *
    **************************************************************/
    
    class Label: public Widgets
    {
    public:
        Label( SDL_Rect upLeft, SDL_Surface* image = NULL, Uint32 backgroundColor = 0, string text = "");
        ~Label();
        virtual inline int redraw( SDL_Surface* screen);
    
    protected:
    
    
    };
    
    /**************************************************************/
    
    /*************************************************************
    *
    *                           Textbox Class
    *
    **************************************************************/
    
    class Textbox: public Widgets
    {
    public:
        Textbox( SDL_Rect upLeft, SDL_Surface* image = NULL, Uint32 backgroundColor = 0, string text = "", string textboxMessage = "" );
        ~Textbox();
    
        virtual inline int redraw( SDL_Surface* screen );
    
        inline void setActiveTextbox() {activeTextbox = true;}
        inline void unsetActiveTextbox() {activeTextbox = false;}
        inline bool isActiveTextbox() { return activeTextbox; }
    
    protected:
        bool activeTextbox;
        bool drawPointer;
        // Message that tells the user what is a particular textbox for.
        string textboxMessage;
    };
    
    /**************************************************************/
    
    /*************************************************************
    *
    *                           Button Class
    *
    **************************************************************/
    
    class Button: public Widgets
    {
    public:
        Button( SDL_Rect upLeft, SDL_Surface* pressedImage = NULL, SDL_Surface* unPressedImage = NULL, Uint32 backgroundColor = 0, string text = "" );
        ~Button();
    
        inline void setPressed() { pressed = true; }
        inline void unsetPressed() { pressed = false; }
        virtual int redraw( SDL_Surface* screen );
    
    protected:
        bool pressed;   // Tells wether the button is being pressed or not.
        SDL_Surface* pressedImage;
        SDL_Surface* unPressedImage;
    };
    
    /**************************************************************/
    
    /*************************************************************
    *
    *                           Frame Class
    *
    **************************************************************/
    
    class Frame
    {
    public:
    
        Frame( SDL_Surface* backgroundImage = NULL, Uint32 backgroundColor = 0 );
        ~Frame();
    
        bool addControl( string identifier, Control* newControl );
    
        //Redraw the whole window, with all it's elements.
        void redraw( SDL_Surface* screen );
    
        //Given a Coordinate, return the string key of the widget that resides in that coordinate.
        Control* getWidget( SDL_Rect clickCoordinate );
    
    protected:
        SDL_Surface* backgroundImage;   //Window background image.
        Uint32 backgroundColor;         // Optional, in case there is no image.
    
        //Contains pointers to all the elements belonging to the frame.
        map< string, Control* > frameControls;
    };
    
    /**************************************************************/
    
    /*************************************************************
    *
    *                           View Class
    *
    **************************************************************/
    
    class View
    {
    public:
        View(string windowMarkerName = "");
        ~View();
    
        inline void setRefresh() { refreshScreen = true; }
    
        bool addFrame( Frame* newFrame);
        void redrawScreen();
    
    protected:
        SDL_Surface* screen;
        vector<Frame *> frameStack;
        bool refreshScreen;
    };
    
    /**************************************************************/

  2. #2
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Try adding a virtual redraw() method to the Control class. This might be declared, in the body of the class definition, using a line
    Code:
       virtual void redraw(SDL_Surface *);
    A definition of this function might then be ....
    Code:
    void Control::redraw(SDL_Surface *surface)
    {
        //  do some default to redrawing action
    }
    Note, if you do this, it is a good idea to declare the destructor of Control virtual as well. If you don't understand why, take it as a fact for now.

    Then every class derived from Control might, optionally, override the redraw function, using a line in their class definition like;
    Code:
         void redraw(SDL_Surface *);
    and the corresponding bodies, for each class, would do (well) what is appropriate for each class. Then your Frame class might implement the redraw function as
    Code:
    void Frame::redraw( SDL_Surface* screen )
    {
        SDL_BlitSurface(backgroundImage, NULL, screen, NULL );
        std::map<std::string, Control *>::iterator iter, end = frameControls.end();
        for(iter = frameControls.begin(); iter != end; ++iter)
        {
             (*iter)->redraw(screen);
        }
    }
    The line (*iter)->redraw(screen) in the loop is relying on virtual function dispatch. That means, for each object, the call of redraw() will resolve to the one corresponding to the actual type of the object.

    Note that there is no need to interrogate the Control class and ask for its type before doing a typecast. The compiler does the bookkeeping to make things work.

    The assumptions I am making are;

    1) You are adding the addresses of valid objects, of various types derived from Control, into the frameControls map.

    2) At some appropriate time (eg in the destructor of Frame) you are correctly cleaning up all the objects.

    3) It makes sense for every class derived from Control to have a function of the form redraw(SDL_Surface *). If it doesn't, you need to adapt your design accordingly.

    4) It makes sense for Control to provide a default behaviour of the redraw() function. If it doesn't, look up the concept of "pure virtual function".

    In practice, it is very rarely necessary to interrogate an object for type information and then do a type conversion. In your design, it should not be necessary at all.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  3. #3
    Registered User
    Join Date
    Dec 2009
    Posts
    5
    Thanks grumpy, you helped me solve my problem. I had to rethink a bit the hierarchy. Now Widgets doesn't inherit from Control, because it didn't make sense for the timers class to inherit the redraw() method.

Popular pages Recent additions subscribe to a feed