Свойства редактора дизайна шаблона? - PullRequest
2 голосов
/ 20 августа 2009

Предупреждение: Это очень глубоко. Я понимаю, что если вы даже не хотите читать это, это в основном для меня, чтобы разобраться в моем мыслительном процессе.

Хорошо, вот что я пытаюсь сделать. У меня есть эти объекты:

image

Когда вы нажимаете на одну (или выбираете несколько), она должна отображать их свойства справа (как показано). Когда вы редактируете указанные свойства, он должен немедленно обновить внутренние переменные.

Я пытаюсь выбрать лучший способ сделать это. Я считаю, что выбранные объекты должны быть сохранены в виде списка указателей. Это либо так, либо иметь isSelected bool для каждого объекта, а затем перебирать все из них, игнорируя невыбранные, что просто неэффективно. Таким образом, мы нажимаем на один или выбираем несколько, а затем заполняется список selectedObjects. Затем нам нужно отобразить свойства. Для простоты пока будем считать, что все объекты относятся к одному и тому же типу (имеют один и тот же набор свойств). Поскольку нет никаких специфичных для экземпляра свойств, я полагаю, что мы, вероятно, должны хранить эти свойства как статические переменные внутри класса Object. Свойства в основном просто имеют имя (например, «Разрешить сон»). Существует один PropertyManager для каждого типа свойства (int, bool, double). PropertyManager хранит все значения для свойств их соответствующего типа (это все из Qt API). К сожалению, поскольку PropertyManager требуется для создания свойств, я не могу отделить их. Я предполагаю, что это означает, что я должен разместить PropertyManager со свойствами (как статические переменные). Это означает, что у нас есть один набор свойств и один набор менеджеров свойств для управления всеми переменными в всех объектах. Каждый менеджер по недвижимости может иметь только один обратный вызов. Это означает, что этот обратный вызов должен обновить все свойства соответствующего типа для всех объектов (вложенный цикл). Это дает что-то вроде этого (в псевдокоде):

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

Что уже немного беспокоит меня, потому что мы используем операторы if, где они нам не нужны. Обходным путем будет создание другого менеджера свойств для каждого отдельного свойства, чтобы у нас были уникальные обратные вызовы. Это также означает, что нам нужно два объекта для каждого свойства, но это может быть цена, которую стоит заплатить за более чистый код (я действительно не знаю, каковы затраты производительности сейчас, но, как я знаю, вы также скажете - это становится проблемой). Итак, мы получаем тонну обратных вызовов:

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

Что устраняет весь мусор if / else, но добавляет еще дюжину слушателей событий. Давайте предположим, что я иду с этим методом. Итак, теперь у нас есть пачка статических свойств вместе с соответствующими им статическими менеджерами свойств. Предположительно, я бы также сохранял список выбранных объектов как Object :: selectedObjects, поскольку они используются во всех обратных вызовах событий, которые логически принадлежат классу объектов. Итак, у нас есть несколько статических обратных вызовов. Это все хорошо и денди.

Так что теперь, когда вы редактируете свойство, мы можем обновлять целочисленные переменные для всех выбранных объектов посредством обратного вызова события. Но что происходит, когда внутренняя переменная обновляется каким-либо другим способом, как мы обновляем свойство? Это симулятор физики, поэтому все переменные постоянно обновляются. Я не могу добавить обратные вызовы для них, потому что физика обрабатывается другой сторонней библиотекой. Я предполагаю, что это означает, что я просто должен предположить, что все переменные были изменены после каждого временного шага. Поэтому после каждого временного шага мне приходится обновлять все свойства для всех выбранных объектов. Хорошо, я могу это сделать.

Последняя проблема (я надеюсь), какие значения мы должны отображать, когда выбрано несколько объектов и есть ли несоответствие? Я предполагаю, что я могу оставить пустым / 0 или отобразить свойства случайного объекта. Я не думаю, что один вариант намного лучше другого, но, надеюсь, Qt предоставляет метод для выделения таких свойств, чтобы я мог хотя бы уведомить пользователя. Итак, как мне определить, какие свойства «выделить»? Я предполагаю, что я перебираю все выбранные объекты и все их свойства, сравниваю их, и как только возникает несоответствие, я могу выделить его. Итак, для уточнения, на выбранных объектах:

  1. добавить все объекты в список выбранных объектов
  2. заполнить редактор свойств
  3. найдите, какие свойства имеют одинаковые значения и обновите редактор соответствующим образом

Я думаю, что я должен также хранить свойства в списке, чтобы я мог просто поместить весь список в редактор свойств, а не добавлять каждое свойство по отдельности. Думаю, следует учесть большую гибкость в будущем.

Я думаю, что по поводу покрытия ... Я до сих пор не уверен, что я чувствую по поводу того, что у меня так много статических переменных и полу-синглтон-класса (статические переменные будут инициализированы один раз, когда будет создан первый объект ). Но я не вижу лучшего решения.

Пожалуйста, напишите свои мысли, если вы действительно читаете это. Думаю, это не совсем вопрос, поэтому позвольте мне перефразировать ненавистников: Какие корректировки я могу внести в предложенный мной шаблон проектирования, чтобы получить более чистый, более понятный или более эффективный код? (или что-то подобное линии).


Похоже, мне нужно уточнить. Под «свойством» я подразумеваю «Разрешить сон» или «Скорость» - все объекты имеют эти свойства - однако их ЗНАЧЕНИЯ уникальны для каждого экземпляра. Свойства содержат строку, которая должна отображаться, допустимый диапазон значений и всю информацию о виджете. PropertyManagers - это объекты, которые на самом деле содержат значение. Они контролируют обратные вызовы и отображаемое значение. Существует также еще одна копия значения, которая фактически используется «внутренне» другой сторонней физической библиотекой.


Попытка реализовать это безумие сейчас. У меня есть EditorView (область рисования черной области на изображении), которая ловит событие mouseClick. Затем события mouseClick сообщают физическому симулятору запросить все тела у курсора. Каждое физическое тело хранит ссылку (пустой указатель!) На мой класс объектов. Указатели возвращаются к объектам и помещаются в список выбранных объектов. EditorView затем отправляет сигнал. EditorWindow затем ловит этот сигнал и передает его в PropertiesWindow вместе с выбранными объектами. Теперь PropertiesWindow должен запросить у объектов список свойств для отображения ... и это насколько я до сих пор получал. Сногсшибательно!


Решение

/* 
 * 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

Ответы [ 2 ]

3 голосов
/ 20 августа 2009

Вы смотрели на (динамическую) систему свойств 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 должен быть заменен списком предметов в вашем случае.

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 голосов
/ 20 августа 2009

Успокойся, твой код не имеет O (n ^ 2) полноты. У вас есть вложенный цикл, но только один считается до N (количество объектов), а другой - до фиксированного числа свойств, которое не связано с N. Таким образом, у вас есть O (N).

Для статических переменных вы пишете «нет никаких свойств, специфичных для экземпляра», позже вы пишете об обновлениях отдельных свойств ваших объектов, которые точно являются свойствами, специфичными для экземпляра. Может быть, вы путаете «Свойства класса» (которые, конечно, являются общими для всех свойств) с отдельными свойствами? Поэтому я думаю, что вам вообще не нужны статические члены.

Хотите ли вы отображать изменения в объектах, только если они появляются, или вы хотите непрерывное отображение? Если ваше оборудование способно справиться с последним, я бы рекомендовал пойти по этому пути. В этом случае вам все равно придется перебирать все объекты и обновлять их по пути.

Редактировать: Разница в том, что в первом (обновление при изменении) рисование инициируется операцией изменения значений, например, перемещением объекта. Для последнего, непрерывного отображения, вы бы добавили QTimer, который запускает, скажем, 60 раз в секунду и вызывает SLOT (render ()), который выполняет фактическую визуализацию всех объектов. В зависимости от скорости изменений это может быть быстрее. И это, вероятно, легче реализовать. Другая возможность - позволить Qt обрабатывать весь чертеж, используя Графический вид , который обрабатывает объекты для рисования внутри в очень эффективной древовидной структуре. Взгляни на http://doc.trolltech.com/4.5/graphicsview.html

Если вы хотите отобразить только изменения, вы можете использовать отдельные обратные вызовы для каждого значения свойства. Каждый раз, когда значение свойства изменяется (в этом случае делая свойства закрытыми и используя setSomeThing (value)), вы вызываете функцию update с помощью emit (update ()). Если вы абсолютно уверены в том, что emit медленен, вы можете использовать «настоящие» обратные вызовы через указатели функций, но я не рекомендую, чтобы connect / signal / slot Qt был намного проще в использовании. И накладные расходы в большинстве случаев действительно пренебрежимы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...