QWebEnginePage: toHtml returns an empty string - c++

I need to retrieve some html from a QWebEnginePage. I found in the documentation the method toHtml but it always returns an empty string. I tried toPlainText
and it works, but this is not what I need.
MyClass::MyClass(QObject *parent) : QObject(parent)
{
_wp = new QWebEnginePage();
_wp->settings()->setAttribute(QWebEngineSettings::AutoLoadImages, false);
_wp->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
connect(_wp, SIGNAL(loadFinished(bool)), this, SLOT(wpLoadFinished(bool)));
}
void MyClass::start()
{
_wp->load(QUrl("http://google.com/"));
}
void MyClass::wpLoadFinished(bool s)
{
_wp->toHtml(
[] (const QString &result) {
qDebug()<<"html:";
qDebug()<<result;
}); // return empty string
/*_wp->toPlainText(
[] (const QString &result) {
qDebug()<<"txt:";
qDebug()<<result;
});*/ //works perfectly
}
What am I doing wrong?

I am getting my head around QWebEngine. It is very cool. I have got the following to work.
The lambada capture needs to be all that is "=", or "this" in the case of signal being emitted. You would also need "mutable" to modify the captured copies. toHtml() is however asynchronous so even if you capture the html it is unlikely it would be available directly after the call to toHtml() in SomeFunction. You can overcome this by using a signal and slot.
protected slots:
void handleHtml(QString sHtml);
signals:
void html(QString sHtml);
void MainWindow::SomeFunction()
{
connect(this, SIGNAL(html(QString)), this, SLOT(handleHtml(QString)));
view->page()->toHtml([this](const QString& result) mutable {emit html(result);});
}
void MainWindow::handleHtml(QString sHtml)
{
qDebug()<<"myhtml"<< sHtml;
}

I think that the problem is more a connection problem. Your code works fine on my appli :
connect(page, SIGNAL(loadFinished(bool)), this, SLOT(pageLoadFinished(bool)));
...
page->load(QUrl("http://google.com/"));
...loading time...
void MaClasse :: pageLoadFinished(bool s){
page->toHtml([this](const QString &result){
qDebug()<<"html:";
qDebug()<<result;
item->setHtml(result);});
}

Related

How to temporarily switch to the GUI thread

I have a program that takes a long action, I run this function on a different thread. Periodically I need to update the information for the user, so I send a signal to the GUI thread. But sometimes I need the user to make a choice, I need to display the QDialog on the GUI thread and pause the slow thread while the user selects an option, and when the user completes the selection, return the value to the slow thread and continue it
it should look something like this:
But I don’t know how to stop and continue the thread and whether it should be done this way.
Header:
class Example:public QObject
{
//...
Q_OBJECT
void mainLoop();
Example();
signals:
void updateGUI(const QString &message);
void sendQuestion(const QString &message);
void continueMainLoop(const QString &answer);
private slots:
void updatuGUIslot(const QString &message);
void showQuestionDialog(const QString &message);
};
Source:
Example::Example()
{
connect(this,&Example::updateGUI,this,&Example::updatuGUIslot);
connect(this,&Example::sendQuestion,this,&Example::showQuestionDialog);
std::thread t(&Example::mainLoop,this);
t.detach();
// in the project it is not in the constructor
}
void Example::mainLoop()
{
while(some condition1)
{
// slow action
if(some condition2)
emit updateGUI("message");
if(some condition3)
{
QString result;
ThreadPtr th = this_thread(); // pseudocode
connect(this,&Example::continueMainLoop,this,[&](const QString &answer)
{
result = answer;
th.continue(); // pseudocode
});
emit sendQuestion("question");
th.wait(); // pseudocode
}
// slow action
}
}
void Example::showQuestionDialog(const QString &message)
{
// show dialog with question
emit continueMainLoop("answer");
}
void Example::updatuGUIslot(const QString &message)
{
// update GUI
}
you need to invoke the method with BlockingQueuedConnection before condition3 for checking which option that selected by the user.
bool updateGui ;
QMetaObject::invokeMethod(this, "showDialog",Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, updateGui));
if(updateGui)
{
//update GUI
}

Make QGenericArgument/Q_ARG from QVariant

I wrote an interface for systemd-timedated:
#include <QtDBus>
#include <dbus/dbus.h>
Q_DECLARE_LOGGING_CATEGORY(timeDateInterfaceCategory)
#define TIMEDATE_DBUS_SERVICE "org.freedesktop.timedate1"
#define TIMEDATE_DBUS_PATH "/org/freedesktop/timedate1"
#define TIMEDATE_DBUS_INTERFACE "org.freedesktop.timedate1"
class TimeDateInterface
: public QDBusAbstractInterface
{
Q_OBJECT
Q_PROPERTY(bool CanNTP MEMBER CanNTP NOTIFY CanNTPChanged)
Q_PROPERTY(bool LocalRTC MEMBER LocalRTC NOTIFY LocalRTCChanged)
Q_PROPERTY(bool NTP MEMBER NTP NOTIFY NTPChanged)
Q_PROPERTY(bool NTPSynchronized MEMBER NTPSynchronized NOTIFY NTPSynchronizedChanged)
Q_PROPERTY(qulonglong RTCTimeUSec MEMBER RTCTimeUSec NOTIFY RTCTimeUSecChanged)
Q_PROPERTY(qulonglong TimeUSec MEMBER TimeUSec NOTIFY TimeUSecChanged)
Q_PROPERTY(QString Timezone MEMBER Timezone NOTIFY TimezoneChanged)
public :
explicit
TimeDateInterface(QObject * const parent = Q_NULLPTR)
: QDBusAbstractInterface{{TIMEDATE_DBUS_SERVICE}, {TIMEDATE_DBUS_PATH}, TIMEDATE_DBUS_INTERFACE,
QDBusConnection::systemBus(),
parent}
{
qDBusRegisterMetaType< QVariantMap >();
if (!isValid()) {
qCCritical(timeDateInterfaceCategory).noquote()
<< tr("Unable to create interface %1: %2")
.arg(service(), lastError().message());
return;
}
if (!connection().connect({service()}, path(), {DBUS_INTERFACE_PROPERTIES}, {"PropertiesChanged"},
//QString::fromLatin1(QMetaObject::normalizedSignature("PropertiesChanged(QString,QVariantMap,QStringList)")),
this, SLOT(propertiesChanged(QString, QVariantMap, QStringList)))) {
Q_ASSERT(false);
}
}
public Q_SLOTS :
Q_SCRIPTABLE
void SetLocalRTC(bool localRtc, bool fixSystem, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetLocalRTC"},
QVariant::fromValue(localRtc),
QVariant::fromValue(fixSystem),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
Q_SCRIPTABLE
void SetNTP(bool useNtp, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetNTP"},
QVariant::fromValue(useNtp),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
Q_SCRIPTABLE
void SetTime(qlonglong usecUtc, bool relative, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetTime"},
QVariant::fromValue(usecUtc),
QVariant::fromValue(relative),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
Q_SCRIPTABLE
void SetTimezone(QString timezone, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetTimezone"},
QVariant::fromValue(timezone),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
private Q_SLOTS :
void propertyChanged(QString const & propertyName)
{
const auto signature = QStringLiteral("%1Changed()").arg(propertyName);
const int signalIndex = staticMetaObject.indexOfSignal(QMetaObject::normalizedSignature(qUtf8Printable(signature)).constData());
if (signalIndex < 0) {
qCCritical(timeDateInterfaceCategory).noquote()
<< tr("There is no signal with %1 signature")
.arg(signature);
return;
}
const auto signal = staticMetaObject.method(signalIndex);
if (!signal.invoke(this, Qt::DirectConnection)) {
qCCritical(timeDateInterfaceCategory).noquote()
<< tr("Unable to emit %1 signal for %2 property")
.arg(signature, propertyName);
}
}
void propertiesChanged(QString interfaceName, QVariantMap changedProperties, QStringList invalidatedProperties)
{
if (interfaceName != interface()) {
return;
}
QMapIterator< QString, QVariant > i{changedProperties};
while (i.hasNext()) {
propertyChanged(i.next().key());
}
for (QString const & invalidatedProperty : invalidatedProperties) {
propertyChanged(invalidatedProperty);
}
}
Q_SIGNALS :
void CanNTPChanged();
void LocalRTCChanged();
void NTPChanged();
void NTPSynchronizedChanged();
void RTCTimeUSecChanged();
void TimeUSecChanged();
void TimezoneChanged();
private :
bool CanNTP;
bool LocalRTC;
bool NTP;
bool NTPSynchronized;
qulonglong RTCTimeUSec;
qulonglong TimeUSec;
QString Timezone;
};
On my system qdbus --system org.freedesktop.timedate1 /org/freedesktop/timedate1 gives:
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
method QString org.freedesktop.DBus.Introspectable.Introspect()
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface, QVariantMap changed_properties, QStringList invalidated_properties)
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface, QString property)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface)
method void org.freedesktop.DBus.Properties.Set(QString interface, QString property, QDBusVariant value)
property read bool org.freedesktop.timedate1.CanNTP
property read bool org.freedesktop.timedate1.LocalRTC
property read bool org.freedesktop.timedate1.NTP
property read bool org.freedesktop.timedate1.NTPSynchronized
property read qulonglong org.freedesktop.timedate1.RTCTimeUSec
property read qulonglong org.freedesktop.timedate1.TimeUSec
property read QString org.freedesktop.timedate1.Timezone
method void org.freedesktop.timedate1.SetLocalRTC(bool, bool, bool)
method void org.freedesktop.timedate1.SetNTP(bool, bool)
method void org.freedesktop.timedate1.SetTime(qlonglong, bool, bool)
method void org.freedesktop.timedate1.SetTimezone(QString, bool)
I consider it is a natural to use PropertiesChanged D-Bus signal to emit somePropertyChanged signals per property. It just connected to propertiesChanged dispatcher slot. I need it to emit somePropertyChanged signals from QML-side singletone (which, in turn, just translates D-Bus properties names to QML properties names (decapitalizing, conversion from usecs since Epoch to QDateTime etc)).
There is requirements in Qt Property System to NOTIFY signal prototype:
NOTIFY signals for MEMBER variables must take zero or one parameter, which must be of the same type as the property.
In QML-side handler of somePropertyChanged signal:
Connections {
target: CppSingleton
onSomePropertyChanged: {
if (someProperty) {
//
}
}
}
symbol someProperty is accessible only if somePropertyChanged signal is declared unary, not nullary. It is handy to have someProperty accessible in signal handler.
When I dispatch PropertiesChanged D-Bus signal, I have a name of a property. It allows to create signal's prototype string "somePropertyChanged()" to get corresponding QObject's method index. Also I have a new value for the property changed. But it is enclosed into QVariant. QMetaMethod::invoke, in turn, accepts Q_ARG values. Therefore I have to use only nullary *Changed signals.
Under the hood Q_ARG is just a QArgument: a pair of typename (const char *) and type-erased value (void *). Evidently it is variant-concept-like thing. I want to convert QVariant to QGenericArgument. Is it possible in general case?
QVariant has similar to QGenericArgument's constructor: QVariant::QVariant(int typeId, const void *copy). But I want to extract both these values. I think it is enough (with aid of some Qt's RTTI, e.g. QVariant's const char *typeToName(int typeId)) to achieve desired.
ADDITIONAL:
There are undocumented methods in QVariant:
void *data();
const void *constData() const;
inline const void *data() const { return constData(); }
Maybe I can use them. But problem of ownership is arose in the case.
ADDITIONAL 2:
Seems QGenericArgument is non-owning being. In conjunction with Qt::DirectConnection in-place-like method call I can use QVariant::data to get acces to QVaraint's internals.
ADDITIONAL 3:
QDBus has a mess with QDBusVariant/QVariant and QDBusArgument internal conversions/representation. It seems unpredictable which "typeinfo" for each property is correctly extracted. (It can be solved using undocumented qdbus_cast< T >(const QVariant &) cast).

boost::asio and multiple client connections using asynch

I need to establish up to three different TCP connections to different servers. All three connections requiring different protocols, different handshakes and different heartbeats. Studying http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp, reading stuff here and following Chris Kohlhoffs advices I tried to implement it as below.
The problem is that with this architecture I'm getting a bad_weak_pointer exception at calling shared_from_this() in doConnect() no matter what I'm doing.
Importent These are just snippets of a not running code, which can contain bugs! Importent
I'm having a base class which is containing some basic methods.
Connection.h
class Connection : public std::enable_shared_from_this<Connection>
{
public:
//! Ctor
inline Connection();
//! Dtor
inline virtual ~Connection();
inline void setReconnectTime(const long &reconnectAfterMilisec)
{
m_reconnectTime = boost::posix_time::milliseconds(reconnectAfterMilisec);
}
inline void setHandshakePeriod(const long &periodInMilisec)
{
m_handshakePeriod = boost::posix_time::milliseconds(periodInMilisec);
}
virtual void doConnect() = 0;
virtual void stop() = 0;
//... and some view more...
}
I have then my three classes which are derived from the base class. Here just one (and also the core part) to depict the approach.
ConnectionA.h
//queues which containing also the age of the messages
typedef std::deque<std::pair<handshakeMsg, boost::posix_time::ptime>> handskMsg_queue;
typedef std::deque<std::pair<errorcodeMsg, boost::posix_time::ptime>> ecMsg_queue;
typedef std::deque<std::pair<A_Msg, boost::posix_time::ptime>> A_Msg_queue;
class ConnectionA : public Connection
{
public:
ConnectionA();
ConnectionA(const std::string& IP, const int &port);
ConnectionA& operator=(const ConnectionA &other);
virtual ~ConnectionA();
virtual void stop() override;
virtual void doConnect() override;
void doPost(std::string &message);
void doHandshake();
void sendErrorCode(const int &ec);
std::shared_ptr<boost::asio::io_service>m_ioS;
private:
std::shared_ptr<tcp::socket> m_socket;
std::shared_ptr<boost::asio::deadline_timer> m_deadlineTimer; // for reconnetions
std::shared_ptr<boost::asio::deadline_timer> m_handshakeTimer; // for heartbeats
void deadlineTimer_handler(const boost::system::error_code& error);
void handshakeTimer_handler(const boost::system::error_code& error);
void doRead();
void doWrite();
std::string m_IP;
int m_port;
handskMsg_queue m_handskMsgQueue;
ecMsg_queue m_ecMsgQueue;
A_Msg_queue m_AMsgQueue;
}
ConnectionA.cpp
ConnectionA::ConnectionA(const std::string &IP, const int &port)
: m_ioS()
, m_socket()
, m_deadlineTimer()
, m_handshakeTimer()
, m_IP(IP)
, m_port(port)
, m_handskMsgQueue(10)
, m_ecMsgQueue(10)
, m_AMsgQueue(10)
{
m_ioS = std::make_shared<boost::asio::io_service>();
m_socket = std::make_shared<tcp::socket>(*m_ioS);
m_deadlineTimer = std::make_shared<boost::asio::deadline_timer>(*m_ioS);
m_handshakeTimer = std::make_shared<boost::asio::deadline_timer> (*m_ioS);
m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
m_handshakeTimer->async_wait(boost::bind(&ConnectionA::handshakeTimer_handler, this, boost::asio::placeholders::error));
}
ConnectionA::~ConnectionA()
{}
void ConnectionA::stop()
{
m_ioS->post([this]() { m_socket->close(); });
m_deadlineTimer->cancel();
m_handshakeTimer->cancel();
}
void ConnectionA::doConnect()
{
if (m_socket->is_open()){
return;
}
tcp::resolver resolver(*m_ioS);
std::string portAsString = std::to_string(m_port);
auto endpoint_iter = resolver.resolve({ m_IP.c_str(), portAsString.c_str() });
m_deadlineTimer->expires_from_now(m_reconnectTime);
// this gives me a bad_weak_pointer exception!!!
auto self = std::static_pointer_cast<ConnectionA>(static_cast<ConnectionA*>(this)->shared_from_this());
boost::asio::async_connect(*m_socket, endpoint_iter, [this, self](boost::system::error_code ec, tcp::resolver::iterator){
if (!ec)
{
doHandshake();
doRead();
}
else {
// don't know if async_connect can fail but set the socket to open
if (m_socket->is_open()){
m_socket->close();
}
}
});
}
void ConnectionA::doRead()
{
auto self(shared_from_this());
boost::asio::async_read(*m_socket,
boost::asio::buffer(m_readBuf, m_readBufSize),
[this, self](boost::system::error_code ec, std::size_t){
if(!ec){
// check server answer for errors
}
doRead();
}
else {
stop();
}
});
}
void ConnectionA::doPost(std::string &message)
{
A_Msg newMsg (message);
auto self(shared_from_this());
m_ioS->post([this, self, newMsg](){
bool writeInProgress = false;
if (!m_A_MsgQueue.empty()){
writeInProgress = true;
}
boost::posix_time::ptime currentTime = time_traits_t::now();
m_AMsgQueue.push_back(std::make_pair(newMsg,currentTime));
if (!writeInProgress)
{
doWrite();
}
});
}
void ConnectionA::doWrite()
{
while (!m_AMsgQueue.empty())
{
if (m_AMsgQueue.front().second + m_maxMsgAge < time_traits_t::now()){
m_AMsgQueue.pop_front();
continue;
}
if (!m_socket->is_open()){
continue;
}
auto self(shared_from_this());
boost::asio::async_write(*m_socket,
boost::asio::buffer(m_AMsgQueue.front().first.data(),
m_AMsgQueue.front().first.A_lenght),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec) // successful
{
m_handshakeTimer->expires_from_now(m_handshakePeriod); // reset timer
m_AMsgQueue.pop_front();
doWrite();
}
else {
if (m_socket->is_open()){
m_socket->close();
}
}
});
}
}
void ConnectionA::deadlineTimer_handler(const boost::system::error_code& error){
if (m_stopped){
return;
}
m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
if (!error && !m_socket->is_open()) // timer expired and no connection was established
{
doConnect();
}
else if (!error && m_socket->is_open()){ // timer expired and connection was established
m_deadlineTimer->expires_at(boost::posix_time::pos_infin); // to reactivate timer call doConnect()
}
}
And finally there is also another class which encapsulate these classes make it more comfortable to use:
TcpConnect.h
class CTcpConnect
{
public:
/*! Ctor
*/
CTcpConnect();
//! Dtor
~CTcpConnect();
void initConnectionA(std::string &IP, const int &port);
void initConnectionB(std::string &IP, const int &port);
void initConnectionC(std::string &IP, const int &port);
void postMessageA(std::string &message);
void run();
void stop();
private:
ConnectionA m_AConnection;
ConnectionB m_BConnection;
ConnectionC m_CConnection;
}
TcpConnect.cpp
CTcpConnect::CTcpConnect()
: m_AConnection()
, m_BConnection()
, m_CConnection()
{}
CTcpConnect::~CTcpConnect()
{}
void CTcpConnect::run(){
[this](){ m_AConnection.m_ioS->run(); };
[this](){ m_BConnection.m_ioS->run(); };
[this](){ m_CConnection.m_ioS->run(); };
}
void CTcpConnect::stop(){
m_AConnection.stop();
m_BConnection.stop();
m_CConnection.stop();
}
void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
m_AConnection = ConnectionA(IP, port);
m_AConnection.setMaxMsgAge(30000);
//... set some view parameter more
m_AConnection.doConnect();
}
// initConnectionB & initConnectionC are quite the same
void CTcpConnect::postMessageA(std::string &message)
{
m_AConnection.doWrite(message);
}
In the beginning I tried also to have only one io_service (for my approach this would be fine), but holding the service just as reference gave some headache, because my implementation requires also a default constructor for the connections. Now each connection has its own io-service.
Any ideas how I can bring this code to run?
Feel free to make suggestion for other architectures. If you could came up this some snippets would be even the better. I'm struggling with this implementation for weeks already. I'm grateful for every hint.
BTW I'm using boost 1.61 with VS12.
This is the problem:
m_AConnection = ConnectionA(IP, port);
That is, ConnectionA derives from Connection which derives from enable_shared_from_this. That means that ConnectionA must be instantiated as a shared pointer for shared_from_this to work.
Try this:
void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
m_AConnection = std::make_shared<ConnectionA>(IP, port);
m_AConnection->setMaxMsgAge(30000);
//... set some view parameter more
m_AConnection->doConnect();
}
EDIT1:
You are right. That was the issue. Now I realised that the way I'm calling io-service.run() is total crap.
It is very uncommon to use more than one io_service, and extremely uncommon to use one per connection :)
However, do you know if I need the cast then calling shared_from_this()? I noticed the asynch_connect() works fine with and without the cast.
Many Asio examples use shared_from_this() for convenience, I for example don't use it in my projects at all. There are certain rules that you need to be careful when working with Asio. For example, one is that the reading and writing buffers must not be destructed before the corresponding callback is executed, if the lambda function captures a shared pointer to the object that holds the buffers, this condition holds trivially.
You could for example do something like this as well:
auto data = std::make_shared<std::vector<uint8_t>>(10);
async_read(socket,
boost::asio::const_buffer(*data),
[data](boost::system::error_code, size_t) {});
It would be valid but would have the performance drawback that you'd be allocating a new data inside std::vector on each read.
Another reason why shared_from_this() is useful can be seen when you look at some some of your lambdas, they often have the form:
[this, self,...](...) {...}
That is, you very often want to use this inside them. If you did not capture self as well, you'd need to use other measures to make sure this has not been destroyed when the handler is invoked.

Command Pattern: Where to create the Command items?

I've used the command pattern quite extensively, and it works well. However, what's usually not discussed is where the instances of the Commands are created.
The following examples illustrate this issue: A Document has a function setText() that sets the text:
class Document {
public:
void setText(const std::string text) {
if (commandManager()->isActive()) {
// called by SetTextCommand
m_text = text;
} else {
// called somewhere in the application
commandManager()->addAndExecute(new SetTextCommand(this, text));
}
}
std::string text() const { return m_text; }
CommandManager * commandManager() const { return m_commandManager; }
private:
std::string m_text;
CommandManager * m_commandManager;
}
Here, the SetTextCommand would execute document->setText(text) like this:
class SetTextCommand : public Command {
public:
SetTextCommand(Document * doc, const std::string & text)
: Command(), m_doc(doc), m_oldText(doc->text()), m_text(text)
{}
void redo() override {
m_doc->setText(m_text);
}
void undo() override {
m_doc->setText(m_oldText, false);
}
}
The SetTextCommand is processed by the CommandManager like this:
CommandManager::addAndExecute(Command * command) {
m_doc->commandManager()->setActive(true); // THIS IS THE TRICK
command->redo();
m_doc->commandManager()->setActive(false); // THIS IS THE TRICK
m_stack->push_back(command);
}
The trick here is, that when running redo(), CommandManager::isActive() is set to true. Hence, Document::setText() will set m_text.
Obviously, all Document setter functions must follow the if (commandManager()->isActive()) { ... } else { ... } paradigm. This is, because the Commands themselves are created in the setter functions.
Question is now: Is this a good way to implement the command pattern? Or are there far cleaner solutions for creating the Commands while at the same time having a nice API?
Please be verbose with your answers.
I think it'd be pretty ugly to have to replicate the if (commandManager()->isActive()) everywhere... probably nicer to have setText always do the SetTextCommand path, and create a new setTextImmediate method which SetTextCommand can use.

Avoid dynamic_cast with derived classes (Cast Derived class)

I am new to C++ and came to a point, where I generate an overhead with classes. I have a QTcpSocket and read messages from it and create objects, for example MessageJoin, MessagePart, MessageUserData etc. I send these objects to my client and display them (+ do some UI updating).
Now here comes my problem. I tested a few design techniques but all of them are not that nice:
Pass each parameter of a message object in a signal/slot connection to the client - small overhead but not that good-looking
Create a method for each Message-Type (messageJoinReceived, messageNoticeReceived etc.)
Create one method and use dynamic_cast to cast für each class and test it
For a better understanding, I added my dynamic_cast version. As a said, the code looks ugly and unusable. My questions are:
Is there a better way to do it with (a) dynamic_cast
Is there another way (For example a design pattern) to solve such a problem ? maybe add a method in the classes and return the type or something like this
I read about the visitor pattern. This pattern is just for dynamic object types in Getter/Setter methods ?
A few side notes
I can use RTTI
Speed isn't a big deal. Clean and understandable code is more important
I use Qt and have the possiblity to use qobject_cast and signal/slots
Here is my code (Pastebin-Link):
// Default class - contains the complete message (untouched)
class Message
{
public:
QString virtual getRawMessage() { return dataRawMessage; }
protected:
QString dataRawMessage;
};
// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
{
dataRawMessage = rawmessage;
dataChannel = channel;
dataUser = user;
}
QString getChannel() { return dataChannel; }
QString getUser(){ return dataUser; }
private:
QString dataChannel;
QString dataUser;
};
// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
MessageNotice(const QString &rawmessage, const QString &text)
{
dataRawMessage = rawmessage;
dataText = text;
}
QString getText() { return dataText;}
private:
QString dataText;
};
// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
if(message)
{
MessageJoin *messagejoin;
MessagePart *messagepart;
MessageNotice *messagenotice;
if((messagejoin = dynamic_cast<MessageJoin *>(message)) != 0)
{
qDebug() << messagejoin->getUser() << " joined " << messagejoin->getChannel();
// Update UI: Add user
}
else if((messagenotice = dynamic_cast<MessageNotice *>(message)) != 0)
{
qDebug() << messagenotice->getText();
// Update UI: Display message
}
else
{
qDebug() << "Cannot cast message object";
}
delete message; // Message was allocated in the library and is not used anymore
}
}
This looks quite similar to the expression problem and AFAIK there is no way to avoid casts if you are going to add new messages and new ways to handle them. However it's not that hard to make more eye pleasing wrap for necessary run-time stuff. Just create a map from message type to corresponding handler using typeid.
#include <functional>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
typedef std::function<void(Message *)> handler_t;
typedef std::unordered_map<
std::type_index,
handler_t> handlers_map_t;
template <class T, class HandlerType>
handler_t make_handler(HandlerType handler)
{
return [=] (Message *message) { handler(static_cast<T *>(message)); };
}
template <class T, class HandlerType>
void register_handler(
handlers_map_t &handlers_map,
HandlerType handler)
{
handlers_map[typeid(T)] = make_handler<T>(handler);
}
void handle(handlers_map_t const &handlers_map, Base *message)
{
handlers_map_t::const_iterator i = handlers_map.find(typeid(*message));
if (i != handlers_map.end())
{
(i->second)(message);
}
else
{
qDebug() << "Cannot handle message object";
}
}
Then register handlers for specific message types:
handlers_map_t handlers_map;
register_handler<MessageJoin>(
handlers_map,
[] (MessageJoin *message)
{
qDebug() << message->getUser() << " joined " << message->getChannel();
// Update UI: Add user
});
register_handler<MessageNotice>(
handlers_map,
[] (MessageNotice *message)
{
qDebug() << message->getText();
// Update UI: Display message
});
And now you can handle messages:
// simple test
Message* messages[] =
{
new MessageJoin(...),
new MessageNotice(...),
new MessageNotice(...),
new MessagePart(...),
};
for (auto m: messages)
{
handle(handlers_map, m);
delete m;
}
Surely you might want to make some improvements like wrapping handlers stuff into reusable class, using QT or boost signals/slots so you can have multiple handlers for a single message, but the core idea is the same.
The visitor pattern could be a good fit i.e.
class Message
{
public:
QString virtual getRawMessage() { return dataRawMessage; }
virtual void accept(Client& visitor) = 0;
protected:
QString dataRawMessage;
};
// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
{
dataRawMessage = rawmessage;
dataChannel = channel;
dataUser = user;
}
QString getChannel() { return dataChannel; }
QString getUser(){ return dataUser; }
void accept(Client& visitor) override
{
visitor.visit(*this);
}
private:
QString dataChannel;
QString dataUser;
};
// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
MessageNotice(const QString &rawmessage, const QString &text)
{
dataRawMessage = rawmessage;
dataText = text;
}
QString getText() { return dataText;}
void accept(Client& visitor) override
{
visitor.visit(*this);
}
private:
QString dataText;
};
void Client::visit(MessageJoin& msg)
{
qDebug() << msg.getUser() << " joined " << msg.getChannel();
// Update UI: Add user
}
void Client::visit(MessageNotice& msg)
{
qDebug() << msg.getText();
// Update UI: Display message
}
// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
if(message)
{
message->visit(this);
delete message; // Message was allocated in the library and is not used anymore
}
}
A better design might be to have an abstract virtual function in the Message class, called process or onReceive or similar, the sub-classes implements this function. Then in Client::messageReceived just call this function:
message->onReceive(...);
No need to for the dynamic_cast.
I would also recommend you to look into smart pointers, such as std::unique_ptr.
If you have private data in the Client class that is needed for the message processing functions, then there are many methods of solving that:
The simplest is to use a plain "getter" function in the client:
class Client
{
public:
const QList<QString>& getList() const { return listContainingUiRelatedStuff; }
// Add non-const version if you need to modify the list
};
If you just want add items to the list in your example, then add a function for that:
void addStringToList(const QString& str)
{ listContainingUiRelatedStuff.push_back(str); }
Or the non-recommended variant, make Client a friend in all message classes.
The second variant is what I recommend. For example, if you have a list of all connected clients and want to send a message to all of them, then create a function sendAll that does it.
The big idea here is to try and minimize the coupling and dependencies between your classes. The less coupling there is, the easier it will be to modify one or the other, or add new message classes, or even completely rewrite one or the other of the involved classes without it affecting the other classes. This is why we split code into interface and implementation and data hiding.