前言
最近学习了开源网速监控悬浮窗软件(github.com/zhongyang21… ),觉得功能很强大,但由于其是基于Windows平台开发的,因此决定采用Qt重新实现该软件的部分功能,并使用CEF与QCefView实现Qt与前端网页的简单交互。文章内容主要包括:1、流量、cpu及内存检测功能及主界面实现;2、Qt与前端交互及cef窗口实现。
一、流量、cpu及内存检测功能及主界面实现
Windows平台下获取网络流量可以参考www.cnblogs.com/chaikefusib… ,主要函数如下,为实现获取每秒的上传网络与下载网络,可通过定时器m_timer.setInterval(1000);connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));实现。
//网络流量获取
PMIB_IFTABLE m_pTable = nullptr;
DWORD m_dwAdapters = 0;
GetIfTable(m_pTable, &m_dwAdapters, TRUE);
DwOctets<DWORD> dwOctets;
for (UINT i = 0; i < m_pTable->dwNumEntries; i++)
{
MIB_IFROW Row = m_pTable->table[i];
dwOctets.m_dwInOctets += Row.dwInOctets;
dwOctets.m_dwOutOctets += Row.dwOutOctets;
}
DwOctets<double> dwbands;
dwbands.m_dwInOctets = (dwOctets.m_dwInOctets - m_lastDwOctets.m_dwInOctets) / 1024;
dwbands.m_dwOutOctets = (dwOctets.m_dwOutOctets - m_lastDwOctets.m_dwOutOctets) / 1024;
获取cpu、内存占有率函数分别如下:
inline __int64 NetCPUMemInfo::Filetime2Int64(const FILETIME &ftime)
{
LARGE_INTEGER li;
li.LowPart = ftime.dwLowDateTime;
li.HighPart = ftime.dwHighDateTime;
return li.QuadPart;
}
double NetCPUMemInfo::getCpuUsage()
{
SystemTime systemTime;
GetSystemTimes(&systemTime.m_idleTime, &systemTime.m_kernelTime, &systemTime.m_userTime);
__int64 idle = Filetime2Int64(systemTime.m_idleTime) - Filetime2Int64(m_lastSystemTime.m_idleTime);
__int64 kernel = Filetime2Int64(systemTime.m_kernelTime) - Filetime2Int64(m_lastSystemTime.m_kernelTime);
__int64 user = Filetime2Int64(systemTime.m_userTime) - Filetime2Int64(m_lastSystemTime.m_userTime);
m_lastSystemTime = systemTime;
return 100.0 * (kernel + user - idle) / (kernel + user);
}
double NetCPUMemInfo::getMemUsage()
{
MEMORYSTATUSEX memStatus;
memStatus.dwLength = sizeof(memStatus);
GlobalMemoryStatusEx(&memStatus);
return 100.0 * (memStatus.ullTotalPhys - memStatus.ullAvailPhys) / memStatus.ullTotalPhys;
}
主界面逻辑及各函数功能说明如下:
//monitor_widget.h
#ifndef __MONITOR_WIDGET_H__
#define __MONITOR_WIDGET_H__
#include "traffic_monitor.h"
#include "http_client/http_client.h"
#include "cef_widget.h"
#include <QWidget>
#include <QLabel>
#include <QTimer>
using namespace base_common;
class MonitorWidget : public QWidget
{
Q_OBJECT
public:
MonitorWidget(QWidget *parent = nullptr);
~MonitorWidget();
bool getFixedValue();
double getTransparency();
protected:
bool event(QEvent* event) override;
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
private slots:
void onTimeout();
private:
void initUI();
void loadBgImage();
QString getFormatDwOctets(double dwOctets);
private:
QPixmap m_bgImage;
qint8 m_currentIndex = 0;
QTimer m_timer;
std::unique_ptr<NetCPUMemInfo> m_netCPUMemInfoPtr;
bool m_bLeftButton = false;
QPoint m_point;
CefWidget* m_cefWidget = nullptr;
double m_transparency = 1.0;
bool m_fixedToScreen = false;
private:
NetCPUMemInfo::DwOctets<double> m_dwOctets;
int m_cpuUsage = 0;
int m_memUsage = 0;
};
#endif
//monitor_widget.cpp
#include "monitor_widget.h"
#include "config/config.h"
#include "thread_pool/thread_pool.h"
#include <QPainter>
#include <QPainterPath>
#include <QGuiApplication>
#include <QScreen>
#include <QMouseEvent>
#include <QGridLayout>
#include <QFileInfo>
static const QString BgImageSuffix = "/project_resource/traffic_monitor/bgImage.jpg";
QEvent::Type TaskFinishEvent = (QEvent::Type)QEvent::registerEventType();
class downloadImageTask : public IThreadHandle {
public:
downloadImageTask(const QString& url, QWidget* parent = nullptr)
: m_url(url), m_parentWidgetPtr(parent) {}
virtual void threadhandle() { // 异步下载背景图片,完成后通过postEvent通知主界面
std::unique_ptr<HttpClient> httpClientPtr(new HttpClient(m_url.toStdString()));
QString bgImagePath = QCoreApplication::applicationDirPath() + BgImageSuffix;
if (httpClientPtr->getResquestToFile(bgImagePath.toStdString(), "", false)) {
if (m_parentWidgetPtr)
QCoreApplication::postEvent(m_parentWidgetPtr, new QEvent(TaskFinishEvent));
}
}
private:
QString m_url;
QPointer<QWidget> m_parentWidgetPtr;
};
MonitorWidget::MonitorWidget(QWidget *parent) : QWidget(parent) {
initUI();
m_netCPUMemInfoPtr.reset(new NetCPUMemInfo);
m_timer.setInterval(1000);
m_timer.start();
connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
}
MonitorWidget::~MonitorWidget(){}
bool MonitorWidget::getFixedValue() {
return m_fixedToScreen;
}
double MonitorWidget::getTransparency() {
return m_transparency;
}
void MonitorWidget::initUI() {
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
loadBgImage();
setFixedSize(QSize(220, 43));
QRect screenRect = QGuiApplication::primaryScreen()->geometry();
move(screenRect.width() * 0.75, screenRect.height() * 0.75);
}
void MonitorWidget::loadBgImage() {
QString bgImagePath = QCoreApplication::applicationDirPath() + BgImageSuffix;
QFileInfo fileInfo(bgImagePath);
if (fileInfo.exists())
m_bgImage = QPixmap(bgImagePath).scaled(220, 43, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
}
bool MonitorWidget::event(QEvent* event) {
if (event->type() == TaskFinishEvent) {
loadBgImage();
repaint();
}
return QWidget::event(event);
}
void MonitorWidget::paintEvent(QPaintEvent *event) {
Q_UNUSED(event); // 重写paintEvent函数,绘制不规则窗口
setWindowOpacity(m_transparency);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(m_bgImage);
painter.setPen(Qt::transparent);
painter.drawRoundedRect(rect(), 5, 5);
painter.setPen(Qt::black);
QFont font;
font.setFamily("Microsoft YaHei");
font.setPointSize(10);
painter.setFont(font);
QString dwOutOctets = QString(tr("upload") + ": %1").arg(getFormatDwOctets(m_dwOctets.m_dwOutOctets));
QString dwInOctets = QString(tr("download") + ": %1").arg(getFormatDwOctets(m_dwOctets.m_dwInOctets));
QString cpuUsage = QString("CPU: %1\%").arg(m_cpuUsage);
QString memoryUsage = QString(tr("memory") + ": %1\%").arg(m_memUsage);
painter.drawText(QRect(10, 3, 120, 18), Qt::AlignLeft, dwOutOctets);
painter.drawText(QRect(120, 3, 120, 18), Qt::AlignLeft, dwInOctets);
painter.drawText(QRect(10, 23, 120, 18), Qt::AlignLeft, cpuUsage);
painter.drawText(QRect(120, 23, 120, 18), Qt::AlignLeft, memoryUsage);
}
void MonitorWidget::mousePressEvent(QMouseEvent *event) {
if (!m_fixedToScreen && event->button() == Qt::LeftButton) {
m_bLeftButton = true;
m_point = event->globalPos() - pos();
}
return QWidget::mousePressEvent(event);
}
void MonitorWidget::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton)
m_bLeftButton = false;
else if (event->button() == Qt::RightButton) {
m_cefWidget = new CefWidget(this);
m_cefWidget->show();
m_cefWidget->move(pos() + QPoint(size().width() / 2, size().height()));
connect(m_cefWidget, &CefWidget::sigSkinChange, this, [this](const QString& imageUrl) {
CThreadPool::getInstance()->pushTask(std::shared_ptr<IThreadHandle>(new downloadImageTask(imageUrl, this)), TASK_LEVEL_MIDUM);
});
connect(m_cefWidget, &CefWidget::sigFixedToScreen, this, [this]() {
m_fixedToScreen = !m_fixedToScreen;
});
connect(m_cefWidget, &CefWidget::sigTransparencyChange, this, [this](int value) {
m_transparency = qMax(0.1, (double)(99 - value) / 99);
repaint();
});
}
return QWidget::mouseReleaseEvent(event);
}
void MonitorWidget::mouseMoveEvent(QMouseEvent *event) {
if (m_bLeftButton) //无边框条件下拖到窗口
move(event->globalPos() - m_point);
return QWidget::mouseMoveEvent(event);
}
void MonitorWidget::onTimeout() {
m_dwOctets = m_netCPUMemInfoPtr->getNetOctets();//每秒查询上传流量与下载流量
m_cpuUsage = m_netCPUMemInfoPtr->getCpuUsage();
m_memUsage = m_netCPUMemInfoPtr->getMemUsage();
repaint();
}
QString MonitorWidget::getFormatDwOctets(double dwOctets) {
QString dwOctetsStr;
if (dwOctets < 10)
dwOctetsStr = QString::number(dwOctets, 'f', 2) + " KB/s";
else if (dwOctets < 100)
dwOctetsStr = QString::number(dwOctets, 'f', 1) + " KB/s";
else if (dwOctets < 1024)
dwOctetsStr = QString::number(dwOctets, 'f', 0) + " KB/s";
else
dwOctetsStr = QString::number(dwOctets / 1024, 'f', 2) + " MB/s";
return dwOctetsStr;
}
最终得到主界面如下:
二、Qt与前端交互及cef窗口实现
Qt与前端的交互可以参考Qt + CEF + Node.js 桌面开发实战 - 杨科山 - 掘金小册 (juejin.cn),对于简单应用,更推荐使用Qt自带的QWebEngine,对于复杂应用,更推荐CEF(github.com/chromiumemb… )。但是第二种方式,Qt调用cef接口与前端交互,加载一个简单的网页也涉及到很多逻辑,个人有些没看懂,于是采用QCefView(cefview.github.io/QCefView/zh… )来实现Qt与前端的交互:
//cef_widget.h
#ifndef __CEF_WIDGET_H__
#define __CEF_WIDGET_H__
#include <QWidget>
#include <QPointer>
#include "QCefView.h"
#include "QCefContext.h"
class MonitorWidget;
class CefWidget : public QWidget {
Q_OBJECT
public:
CefWidget(QWidget *parent = Q_NULLPTR);
~CefWidget();
void initCef();
protected:
void paintEvent(QPaintEvent* event) override;
signals:
void sigSkinChange(const QString& imageUrl);
void sigFixedToScreen();
void sigTransparencyChange(int value);
private slots:
void onInvokeMethod(int browserId, int frameId, const QString& method, const QVariantList& arguments);
void onQCefQueryRequest(int browserId, int frameId, const QCefQuery& query);
private:
void sendCefEvent(const QString& cefEvent, const QVariant& value);
private:
QCefView* m_cefViewWidget = nullptr;
QPointer<MonitorWidget> m_monitorPtr;
};
#endif
//cef_widget.cpp
#include "cef_widget.h"
#include "monitor_widget.h"
#include "config/config.h"
#include <QGuiApplication>
#include <QScreen>
#include <QVBoxLayout>
#include <QLabel>
#include <QDir>
#include <QPainter>
#include <QPainterPath>
CefWidget::CefWidget(QWidget* parent)
: QWidget(parent)
, m_monitorPtr(qobject_cast<MonitorWidget*>(parent)) {
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() | Qt::Popup | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
resize(135, 140);
initCef();
}
CefWidget::~CefWidget() {
if (m_cefViewWidget)
delete m_cefViewWidget;
}
void CefWidget::initCef() {
QCefSetting setting;
std::string cefWidgetUrl = Config::getInstance()->getTrafficMonitorConfig()->m_cefWidgetUrl;
m_cefViewWidget = new QCefView(cefWidgetUrl.c_str(), &setting, this);
QGridLayout* layout = new QGridLayout(this);
layout->addWidget(m_cefViewWidget);
connect(m_cefViewWidget, &QCefView::invokeMethod, this, &CefWidget::onInvokeMethod);
connect(m_cefViewWidget, &QCefView::cefQueryRequest, this, &CefWidget::onQCefQueryRequest);
}
void CefWidget::paintEvent(QPaintEvent* event) {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::white);
painter.setPen(Qt::transparent);
QPainterPath path;
path.addRoundedRect(rect(), 5, 5);
painter.fillPath(path, Qt::white);
}
void CefWidget::onInvokeMethod(int browserId, int frameId, const QString& method, const QVariantList& arguments) {
if (0 == method.compare("change skin")) {
emit sigSkinChange(arguments.at(0).toString());
}
else if (0 == method.compare("fixed to screen")) {
emit sigFixedToScreen();
sendCefEvent("fixedToScreen", m_monitorPtr->getFixedValue() ? tr("unfixed to screen") : tr("fixed to screen"));
}
else if (0 == method.compare("modify transparency")) {
int value = arguments.at(0).toInt();
emit sigTransparencyChange(value);
}
}
void CefWidget::onQCefQueryRequest(int browserId, int frameId, const QCefQuery& query) {
if (query.request() == "initFixedToScreenAndTransparency") {
QString response(m_monitorPtr->getFixedValue() ? tr("unfixed to screen") : tr("fixed to screen"));
response.append(";");
response.append(QString::number(99 - m_monitorPtr->getTransparency() * 99));
query.setResponseResult(true, response);
m_cefViewWidget->responseQCefQuery(query);
}
}
void CefWidget::sendCefEvent(const QString& cefEvent, const QVariant& value) {
if (!m_cefViewWidget)
return;
QCefEvent event(cefEvent);
QVariantList list;
list.emplace_back(value);
event.setArguments(list);
m_cefViewWidget->broadcastEvent(event);
}
右击主界面即可弹出cef界面,包括换肤、是否固定到屏幕及更改主窗透明度三个功能,最终得到cef界面如下:
// trafficMonitor.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>trafficMonitor</title>
<link rel="stylesheet" type="text/css" href="trafficMonitor.css"/>
</head>
<body onload="onLoad()" id="main">
<button class="btn warning" onclick="onChangeSkinClicked('change skin')">换肤</button><br/>
<button id="fixedToScreenBtn" class="btn info" onclick="onInvokeMethodClicked('fixed to screen')">固定到屏幕</button><br/>
<div class="slidecontainer">
<input type="range" id="transparencySlider" class="slider" min="0" max="99" step="1" value="0" onchange="transparencyChange()"/><br/>
</div>
<script>
var index = 0;
var query = {
request: "initFixedToScreenAndTransparency",
onSuccess: on_success,
};
window.CefViewQuery(query);
function on_success(response) {
arr = response.split(";");
var button = document.getElementById("fixedToScreenBtn");
button.textContent = arr[0];
var slider = document.getElementById("transparencySlider");
slider.value = arr[1];
}
function onChangeSkinClicked(name, ...arg) {
var suffix = "http://xx.xx.xx.xx/bgImage" + index + ".jpg";
window.CallBridge.invokeMethod(name, suffix);
++index
if (index >= 30) {
index = 0;
}
}
function onInvokeMethodClicked(name, ...arg) {
window.CallBridge.invokeMethod(name, ...arg);
}
function onLoad() {
CallBridge.addEventListener("fixedToScreen", fixedToScreen);
}
function fixedToScreen(flag, ...arg) {
var button = document.getElementById("fixedToScreenBtn");
button.textContent = flag;
}
function transparencyChange(){
ran = document.getElementById("transparencySlider");
window.CallBridge.invokeMethod("modify transparency", ran.value);
}
</script>
</body>
</html>