Draw objects from QList



  • Hello everyone ! I need help to create the QList of objects which i want to paint by paintEvent from QWidget.
    I have a class for creating objects such as:
    class Objects
    {
    int x; // x - axis position
    int y; // y - position
    int heigth , weight;
    QPixmap image; // the image which i want to draw
    }

    How create the QList of this objects(maybe pointers on this objects) And how create the function which will return the QList ( QList <Object*> objects() ) ? And for the returned QList i use
    foreach ( Objects* obj , objects() )
    {
    obj->drawPixmap(obj->x,obj->y, obj->height, obj->weight , QPixmap (obj->image));
    }
    to draw all objects from this QList;


  • Moderators

    Hi!

    class Object
    {
    public:
        int x; // x - axis position
        int y; // y - position
        int heigth , weight;
        QPixmap image; // the image which i want to draw
    
        void drawObject() const {
            qDebug() << "do something useful";
        }
    };
    
    
    QList<Object> objects;
    
    objects.append( Object() );
    objects.append( Object() );
    objects.append( Object() );
    objects.append( Object() );
    
    Object tmpObj;
    foreach (tmpObj, objects) {
        tmpObj.drawObject();
    }
    

    Cheers!
    :-)


  • Moderators

    @Wieland

    Object tmpObj;
    foreach (tmpObj, objects) {
        tmpObj.drawObject();
    }
    

    Please don't do that. There's no need for a copy and an Object shouldn't know about drawing. Do this instead:

    foreach (const Object& obj, objects) {
        someObjectPainter->drawObject(obj);
    }
    


  • @Chris-Kawa Thanks for advice ! And one more qustion. I need to create function which return this QList, and then i paint it in another class.
    How I should define it ?


  • Moderators

    @Chris-Kawa said:

    There's no need for a copy

    Agreed! In my defense, I did a quick copy and paste from the docs:

    QLinkedList<QString> list;
    ...
    QString str;
    foreach (str, list)
        qDebug() << str;
    

    an Object shouldn't know about drawing.

    Now it's getting philosophical. (Btw: Is "getting philosophical" proper english?) ^_^


  • Moderators

    @Afterwork It depends. If you create the list in that function and discard it after drawing then there's not much to say about it (except maybe that you should prefer QVector):

    QVector<Object> giveMeSomeSweetObjects() {
       QVector<Object> objects;
       objects.reserve(42); //if you know how much you're going to append
       for( /*...*/ ) objects.append( /* ... */ ); //get the objects from somewhere or construct
       return objects;
    }
    

    If you worry about returning by value - don't. RVO will take care of that.

    If it's a resident list then there's yet less work:

    class SomeMotherClass {
    public:
       //depending what you want to do with it:
       QVector<Object> objects() const { return objects_ } //a copy
       const QVector<Object>& objects() const { return objects_ } // or a non-modifiable reference
       QVector<Object>& objects() { return objects_ } //or a modifiable reference
    private:
        QVector<Object> objects_;
    }
    

    @Wieland It's not philosophical. It's actually very down to earth problem of responsibility management and maintenance burden. Imagine you've got this drawObject method implemented. Fine. Then you need to print the object to a printer so you add a printObject method. Then you need to serialize it to some file so you add saveToXML. But your colleague needs it in JSON so you add another method... At some point the drawing system changes (say from QPainter to OpenGL) and now you need to update your Object class (which has nothing to do with OpenGL in the first place), but that breaks somebody's legacy code so you split it to drawObject and drawObjectGL. Now another colleague asks why he needs to link to OpenGL, Gui, JSON and printing libraries if all he needs is a simple list of objects in his network server program... I think you see where this is going. You end up with God Object that you and everybody hate but you need to maintain it just because it has so much useful functionality it would be nightmare to rewrite and other people would eat you alive if you told them you're gonna break their code and remove that class because it's too much to maintain.
    Oh gosh. I think I wented a little :)



  • I have to side with Chris with a grain of salt.

    In my opinion, in the vast majority of use cases passing your QList object into some method to do the drawing is best. As Chris says this way you can change stuff in one place and little else has to change. This also supports the single purpose rule. A class should have a single purpose. Hold data, compute data, but not hold data AND draw data.

    But the "other" ways are not wrong and in fact in very rare cases can be better. I emphasize the word "rare". But having your object have built in class code to do something at times can be very helpful. In this case your object might violate the single purpose rule slightly but if you carefully consider the life and purpose it may be ok.

    Consider a large list of I/O type objects. Some of which may talk over RS-232, some may open and do I/O to a ring-0 (low-level driver). Others might do USB.

    In this scenario having a single "output" function still might make sense but in the function you would have something like:

    switch( IO->type )
    {
    case RS-232:
      // do RS-232 stuff
      break;
    case  DRIVER:
      // do driver stuff
      break;
     case USB
        // do USB stuff
       break;
    }
    

    While a little messy it makes it very easy to change all "DRIVER" code to "NEWDRIVER" in one place. On the flip side if you know with absolute certainly that some IO device will ALWAYS talk over USB then putting an output function in the class and having a call like:

     IO->output();
    

    Also makes sense because once a device is physically created to use USB it takes a major re-work to convert it to some new interface. So having the code to talk to it associated directly with the object is ok. Again, I still prefer Chris's suggestion as it support changes more easily.

    Another option is to inject with each object on your list the "control" or output object. In this case your objects would each have a pointer to the "output" object and Chris's loop becomes:

    foreach (const Object& obj, objects) {
       obj.process();
    }
    

    If you design your "output" objects right then you still only have to make changes in one place. This kind of violates the single purpose rule. As Chris said your object should know nothing about drawing.

    My point of replying to this at all is that I've been involved in some large software projects where excessive amounts of time were spent to perfectly follow dependency injection and SOLID principles and this was done on code that was unlikely to ever change or in some cases even be used after a short period of time. The point is no way is technically wrong. You just have to weigh the life time and purpose of your code and decide how to organize it.


  • Moderators

    @SysTech Honestly I can't agree even to the example you gave. In such case I would create a (possibly abstract) interface class with stable api and then implement the various access types (USB, RS-232, network, driver etc.) in specialized subclasses. Kinda what QIODevice does. I see no reason why USB handling class should have dependency (i.e. link to) on a network driver for example. Also it's not a good solution performance wise (the switch/case in every method).
    With specialization you still change DRIVER to NEWDRIVER in a single place - the specialized driver subclass. And people that don't use that functionality don't even have to recompile.

    The other possibility, a process method that takes an object that will handle it is a little better, but not much. Why should an object know it's gonna be processed? A state object should hold its internal data invariant and provide means to access some (or all of it) publicly. My stand on this is if you want to process objects then make ObjectPainter, ObjectPrinter, ObjectSerializer classes. If some of them use the same interface (e.g. ObjectNativePainter and ObjectGLPainter) make them implement the same interface class, e.g. ObjectPainterInterface.

    All this is not that important in small projects like a few classes that you recompile often anyway, but you should follow the good practices even then, to avoid developing bad programming habits. It's a real actual problem that takes years to revert yourself from. I know! I did it (and to my despair still sometimes do) too.



  • @Chris-Kawa
    Hi Chris,

    Trust me I completely agree. I mention my points ONLY because I see younger programmers spending literally thousands of hours a year forcing themselves down the principle road when like with everything in life there is point where the value of return in minimal.

    I worked under a lead for a while that declared "there will be zero global variables!" yet she could never quite understand or accept that you could say the program itself is a global.

    My point is simply that blind adherence a principle or method can actually be worse than fully understanding the needs and goals of what you are trying to do.

    I completely agree that an object should have a single purpose. It should not know how to "process" itself. Something else should process it. Trust me... I agree.

    Take myself for example. While I am a member of a very large development team world wide I also have some work I do ONLY myself. I am currently working with an electrical guy on a very specific instrument. I know exactly what his USB device will do so I wrote an object that is VERY SPECIFIC to that device. It is HIGHLY unlikely this device will change in the product life time. I am the ONLY developer so no other will in the life time of the product have to use my object as if it was a black-box.

    Still I am following the general principles we are discussing because as you say it is what I want to do as a way of life. But if in my specific example above I decide to put an "output()" method in my object it simply is not wrong.

    I can guarantee because I own the design, own the electronics, etc that at least until someone pays me a couple of million to buy it from me that I am the ONLY ONE that will ever see the source and work with it. Having output() in the class is not optimal. But it will never be used for any other purpose.

    So rather than blindly follow the principles and buzz programming trends I am just trying to emphasize that we all need to THINK (and you said this in your first paragraph) about responsibility management and maintenance and I'm just adding in there, return on time invested.

    Lastly my point and this is getting really philosophical is what defines wrong? I've read many of your posts and have great respect for your abilities, answers and talents. But as we both know there really are no "wrong" ways to do anything. There are only much better ways to do something.


  • Moderators

    My point is simply that blind adherence a principle or method can actually be worse than fully understanding the needs and goals of what you are trying to do.

    Amen.



Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.