Warning:这是超级深入的。我理解,如果你根本不想读这篇文章,这主要是为了我整理一下我的思维过程。
好的,这就是我想做的。我有这些对象:
当您单击一个(或选择多个)时,它应该在右侧显示它们的属性(如图所示)。当您编辑所述属性时,它应该立即更新内部变量。
我正在尝试决定最好的方法来做到这一点。我认为所选对象应该存储为指针列表。要么是这样,要么在每个对象上有一个 isSelected bool ,然后迭代所有对象,忽略未选定的对象,这是低效的。因此,我们单击一个,或选择多个,然后填充 selectedObjects 列表。然后我们需要显示属性。为了暂时保持简单,我们假设所有对象都属于同一类型(共享同一组属性)。由于没有任何特定于实例的属性,我认为我们应该将这些属性存储为 Object 类中的静态变量。属性基本上只有一个名称(例如“允许睡眠”)。每种类型的属性(int、bool、double)都有一个 PropertyManager。 PropertyManagers 商店all各自类型的属性值(这全部来自 Qt API)。不幸的是,因为创建属性需要 PropertyManager,所以我无法真正将两者解耦。我想这意味着我必须将 PropertyManager 与属性一起放置(作为静态变量)。这意味着我们有一组物业,以及一组需要管理的物业经理all中的变量all对象。每个物业经理只能有一个回调。这意味着这个回调必须更新all其各自类型的属性,对于all对象(嵌套循环)。这会产生类似这样的结果(以伪代码形式):
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 垃圾,但增加了十几个事件侦听器。假设我采用这种方法。现在我们有了一堆静态属性,以及它们相应的静态 PropertyManager。据推测,我也会将选定对象的列表存储为 Object::selectedObjects ,因为它们在所有事件回调中使用,这些事件回调在逻辑上属于对象类。那么我们也有一堆静态事件回调。一切都很好。
因此,现在当您编辑属性时,我们可以通过事件回调更新所有选定对象的内部变量。但是当内部变量通过其他方式更新时会发生什么,我们如何更新属性呢?这恰好是一个物理模拟器,因此所有对象的许多变量都会不断更新。我无法为这些添加回调,因为物理是由另一个第三方库处理的。我想这意味着我只需要假设所有变量have每个时间步后都会改变。因此,在每个时间步骤之后,我必须更新所有选定对象的所有属性。好吧,我能做到。
最后一个问题(我希望)是,当选择多个对象并且存在不一致时,我们应该显示什么值?我想我的选择是将其留空/0 或显示随机对象的属性。我不认为一个选项比另一个更好,但希望 Qt 提供一种方法来突出显示这些属性,以便我至少可以通知用户。那么我如何找出要“突出显示”哪些属性呢?我想我会迭代所有选定的对象及其所有属性,比较它们,一旦出现不匹配,我就可以突出显示它。因此,为了澄清,在选择一些对象时:
- 将所有对象添加到 selectedObjects 列表
- 填充属性编辑器
- 查找哪些属性具有相同的值并相应地更新编辑器
我认为我也应该将属性存储在列表中,以便我可以将整个列表推送到属性编辑器中,而不是单独添加每个属性。我认为未来应该允许更多的灵活性。
我认为这涵盖了它......我仍然不确定我对拥有这么多静态变量和半单例类的感觉如何(我猜静态变量将在创建第一个对象时初始化一次)。但我没有看到更好的解决方案。
如果您真正读过本文,请发表您的想法。我想这并不是一个真正的问题,所以让我为那些讨厌的人重新表述一下,我可以对建议的设计模式进行哪些调整,以生成更清晰、更易于理解或更高效的代码?(或类似的规定)。
看来我有必要澄清一下。我所说的“属性”是指“允许睡眠”或“速度”——所有对象都具有这些属性——但是它们的值对于每个实例都是唯一的。特性保存需要显示的字符串、值的有效范围以及所有小部件信息。物业经理是实际持有价值的对象。它们控制回调以及显示的值。还有该值的另一个副本,它实际上由其他第 3 方物理库“内部”使用。
现在正在尝试实际实施这种疯狂。我有一个 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