Thread: GUI structure in C

  1. #1
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77

    GUI structure in C

    Although I understand OOP may be a better way to go with my GUI project, I decided to stick it out in understanding how to implement this in C. The basic building blocks are:

    a) A rectangle
    b) A button
    c) A textbox

    This is an edited (to keep the post shorter) version of my struct for the rectangle:

    Code:
    typedef struct{
        float convertToPixel, convertToOrtho;
        
        float x1, y1, x2, y2;  //bottom left, top right coordinates
        float centreXOrtho, centreYOrtho, length, height;
        
        //Label
        int labelLengthChars;
        float labelLengthOrtho, labelHeightOrtho;
            
        //Final data for drawing functions
        float vertices[4][2];
        float colour[3];
        char label[MAX_LABEL_LENGTH];
        float rasterStartPos[2];
    
    }Rectangle;
    
    //.... a series of functions to initialise struct, setters, getters etc

    My question is about which route would other people go in creating say a textbox from here. Would it be better to:

    a) Copy the Rectangle struct into another header file, relabel the typedef "textbox", copy and paste existing functions (renaming and editing the functions), and add additional functions as necessary

    Code:
    typedef struct {
    //....pretty much same or identical contents to Rectangle
    }Textbox;
    
    void textboxNew(Textbox *r, float coords[4]);
    void textboxGetText(Textbox *r, char *buffer);
    b) Leave the struct as it is (perhaps adding additional contents to it though), but have a separate set of functions which initialise and use the struct, e.g.:

    Code:
    typedef struct {
    //....
    }Rectangle;
    
    void buttonNew(Rectangle *r, float coords[4], char *label);  // initialise button
    
    void textboxNew(Rectangle *r, float coords[4]); //initialise textbox
    void textboxGetText(Rectangle *r, char * buffer);
    Thanks for any thoughts on this

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,656
    I'd go with
    Code:
    typedef struct {
        Rectangle base;
        // other members specific to TextBox
    }Textbox;
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    Agreed. I would also really suggest this:
    Code:
    TextBox* newTextBox(float coords[4])
    rather than your void version, in order to create something in one line.

  4. #4
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    Thanks for the clear direction on this Salem - your advice makes sense once I started to write the textbox code and makes the whole thing tidier and clearer. In the end I kept a single header file with the two structs (rectangle and textbox) in it and function declarations, but created a new source file for the textbox.

    C_ntua - I remember trying this a while ago, but got stuck with the problem that creating instances within functions makes them local, and I ended up with a pointer which pointed into the abyss....

    However, it's a much better way to go, so I gave your idea more thought and tried a small program, using the static declaration. I'll post my test program below - is this the right way to go?

    Code:
    #include <stdio.h>
    
    typedef struct number {
        int x;
    }Number;
    
    Number * newNumber(int x);
    
    
    int main (int argc, const char * argv[]) {
        Number *m = newNumber(5);
        printf("\nNumber = %d", m->x);
        return 0;
    }
    
    Number *newNumber(int z) {
        static Number n; //is this the right way to do this???
        n.x = z;
        return &n;
    }

  5. #5
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    No, you'd only be able to create one number. As you're returning the address of the static variable (which won't be destroyed when the function returns).

    Use malloc() / free(), ie

    Code:
    /* returns NULL on failure */
    Number * newNumber(int z)
    {
       Number * n = NULL;
    
       n = malloc(sizeof(Number));
       if(n != NULL)
          n->x = z;
    
       return n;
    }
    
    void freeNumber(Number ** num)
    {
       if(num != NULL)
       {
          free(*num);
          *num = NULL;
       }
    }

  6. #6
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    Brilliant. This is really helpful zacs7. So, the summary that I take from this is that... if malloc'd memory is created within a function it won't disappear when the function ends, and it's available outside of the function as long as I store the returned address.

  7. #7
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    Yeah, it's allocated on the "heap".

    You can do it all on the stack too,

    Code:
    Number newNumber(int z)
    {
       Number num;
       num.x = z;
       return num;
    }
    But I wouldn't recommend that.

  8. #8
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by zacs7 View Post
    Yeah, it's allocated on the "heap".

    You can do it all on the stack too,

    Code:
    Number newNumber(int z)
    {
       Number num;
       num.x = z;
       return num;
    }
    But I wouldn't recommend that.
    I would. As long as the struct is small (and this only contains one integer), returning a struct, rather than a pointer to struct is faster than allocating memory.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  9. #9
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    Quote Originally Posted by officedog View Post
    Brilliant. This is really helpful zacs7. So, the summary that I take from this is that... if malloc'd memory is created within a function it won't disappear when the function ends, and it's available outside of the function as long as I store the returned address.
    Yeah. In C++ you would do rectangle* rec = new rectangle();
    In C you would do rectangle* rec = new_rectangle();
    So, it will be easy to use as in C++.

    Also, you can try function pointers:
    Code:
    typedef Orthogonio {
        //blah blah
        void (*resize)(Point p1, Point p2);
    } Rectangle;
    
    rectangle *rec = new_rectangle(Point p1, Point p2)
    {
         rectangle* rec = malloc(sizeof(*rec)); 
         //rest of initialization
         rec->resize = rectangleResize;
         return rec;
    }
    
    Rectangle* rec = new_rectangle(p1, p2);
    rec->resize(p1, p2, rec);
    So you can hide the rectangleResize function in the resize function. That way every object can have a resize function put each will do what you want. Unfortunately you can't avoid passing rec parameter (which would be the this paramater automatically passed in C++).

    EDIT: You can also use the above idea and have a init (constructor) function for each object. Then you will be able to choose. For static allocation:
    Code:
    rectangle rec;
    rec.init(Point p1, Point p2, &rec);
    dynamic allocation:
    Code:
    rectangle* rec = new_rectangle(Point p1, Point p2, rec);
    
    rectangle* rec new_rectangle(Point p1, Point p2, rectangle* rec) {
        //as before
        rec->init(Point p1, Point p2, rec);
    }
    Last edited by C_ntua; 11-12-2008 at 04:43 PM.

  10. #10
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    Wow! Thanks C_ntua. This is exactly the sort of thing I'm looking for as I am trying to pull together issues of code reuse etc (possibly even inheritance??) without going the whole distance into an OOP language like C++. It'll take a while to sink in though as it feels a bit advanced. I'll read this a few times and let the idea sink in, then see if I can use the ideas. Might have to come back with some questions

  11. #11
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    > I would. As long as the struct is small (and this only contains one integer), returning a struct, rather than a pointer to struct is faster than allocating memory.

    True, but I wouldn't recommend it in general. Perhaps I should be less ambiguous in my posts .

  12. #12
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by zacs7 View Post
    > I would. As long as the struct is small (and this only contains one integer), returning a struct, rather than a pointer to struct is faster than allocating memory.

    True, but I wouldn't recommend it in general. Perhaps I should be less ambiguous in my posts .
    But that wouldn't be half as fun

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  13. #13
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    And it's always you who picks it up... Oh I'm onto you

  14. #14
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    Quote Originally Posted by officedog View Post
    Wow! Thanks C_ntua. This is exactly the sort of thing I'm looking for as I am trying to pull together issues of code reuse etc (possibly even inheritance??) without going the whole distance into an OOP language like C++. It'll take a while to sink in though as it feels a bit advanced. I'll read this a few times and let the idea sink in, then see if I can use the ideas. Might have to come back with some questions
    Your welcome, but in the end a class is just a struct with function pointers If you kind of think how C++ was implemented, you will see that you can do almost everything with C. You will just have to write more code. But it will be a fun practice.

    Like you can even gain some polymorphism. Think of virtual functions.
    Code:
    typedef struct {...} Control;
    typedef struct {...} Button;   // : Control
    typedef struct {...} DropList; // : Control
    Now you have this:
    Code:
    typdef struct {
        base Control;
        //rest of control
        void (*resize)(Point P1, Point P2);
    } Button;
    the same for DropList. You want override base.resize which as noted before is a function pointer that points to rectangleResize. So what do you do? You make your buttonResize function and do this:
    Code:
    myButton->base.resize = buttonResize;
    myButton->resize = myButton->base.resize;
    And you have override the function resize. Now both base->resize and resize point to buttonResize.
    Now, you can use a void pointer to point to any struct you desire. So you can do this:
    Code:
    Button* b = new_Button();
    DropList* d = new_DropList();
    List* list = new_list(); //a list that has void* pointers that can point to anything
    list.Add(b, d); //sets the void pointers in the list
    Control* c = Control_cast((list.At(1)); // c = b
    c->resize();  //call the overriden function
    The key to the above code is the Control_cast() instead of (Control *). Since there is no built in polymorphism, you want c, that is a Control* to actually point to a Control*. So:
    Code:
    Control* Control_Cast(void* ptr)
    {
         switch(findType(void* ptr)
         {
         Control* c, Button* b, DropList* d;
         case CONTROL:
                                    c = (Control*)ptr;
                                    return c;
         case BUTTON:
                                    b = (Button*)ptr;
                                    c = &(b->base);
                                    return c;
        ........
         default:           //not derived, faulty cast!
                       //something
                       exit(1);                           
         }
    }
    So you just need to know which class derives for what class. That can be done by having a table that saves the type of each struct created. That class will be returned as an enumeration from the findType() function. So each struct that is created will write the value of its address (thus a pointer) to a table with its type. So this two will do the same thing:
    Code:
    Button* b = new_Button();
    Control* c = Control_cast(b);
    c->resize();  //refers to b->base.resize()
    b->resize(); //refers to b->resize which is equal to b->base.resize();
    Of course, you need to inform the table that Button derives from Control (and make sure all the above actually work :P)

    Of course this will be less effective than C++, because something are done automatically in compile time, though everything now will be done in run-time. So you will not have something faster because it is C.

    EDIT: Well, the above code needs one more modification. You have to also pass the pointer to the struct inside resize. But that will need to be Button* not Control*. Or DropList*. This can be done, but it will need a bit more work and it will look a bit uglier. My main point stands the same though
    Last edited by C_ntua; 11-13-2008 at 04:38 AM.

  15. #15
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    OK, just to share my first attempt at embedding function within a struct. I kept it simple so I get the general patterns, before moving back to my GUI. I thought I'd share it, partly for critical feedback, partly cause others might be interested in this. This seems to work reasonably well. Involves some mental gymnastics - the idea of a 'pointer to a function' ...... makes me wonder how a function is actually represented.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct coords {
        int x,y;
        void (*add)(struct coords *c);  //why does this need to be in brackets??
    } Coords;
    
    void addOne(Coords *c);
    
    Coords * coordNew();
    
    int main (int argc, const char * argv[]) {
        Coords *num = coordNew();
        num->add(num);
    }
    
    Coords * coordNew() {
        Coords *c = malloc(sizeof(Coords));
        c->x = 1;
        c->y = 100;
        c->add = addOne; //assign function
        return c;
    }
    
    void addOne(Coords *c) {
        printf("\nStart: x,y = %d, %d", c->x,c->y);
        c->x ++;
        c->y ++;
        printf("\nEnd: x,y = %d, %d", c->x,c->y);
    }

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Problem referencing structure elements by pointer
    By trillianjedi in forum C Programming
    Replies: 19
    Last Post: 06-13-2008, 05:46 PM
  2. Dikumud
    By maxorator in forum C++ Programming
    Replies: 1
    Last Post: 10-01-2005, 06:39 AM
  3. Window priority handling for a GUI - data structure
    By rmullen3 in forum C++ Programming
    Replies: 0
    Last Post: 04-15-2003, 07:06 PM
  4. Serial Communications in C
    By ExDigit in forum Windows Programming
    Replies: 7
    Last Post: 01-09-2002, 10:52 AM
  5. C structure within structure problem, need help
    By Unregistered in forum C Programming
    Replies: 5
    Last Post: 11-30-2001, 05:48 PM