Qt开发技巧(十二)Qt日志重定向,匿名槽函数的写法,QString转换到char*的坑,QList和QVector选择问题,列表窗体的资源释放,列表窗体的选中样式,表单窗体的列宽设置

继续讲一些Qt开发中的技巧操作:
1.Qt日志重定向
Qt自带的日志重定向机制非常简单好用,自从用了以后再也不用什么断点调试啥的了,在需要的地方支持qdebug输出对应的信息,而且发布程序以后也可以开启调试日志将其输出查看等。

//Qt5开始提供了日志上下文信息输出,比如输出当前打印消息所在的代码文件、行号、函数名等。
//如果是release还需要在pro中加上 DEFINES += QT_MESSAGELOGCONTEXT 才能输出上下文,默认release关闭的。
//切记不要在日志钩子函数中再写qdebug之类的,那样就死循环了。
//日志重定向一般就三种处理
//1: 输出到日志文件比如txt文本文件,.log文件(实际上也是txt)。
//2: 存储到数据库,可以分类存储,以便相关人员查询分析。
//3: 重定向到网络,对方用小工具连接程序后,所有打印信息通过tcp发过去。
//日志重定向
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    void Log(QtMsgType type, const QMessageLogContext &context, const QString
             &msg)
        #else
    void Log(QtMsgType type, const char *msg)
        #endif
    {
        //加锁,防止多线程中qdebug太频繁导致崩溃
        static QMutex mutex;
        QMutexLocker locker(&mutex);
        QString content;
        //这里可以根据不同的类型加上不同的头部用于区分
        switch (type) {
        case QtDebugMsg:
            content = QString("%1").arg(msg);
            break;
        case QtWarningMsg:
            content = QString("%1").arg(msg);
            break;
        case QtCriticalMsg:
            content = QString("%1").arg(msg);
            break;
        case QtFatalMsg:
            content = QString("%1").arg(msg);
            break;
        }
        //加上打印代码所在代码文件、行号、函数名
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
        if (SaveLog::Instance()->getUseContext()) {
            int line = context.line;
            QString file = context.file;
            QString function = context.function;
            if (line > 0) {
                content = QString("行号: %1 文件: %2 函数:%3\n%4").arg(line).arg(file).arg(function).arg(content);
            }
        }
#endif
        //将内容传给函数进行处理
        SaveLog::Instance()->save(content);
    }
    //安装日志钩子,输出调试信息到文件,便于调试
    void SaveLog::start()
    {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
        qInstallMessageHandler(Log);
#else
        qInstallMsgHandler(Log);
#endif
    }
    //卸载日志钩子
    void SaveLog::stop()
    {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
        qInstallMessageHandler(0);
#else
        qInstallMsgHandler(0);
#endif
    }

2.匿名槽函数的写法
自从c++11标准以后,各种语法糖层出不穷,其中lambda表达式用的最广,基本上从Qt5以后就支持lambda表达式,也就是匿名槽函数,匿名槽函数根据场景不同,有好几种写法,下面简单举例几个

[capture](parameters) ->return-type{statement};
//[capture]:捕捉列表,捕捉列表能够捕捉上下文中的变量以供Lambda函数使用
//(parameters):参数列表,与普通函数的参数列表一致,如果不需要参数传递,则可以连同括号 ()一起省略
//>return-type:返回类型,用追踪返回类型形式声明函数的返回类型,我们可以在不需要返回值的时候也可以连同符号 -> 一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导(这里其实也隐含用到了C++11的自动类型推导)
//{statement}:函数体,内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量

重点看捕捉列表,有以下几种形式:
[var]表示值传递方式捕捉变量var。
[=]表示值传递方式捕捉所有父作用域的变量(包括this)。
[&var]表示引用传递捕捉变量var。
[&]表示引用传递方式捕捉所有父作用域的变量(包括this)。
[this]表示值传递方式捕捉当前的this指针。

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//按钮单击不带参数
connect(ui->pushButton, &QPushButton::clicked, [] {
qDebug() << "hello lambda";
});
//按钮单击带参数
connect(ui->pushButton, &QPushButton::clicked, [] (bool isCheck) {
qDebug() << "hello lambda" << isCheck;
});
//带捕获又传参
    int nTest = 0;
    connect(ui->pushButton, &QPushButton::clicked, [nTest] (bool isCheck) {
        if(nTest != 0)
            qDebug() << "nTest is not 0" << isCheck;
        else
            qDebug() << "nTest is 0" << isCheck;
    });
//自定义信号带参数
connect(this, &MainWindow::sig_test, [] (int i, int j) {
qDebug() << "hello lambda" << i << j;
});
emit sig_test(5, 8);
}

各种写法都可以去试试,练多了就熟了~

3.QString转换到char*的坑
在使用QString转换到char *或者const char *的时候,务必记得分两步来完成,血的教训,在一个场景中,就因为没有分两步走,现象是msvc的debug异常release正常,mingw和gcc的debug和release都正常,这就很无语了,找问题找天,对比法排除法按道理要么都有问题才对。

QString text = "xxxxx";
//下面这样转换很可能会有问题
char *data = text.toUtf8().data();
//分两步转换肯定不会有问题
QByteArray buffer = text.toUtf8();
char *data = buffer.data();
const char *data = buffer.constData();
void test(const char *text) {}
//分两步走可以确保万无一失
QByteArray buffer = QString("xxx").toUtf8();
const char *text = buffer.constData();
test(text);
//可以直接作为参数传入也是正确的
test(QString("xxx").toUtf8().constData());

//还有一种方式用局部静态的变量
    //设置BLE名称,先定义一个静态的局部变量bleName
    static char bleName[64] = {0};
    //将QString类型的m_strBleName拷贝给bleName
    strcpy(bleName, m_strBleName.toUtf8().constData());
    //再将bleName赋值给const char*类型的ble_name
    g_bt_content.ble_content.ble_name = bleName;
    //如果不用局部静态变量,传值无效

4.QList和QVector选择问题
其实这两个玩意提供的的接口函数基本一致,比如插入、删除、取值等;大多数情况下可以用QList。像append,prepend、insert这种操作,通常QList比QVector快的多;QList是基于index标签存储它的元素项在内存中,比那种依赖iterator迭代的更快捷,而且你的代码也更少;如果你需要开辟连续的内存空间存储,或者你的元素远比一个指针大,这时你需要避免个别插入操作,出现堆栈溢出,这时候用QVector;如果更在意取值的速度则用QVector,QCustomPlot用的就是QVector,需要频繁大量的取出数据进行绘制;如果更在意更新数据(添加、删除等)的速度则用QList(对应操作是[]=值);
总之:由于QList是链式结构,靠指针指向串起来的,所以插入,删除等,更新数据速度快;而QVector是顺序结构,所以顺序型的检索有天然优势,读取数据速度快;

5.列表窗体的资源释放
关于QListWidget,QTableWidget等控件调用自带的removeRow、clearContents、clear函数删除了里面的item和内容,会自动调用item或者cellwidget的析构函数进行资源释放,不用自己手动再去释放

//每次调用 clearContents 都会自动清理之前的item
ui->tableWidget->clearContents();
for (int i = 0; i < count; ++i) {
ui->tableWidget->setItem(i, 0, new QTableWidgetItem("aaa"));
ui->tableWidget->setItem(i, 1, new QTableWidgetItem("bbb"));
ui->tableWidget->setCellWidget(i, 2, new QPushButton("ccc"));
}

//重刷订单列表界面
void TireReportForm::RefreshList()
{
    if(layoutV == nullptr)
    {
        layoutV = new QVBoxLayout(ui->widgetList);
    }
    if(0 != layoutV->count())
    {
        QLayoutItem* child;
        while((child = layoutV->takeAt(0)) != 0)
        {
            QWidget* widget = child->widget();
            QLayout* layout = child->layout();
            //QSpacerItem* sp = child->spacerItem(); // 当其它成员都被删除后,QSpacerItem 默认会被删除,因此可以省略对该控件的操作
            if(widget != nullptr)
            {
                layoutV->removeWidget(widget);
                widget->deleteLater();
            }

            if(layout != nullptr)
            {
                layoutV->removeItem(layout);
                layout->deleteLater();
            }
            // 删除可省略,最后会被自动删除掉
            //            if(sp != nullptr)
            //            {
            //                layoutV->removeItem(sp);
            //                delete sp;
            //            }
        }
    }
    for(int n = 0; n < m_listItem.count(); n++)
    {
        layoutV->addWidget(m_listItem[n]);
    }
    layoutV->setSpacing(10);
    QSpacerItem* space = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
    layoutV->addSpacerItem(space);
    ui->widgetList->setLayout(layoutV);
}

6.列表窗体的选中样式
对于QListView(QListWidget)、QTreeView(QTreeWidget)、QTableView(QTableWidget)这种类型的控件,可以通过setChecked来让对应的item产生复选框效果,很多人(包括曾经的自己)误以为这就是复选框控件,其实不是的,他是对应控件的indicator指示器,所以想要更换样式,不能说设置了QCheckBox的样式就有效果,而要单独对齐indicator指示器设置样式才行

QCheckBox::indicator,QGroupBox::indicator,QTreeWidget::indicator,QListWidget
::indicator{
width:13px;
height:13px;
}
QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeWidget::i
ndicator:unchecked,QListWidget::indicator:unchecked{
image:url(:/qss/flatwhite/checkbox_unchecked.png);
}
QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disab
led,QTreeWidget::indicator:unchecked:disabled,QListWidget::indicator:disable
d{
image:url(:/qss/flatwhite/checkbox_unchecked_disable.png);
}
QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeWidget::indic
ator:checked,QListWidget::indicator:checked{
image:url(:/qss/flatwhite/checkbox_checked.png);
}
QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,
QTreeWidget::indicator:checked:disabled,QListWidget::indicator:checked:disab
led{
image:url(:/qss/flatwhite/checkbox_checked_disable.png);
}
QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeW
idget::indicator:indeterminate,QListWidget::indicator:indeterminate{
image:url(:/qss/flatwhite/checkbox_parcial.png);
}
QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indetermina
te:disabled,QTreeWidget::indicator:indeterminate:disabled,QListWidget::indic
ator:indeterminate:disabled{
image:url(:/qss/flatwhite/checkbox_parcial_disable.png);
}

7.表单窗体的列宽设置
关于QTableView(采用model数据源)、QTableWidget列名列宽设置,有时候发现没有起作用,原来是对代码设置的顺序有要求,比如setColumnWidth前必须先setColumnCount,不然列数都没有,哪来的列宽,包括setHorizontalHeaderLabels设置列标题集合也是,前提都要先有列。

void frmSimple::initForm()
{
//实例化数据模型
model = new QStandardItemModel(this);
//设置行数列数
row = 100;
column = 10;
//设置列名列宽
for (int i = 0; i < column; ++i) {
columnNames << QString("列%1").arg(i + 1);
columnWidths << 60;
}
}

void frmSimple::on_btnLoad2_clicked()
{
//设置列标题和列数及列宽
ui->tableWidget->setColumnCount(column);
//简便方法设置列标题集合
ui->tableWidget->setHorizontalHeaderLabels(columnNames);
for (int i = 0; i < column; ++i) {
ui->tableWidget->setColumnWidth(i, columnWidths.at(i));
}
//添加数据
QDateTime now = QDateTime::currentDateTime();
ui->tableWidget->setRowCount(row);
for (int i = 0; i < row; ++i) {
for (int j = 0; j < column; ++j) {
QTableWidgetItem *item = new QTableWidgetItem;
//最后一列显示时间区别开来
if (j == column - 1) {
item->setText(now.addSecs(i).toString("yyyy-MM-dd
HH:mm:ss"));
} else {
item->setText(QString("%1_%2").arg(i + 1).arg(j + 1));
}
ui->tableWidget->setItem(i, j, item);
}
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值