Before I dive into the answer, let me describe a widget I added to make the example more interactive, by allowing the text to be modified by hand from either the server or the client. The widget contains:
- A label indicating which side changed the text.
- The text of the object.
For the sake of providing a code that works by itself without making people copy parts of your question, I will reproduce everything, including what works from what you wrote.
Server-side
Very few mistakes there.
If anything, I'd say you must specify a name in enableRemoting()
(I called it "MySharedObject"
)
// remoteobject.h
#include <QtRemoteObjects/QRemoteObjectRegistry>
#include <QtRemoteObjects/QRemoteObjectRegistryHost>
#include <QtRemoteObjects/QRemoteObjectHost>
class SharedObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
QString text() const { return m_text; }
void setText(const QString& text)
{
if (m_text != text) {
m_text = text;
emit textChanged(m_text);
}
}
signals:
void textChanged(const QString& text);
private:
QString m_text;
};
// main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include "remoteobject.h"
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
a.setQuitOnLastWindowClosed(true);
SharedObject sharedObject;
QRemoteObjectHost host;
host.setHostUrl(QUrl("local:sharedObject"));
host.enableRemoting(&sharedObject, QStringLiteral("MySharedObject"));
QWidget w;
w.setFixedSize(250, 80);
w.setWindowTitle("Server");
QLineEdit edit(&w);
edit.setFixedWidth(250);
edit.move(0, 40);
edit.setText(sharedObject.text());
QObject::connect(&edit , &QLineEdit::textEdited,
&sharedObject, &SharedObject::setText);
QObject::connect(&sharedObject, &SharedObject::textChanged,
&edit , &QLineEdit::setText);
QLabel label(&w);
label.setFixedWidth(250);
QObject::connect(&edit , &QLineEdit::textEdited,
[&label]() { label.setText("Changed from Server"); });
QObject::connect(&sharedObject, &SharedObject::textChanged,
[&label]() { label.setText("Changed from Client"); });
w.show();
return a.exec();
}
Client-side
This is where you got lost.
The error 'SharedObject::SharedObject': no overloaded function takes 2 arguments
is about the constructor expected to have 2 parameters. If you do not know what parameters are expected there, it is absolutely normal and I am going to describe what you should have done instead in a minute.
Before that, let us see what changes I made to the client's main.cpp
:
- I added the
"MySharedObject"
object identifier.
- I changed the class name from
SharedObject
to SharedObjectReplica
.
// main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include "remoteobject.h"
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
a.setQuitOnLastWindowClosed(true);
QRemoteObjectNode node;
node.connectToNode(QUrl("local:sharedObject"));
auto sharedObject = node.acquire<SharedObjectReplica>(QStringLiteral("MySharedObject"));
QWidget w;
w.setFixedSize(250, 120);
w.setWindowTitle("Client");
QLineEdit edit(&w);
edit.setFixedWidth(250);
edit.move(0, 80);
edit.setText(sharedObject->text());
QObject::connect(&edit, &QLineEdit::textEdited, sharedObject, &SharedObjectReplica::setText);
QObject::connect(sharedObject, &SharedObjectReplica::textChanged, &edit, &QLineEdit::setText);
QLabel label1(&w), label2(&w);
label1.setFixedWidth(250);
QObject::connect(&edit, &QLineEdit::textEdited, [&label1]() { label1.setText("Changed from Client"); });
QObject::connect(sharedObject, &SharedObjectReplica::textChanged, [&label1]() { label1.setText("Changed from Server"); });
label2.setFixedWidth(250);
label2.move(0, 40);
//label2 will be used later
w.show();
return a.exec();
}
Now, we have a SharedObjectReplica
defined nowhere, whose constructor must have 2 parameters that we do not choose...
The trick to get ourselves unstuck is to have Qt generate the SharedObjectReplica
class for us.
The server has the object with all the code; the client only needs a reference of the remote data.
To do this:
We create a very simple remoteobject.rep
file (documented here). Your SharedObject
class only has 1 read/write property with a signal. That is exactly what we type in the file.
class SharedObject
{
PROP(QString text READWRITE)
};
Similar to the meta-object compiler (moc.exe
), we compile the above file with the replica compiler (repc.exe
).
This creates our SharedObjectReplica
class in a header file, with the correct property, methods and signal. You will notice the constructor has 2 parameters like we wanted.
repc.exe" -o replica remoteobject.rep remoteobject.h
Finally, we moc remoteobject.h
The result
- You get 1 server + 1 client applications, each showing a window where you can edit the object's text.
- You are free to start the client after or before the server, it will work anyway.
- You will probably notice the window of the client application only shows the data is changed by the server.
This is something I wanted to demonstrate: when you change the text from the client window, you will immediately get the textChanged
signal from the server in return, and it happens so fast it is practically impossible to event see the text flicker (keep your finger onto a key and you will see this happen, eventually).
This is usually not a problem, because it never matters where the change originated from, but you may want to keep that in mind ... maybe.
Going beyond
(Edit to address the comment with link to there).
Structures can indeed be shared with a bit of effort. You must follow this page (a link to it is provided in the repc
helppage from above).
It is important MyStruct
supports queued connections (I know there is a warning somewhere about this fact but I could not find it to add the link).
-> we need to follow the instruction until and including the section [Creating and Destroying Custom Objects].
We declare the struct
with required default constructor, destructor, and copy constructor.
//MyStruct.h
#include <QtCore/QDataStream>
#include <QtCore/QString>
struct MyStruct {
MyStruct() = default;
~MyStruct() = default;
MyStruct(int val, const QString& text) : x(val), test(text) {}
MyStruct(const MyStruct& other) {
x = other.x;
test = other.test;
}
MyStruct& operator=(const MyStruct&) = default;
int x = 0;
QString test;
};
Q_DECLARE_METATYPE(MyStruct)
QDataStream& operator<<(QDataStream& out, const MyStruct& myStruct);
QDataStream& operator>>(QDataStream& in, MyStruct& myStruct);
//MyStruct.cpp
QDataStream& operator<<(QDataStream& out, const MyStruct& myStruct) {
out << myStruct.x << myStruct.test;
return out;
}
QDataStream& operator>>(QDataStream& in, MyStruct& myStruct) {
in >> myStruct.x >> myStruct.test;
return in;
}
As stated above, in the main
function, we need to add qRegisterMetaType<MyStruct>();
.
In the server's ShareObject
class, we add the property (that I leave for now incomplete, to illustrate something later):
Q_PROPERTY(MyStruct dataStruct READ getStruct WRITE setStruct)
I let you implement the getter/setter for the property.
On the client's side, we include the same declaration/definition for MyStruct
, operator>>
and operator<<
.
The remoteobject.rep
file must now look like:
#include "MyStruct.h"
class SharedObject
{
PROP(QString text READWRITE)
PROP(MyStruct dataStruct READWRITE)
};
This will generate:
- a
dataStruct()
and a setDataStruct
method for the replica (it does not matter the names are not the same as the server's object).
- The
qRegisterMetaType<MyStruct>();
inside the generated class, so we do not have to add it ourselves.
I declare the property with READWRITE
for the sake of having a more complete illustration but I invite you to test the other keyworks CONSTANT
, READONLY
, PERSISTED
, READWRITE
, READPUSH
(default), SOURCEONLYSETTER
and look at the generated class to see what getter, setter and signal you get out of each.
The code from the above sample can now be edited for illustration:
On the server, I changed the setText
method so that the attached struct counts how many times it was edited + what the last value was (I declared the private member as MyStruct myStruct;
):
void SharedObject::setText(const QString& text)
{
if (m_text != text) {
myStruct.x += 1;
myStruct.test = m_text;
m_text = text;
emit textChanged(m_text);
}
}
On the client, we can now make use of the previously unused label2
, from the main
function, by adding:
QObject::connect(sharedObject, &SharedObjectReplica::textChanged,
[sharedObject, &label2]() {
auto newStruct = sharedObject->dataStruct();
label2.setText(QString("sharedStruct: %1 , %2").arg(newStruct.x).arg(newStruct.test));
if (newStruct.x >= 10)
sharedObject->setDataStruct(MyStruct{ 0, "Reset" });
}
);
You will notice that label2
does not get updated unless you restart the client, contrary to what the connection would suggest. It is able to write a new structure into the server but nothing is visible in real time.
This is caused by the fact no signal is attached to the dataStruct
property.
Without a signal, the client does not know the structure changes and keeps a copy of it; this is the same behavior as property binding in QML.
To correct it, you therefore need to change the property declaration to
Q_PROPERTY(MyStruct dataStruct READ getStruct WRITE setStruct NOTIFY dataStructChanged)
Then add the signal to your class. Do not forget to actually emit the dataStructChanged(...);
inside the modified setText
method.
And voila! the client can get the structure, gets notified when it changes and can also write into it.