来自 Qt 中 main.cpp 代码的 MainWindow

Posted

技术标签:

【中文标题】来自 Qt 中 main.cpp 代码的 MainWindow【英文标题】:MainWindow from code from the main.cpp in Qt 【发布时间】:2016-09-22 15:47:48 【问题描述】:

想了解MainWindowmain.cpp 之间的代码区别。具体来说,专门编写在main.cpp 中的代码块需要如何修改为mainwindow.cppmainwindow.h 的一部分。

作为一个例子,我正在尝试修改这个罚款answer 中的代码以在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();

编辑

在同一答案中添加了计时器部分;请演示这个带有计时器的版本是如何完成的

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

我很难将networkManager 修改为类成员,如何构造代码以及如何替换 lambda 函数。

如果有人可以为我提供所有必需的修改以更好地理解,那就太好了。

【问题讨论】:

如答案中所述,一个选项是在堆上分配networkManager 对象,并将指向它的指针作为MainWindow 类中的成员保留。这样做会遇到什么问题? @Mike 我是 C++ 和 Qt 的新手,所以这些步骤中的每一个似乎都令人生畏。我尝试在头文件中定义networkManager,然后尝试在MainWindow.cpp 中创建它的新对象,但不知道如何继续并调用它。得到许多错误。同样对于 lambda,我找不到任何在线资源来指导我。如果您能再次节省时间并为我提供代码供我学习,我将不胜感激。 【参考方案1】:

您可能希望将用户界面和控制器(业务逻辑)分离到单独的类中。

main() 的主体实例化了 ui 和控制器并将它们连接起来。每 5 秒获取新结果的计时器。计时器也可以滚动到 Controller 中 - 我将它作为一个示例将其分离出来,作为在不修改现有类的情况下添加功能的示例。

main.cpp

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

int main(int argc, char *argv[])

   QApplication appargc, 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();

控制器对用户界面一无所知,只处理请求。每次开始处理请求时,它都会发出一个busy 信号。

如果您想为多个活动请求提供更好的反馈,则仅在没有待处理请求并添加新请求时才需要发出 busy 信号,而当上一个请求已完成,没有更多待处理的请求。

控制器.h

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QtNetwork>

class Controller : public QObject 
   Q_OBJECT
   QNetworkAccessManager managerthis;
   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);

用户界面对任何业务逻辑一无所知,它提供的 API 足以让业务逻辑使用它。它可以处于以下三种状态之一:Normal 结果可见的状态、Loading 显示忙碌反馈的状态和Error 显示错误信息的状态。 setFields 槽将状态返回到Normal

主窗口.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtWidgets>

class MainWindow : public QWidget 
  Q_OBJECT
  QFormLayout layoutthis;
  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

主窗口.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);
   

【讨论】:

太好了,我现在看到了我的大部分缺点 + 我看到了分离设计的重要性。您是否还可以添加带有计时器的版本,因为它可以在其他答案的编辑(***.com/a/39521698)中看到?尝试按照您的示例进行操作,但不确定我是否必须为其创建一个新的私人插槽.. @nk-fford 如果您在MainWindow 类中创建计时器,您可以将计时器的QTimer::timeout 信号连接到MainWindow::request 信号。 @thuga 能否请您显示代码(也许编辑答案)以说明您的意思,因为我尝试了这个但我得到但到处都是错误.. 我是 qt noob @nk-fford 哪一部分让您感到困惑?将信号连接到信号?启动计时器?您将其与按钮完全相同,只需使用计时器对象和QTimer::timeout 信号即可。 @thuga 因此,在 mainwindow.h 中,我将按钮的专用插槽替换为 onTimeout。在connect 之前的mainwindow.cpp 中,我创建了一个QTimer 对象并启动它,然后我使用该对象定义connect(&amp;timer,&amp;QTimer::timeout,this,&amp;MainWindow::onTimeout);。最后,我再次用 onTimeout 东西替换了最后的按钮槽定义。但我没有得到任何数据。 (为了简单起见,我使用了 froggato 的版本)。请帮助我迷路了【参考方案2】:

您可以将所有这些代码放入您的 QMainWindow 的构造函数中,并按原样保留 lambda 函数。

另一种更简洁的方法是将这些 lambda 函数转换为私有插槽。使用这种方式,您应该将networkManager 定义为QMainWindow 类的类成员,并且它应该分配在堆内存而不是堆栈中。为此,只需定义一个 QNetworkManager* 类成员并在 QMainWindow 构造函数中对其进行初始化。

this->networkManager = new QNetworkManager(this);

一旦它被初始化,你就可以在你的QMainWindow类的所有槽中使用它。

一个简单的经验法则是:lambda 函数和主作用域之间的所有共享变量都应该是这样的类成员。


代码。 (我测试过,效果很好)

ma​​in.cpp

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

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();

ma​​inwindow.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. . .");

ma​​inwindow.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

【讨论】:

您能否说得更清楚一点,因为我是这一切的新手?例如,我将所有内容(除了main 定义和QApplication a(argc, argv))都放在mainwindow.cppui-&gt;setupUi(this); 行下。我得到了很多错误..请你显示代码供我学习吗? @nk-fford 好的。我将在这里编写整个代码,供您学习如何编码。告诉我你知道如何在 Designer 中设计 UI 吗? 我很感激。是的,我可以在设计器中制作 GUI。 networkManager 对象正在泄漏,请使用networkManager = new QNetworkAccessManager(this); 对其进行实例化,以便在MainWindow 被破坏时将其删除。此外,readOnly 属性最好在设计器中完成。否则,我认为应该是这样,谢谢@HiImFrogatto。 @nk-fford 请接受 Kuba 的回答,它比我的要好。总是尝试将东西分成单一职责的类。 Single responsibility principle.

以上是关于来自 Qt 中 main.cpp 代码的 MainWindow的主要内容,如果未能解决你的问题,请参考以下文章

如何在 qt creator 的 main.cpp 中有一个计时器,以便在单击按钮时更新 GUI?

Ubuntu中Qt+opencv图像显示

QT第一个QT程序

qml-main.cpp中的两种启动Qt Quick App模式

main.cpp 和 mainwindow.cpp 之间的交互

qt 创建完工程运行main.cpp错误提示:-1: error: Cannot run compiler 'cl'. Output解决办法