属性编辑器设计模式?

2024-01-05

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 提供一种方法来突出显示这些属性,以便我至少可以通知用户。那么我如何找出要“突出显示”哪些属性呢?我想我会迭代所有选定的对象及其所有属性,比较它们,一旦出现不匹配,我就可以突出显示它。因此,为了澄清,在选择一些对象时:

  1. 将所有对象添加到 selectedObjects 列表
  2. 填充属性编辑器
  3. 查找哪些属性具有相同的值并相应地更新编辑器

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

我认为这涵盖了它......我仍然不确定我对拥有这么多静态变量和半单例类的感觉如何(我猜静态变量将在创建第一个对象时初始化一次)。但我没有看到更好的解决方案。

如果您真正读过本文,请发表您的想法。我想这并不是一个真正的问题,所以让我为那些讨厌的人重新表述一下,我可以对建议的设计模式进行哪些调整,以生成更清晰、更易于理解或更高效的代码?(或类似的规定)。


看来我有必要澄清一下。我所说的“属性”是指“允许睡眠”或“速度”——所有对象都具有这些属性——但是它们的值对于每个实例都是唯一的。特性保存需要显示的字符串、值的有效范围以及所有小部件信息。物业经理是实际持有价值的对象。它们控制回调以及显示的值。还有该值的另一​​个副本,它实际上由其他第 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


你看过Qt的吗(动态)财产系统 http://doc.trolltech.com/4.5/properties.html?

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

这是一个不完整的例子,旨在向您展示我对财产系统的想法。
我猜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;
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

属性编辑器设计模式? 的相关文章

  • 使用遗留代码(使用reinterpret_cast)真的是一种很好的技术吗?

    下面的代码来自一篇关于C 面试问题的帖子here https www toptal com c plus plus interview questions 我从来不知道这种技术 尽管它声称是一种很好的技术 我的问题是 什么情况下需要使用它
  • std::map find 在 C++ 中不起作用[重复]

    这个问题在这里已经有答案了 我使用以下几行创建了一个哈希映射和一个迭代器 std map
  • 处理器关联组 C#

    我使用的是 72 核的 Windows Server 2016 我看到有两组处理器 我的 net 应用程序将使用一个或其他组 我需要能够强制我的应用程序使用我选择的组 我看到下面的代码示例 但我无法使其工作 我可能传递了错误的变量 我希望应
  • 使用 C 的另一个结构内的灵活长度结构数组

    你好 我正在尝试使用 C 来实现一个简单的结构 2 个盒子 每个盒子包含不同数量的颗粒 main 中传递的粒子的确切数量 我写了以下代码 typedef struct Particle float x float y float vx fl
  • 字符串/分段错误

    Program to calculate trip and plan flights define TRIP 6 define NAMEMAX 40 define DEST 1 include
  • 二叉树和快速排序?

    我有一个家庭作业 内容如下 别生气 担心 我是not请你帮我做作业 编写一个程序 通过使用二分查找的快速排序方法对一组数字进行排序 树 推荐的实现是使用递归算法 这是什么意思 到目前为止 这是我的解释 正如我在下面解释的那样 我认为两者都有
  • 如何在 Google Mock 中使用可选参数来模拟方法?

    如何使用可选参数模拟方法谷歌模拟 例如 class A public void set enable bool enabled true class MockA public A MOCK METHOD1 set enable void b
  • 图片框、双击和单击事件

    我有一个奇怪的问题 我有一个图片框双击事件以及单击事件 问题是即使我双击该控件 也会引发单击事件 如果我禁用单击事件 则双击事件正在工作 这个问题已经在这里讨论过 https stackoverflow com questions 1830
  • 如何将 dll 中包含的组件嵌入到 exe 中,以便它可以从内存运行?

    我正在尝试制作一个必须从内存运行的程序 通过Assembly Load bin 如上所述here http www codeproject com Articles 13897 Load an EXE File and Run It fro
  • MVC BaseController 处理 CRUD 操作

    我想重构我的基本 CRUD 操作 因为它们非常重复 但我不确定最好的方法 我的所有控制器都继承 BaseController 如下所示 public class BaseController
  • R 包与 Rcpp 的链接错误:“未定义符号:LAPACKE_dgels”

    我正在创建一个 R 包 lapacker 以使用 R API 头文件 R ext Lapack h 为 R 提供和使用的内部 LAPACK 库 仅具有双精度和双复数 提供 C 接口 源代码 https github com ypan1988
  • C 编程中的 rand() 问题? [复制]

    这个问题在这里已经有答案了 可能的重复 为什么我总是用 rand 得到相同的随机数序列 https stackoverflow com questions 1108780 why do i always get the same seque
  • 如何使用 itextsharp 更改 PDF 公式的按钮图标?

    我目前正在尝试使用 itextsharp 填写预定义的表单 除了添加图像之外 一切正常 这之前已经在 Adob e 的 FDF 工具包中运行过 该工具包已编译为 NET 1 1 这不再适用于 NET 4 0 我改用了 itextsharp
  • 如何通过分解 y 轴来减小 mschart 的高度

    如何降低 mschart 的高度 如下所示 编辑 就我而言 我不想查看中断图表 this chart1 ChartAreas 0 AxisY ScaleBreakStyle Enabled false 您似乎正在寻找AxisY ScaleB
  • Qt - 添加超链接到对话框

    有没有办法在 Qt 对话框中添加可点击的超链接 IE 它应该看起来像一个超链接 蓝色文本 当您单击它时 它应该在浏览器中打开该超链接 像这样的东西 Use QLabel setOpenExternalLinks bool 并在标签上设置文本
  • PyQt:在运行时向滚动区域添加小部件

    我试图在运行时通过按下按钮来添加新的小部件 在下面的示例中我使用标签 这里是例子 import sys from PyQt4 QtCore import from PyQt4 QtGui import class Widget QWidge
  • 结构大小与 typedef 版本不同?

    我的代码中有以下结构声明和 typedef struct blockHeaderStruct bool allocated unsigned int length typedef struct blockHeaderStruct block
  • Microsoft Visual Studio 2017 中的 wxWidgets 设置

    我花了大约 20 个小时试图弄清楚如何在 Microsoft Visual Studio 2017 中设置 wxWidgets 我遵循 https wiki wxwidgets org Microsoft Visual C 2B 2B Gu
  • C# 使用 .Equals() 比较两个 double

    我使用 ReShaper 当我用 比较两个双精度值时 它建议我应该使用 Math 具有公差的 ABS 方法 看 https www jetbrains com help resharper 2016 2 CompareOfFloatsByE
  • 在派生类中访问基类变量

    class Program static void Main string args baseClass obj new baseClass obj intF 5 obj intS 4 child obj1 new child Consol

随机推荐

  • 是否可以在不使用 ID 的情况下实现自增编号?

    我继续谷歌搜索并发现唯一的方法是使用 Id GeneratedValue strategy GenerationType Identity 但我已经有一个主键 我只需要另一个自动递增的字段 通过手动计算来编码确实很困难 我看到以下选项 1
  • iOS10 上的地址簿崩溃

    在 iOS10 0 中 从联系人选择器中选择联系人会使应用程序崩溃 联系人选择器显示使用ABPeoplePickerNavigationController像这样 let contactsPicker ABPeoplePickerNavig
  • maven 使用简单的命令行安装和部署第 3 方依赖项

    我们有许多未在任何地方托管的第三方依赖项 对于每一个 我们都有一个 jar 文件 我们希望能够将其安装和 或部署到我们的存储库 一些 jar 文件有自己的依赖项 我们也需要声明它们 我们为每个 jar 文件创建了 pom xml 文件 声明
  • ANR 错误 - 屏幕关闭 - 我该如何处理它们?

    我在开发人员控制台上收到此消息 指出我的应用程序已冻结 因为 ANR 意图广播 act android intent action SCREEN OFF flg 0x40000000 没有堆栈跟踪 因为这是由 Froyo 之前的用户提出的
  • 如何从 Sails JS 中的现有数据库生成模型?

    我首先从SailsJS and MySQL 我的数据库中有很多表 所以 我不知道在SailsJS有一个从数据库生成模型的工具 例如Database First in Entity Framework ASP 您应该使用 自动生成现有模型库数
  • 带有自定义适配器的 ListView

    我遵循了几个教程 但仍然无法填充我的列表视图 我究竟做错了什么 这是布局 spaced list xml
  • 在 iPhone 中计算行驶距离

    我需要找到两个地点之间的行驶距离 我不需要在地图中显示方向 只需要计算距离 我需要在我的应用程序中使用它 MapKit 允许这样做吗 有没有可以使用的替代方案 我可以使用 CloudMade 进行前向地理编码 但似乎没有获取行驶距离的选项
  • Haskell 智能构造函数的编译时检查

    我正在学习 Haskell 通过讲座 http www cis upenn edu cis194 spring13 http www cis upenn edu cis194 spring13 我有 module HanoiDisk Han
  • 如果未输入文本,则保持按钮禁用

    我想实现这样的目标 我有 4 个文本字段和 1 个选择字段 这是提交表单所需的 我想保持按钮禁用 除非文本字段具有值并且从下拉列表中选择了一个选项 我的代码是here http jsfiddle net C2mRd 3 我遇到的问题是 每当
  • 获得一副牌中的牌的得分值的最佳方法是什么

    我想知道创建一副带有分数的纸牌或计算分数的最佳方法是什么 正如您将在下面看到的 我有一个计算类来处理卡片分数 检查卡片是什么并给它一个值并计算获胜者 我正在创建一个二十一点游戏 我已经随机生成了卡牌 但现在每张卡牌的分数都遇到了麻烦 我只是
  • 如何重新打开 OS X 中情节提要中创建的关闭窗口

    我的问题很重要 但答案似乎不适用于 Swift Storyboards Cocoa 用 X 关闭后以编程方式显示主窗口 https stackoverflow com questions 6201958 cocoa programmatic
  • 查找列中的 n 个最大值

    我试图找到 SQL Server 中特定列中的 n 个最大数字 我们可以轻松找到一列中的最大值和第二大值 但是我如何找到一列中的 5 个最大值呢 您为 MySQL 和 SQL Server 都标记了它 在 SQL Server 中您可以使用
  • Python,向目录字符串添加尾部斜杠,操作系统独立

    如何添加尾部斜杠 对于 nix 对于 win32 到目录字符串 如果尾部斜杠尚未存在 谢谢 os path join path 如果尾部斜杠尚不存在 则会添加尾部斜杠 你可以做os path join path or os path joi
  • 如何在代码中访问x:Name-property?

    我将 XAML 文件中的 x Name 分配给了一个可以触发 MouseDown 事件的对象 在这种情况下 我想再次获取发件人的 x name attribute 我怎么做 该对象看起来像这样
  • float 的访问说明符的行为不符合预期

    include
  • 即使经过 360 度后如何获得以度为单位的相对旋转

    如果我有一个旋转的圆 并且达到 360 度的两倍 我会得到 720 度 但是当我旋转到 360 或 720 时 结果会给我 0 度 但我需要相对度数 所以如果圆旋转到 360 度 结果应该是 360 度而不是 0 是否有公式或函数可以在经过
  • Google Slides API - 如何更改特定颜色的所有形状的文本颜色

    我有 11 个文件 每个文件有 140 多张幻灯片 并且没有一个形状与主题 母版相关联 我的目标是更改主字体 并用黑色文本替换所有红色文本 有很多红色文本 我成功更新了主字体 感谢https gist github com dsottima
  • Java - 在一个映射中交换值和键?

    我正在为我的编程课制作一个加密和解密程序 但是我比小组领先一年 所以我想我可以利用去年学到的东西来简化事情 我决定使用树状图 该程序的作用是接收一个文件 读取包含字母编码方式的加密数据的第一行 它的格式为 A gt B B gt C C g
  • Flash 不支持 URL 中的用户名/密码吗?我有什么选择?

    我一直在尝试从开头带有用户名 密码的 URL 加载一些 JSON 所以 URL 类似于 http 用户名 电子邮件受保护 api 配置文件 http username password www myaddress org uk api pr
  • 属性编辑器设计模式?

    Warning 这是超级深入的 我理解 如果你根本不想读这篇文章 这主要是为了我整理一下我的思维过程 好的 这就是我想做的 我有这些对象 当您单击一个 或选择多个 时 它应该在右侧显示它们的属性 如图所示 当您编辑所述属性时 它应该立即更新