I have some class like this:
class QObjectDerived : public QObject
{
Q_OBJECT
// ...
};
Q_DECLARE_METATYPE(QObjectDerived*)
When this class was stored to QVariant such behaviour occures
QObjectDerived *object = new QObjectDerived(this);
QVariant variant = QVariant::fromValue(object);
qDebug() << variant; // prints QVariant(QObjectDerived*, )
qDebug() << variant.value<QObject*>(); // prints QObject(0x0)
qDebug() << variant.value<QObjectDerived*>(); // QObjectDerived(0x8c491c8)
variant = QVariant::fromValue(static_cast<QObject*>(object));
qDebug() << variant; // prints QVariant(QObject*, QObjectDerived(0x8c491c8) )
qDebug() << variant.value<QObject*>(); // prints QObjectDerived(0x8c491c8)
qDebug() << variant.value<QObjectDerived*>(); // QObject(0x0)
Is there any way to store it in QVariant and be able to get it as QObject* and QObjectDerived*?
Only by writing
QObject *value = variant.value<QObjectDerived*>();
It may be possible to partially specialize qvariant_cast for your type, but that's not a documented supported use case, and I'd be reluctant to rely on it.
qvariant.h (Qt 4.8.6):
template<typename T>
inline T value() const
{ return qvariant_cast<T>(*this); }
...
template<typename T> inline T qvariant_cast(const QVariant &v)
{
const int vid = qMetaTypeId<T>(static_cast<T *>(0));
if (vid == v.userType())
return *reinterpret_cast<const T *>(v.constData());
if (vid < int(QMetaType::User)) {
T t;
if (qvariant_cast_helper(v, QVariant::Type(vid), &t))
return t;
}
return T();
}
QObject * is stored as a built-in QMetaType::QObjectStar type, and QObjectDerived is a user-defined type with id, defined by Meta-type system. Which means, you'll have to cast it manually.
Related
I'm trying to integrate Lua with Qt's QMetaObject system. I have a class that derives from QObject that I bind to Lua based on the class name using QObject::staticMetaObject.
main.h:
#ifndef MAIN_H
#define MAIN_H
class Test : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE Test(QObject *parent = 0) : QObject(parent){}
~Test(){}
};
Q_DECLARE_METATYPE(Test*)
#endif
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include "main.h"
#include "lua_src/lua.hpp" //Lua include
int CreateUserData(lua_State *L)
{
const QMetaObject *metaObject = (const QMetaObject*)lua_touserdata(L, lua_upvalueindex(1));
//PROBLEM AREA
int typeId = QMetaType::type(metaObject->className());
if(typeId != QMetaType::UnknownType)//typeId is always unknown
{
QMetaType meta(typeId);
void *ptr = lua_newuserdata(L, meta.sizeOf());
meta.construct(ptr);
}
//PROBLEM AREA
lua_newtable(L);
lua_setuservalue(L, 1);
return 1;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QString luaScript("local test = Test.new()");
lua_State *L = luaL_newstate();
//bind Test class to lua
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "Test");
lua_pushvalue(L, -1);
lua_pushlightuserdata(L, (void*)&Test::staticMetaObject);
lua_pushcclosure(L, CreateUserData, 1);
lua_setfield(L, -2, "new");
//start script
luaL_dostring(L, luaScript.toStdString().c_str());
lua_close(L);
}
The issue is that lua will allocate memory for userdata but will not construct the object it represents. All documentation says to use placement new to construct your object at the ptr of the lua userdata, however QMetaObject doesn't allow placement new out of the box.
I've included suggestions from ixSci about using QMetaType to construct the object at ptr. However, typeId always comes back as unknown.
Looks like what you need is available in the QMetaType class.
So to get what you ask for you need something like this (not tested!):
int typeId = QMetaType::type(metaObject->className());
if (typeId != QMetaType::UnknownType)
{
QMetaType meta(typeId);
meta.construct(ptr, objectToCopy);
}
Your Test class miss a
Q_DECLARE_METATYPE(Test*)
and a
qRegisterMetaType<Test*>("Test");
to have the type correctly registered in Qt Meta-system.
Note the pointer declared. You need to declare a pointer because the copy constructor is disabled for QObject.
than you can correctly call:
Test* test = new Test();
auto name = test.metaObject()->className();
auto type = QMetaType::type(name);
Test* instance = static_cast<Test*>(QMetaType::construct(type));
Edit: A complete working implementation (it actually add the qMetaTypeConstructHelper)
somevalue.h
#include <QObject>
#include <QMetaType>
class SomeValue : public QObject
{
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
explicit Q_INVOKABLE SomeValue(QObject* parent = nullptr);
~SomeValue() override = default;
int value() const;
signals:
void valueChanged(int value);
public slots:
void setValue(int value);
private:
int _value;
};
somevalue.cpp
#include "somevalue.h"
Q_DECLARE_METATYPE(SomeValue*)
template <>
void* qMetaTypeConstructHelper<SomeValue>(const SomeValue*)
{
return new SomeValue();
}
static struct SomeValueMetaId
{
SomeValueMetaId()
{
qRegisterMetaType<SomeValue>("SomeValue");
}
} _SomeValueMetaId;
SomeValue::SomeValue(QObject* parent)
: QObject(parent),
_value{100}
{
}
int SomeValue::value() const
{
return _value;
}
void SomeValue::setValue(int value)
{
if (_value == value)
return;
_value = value;
emit valueChanged(_value);
}
main.cpp
int main()
{
SomeValue pint;
auto pintName = pint.metaObject()->className();
auto pintType = QMetaType::type("SomeValue");
qDebug() << pintName << pintType << QMetaType::typeName(pintType);
qDebug() << QMetaType::isRegistered(QMetaType::type("SomeValue*"));
auto otherObj = static_cast<SomeValue*>(QMetaType::construct(pintType));
qDebug() << pint.value();
qDebug() << otherObj->value();
qDebug() << "new classname" << otherObj->metaObject()->className();
qDebug() << otherObj->metaObject()->propertyCount();
int valueId = pint.metaObject()->indexOfProperty("value");
auto minname = pint.metaObject()->property(valueId).name();
qDebug() << "value name" << minname;
auto minvariant = pint.property(minname);
qDebug() << "value type name" << minvariant << minvariant.typeName();
qDebug() << QMetaType::type(minvariant.typeName());
return 0;
}
I have found a solution for my situation.
After reviewing the answers from Moia and ixSci, I have realized that I was correct in my statement that placement new cannot be used on a QObject because QObject has it's copy constructor private (and shouldn't be made public).
A more efficient method is to (obviously) store pointers to the QObject* created from metaObject->newInstance(). That's right, pointers to pointers.
New code is as follows:
const QMetaObject *metaObject = (const QMetaObject*)lua_touserdata(L, lua_upvalueindex(1));
uintptr_t *ptr = (uintptr_t*)lua_newuserdata(L, sizeof(QObject*));
QObject *object = metaObject->newInstance();
*ptr = reinterpret_cast<uintptr_t>(object);
And for retrieving:
uintptr_t *objectPointer = (uintptr_t*)lua_touserdata(L, -1);
QObject *object = static_cast<QObject*>((void*)*objectPointer);
The upside is that lua can allocate fixed size for any class object since it is always 4 (just a pointer). This means I don't have to do any type checking.
The obvious downside to this is that I can't do any type checking since it will always just be pointers. Also, all interactions with these types inside the Lua script will behave as pointers. All copies will be pointer copies instead of QObject copies. As a result, I will have to implement my own copy constructor for my QObject's depending on my specific use case.
Thanks for all your assistance!
I've got issue sending a custom class as an argument of a signal through Qt's web channel.
There isn't any error in the console, not even a warning, for both compilation and run time. Even though, I've got a null as parameters in my JavaScript signal handler. It works perfectly well with QString, int and others but not with my objects.
I saw this post: How to register a class for use it in a QWebChannel signal in Qt. The problem seems to be similar at first, but their solution doesn't work me. I have a public default constructor, a public copy constructor, and a public destructor. I used both qRegisterMetaType() and Q_DECLARE_METATYPE and still nothing.
By printing messages received by qwebchannel.js, I realized that the C++ send the null value. Which means that it doesn't know how to send my class.
Does anyone have an idea of what could be happening or how to solve it? (Propose even if you don't know)
Thanks in advance!
EDIT:
After Research, I realized that issue come from this line in QWebChannel (singalhandler_p.h):
template<class Receiver>
void SignalHandler<Receiver>::dispatch(const QObject *object, const int signalIdx, void **argumentData)
{
Q_ASSERT(m_signalArgumentTypes.contains(object->metaObject()));
const QHash<int, QVector<int> > &objectSignalArgumentTypes = m_signalArgumentTypes.value(object->metaObject());
QHash<int, QVector<int> >::const_iterator signalIt = objectSignalArgumentTypes.constFind(signalIdx);
if (signalIt == objectSignalArgumentTypes.constEnd()) {
// not connected to this signal, skip
return;
}
const QVector<int> &argumentTypes = *signalIt;
QVariantList arguments;
arguments.reserve(argumentTypes.count());
// TODO: basic overload resolution based on number of arguments?
for (int i = 0; i < argumentTypes.count(); ++i) {
const QMetaType::Type type = static_cast<QMetaType::Type>(argumentTypes.at(i));
QVariant arg;
if (type == QMetaType::QVariant) {
arg = *reinterpret_cast<QVariant *>(argumentData[i + 1]);
} else {
arg = QVariant(type, argumentData[i + 1]);
}
arguments.append(arg);
}
m_receiver->signalEmitted(object, signalIdx, arguments);
}
As you can see, when a signal is sent from C++ through the channel, the arguments are converted in QVariant.
After a few tests, I realized that the QVariant::typeName() return the name of my type, but doesn't contain anything from it.
I tried a single code to understand what I'm doing wrong.
MyClass myClass;
QVariant variant = QVariant::fromValue(myClass);
std::cout << (int)doc.isArray() << std::endl;
std::cout << (int)doc.isEmpty() << std::endl;
std::cout << (int)doc.isNull() << std::endl;
std::cout << (int)doc.isObject() << std::endl;
std::cout << variant.typeName() << ": [" << variant.toJsonDocument().toJson().toStdString() << "]" << std::endl;
The header for MyClass is:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(int a MEMBER a)
public:
MyClass(QObject *parent = NULL);
MyClass(const MyClass &);
int a;
};
Q_DECLARE_METATYPE(MyClass)
And this give the output:
0
1
1
0
MaClass: []
I don't understand why it's always empty. I tried to create getters and setters but the result is the same.
C++ has limited ability to use pointer-to-member functions. I need something that will allow me to dynamically choose a callback member function, in order to use the Visitor pattern of the XMLNode::Accept(XMLVisitor *visitor) method from the TinyXML2 library.
To use XMLNode::Accept(), I must call it with a class which implements the XMLVisitor interface. Hence:
typedef bool (*Callback)(string, string);
class MyVisitor : public tinyxml2::XMLVisitor {
public:
bool VisitExit(const tinyxml2::XMLElement &e) {
callback(e.Name(), e.GetText());
}
Callback callback;
}
This works fine if my caller is NOT an object which wants to use one of its own methods as a callback function (so that it can access class variables). For example, this works:
bool myCallBackFunc(string e, string v) {
cout << "Element " << e << " has value " << v << endl;
return true;
}
int main(...) {
tinyxml2::XMLDocument doc;
doc.LoadFile("somefile.xml");
MyVisitor visit;
visit.callback = myCallBackFunc;
doc.Accept(&visit);
}
However, in my use case, the parsing is done inside a method in a class. I have multiple applications which have similar but unique such classes. I'd like to use only one generic MyVisitor class, rather than have the visitor class have unique knowledge of the internals of each class which will call it.
Thus, it would be convenient if the callback function were a method in each calling class so that I can affect the internal state of the object instantiated from that calling class.
Top level: I have 5 server applications which talk to 5 different trading partners, who all send XML responses, but each is enough different that each server app has a class which is unique to that trading partner. I'm trying to follow good OO and DRY design, and avoid extra classes having unique knowledge while still doing basically the same work.
Here's the class method I want Accept() to call back.
ServiceClass::changeState(string elem, string value) {
// Logic which sets member vars based on element found and its value.
}
Here's the class method which will call Accept() to walk the XML:
ServiceClass::processResponse(string xml) {
// Parse XML and do something only if certain elements present.
tinyxml2::XMLDocument doc;
doc.Parse(xml.c_str(), xml.length());
MyVisitor visit;
visit.callback = &changeState; // ERROR. Does not work.
visit.callback = &ServiceClass::changeState; // ERROR. Does not work.
doc.Accept(&visit);
}
What's a simple way to get what I want? I can imagine more classes with derived classes unique to each situation, but that seems extremely verbose and clumsy.
Note: In the interest of brevity, my sample code above has no error checking, no null checking and may even have minor errors (e.g. treating const char * as a string ;-).
Below is the std::bind(..) example for what you're trying to do in C++11. For earlier C++ versions you could use the boost::bind utilities.
Fix your MyVisitor::VisitExit(...) method to return a boolean, by the way.
The code is converting const char * to std::string. tinyxml2 does not guarantee that the char * arguments from Name() or GetText() are not null. In fact in my experience they will be null at some point. You should guard against this. For the sake of not modifying your example too much I've not protected against this possibility everywhere in the example.
typedef bool(*Callback)(string, string);
using namespace std;
class MyVisitor : public tinyxml2::XMLVisitor {
public:
bool VisitExit(const tinyxml2::XMLElement &e) {
// return callback(e.Name(), e.GetText());
return true;
}
Callback callback;
};
/** Typedef to hopefully save on confusing syntax later */
typedef std::function< bool(const char * element_name, const char * element_text) > visitor_fn;
class MyBoundVisitor : public tinyxml2::XMLVisitor {
public:
MyBoundVisitor(visitor_fn fn) : callback(fn) {}
bool VisitExit(const tinyxml2::XMLElement &e) {
return callback(e.Name() == nullptr ? "\0" : e.Name(), e.GetText() == nullptr ? "\0": e.GetText());
}
visitor_fn callback;
};
bool
myCallBackFunc(string e, string v) {
cout << "Element " << e << " has value " << v << endl;
return true;
}
int
main()
{
tinyxml2::XMLDocument doc;
doc.LoadFile("somefile.xml");
MyVisitor visit;
visit.callback = myCallBackFunc;
doc.Accept(&visit);
visitor_fn fn = myCallBackFunc; // copy your function pointer into the std::function<> type
MyBoundVisitor visit2(fn); // note: declare this outside the Accept(..) , do not use a temporary
doc.Accept(&visit2);
}
So from within the ServiceClass method you'd do:
ServiceClass::processResponse(string xml) {
// Parse XML and do something only if certain elements present.
tinyxml2::XMLDocument doc;
doc.Parse(xml.c_str(), xml.length());
// presuming changeState(const char *, const char *) here
visitor_fn fn = std::bind(&ServiceClass::changeState,this,std::placeholders::_1,std::placeholders::_2);
MyBoundVisitor visit2(fn); // the method pointer is in the fn argument, together with the instance (*this) it is a method for.
doc.Accept(&visit);
}
You can use generics in order to support whichever callback you'd like.
I've tried to mock the classes of the library in order to give you a fully runnable example:
#include <string>
#include <iostream>
#include <functional>
class XmlNode {
public:
XmlNode(const std::string& n, const std::string t) : name(n), txt(t) {}
const std::string& Name() const { return name; }
const std::string& GetText() const { return txt; }
private:
std::string name;
std::string txt;
};
class XMLVisitor {
public:
virtual void VisitExit(const XmlNode& node) = 0;
virtual ~XMLVisitor() {}
};
template<typename T>
class MyVisitor : XMLVisitor {
public:
MyVisitor() {}
void myInnerPrint(const XmlNode& node) {
std::cout << "MyVisitor::myInnerPrint" << std::endl;
std::cout << "node.Name(): " << node.Name() << std::endl;
std::cout << "node.GetText(): " << node.GetText() << std::endl;
}
void SetCallback(T newCallback) {
callback = newCallback;
}
virtual void VisitExit(const XmlNode& node) {
callback(node);
}
T callback;
};
int main() {
XmlNode node("In", "Member");
MyVisitor<std::function<void(const XmlNode&)>> myVisitor;
auto boundCall =
[&myVisitor](const XmlNode& node) -> void {
myVisitor.myInnerPrint(node);
};
myVisitor.SetCallback(boundCall);
myVisitor.VisitExit(node);
return 0;
}
First define a template and a helper function:
namespace detail {
template<typename F>
struct xml_visitor : tinyxml2::XMLVisitor {
xml_visitor(F&& f) : f_(std::move(f)) {}
virtual void VisitExit(const tinyxml2::XMLElement &e) {
f_(e);
}
private:
F f_;
};
}
template<class F>
auto make_xml_visitor(F&& f)
{
return detail::xml_visitor<std::decay_t<F>>(std::forward<F>(f));
}
Then use the helper function to construct a custom visitor from a lambda which captures this:
void ServiceClass::processResponse(std::string xml) {
// Parse XML and do something only if certain elements present.
tinyxml2::XMLDocument doc;
doc.Parse(xml.c_str(), xml.length());
auto visit = make_xml_visitor([this](const auto& elem)
{
this->changeState(elem.Name(), elem.GetText);
});
doc.Accept(std::addressof(visit));
}
The rule is that a function pointer must always accept a void * which is passed in to the module which calls it, and passed back. Or use a lambda which is the same thing with some of the machinery automated for you. (The void * is the "closure").
So
typedef bool (*Callback)(string, string, void *context);
class MyVisitor : public tinyxml2::XMLVisitor {
public:
bool VisitExit(const tinyxml2::XMLElement &e) {
callback(e.Name(), e.GetText(), contextptr);
}
Callback callback;
void *contextptr;
}
bool myCallBackFunc(string e, string v, void *context) {
ServiceClass *service = (ServiceClass *) context;
cout << "Element " << e << " has value " << v << endl;
service->ChangeState(e, v);
return true;
}
I'm trying to convert a QVariantMap to a custom class derived from QObject but I'm getting the return value of false from setProperty() when it comes to set the property of my enum type. Code goes below:
The MessageHeader.h file:
// deserialization class header
class MessageHeader : public QObject
{
Q_OBJECT
public:
MessageHeader(QObject *parent = 0);
~MessageHeader();
enum class MessageType
{
none = 0,
foo = 1,
baa = 2
};
Q_ENUM(MessageType)
Q_PROPERTY(MessageType type READ getType WRITE setType)
Q_PROPERTY(int ContentLength READ getContentLength WRITE setContentLength)
void setType(MessageType type);
void setContentLength(int ContentLength);
MessageType getType();
int getContentLength();
QString toString();
MessageType type = MessageType::none;
int ContentLength = 0;
};
The MessageHeader.cpp file:
MessageHeader::MessageHeader(QObject *parent)
: QObject(parent)
{
}
MessageHeader::~MessageHeader()
{
}
MessageType MessageHeader::getType()
{
return type;
}
int MessageHeader::getContentLength()
{
return ContentLength;
}
void MessageHeader::setType(MessageType type)
{
this->type = type;
}
void MessageHeader::setContentLength(int ContentLength)
{
this->ContentLength = ContentLength;
}
QString MessageHeader::toString()
{
return QString("NOT IMPLEMENTED YET");
}
And the deserialize function template helper:
template<typename T>
T* Deserialize(const QString &json)
{
bool status = false;
QJson::Parser parser;
QVariantMap map = parser.parse(json.toUtf8(), &status).toMap();
if(!status)
return NULL;
T *obj = new T(); //don't worry about this, I'll rather take this from paramters once this is working
QObject *p = (QObject *) obj; // cast done so that I see setProperty() method
for(QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
{
const char *name = iter.key().toLatin1();
const QVariant value = iter.value();
qDebug() << "setting " << name << "=" << value;
// the issue goes below. Here setProperty() return false.
// At this point, name = 'type' and value = 2
assert(p->setProperty(name, value));
}
//QJson::QObjectHelper::qvariant2qobject(map, obj);
return obj;
}
The JSON input string to above function is like this:
"{\"ContentLength\": 100, \"type\": 2}"
The enum type is registered in the main funcction before anything else:
qRegisterMetaType<MessageType>("MessageType");
And here's the QJson library used in this example. I build it on Windows with this .pro file
EDIT:
I just found that the type property can't be find by indexOfProperty()
qDebug() << "id = " << meta->indexOfProperty(name); // print -1, name = 'type'
The enum property can only be set if the variant type is either a QString, QInt or QUInt as could be seen here. So to successfully set the enum property, the variant needs to be one of these types and nothing else. QJson parses any unsigned integers as QULongLong as can be seen here, line 84. So one way is to fork QJson and modify the code so the integer values are converted to QInt and QUInt or read/write the enum values as strings.
Also, putting statements within an assert is not a good idea, but I assume you just wrote that code trying to figure out the problem.
Just as a side note, according to Qt documentation,
[qRegisterMetaType] is useful only for registering an alias (typedef) for every other use case Q_DECLARE_METATYPE and qMetaTypeId() should be used instead.
so replacing qRegisterMetaType<MessageHeader::MessageType>("MessageType") with Q_DECLARE_METATYPE(MessageHeader::MessageType) in your header would be a reasonable move.
Building up on Rostislav's answer, if you have no choice but to receive a QULongLong as input, here is a code snippet to convert it if the property to set is an enum:
#include <QMetaProperty>
const QMetaObject* meta = object->metaObject();
const int index = meta->indexOfProperty(propName);
if (index == -1) {/* report error*/}
if (meta->property(index).isEnumType())
// special case for enums properties: they can be set from QInt or QUInt variants,
// but unsigned integers parsed from json are QULongLong
object->setProperty(propName, propVariant.value<unsigned int>());
else
object->setProperty(propName, propVariant);
How would I return a(n existing) pointer to a known type from a string of its name? Say I created some map<string, double> called map1 and some vector<string> called vec1. I'd like to write a function that will return map1 from "map1" and vec1 from "vec1" (and an additional argument specifying the respective type of each).
Is this possible?
My ultimate goal is to get a QWidget by its name in QString form, since I'm working with a large number of them, though an answer should be framework-independent, right?
You need to perform some kind of dynamic dispatch. To do so you can simply start with exactly what you proposed:
enum class types { A = 0, B = 1 };
void (*handlers[])(void*) = { &HandleA, &HandleB };
::std::unordered_map<::std::string, ::std::tuple<types, void*>> registry;
Now all that remains is to perform the lookup:
void lookup(::std::string const& name)
{
auto& t = registry.at(name);
handlers[static_cast<size_t>(::std::get<0>(t))](::std::get<1>(t));
}
Automagic argument casting for handlers
The handlers all take an argument of type void* - this can be dealt with by adding a little template magic:
template<typename T, void(*f)(T*)>
void handle(void* arg)
{
f(static_cast<T*>(arg));
}
void (*handlers[])(void*) = { &handle<A, &HandleA>, &handle<B, &HandleB> };
Now, the prototype is e.g. void HandleA(A*).
Simple adding of objects to registry
With the current code, you can add objects to your registry like so:
A a;
registry.emplace("A #1", ::std::make_tuple(types::A, &a));
While this works perfectly, we would like to do something a bit more elegant. Let us start by changing the enum class types to something which also knows about the type we whish to represent it:
template<typename T> struct types;
template<> struct types<A> { static const size_t id = 0; };
template<> struct types<B> { static const size_t id = 1; };
Of course, now we need to fix the registry type:
::std::unordered_map<::std::string, ::std::tuple<size_t, void*>> registry;
And finally we can provide a simple insert function:
template<typename T>
void insert(::std::string const& name, T* object)
{
registry.emplace(name, ::std::make_tuple(types<T>::id, static_cast<void*>(object)));
}
Final usage example
A a;
insert("A #1", &a);
lookup("A #1");
The meta-object system already handles this, so the answer will be framework-specific because you generally need a code generator to get metadata about the C++ types that's not otherwise available.
QLineEdit * ed = ...;
ed->setObjectName("myObject");
... elsewhere in the code
foreach(QWidget * w, QCoreApplication::allWidgets()) {
// Lookup by name
if (w->objectName() == "myObject") {
...
}
// Lookup by type
if (qobject_cast<QLineEdit*>(w)) {
...
}
}
If you want to speed up the lookup, and the objects have unique names:
class Widgets {
typedef QMap<QString, QPointer<QWidget>> Data;
mutable Data m_map;
public:
Widgets() {
foreach(QWidget * w, QCoreApplication::allWidgets()) {
if (w->objectName().isEmpty()) continue;
m_map.insert(w->objectName(), w);
}
}
QWidget * lookupWidget(const QString & name) const {
Data::iterator it = m_map.find(name);
if (it == m_map.end()) return nullptr;
QWidget * w = it->data();
if (!w) m_map.erase(it); // The widget doesn't exist anymore
return w;
}
template <typename T> T * lookup(const QString & name) const {
return qobject_cast<T*>(lookupWidget(name));
}
void setName(QWidget * w, const QString & name) {
Q_ASSERT(! name.isEmpty());
w->setObjectName(name);
m_map.insert(name, w);
}
};
In your code, use widgets->setName() instead of setObjectName.
If you want to look-up by both name and type, where duplicate names are OK as long as they are all of different types:
class Widgets2 {
typedef QPair<QString, QString> Key;
typedef QMap<Key, QPointer<QWidget>> Data;
mutable Data m_map;
static Key keyFor(QWidget * w) {
return qMakePair(w->objectName(),
QString::fromLatin1(w->metaObject()->className()));
public:
Widgets2() {
foreach(QWidget * w, QCoreApplication::allWidgets()) {
if (w->objectName().isEmpty()) continue;
m_map.insert(keyFor(w), w);
}
}
QWidget * lookupWidget(const QString & name, const QString & type) const {
Data::iterator it = m_map.find(qMakePair(name, type));
if (it == m_map.end()) return nullptr;
QWidget * w = it->data();
if (!w) m_map.erase(it); // The widget doesn't exist anymore
return w;
}
template <typename T> T * lookup(const QString & name) const
{
return qobject_cast<T*>(lookupWidget(name,
QString::fromLatin1(T::staticMetaObject.className())));
}
void setName(QWidget * w, const QString & name) {
Q_ASSERT(! name.isEmpty());
w->setObjectName(name);
m_map.insert(keyFor(w), w);
}
};
The lookup works as follows:
widgets2->lookup<QLineEdit>("myObject")->setText("foo");
I'm leveraging the QObject and QPointer to make the widget registry safe to widget deletions - you won't ever get a dangling pointer back.
It is also possible to track object name changes, if you so wish: QObject emits the objectNameChanged signal.
All of this is of course a horrible hack around a broken design of your code. The fact that you need this means that you're very tightly coupling business logic and the GUI. You should use some kind of model-view architecture.