属性编辑器设计模式? [英] Properties editor design pattern?

查看:261
本文介绍了属性编辑器设计模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

警告:这是超级深入。我理解,如果你甚至不想读这个,这主要是为了整理我的思想过程。

Warning: This is super in-depth. I understand if you don't even want to read this, this is mostly for me to sort out my thought process.

好吧,这里是我试图做。我有这些对象:

Okay, so here's what I'm trying to do. I've got these objects:

当你点击一个(或选择几个)它应该在右边显示它们的属性(如图所示)。当你编辑这些属性时,它应该立即更新内部变量。

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

我想决定最好的方法。我想所选择的对象应该存储为一个指针列表。它是或者在每个对象上有一个isSelected bool,然后遍历所有的,忽略非选择的,这是低效的。因此,我们单击一个,或选择几个,然后填充selectedObjects列表。然后我们需要显示属性。为了使事情简单,我们假设所有对象都是相同的类型(共享同一组属性)。由于没有任何特定于实例的属性,我认为我们应该将这些属性作为静态变量存储在Object类中。属性基本上只有一个名称(如Allow Sleep)。每种类型的属性都有一个PropertyManager(int,bool,double)。 PropertyManager会存储 all 各自类型属性的值(这全部来自Qt API)。不幸的是,因为PropertyManager需要创建属性我不能真正解耦这两者。我想这意味着我必须放置PropertyManager与属性(作为静态变量)。这意味着我们有一组属性和一组属性管理器来管理全部所有对象中的变量。每个属性管理器只能有一个回调。这意味着此回调必须为所有对象(嵌套循环)更新其各自类型的属性。这产生类似这样的东西(在伪代码中):

I'm trying to decide on the best way to do this. I figure the selected objects should be stored as a list of pointers. It's either that, or have an isSelected bool on each object, and then iterate over all of them, ignoring the non-selected ones, which is just inefficient. So we click on one, or select several, and then the selectedObjects list is populated. We then need to display the properties. To keep things simple for the time being, we'll assume that all objects are of the same type (share the same set of properties). Since there aren't any instance-specific properties, I figure we should probably store these properties as static variables inside the Object class. Properties basically just have a name (like "Allow Sleep"). There is one PropertyManager for each type of property (int,bool,double). PropertyManagers store all the values for properties of their respective type (this is all from the Qt API). Unfortunately, because PropertyManagers are required to create Properties I can't really decouple the two. I suppose this means that I have to place the PropertyManagers with the Properties (as static variables). This means we have one set of properties, and one set of property managers to manage all the variables in all the objects. Each property manager can only have one callback. That means this callback has to update all the properties of its respective type, for all objects (a nested loop). This yields something like this (in pseudo-code):

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

打扰我一点,因为我们使用if语句,我们不需要它们。这种方法是为每个属性创建一个不同的属性管理器,以便我们可以有唯一的回调,这也意味着我们需要为每个属性两个对象,但它可能是一个价值值得支付更清洁的代码(我真的不知道什么性能成本是现在,但正如我知道你也会说 - 优化,当它成为一个问题)。所以我们最终得到了大量的回调函数:

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

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

这消除了整个if / else垃圾,但增加了十几个事件监听器。让我们假设我使用这个方法,所以现在我们有一堆静态属性,以及它们对应的静态PropertyManager。可能我将所选对象的列表存储为Object :: selectedObjects,因为它们用于所有事件回调,它们在逻辑上属于对象类。所以,我们也有一堆静态事件回调。这是所有罚款和dandy。

Which eliminates the entire if/else garbage but adds a dozen more event-listeners. Let's assume I go with this method. So now we had a wad of static Properties, along with their corresponding static PropertyManagers. Presumably I'd store the list of selected objects as Object::selectedObjects too since they're used in all the event callbacks, which logically belong in the object class. So then we have a wad of static event callbacks too. That's all fine and dandy.

现在当你编辑一个属性,我们可以通过事件回调更新所有选定的对象的interal变量。但是当内部变量通过其他方法更新时会发生什么,我们如何更新属性?这恰好是一个物理模拟器,所以所有的对象将有很多他们的变量不断更新。我不能为这些添加回调,因为物理由另一个第三方库处理。我想这意味着我只是假设所有的变量 在每个时间步骤后都被改变。所以在每个时间步之后,我必须更新所有选定对象的所有属性。好的,我可以这样做。

So now when you edit a property, we can update the interal variables for all the selected objects via the event callback. But what happens when the internal variable is updated via some other means, how do we update the property? This happens to be a physics simulator, so all the objects will have many of their variables continuously updated. I can't add callbacks for these because the physics is handled by another 3rd party library. I guess this means I just have to assume all the variables have been changed after each time step. So after each time step, I have to update all the properties for all the selected objects. Fine, I can do that.

上次问题(我希望),当选择多个对象时,我们应该显示什么值?我想我的选择是留空/ 0或显示随机对象的属性。我不认为一个选项比另一个更好,但希望Qt提供了一个方法来突出显示这样的属性,以便我可以至少通知用户。那么如何确定哪些属性突出显示?我想我迭代所有选定的对象,和所有的属性,比较它们,一旦有不匹配,我可以突出它。所以要澄清,在选择一些对象后:

Last issue (I hope), is what values should we display when multiple objects are selected an there is an inconsistency? I guess my options are to leave it blank/0 or display a random object's properties. 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 figure out which properties to "highlight"? I guess I iterate over all the selected objects, and all their properties, compare them, and as soon as there is a mismatch I can highlight it. So to clarify, upon selected some objects:


  1. 将所有对象添加到selectedObjects列表


我认为这些属性具有相同的值,我应该将属性存储在列表中,以便我可以将整个列表推送到属性编辑器,而不是单独添加每个属性。应该允许更多的灵活性下来我认为。

I think I should store the properties in a list too so that I can just push the whole list onto the properties editor rather than adding each property individually. Should allow for more flexibility down the road I think.

我认为这涵盖了它...我还不确定我有这么多静态变量的感觉,和一个半singleton类(静态变量将被初始化一次,当第一个对象创建我猜)。但我没有看到更好的解决方案。

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

如果你真的看过这个请发布你的想法。我想这不是一个问题,所以让我再说一遍的仇恨,我可以对我的建议的设计模式,以产生更清晰,更易于理解或更有效的代码进行什么调整?(或那些行)。

Please post your thoughts if you actually read this. I guess that's not really a question, so let me rephrase for the haters, What adjustments can I make to my suggested design-pattern to yield cleaner, more understandable, or more efficient code? (or something along those lines).

看起来我需要澄清。 属性我的意思是允许睡觉或速度 - 所有对象都有这些属性 - 但它们的VALUES是唯一的每个实例。 属性包含需要显示的字符串,值的有效范围和所有窗口小部件信息。 PropertyManagers 是实际保存值的对象。它们控制回调和显示的值。还有另一个值的副本,实际上由其他第三方物理库内部使用。

Looks like I need to clarify. By "property" I mean like "Allow Sleeping", or "Velocity" -- all objects have these properties -- their VALUES however, are unique to each instance. Properties hold the string that needs to be displayed, the valid range for the values, and all the widget info. PropertyManagers are the objects that actually hold the value. They control the callbacks, and the value that's displayed. There is also another copy of the value, that's actually used "internally" by the other 3rd party physics library.

现在尝试实现这个疯狂。我有一个EditorView(图像中的黑色区域绘图区域),捕获mouseClick事件。 mouseClick事件然后告诉物理模拟器查询光标处的所有物体。每个物理体存储一个引用(一个void指针!)回我的对象​​类。指针被转换回对象被推送到所选对象的列表。然后EditorView发出一个信号。 EditorWindow然后捕获此信号,并将其与所选对象一起传递到PropertiesWindow。现在,PropertiesWindow需要查询对象的属性列表,以显示...,这就是我到目前为止。难以置信!

Trying to actually implement this madness now. I have an EditorView (the black area drawing area in the image) which catches the mouseClick event. The mouseClick events then tells the physics simulator to query all the bodies at the cursor. Each physics body stores a reference (a void pointer!) back to my object class. The pointers get casted back to objects get pushed onto a list of selected objects. The EditorView then sends out a signal. The EditorWindow then catches this signal and passes it over to the PropertiesWindow along with the selected objects. Now the PropertiesWindow needs to query the objects for a list of properties to display... and that's as far as I've gotten so far. Mind boggling!

/* 
 * 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)));
}

非常感谢 TimW

推荐答案

您看过Qt的(动态)属性系统

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);
   }
}


这是一个不完整的示例,可以帮助我了解属性系统。

我猜 SelectableItem * selectedItem 必须替换为您的案例中的项目列表。

This is an incomplete example to give you my idea about the property system.
I guess SelectableItem * selectedItem must 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;
};

这篇关于属性编辑器设计模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆