Property Editor Design Pattern?

Warning: . It's super deep. I understand that if you don't even want to read this, it's mostly for me to get my head around my thought process.

Okay, here's what I'm trying to do. I have these objects:

uwBko.png

When you click on one (or select several), it should display its properties on the right (as shown). When you edit the specified properties, it should update the internal variables immediately.

I am trying to figure out how to do this. I suppose the selected objects should be stored as a list of pointers. It either then or has an isSelected bool for each object and then iterates over all of them, ignoring the unselected, which is just inefficient. So we click one or select multiple and then select the selectedObjects list. Then we need to display the properties. To keep things simple, we will assume that all objects are of the same type (share the same set of properties). Since there are no concrete instance properties, I suppose we should probably store these properties as static variables inside the Object class. Properties are mostly just named (eg "Allow Sleep"). There is one PropertyManager for each property type (int, bool, double).The PropertyManager stores all property values ​​of the corresponding type (all from the Qt API). Unfortunately, since the PropertyManager is required to create properties, I cannot separate them. I suppose this means that I have to place the PropertyManager using properties (as static variables). This means that we have one set of properties and one set of property managers to manage all the variables in all objects. Each property manager can only have one callback. This means that this callback must update all properties of the appropriate type for all objects (nested loop). This gives something like this (in pseudocode):that I have to place the PropertyManager using properties (as static variables). This means that we have one set of properties and one set of property managers to manage all the variables in all objects. Each property manager can only have one callback. This means that this callback must update all properties of the appropriate type for all objects (nested loop). This gives something like this (in pseudocode):that I have to place the PropertyManager using properties (as static variables). This means that we have one set of properties and one set of property managers to manage all the variables in all objects. Each property manager can only have one callback. This means that this callback must update all properties of the appropriate type for all objects (nested loop). This gives something like this (in pseudocode):This gives something like this (in pseudocode):This gives something like this (in pseudocode):

function valueChanged(property, value) {
    if(property == xPosProp) {
        foreach(selectedObj as obj) {
            obj->setXPos(value);
        }
    } else if(property == ...

      

Which bothers me a little, because we're using if statements that don't need them. The way around this would be to create a different property manager for each individual property so that we can have unique callbacks. This also means that we need two objects for each property, but that might be a price worth paying for cleaner code (I really don't know what the performance costs are now, but as I know you will also say - optimize. when it becomes a problem). So we get a ton of callbacks:

function xPosChanged(property, value) {
    foreach(selectedObj as obj) {
        obj->setXPos(value);
    }
}

      

This removes all the if / else garbage, but adds a dozen more listener events. Suppose I go with this method. So now we have a bunch of static properties along with their respective static PropertyManager. Presumably I would keep the list of selected objects as Object :: selectedObjects since they are used in all event callbacks that logically belong to the object class. So we have a bunch of static event callbacks. It's all fine and dandy.

So now, when you edit a property, we can update the intermediate variables for all the selected objects through the event callback. But what happens when the internal variable is updated by other means, how do we update the property? This is a physics simulator, so all objects will be constantly updated with many of their variables. I cannot add callbacks for these because the physics is handled by another third party library. I guess this means that I just have to assume that all the variables were changed after each time step. So after every time step, I have to update all properties for all selected objects. Okay, I can do it.

Last question (hopefully) what values ​​should we display when selecting multiple objects, is there an inconsistency? I am assuming that my options are leaving it blank / 0 or displaying properties of random objects. I don't think one option is much better than the other, but hopefully Qt provides a method to highlight such properties so that I can at least notify the user. So how do I determine which properties to "highlight"? I think I iterate over all the selected objects and all their properties, compare them, and as soon as a mismatch occurs, I can highlight it. To clarify, when selecting some objects:

  • add all objects to the list of favorites
  • fill property editor
  • find which properties have the same values ​​and update the editor accordingly.

I think I should keep the properties in the list so that I can just move the entire list to the property editor rather than add each property individually. There must be more flexibility along the way, I think.

I think it covers this ... I'm still not sure how I feel about having so many static variables, and a semi-single class (static variables will be initialized once when the first object is created.). But I don't see a better solution.

Please post your thoughts if you really read this. I guess this is not a question, so let me rephrase for the haters, What tweaks can I make to my suggested design pattern to get cleaner, cleaner, or more efficient code? (or something else these lines).


It looks like I need to clarify. By "property" I mean "Allow Sleeping" or "Velocity" - all objects have these properties - their VALUES, however, are unique to each instance. Properties contain the string to be displayed, the valid range of values, and all information about the widgets. PropertyManagers are objects that actually store a value. They control the callbacks and the displayed value. There is also another copy of the value that is actually being used "internally" by another third physics library.


Trying to actually implement this madness now. I have an EditorView (drawing area of ​​the black area in the image) that captures the mouseClick event. The mouseClick events then tell the physics simulator to request all bodies in the cursor. Each physical object retains a reference (void pointer!) Back to my object class. Pointers return to objects that fall in the list of selected objects. The EditorView then sends a signal. The EditorWindow then captures this signal and passes it to the PropertyWindow along with the selected objects. Now PropertiesWindow should request objects for a list of properties to display ... and that's as far as I got it so far. The skill is stunning!


Decision

/* 
 * File:   PropertyBrowser.cpp
 * Author: mark
 * 
 * Created on August 23, 2009, 10:29 PM
 */

#include <QtCore/QMetaProperty>
#include "PropertyBrowser.h"

PropertyBrowser::PropertyBrowser(QWidget* parent)
: QtTreePropertyBrowser(parent), m_variantManager(new QtVariantPropertyManager(this)) {
    setHeaderVisible(false);
    setPropertiesWithoutValueMarked(true);
    setIndentation(10);
    setResizeMode(ResizeToContents);
    setFactoryForManager(m_variantManager, new QtVariantEditorFactory);
    setAlternatingRowColors(false);

}

void PropertyBrowser::valueChanged(QtProperty *property, const QVariant &value) {
    if(m_propertyMap.find(property) != m_propertyMap.end()) { 
        foreach(QObject *obj, m_selectedObjects) {
            obj->setProperty(m_propertyMap[property], value);
        }
    }
}

QString PropertyBrowser::humanize(QString str) const {
    return str.at(0).toUpper() + str.mid(1).replace(QRegExp("([a-z])([A-Z])"), "\\1 \\2");
}

void PropertyBrowser::setSelectedObjects(QList<QObject*> objs) {
    foreach(QObject *obj, m_selectedObjects) {
        obj->disconnect(this);
    }
    clear();
    m_variantManager->clear();
    m_selectedObjects = objs;
    m_propertyMap.clear();
    if(objs.isEmpty()) {
        return;
    }
    for(int i = 0; i < objs.first()->metaObject()->propertyCount(); ++i) {
        QMetaProperty metaProperty(objs.first()->metaObject()->property(i));
        QtProperty * const property
                = m_variantManager->addProperty(metaProperty.type(), humanize(metaProperty.name()));
        property->setEnabled(metaProperty.isWritable());
        m_propertyMap[property] = metaProperty.name();
        addProperty(property);
    }
    foreach(QObject *obj, m_selectedObjects) {
        connect(obj, SIGNAL(propertyChanged()), SLOT(objectUpdated()));
    }
    objectUpdated();
}

void PropertyBrowser::objectUpdated() {
    if(m_selectedObjects.isEmpty()) {
        return;
    }
    disconnect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)), 
            this, SLOT(valueChanged(QtProperty*, QVariant)));
    QMapIterator<QtProperty*, QByteArray> i(m_propertyMap);
    bool diff;
    while(i.hasNext()) {
        i.next();
        diff = false;
        for(int j = 1; j < m_selectedObjects.size(); ++j) {
            if(m_selectedObjects.at(j)->property(i.value()) != m_selectedObjects.at(j - 1)->property(i.value())) {
                diff = true;
                break;
            }
        }
        if(diff) setBackgroundColor(topLevelItem(i.key()), QColor(0xFF,0xFE,0xA9));
        else setBackgroundColor(topLevelItem(i.key()), Qt::white);
        m_variantManager->setValue(i.key(), m_selectedObjects.first()->property(i.value()));
    }
    connect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)), 
            this, SLOT(valueChanged(QtProperty*, QVariant)));
}

      

Many thanks to TimW

+2


source to share


2 answers


Have you looked at the Qt (dynamic) property system ?

bool QObject::setProperty ( const char * name, const QVariant & value );
QVariant QObject::property ( const char * name ) const
QList<QByteArray> QObject::dynamicPropertyNames () const;
//Changing the value of a dynamic property causes a 
//QDynamicPropertyChangeEvent to be sent to the object.


function valueChanged(property, value) {
       foreach(selectedObj as obj) {
           obj->setProperty(property, value);
   }
} 

      




Example

This is an incomplete example to give you my understanding of the property system.
I think SelectableItem * selectedItem

should be replaced with a list of items in your case.

class SelectableItem : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName );
    Q_PROPERTY(int velocity READ velocity WRITE setVelocity);

public:
    QString name() const { return m_name; }
    int velocity() const {return m_velocity; }

public slots:
    void setName(const QString& name) 
    {
        if(name!=m_name)
        {
            m_name = name;
            emit update();
        }
    }
    void setVelocity(int value)
    {
        if(value!=m_velocity)
        {
            m_velocity = value;
            emit update();
        }
    }

signals:
    void update();

private:
    QString m_name;
    int m_velocity;
};

class MyPropertyWatcher : public QObject
{
    Q_OBJECT
public:
    MyPropertyWatcher(QObject *parent) 
    : QObject(parent), 
      m_variantManager(new QtVariantPropertyManager(this)),
      m_propertyMap(),
      m_selectedItem(),
      !m_updatingValues(false)
    {
        connect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)), SLOT(valueChanged(QtProperty*,QVariant)));
        m_propertyMap[m_variantManager->addProperty(QVariant::String, tr("Name"))] = "name";
        m_propertyMap[m_variantManager->addProperty(QVariant::Int, tr("Velocity"))] = "velocity";
        // Add mim, max ... to the property
        // you could also add all the existing properties of a SelectableItem
        // SelectableItem item;
        // for(int i=0 ; i!=item.metaObject()->propertyCount(); ++i)
        // {
        //     QMetaProperty metaProperty(item.metaObject()->property(i));
        //     QtProperty *const property 
        //         = m_variantManager->addProperty(metaProperty.type(), metaProperty.name());
        //     m_propertyMap[property] = metaProperty.name()
        // }
    }

    void setSelectedItem(SelectableItem * selectedItem)
    {
        if(m_selectedItem)
        {
            m_selectedItem->disconnect( this );
        }

        if(selectedItem)
        {
            connect(selectedItem, SIGNAL(update()), SLOT(itemUpdated()));
            itemUpdated();
        }
        m_selectedItem = selectedItem;
    }


private slots:
    void valueChanged(QtProperty *property, const QVariant &value)
    {
        if(m_updatingValues)
        {
            return; 
        }

        if(m_selectedItem && m_map)
        {
            QMap<QtProperty*, QByteArray>::const_iterator i = m_propertyMap.find(property);
            if(i!=m_propertyMap.end())
                m_selectedItem->setProperty(m_propertyMap[property], value);
        }
    }  

    void itemUpdated()
    {
        m_updatingValues = true;
        QMapIterator<QtProperty*, QByteArray> i(m_propertyMap);
        while(i.hasNext()) 
        {
            m_variantManager->next();
            m_variantManager->setValue(
                i.key(), 
                m_selectedItem->property(i.value()));                
        }
        m_updatingValues = false;
    }

private:
    QtVariantPropertyManager *const m_variantManager;
    QMap<QtProperty*, QByteArray> m_propertyMap;
    QPointer<SelectableItem> m_selectedItem;
    bool m_updatingValues;
};

      

+3


source


Take it easy, your code doesn't have O (n ^ 2). You have a nested loop, but only one counts down to N (number of objects), the other takes into account a fixed number of properties that are not related to N. So you have O (N).

For static variables, you write "there are no instance specific properties", later you write about updates to individual properties of your objects that are especially instance specific properties. Maybe you are confusing "Class Properties" (which, of course, is shared among all properties) with individual properties? So I think you don't need static members at all.

Do you want to display changes to objects only if they appear, or do you want a continuity display to be displayed? If your hardware can handle the latter, I would recommend it. In this case, you will have to iterate over all the objects anyway and update them along the way.



Edit: the difference is that in the first (update on change) the drawing is initiated by an operation of changing values, such as moving an object. For the last, continuous display, you must add a QTimer that fires 60 times per second and calls SLOT (render ()), which actually renders all objects . Depending on the rate of change, this may be faster. And it is probably easier to implement. Another possibility is to let Qt handle the entire drawing using the Graphics View , which handles drawing objects internally in a very efficient tree structure. Take a look at http://doc.trolltech.com/4.5/graphicsview.html

If you only want to display changes, you can use individual callbacks for each property value. Every time a property value changes (in this case private vlaues properties and using setSomeThing (value)) you call the update function with emit (update ()). If you are absolutely not worried about choosing slow mode, you can use "real" callbacks using function pointers, but I do not recommend that Qt connect / signal / slot be much easier to use. And the overhead is very careless in most cases.

+3


source







All Articles