Modello di progettazione dell’editor delle proprietà?

Attenzione: questo è super approfondito. Capisco che se non vuoi nemmeno leggerlo, questo è principalmente per me per risolvere il mio processo di pensiero.

Ok, ecco cosa sto cercando di fare. Ho questi oggetti:

Quando fai clic su uno (o ne selezioni diversi) dovrebbe mostrare le loro proprietà a destra (come mostrato). Quando si modificano le proprietà dette, è necessario aggiornare immediatamente le variabili interne.

Sto cercando di decidere il modo migliore per farlo. Immagino che gli oggetti selezionati debbano essere archiviati come una lista di puntatori. È o quello, o ha un booster isSelected su ogni object, e quindi itera su tutti loro, ignorando quelli non selezionati, che è solo inefficiente. Quindi clicchiamo su uno, o selezioniamo diversi, e poi l’elenco degli oggetti selezionati viene popolato. Abbiamo quindi bisogno di visualizzare le proprietà. Per mantenere le cose semplici per il momento, assumeremo che tutti gli oggetti siano dello stesso tipo (condividono lo stesso insieme di proprietà). Dato che non ci sono proprietà specifiche dell’istanza, immagino che dovremmo probabilmente memorizzare queste proprietà come variabili statiche all’interno della class Object. Le proprietà in pratica hanno solo un nome (come “Consenti sonno”). C’è un PropertyManager per ogni tipo di proprietà (int, bool, double). I PropertyManager memorizzano tutti i valori per le proprietà del loro rispettivo tipo (questo è tutto dall’API Qt). Sfortunatamente, poiché ai PropertyManager è richiesto di creare Proprietà, non posso davvero disaccoppiare i due. Suppongo che questo significhi che devo posizionare i PropertyManager con le Proprietà (come variabili statiche). Ciò significa che abbiamo un set di proprietà e un set di gestori di proprietà per gestire tutte le variabili in tutti gli oggetti. Ogni gestore di proprietà può avere solo un callback. Ciò significa che questa richiamata deve aggiornare tutte le proprietà del rispettivo tipo, per tutti gli oggetti (un ciclo annidato). Questo produce qualcosa di simile a questo (in pseudo-codice):

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

Il che mi infastidisce già un po ‘, perché stiamo usando le dichiarazioni in cui non dovremmo averne bisogno. Il modo per aggirare questo sarebbe creare un gestore di proprietà diverso per ogni singola proprietà, in modo da poter avere callback univoci. Ciò significa anche che abbiamo bisogno di due oggetti per ogni proprietà, ma potrebbe essere un prezzo che vale la pena pagare per un codice più pulito (non so davvero quali siano i costi delle prestazioni in questo momento, ma come so che dirai anche tu – ottimizza quando diventa un problema). Quindi finiamo con una tonnellata di callback:

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

Che elimina l’intero se / else spazzatura ma aggiunge una dozzina di altri eventi-listener. Supponiamo che vada con questo metodo. Quindi ora avevamo una manciata di proprietà statiche, insieme ai corrispondenti PropertyManager statici. Presumibilmente memorizzerei la lista degli oggetti selezionati come Object: selectedObjects anche perché sono usati in tutti i callback degli eventi, che logicamente appartengono alla class dell’object. Quindi abbiamo anche una manciata di callback di eventi statici. Va tutto bene e dandy.

Così ora quando modifichi una proprietà, possiamo aggiornare le variabili interali per tutti gli oggetti selezionati tramite il callback dell’evento. Ma cosa succede quando la variabile interna viene aggiornata con altri mezzi, come si aggiorna la proprietà? Questo sembra essere un simulatore di fisica, quindi tutti gli oggetti avranno molte delle loro variabili continuamente aggiornate. Non posso aggiungere callback per questi perché la fisica è gestita da un’altra libreria di terze parti. Immagino che questo significhi che devo solo supporre che tutte le variabili siano state cambiate dopo ogni passo temporale. Quindi, dopo ogni passaggio temporale, devo aggiornare tutte le proprietà per tutti gli oggetti selezionati. Bene, posso farlo.

L’ultimo numero (spero), è quali valori dovremmo visualizzare quando vengono selezionati più oggetti e c’è un’incongruenza? Immagino che le mie opzioni siano lasciare vuoto / 0 o mostrare le proprietà di un object casuale. Non credo che un’opzione sia molto migliore dell’altra, ma spero che Qt fornisca un metodo per evidenziare tali proprietà in modo tale che io possa almeno avvisare l’utente. Quindi, come faccio a capire quali proprietà “evidenziare”? Immagino di scorrere tutti gli oggetti selezionati, e tutte le loro proprietà, confrontarli, e non appena c’è un disallineamento posso metterlo in evidenza. Quindi per chiarire, dopo aver selezionato alcuni oggetti:

  1. aggiungi tutti gli oggetti a un elenco di oggetti selezionati
  2. compilare l’editor delle proprietà
  3. trova le proprietà con valori identici e aggiorna l’editor in modo appropriato

Penso che dovrei memorizzare le proprietà in una lista anche così posso solo spingere l’intera lista sull’editor delle proprietà piuttosto che aggiungere ciascuna proprietà singolarmente. Dovrei consentire una maggiore flessibilità lungo la strada, penso.

Penso che riguardi … Non sono ancora sicuro di come mi sento di avere così tante variabili statiche e una class semi-singleton (le variabili statiche verrebbero inizializzate una volta quando il primo object verrà creato immagino). Ma non vedo una soluzione migliore.

Per favore pubblica i tuoi pensieri se effettivamente leggi questo. Immagino che non sia davvero una domanda, quindi fammi riformulare per gli odiatori, quali adattamenti posso apportare al mio modello di progettazione suggerito per ottenere un codice più pulito, più comprensibile o più efficiente? (o qualcosa del genere).


Sembra che ho bisogno di chiarire. Per “proprietà” intendo come “Consenti dormire” o “Velocità”: tutti gli oggetti hanno queste proprietà – i VALORI, tuttavia, sono unici per ogni istanza. Le proprietà contengono la stringa che deve essere visualizzata, l’intervallo valido per i valori e tutte le informazioni del widget. I PropertyManager sono gli oggetti che contengono effettivamente il valore. Controllano i callback e il valore visualizzato. C’è anche un’altra copia del valore, che è effettivamente usata “internamente” dall’altra libreria di fisica di terze parti.


Sto cercando di implementare davvero questa follia ora. Ho un EditorView (l’area di disegno dell’area nera nell’immagine) che cattura l’evento mouseClick. Gli eventi mouseClick quindi indicano al simulatore di fisica di interrogare tutti i corpi sul cursore. Ogni corpo fisico memorizza un riferimento (un puntatore del vuoto!) Alla class dell’object. I puntatori vengono restituiti agli oggetti vengono inseriti in un elenco di oggetti selezionati. L’EditorView invia quindi un segnale. L’EditorWindow cattura quindi questo segnale e lo passa alla Finestra Proprietà insieme agli oggetti selezionati. Ora PropertiesWindow ha bisogno di interrogare gli oggetti per un elenco di proprietà da visualizzare … e questo è quanto ho ottenuto finora. Mente incredibile!


La soluzione

 /* * File: PropertyBrowser.cpp * Author: mark * * Created on August 23, 2009, 10:29 PM */ #include  #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("([az])([AZ])"), "\\1 \\2"); } void PropertyBrowser::setSelectedObjects(QList 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 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 i(m_propertyMap); bool diff; while(i.hasNext()) { i.next(); diff = false; for(int j = 1; 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))); } 

Grazie mille a TimW

Hai dato un’occhiata al sistema di proprietà (dinamico) di Qt?

 bool QObject::setProperty ( const char * name, const QVariant & value ); QVariant QObject::property ( const char * name ) const QList 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); } } 

Esempio

Questo è un esempio incompleto per darti un’idea del sistema di proprietà.
Immagino che SelectableItem * selectedItem debba essere sostituito con un elenco di elementi nel tuo caso.

 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::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 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 m_propertyMap; QPointer m_selectedItem; bool m_updatingValues; }; 

Calmati, il tuo codice non ha complessità di O (n ^ 2). Hai un ciclo annidato, ma solo uno conta su N (il numero di oggetti), l’altro conta su un numero fisso di proprietà, che non è correlato a N. Quindi hai O (N).

Per le variabili statiche, si scrive “non ci sono proprietà specifiche dell’istanza”, in seguito si scrive sugli aggiornamenti delle singole proprietà dei propri oggetti, che sono esattamente proprietà specifiche dell’istanza. Forse stai confondendo le “proprietà di class” (che è ovviamente condivisa tra tutte le proprietà) con le singole proprietà? Quindi penso che non hai bisogno di membri statici.

Vuoi visualizzare le modifiche agli oggetti solo se compaiono, o vuoi una visualizzazione continua? Se il tuo hardware è in grado di gestire quest’ultimo, ti consiglio di andare in quel modo. In tal caso, devi comunque eseguire l’iterazione su tutti gli oggetti e aggiornarli lungo il percorso.

Modifica: la differenza è che nel primo (aggiornamento sulla modifica) il disegno viene avviato dall’operazione di modifica dei valori, ad esempio un movimento dell’object. Per quest’ultimo, un display continuo, si dovrebbe aggiungere un QTimer, che scatta dire 60 volte al secondo e chiama uno SLOT (render ()) che esegue il rendering effettivo di tutti gli oggetti. A seconda della velocità delle modifiche, questo potrebbe effettivamente essere più veloce. Ed è probabilmente più facile da implementare. Un’altra possibilità è che Qt gestisca l’intero disegno, usando una Vista Grafica , che gestisce gli oggetti da disegnare internamente in una struttura ad albero molto efficiente. Dai un’occhiata a http://doc.trolltech.com/4.5/graphicsview.html

Se si desidera visualizzare solo le modifiche, è ansible utilizzare singoli callback per ciascun valore di proprietà. Ogni volta che viene modificato il valore di una proprietà (in questo caso rendendo private le proprietà vlaues e utilizzando setSomeThing (valore)), si chiama la funzione di aggiornamento con un emit (update ()). Se si è assolutamente preoccupati di emettere un rallentamento, è ansible utilizzare i callback “reali” tramite i puntatori di funzione, ma non è consigliabile, la connessione / segnale / slot di Qt è molto più semplice da utilizzare. E il sovraccarico è nella maggior parte dei casi davvero trascurabile.