MainWindow from code from the main.cpp in Qt

2019-01-29 04:20发布

Want to understand the difference in code between the MainWindow and the main.cpp. Specifically, how a chunk of code written exclusively in the main.cpp needs to be modified to be part of the mainwindow.cpp and mainwindow.h.

As an example, I am trying to modify the code from this fine answer to work in MainWindow.

main.cpp

#include <QtWidgets>
#include <QtNetwork>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //setup GUI (you could be doing this in the designer)
    QWidget widget;
    QFormLayout layout(&widget);
    QLineEdit lineEditName;
    QLineEdit lineEditGender;
    QLineEdit lineEditRegion;
    auto edits = {&lineEditName, &lineEditGender, &lineEditRegion};
    for(auto edit : edits) edit->setReadOnly(true);
    layout.addRow("Name:", &lineEditName);
    layout.addRow("Gender:", &lineEditGender);
    layout.addRow("Region:", &lineEditRegion);
    QPushButton button("Get Name");
    layout.addRow(&button);

    //send request to uinames API
    QNetworkAccessManager networkManager;
    QObject::connect(&networkManager, &QNetworkAccessManager::finished,
                 [&](QNetworkReply* reply){
        //this lambda is called when the reply is received
        //it can be a slot in your GUI window class
        //check for errors
        if(reply->error() != QNetworkReply::NoError){
            for(auto edit : edits) edit->setText("Error");
            networkManager.clearAccessCache();
        } else {
            //parse the reply JSON and display result in the UI
            QJsonObject jsonObject=     QJsonDocument::fromJson(reply->readAll()).object();
            QString fullName= jsonObject["name"].toString();
            fullName.append(" ");
            fullName.append(jsonObject["surname"].toString());
            lineEditName.setText(fullName);
            lineEditGender.setText(jsonObject["gender"].toString());
            lineEditRegion.setText(jsonObject["region"].toString());
        }
        button.setEnabled(true);
        reply->deleteLater();
        });
     //url parameters
    QUrlQuery query;
    query.addQueryItem("amount", "1");
    query.addQueryItem("region", "United States");
    QUrl url("http://uinames.com/api/");
    url.setQuery(query);
    QNetworkRequest networkRequest(url);
    //send GET request when the button is clicked
    QObject::connect(&button, &QPushButton::clicked, [&](){
        networkManager.get(networkRequest);
        button.setEnabled(false);
        for(auto edit : edits) edit->setText("Loading. . .");
    });

    widget.show();
    return a.exec();
}

edit

added the timer part of the same answer; please demonstrate how this version with the timer can done as well

QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&](){
    networkManager.get(networkRequest);
    button.setEnabled(false);
    for(auto edit : edits) edit->setText("Loading. . .");
});
timer.start(60000); //60000 msecs = 60 secs

I struggle with modifying the networkManager as class members, how to structure the code and how to replace the lambda functions.

If someone can provide all the required modifications for me to gain a better understanding that would be great.

2条回答
放荡不羁爱自由
2楼-- · 2019-01-29 04:51

You would want to separate out the user interface and the controller (business logic) into separate classes.

The body of main() instantiates the ui and the controller and connects them. A timer that fetches new results every 5 seconds. The timer could be rolled into the Controller, too - I show it separated out as an example of adding functionality to an existing class without modifying it.

main.cpp

// https://github.com/KubaO/stackoverflown/tree/master/questions/into-mainwin-39643510
#include "mainwindow.h"
#include "controller.h"

int main(int argc, char *argv[])
{
   QApplication app{argc, argv};
   MainWindow ui;
   Controller ctl;
   QTimer timer;
   timer.start(5*1000);
   QObject::connect(&timer, &QTimer::timeout, &ctl, &Controller::get);

   QObject::connect(&ctl, &Controller::busy, &ui, [&]{ ui.setState(MainWindow::Loading); });
   QObject::connect(&ui, &MainWindow::request, &ctl, &Controller::get);
   QObject::connect(&ctl, &Controller::error, &ui, [&]{ ui.setState(MainWindow::Error); });
   QObject::connect(&ctl, &Controller::values, &ui, &MainWindow::setFields);
   ui.show();
   return app.exec();
}

The controller knows nothing of the user interface, and deals with processing the requests only. It emits a busy signal every time a request starts being processed.

If you wanted to provide better feedback for multiple active requests, the busy signal would need to be emitted only when there were no requests pending and a new one is added, and an idle signal would be emitted when the last request has finished and there are no more pending ones.

controller.h

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QtNetwork>

class Controller : public QObject {
   Q_OBJECT
   QNetworkAccessManager manager{this};
   QNetworkRequest request;
   Q_SLOT void onReply(QNetworkReply *);
public:
   explicit Controller(QObject * parent = nullptr);
   Q_SLOT void get();
   Q_SIGNAL void busy();
   Q_SIGNAL void error(const QString &);
   Q_SIGNAL void values(const QString & name, const QString & gender, const QString & region);
};

#endif // CONTROLLER_H

controller.cpp

#include "controller.h"

Controller::Controller(QObject *parent) : QObject(parent)
{
   QUrlQuery query;
   query.addQueryItem("amount", "1");
   query.addQueryItem("region", "United States");
   QUrl url("http://uinames.com/api/");
   url.setQuery(query);
   request = QNetworkRequest(url);
   connect(&manager, &QNetworkAccessManager::finished, this, &Controller::onReply);
}

void Controller::onReply(QNetworkReply * reply) {
   if (reply->error() != QNetworkReply::NoError) {
      emit error(reply->errorString());
      manager.clearAccessCache();
   } else {
      //parse the reply JSON and display result in the UI
      auto jsonObject = QJsonDocument::fromJson(reply->readAll()).object();
      auto fullName = jsonObject["name"].toString();
      fullName.append(" ");
      fullName.append(jsonObject["surname"].toString());
      emit values(fullName, jsonObject["gender"].toString(), jsonObject["region"].toString());
   }
   reply->deleteLater();
}

void Controller::get() {
   emit busy();
   manager.get(request);
}

The user interface knows nothing of any business logic, it provides an API that's sufficient for the business logic to use it. It can be in one of three states: Normal state where results are visible, Loading state where a busy feedback is shown, and Error state where error information is shown. The setFields slot returns the state to Normal.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtWidgets>

class MainWindow : public QWidget {
  Q_OBJECT
  QFormLayout layout{this};
  QLineEdit lineEditName;
  QLineEdit lineEditGender;
  QLineEdit lineEditRegion;
  QPushButton button{"Get Name"};
  QLineEdit * edits[3] = {&lineEditName, &lineEditGender, &lineEditRegion};
public:
  enum State { Normal, Loading, Error };
  explicit MainWindow(QWidget * parent = nullptr);
  Q_SLOT void setFields(const QString & name, const QString & gender, const QString & region);
  Q_SLOT void setState(State);
  Q_SIGNAL void request();
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
{
   for(auto edit : edits) edit->setReadOnly(true);
   layout.addRow("Name:", &lineEditName);
   layout.addRow("Gender:", &lineEditGender);
   layout.addRow("Region:", &lineEditRegion);
   layout.addRow(&button);
   connect(&button, &QPushButton::clicked, this, &MainWindow::request);
}

void MainWindow::setFields(const QString & name, const QString & gender, const QString & region) {
   setState(Normal);
   lineEditName.setText(name);
   lineEditGender.setText(gender);
   lineEditRegion.setText(region);
}

void MainWindow::setState(MainWindow::State state) {
   if (state == Normal) {
      for (auto edit : edits) edit->setEnabled(true);
      button.setEnabled(true);
   }
   else if (state == Loading) {
      for (auto edit : edits) edit->setEnabled(false);
      button.setEnabled(false);
   }
   else if (state == Error) {
      for (auto edit : edits) edit->setText("Error...");
      button.setEnabled(true);
   }
}
查看更多
Evening l夕情丶
3楼-- · 2019-01-29 05:02

You can put all of this code in constructor of your QMainWindow and retaining lambda functions as-is.

Another more clean way would be transforming those lambda functions into private slots. Using this way, you should define networkManager as a class member of your QMainWindow class and also it should be allocated in heap memory not stack. To get this done, just define a QNetworkManager* class member and initialize it in your QMainWindow constructor.

this->networkManager = new QNetworkManager(this);

Once it's been initialized, you can use it in all slots of your QMainWindow class.

A simple rule of thumb is: all shared variables among lambda functions and the main scope should be class members in this way.


The code. (I tested it and works fine)

main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ui->lineEditGender->setReadOnly(true);
    ui->lineEditRegion->setReadOnly(true);
    ui->lineEditName->setReadOnly(true);

    networkManager = new QNetworkAccessManager(this);

    connect(networkManager, &QNetworkAccessManager::finished, this, &MainWindow::onNetworkManagerFinished);
    connect(ui->btnGetName, &QPushButton::clicked, this, &MainWindow::onBtnGetNameClicked);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onNetworkManagerFinished(QNetworkReply *reply)
{
    if(reply->error() != QNetworkReply::NoError){
        ui->lineEditName->setText("Error");
        ui->lineEditGender->setText("Error");
        ui->lineEditRegion->setText("Error");

        networkManager->clearAccessCache();
    } else {
        //parse the reply JSON and display result in the UI
        QJsonObject jsonObject = QJsonDocument::fromJson(reply->readAll()).object();
        QString fullName= jsonObject["name"].toString();
        fullName.append(" ");
        fullName.append(jsonObject["surname"].toString());
        ui->lineEditName->setText(fullName);
        ui->lineEditGender->setText(jsonObject["gender"].toString());
        ui->lineEditRegion->setText(jsonObject["region"].toString());
    }
    ui->btnGetName->setEnabled(true);
    reply->deleteLater();
}

void MainWindow::onBtnGetNameClicked()
{
    QUrlQuery query;
    query.addQueryItem("amount", "1");
    query.addQueryItem("region", "United States");
    QUrl url("http://uinames.com/api/");
    url.setQuery(query);
    QNetworkRequest networkRequest(url);

    //send GET request when the button is clicked
    networkManager->get(networkRequest);
    ui->btnGetName->setEnabled(false);

    ui->lineEditName->setText("Loading. . .");
    ui->lineEditGender->setText("Loading. . .");
    ui->lineEditRegion->setText("Loading. . .");
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void onNetworkManagerFinished(QNetworkReply* reply);
    void onBtnGetNameClicked();

private:
    Ui::MainWindow *ui;
    QNetworkAccessManager *networkManager;
};

#endif // MAINWINDOW_H
查看更多
登录 后发表回答