传递自定义结构体
在项目开发过程中,不可能只用基本类型或一些Qt对象来作为信号槽的参数,结构体是必不可少的。由于结构体无法被信号槽识别,需要用Q_DECLARE_METATYPE来注册结构体。
以下继续用点餐软件来举例子, 先看看优化后的版本:
我需要一份热干面和一碗糊汤米酒,在界面上点击按钮,UI小部件会将相应的参数(数量、价格等)传递到主界面上。这种情况下,需要新增一个结构体作为信号槽的参数。
首先注册以下结构体,让参数可识别:
typedef struct {
int id; //ID
double price; //价格
int count; //数量
int count_diff; //添加或减少的数量
}FoodData;
Q_DECLARE_METATYPE(FoodData);
然后在UI设计器上设计小部件:
实现小部件的基本功能,以下是完整的代码。
#ifndef FOODWIDGET_H
#define FOODWIDGET_H
#include <QWidget>
#include <QPaintEngine>
namespace Ui {
class FoodWidget;
}
typedef struct {
int id; //物品ID
double price; //价格
int count; //数量
int count_diff; //添加或减少的数量
}FoodData;
Q_DECLARE_METATYPE(FoodData);
class FoodWidget : public QWidget
{
Q_OBJECT
public:
explicit FoodWidget(int id, const QString &name, double price, QWidget *parent = nullptr);
~FoodWidget();
private:
void emitSignal(int diff);
protected:
void paintEvent(QPaintEvent *event);
signals:
//数量发生了变化
void itemChanged(FoodData);
private slots:
void on_btn_up_clicked();
void on_btn_down_clicked();
private:
Ui::FoodWidget *ui;
//当前物品ID
int m_id;
//当前数量
int m_count;
//单价
double m_price;
};
#endif // FOODWIDGET_H
#include "foodwidget.h"
#include "ui_foodwidget.h"
FoodWidget::FoodWidget(int id, const QString &name, double price, QWidget *parent) :
QWidget(parent),
ui(new Ui::FoodWidget),
m_id(id),
m_count(0),
m_price(price)
{
ui->setupUi(this);
//设置物品ICON
QPixmap pixmap(":/image/" + QString::number(id) + ".png");
ui->label_icon->setPixmap(pixmap.scaled(110,110, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
//设置名称
ui->label_name->setText(name);
//价格
ui->label_price->setText( QString::number(price, 'f', 2));
//数量
ui->label_cnt->setText("0");
ui->label_cnt->setAlignment(Qt::AlignCenter);
//添加、减少按钮样式:无边框与背景透明
ui->btn_up->setStyleSheet("border:none; background: transparent;");
ui->btn_down->setStyleSheet("border:none; background: transparent;");
ui->btn_down->hide();
ui->label_cnt->hide();
}
FoodWidget::~FoodWidget()
{
delete ui;
}
//绘制背景, 刷白
void FoodWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(Qt::NoPen);
painter.setRenderHint(QPainter::Antialiasing, false);
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(QColor(Qt::white)));
painter.drawRect(rect());
}
//发送结构体
void FoodWidget::emitSignal(int diff)
{
FoodData data;
data.id = m_id;
data.price = m_price;
data.count = m_count;
data.count_diff = diff;
emit itemChanged(data);
}
//添加
void FoodWidget::on_btn_up_clicked()
{
ui->btn_down->show();
ui->label_cnt->show();
ui->label_cnt->setText(QString::number(++m_count));
emitSignal(1);
}
//减少
void FoodWidget::on_btn_down_clicked()
{
if (m_count == 0) { //数量已经为0
return;
}else if (m_count == 1) { //即将为0
m_count--;
ui->label_cnt->setText("0");
ui->btn_down->hide();
ui->label_cnt->hide();
}else if (m_count > 1){
ui->label_cnt->setText(QString::number(--m_count));
}
emitSignal(-1);
}
UI小部件完成后,开始在UI设计器上设置主界面结构:
我在ScrollArea中加入了垂直布局,然后在代码中,将UI小部件都放入其中:
const QStringList list_menu = {"热干面", "牛肉拌面", "三鲜豆皮", "油饼包烧麦", "包子", "面窝", "糊汤粉", "鸡冠饺", "油酥饼", "欢喜坨", "糯米鸡", "糊汤米酒"};
const QList<double> list_price = {6.0, 12.0, 8.5, 7.0, 10.0, 2.5, 5.0, 2.5, 2.0, 1.5, 3.0, 3.0};
int menu_count = list_menu.count();
for(int i = 0; i < menu_count; i++) {
FoodWidget *widget = new FoodWidget(i+1, list_menu[i], list_price[i], this);
connect(widget, SIGNAL(itemChanged(FoodData)), this, SLOT(onItemChanged(FoodData)));
ui->vlay->insertWidget(i, widget, 0, Qt::AlignTop);
}
将所有的小部件的信号与主界面的槽函数相连,由于itemChanged信号参数是注册过的,就不会报错了,可以直接传递。
在主界面槽函数中,计算当前份数和总价:
void MainWindow::onItemChanged(FoodData data)
{
//计算份数
m_total_count += data.count_diff;
if (m_total_count > 0)
ui->label_count->setText(QString::number(m_total_count) + "份");
else
ui->label_count->setText("");
//计算总价
m_total_price += (data.count_diff * data.price);
if (m_total_price > 0)
ui->label_total_price->setText("共计" + QString::number(m_total_price, 'f', 2) + "元");
else
ui->label_total_price->setText("");
}