今天在使用QT官方提供的函数qInstallMessageHandler输出日志时,因为这个函数的参数需要传的是一个函数,当这个函数是成员函数的时候需要是一个静态函数。但同时当我又需要在这个静态函数中发送消息,这时候就出现问题了,在静态函数中发送信号,会报错:非静态成员函数的非法调用。
但同时我们的信号又不能被声明为静态的,这时候我们就需要在静态函数中发送信号,我这里使用的方法就是新建一个类,用静态函数发送信号,用新建的类接收,然后再将这个信号发送出去。
虽然信号不能被声明为静态的,但是类的对象可以是静态的,那我们就可以在静态函数中发射一个静态对象的信号。
代码示例
Tool类中静态函数发送信号,由MainWindow接收
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
// 工具类,用于转发静态信号
class Tool : public QObject
{
Q_OBJECT
public:
explicit Tool(QObject *parent = nullptr);
static void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
private:
static Tool *mytool; // 定义一个静态ToolA类
signals:
void SigDeliverMess(QString message); // 真正发出去的信号
void SigDeliverMessStatic(QString message); // 内部信号 用于静态函数调用
private slots:
void SlotDeliverMessStatic(QString message); // 内部槽 用于响应内部信号
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_clicked();
void ShowDebugContent(const QString &text);
private:
Ui::MainWindow *ui;
Tool *m_tool;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_tool = new Tool();
connect(m_tool,&Tool::SigDeliverMess, this, &MainWindow::ShowDebugContent);
qInstallMessageHandler(m_tool->myMessageOutput);
}
MainWindow::~MainWindow()
{
delete ui;
delete m_tool;
}
void MainWindow::on_pushButton_clicked()
{
qDebug() << "hahaha";
}
void MainWindow::ShowDebugContent(const QString &text)
{
ui->textEdit->append(text);
}
Tool *Tool::mytool = nullptr; // 类外初始化静态Tool类 mytool
Tool::Tool(QObject *parent) : QObject(parent)
{
mytool = this;
connect(this,&Tool::SigDeliverMessStatic,this,&Tool::SlotDeliverMessStatic); // 关联内部信号与槽
}
void Tool::SlotDeliverMessStatic(QString message)
{
emit mytool->SigDeliverMess(message); // 发射出去信号
}
void Tool::myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtInfoMsg:
fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtWarningMsg:
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtCriticalMsg:
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtFatalMsg:
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
abort();
}
QString message = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + "\r\n";
message.append(localMsg);
emit mytool->SigDeliverMessStatic(message);
}
使用包装器
函数指针是C风格的,只能指向普通函数(无捕获的Lambda函数)或静态成员函数。如果是自己定义的函数,需要使用回调函数,建议使用std::function代替函数指针,std::function是C++11引入的通用可调用对象包装器,可以捕获this的Lambda、成员函数等。
如原本的函数指针定义是:
typedef void (*fPrintCallBack)(int percent);
原本的函数定义为:
void Test(fPrintCallBack callback);
可以把函数修改为:
void Test(const std::function<void(int)>& callback);
使用std::function后,就不需要新建一个类,用静态函数发送信号。可以直接传入一个捕获this的Lambda函数,在Lambda函数中使用QMetaObject的invokeMethod函数调用一个类的槽函数:
auto print_progress = [=](int percent) {
// invokeMethod调用当前类的槽函数updateProgress,updateProgress中可以正常调用Ui
QMetaObject::invokeMethod(this, "updateProgress", Qt::QueuedConnection, Q_ARG(int, percent));
};
// 函数调用
Test(print_progress);