QT控件 修改QtTreePropertyBrowser自定义属性编辑器源码,添加第一列标题勾选,按钮,右键菜单事件等功能

news2025/4/12 15:03:35

头阵子遇到一个需要修改QtTreePropertyBrowser控件的需求,QT开发做这么久了,这个控件倒是第一次用,费了点时间研究,在这里做个简单的总结。
QtTreePropertyBrowser控件 是 Qt 解决方案 (Qt Solutions) 中的一个组件,用于创建和管理属性浏览器界面。它提供了一个树形结构的属性编辑器,能实现自定义属性的编辑,支持大部分QVariant数据类型,
以前实现属性编辑器这种功能我都是使用的QTreeWidget实现的,但是有了QtTreePropertyBrowser控件这个控件,功能实现起来就简单了。

目录导读

    • 前言
    • 源码修改
      • Qss样式支持 修改
        • QComboBox 支持qss修改项行高
        • 修改QtTreePropertyBrowser控件的每行高度
      • 修改控件
        • 添加标题允许勾选,添加设置按钮
        • 设置修改自定义属性表 表头名称,获取QtTreeWidget控件等
        • 根据鼠标坐标 获取选中属性
    • 调用示例

前言

QtTreePropertyBrowser控件 不在QT的的UI设计器里面,如果要使用QtTreePropertyBrowser控件,需要注意到安装目录下的Src文件夹中查找(前提是安装qt的时候下载了Src源码文件)

  • 参考目录:

(在Pro头文件夹中添加)
include($$[QT_INSTALL_PREFIX]/../Src/qttools/src/shared/qtpropertybrowser/qtpropertybrowser.pri)

  • 支持的属性数据类型:

大部分QVariant数据类型,包括点,矩形,时间,字体,颜色等都支持在线编辑修改

默认已处理类型:
QVariant::Int
QVariant::Double
QVariant::Bool
QVariant::String
QVariant::Date
QVariant::Time
QVariant::DateTime
QVariant::KeySequence
QVariant::Char
QVariant::Locale
QVariant::Point
QVariant::PointF
QVariant::Size
QVariant::SizeF
QVariant::Rect
QVariant::RectF
QVariant::Color
QVariant::SizePolicy
QVariant::Font
QVariant::Cursor

  • 实现效果:

通过对QtTreePropertyBrowser控件的源码简单修改,实现添加标题勾选,单击按钮,右键事件等功能。
在这里插入图片描述
需要注意的是 不同QT版本下的QtTreePropertyBrowser控件源码,不一定能直接编译
使用时最好使用同一版本下的源码文件,
例如我使用的Qt5.13.1版本的QtTreePropertyBrowser控件源码在Qt5.15.2版本下Msvc2019编译器无法编译!
相关参考:
官方案例 qtpropertybrowser/examples
详解Qt5.12.9属性表控件:QtPropertyBrowser的使用示例/折叠/展开/小数位数/QSS样式/标题修改/选中行号等

源码修改

因为新添加的标题勾选,按钮,右键菜单这些功能改动不大,
不需要动QtTreePropertyBrowser控件的关键代码,只需要简单修改几行就可以了。

Qss样式支持 修改

  • QComboBox 支持qss修改项行高

修改qteditorfactory.cpp 文件 1919行
添加 editor->setView(new QListView());
如图示:
在这里插入图片描述让多选项的行高能够修改
QSS:QComboBox QAbstractItemView::item{height:30px;}

  • 修改QtTreePropertyBrowser控件的每行高度

QtTreePropertyBrowser控件默认是不支持设置行高的,即使我通过修改内部变量的QTreeWidget控件修改行高,也不支持。因为内置的QtPropertyEditorDelegate 委托阻止了设置行高。
修改 qttreepropertybrowser.cpp文件 382行
修改 sizeHint(const QStyleOptionViewItem &option,const QModelIndex &index) const函数

QSize QtPropertyEditorDelegate::sizeHint(const QStyleOptionViewItem &option,
                                         const QModelIndex &index) const
{
    //add to 2025-04-17 为了获取qss的heigth高度兼容
    // 获取 QTreeView 的样式表
    QTreeView *treeView = qobject_cast<QTreeView*>(parent());
    if (!treeView) {
        return QItemDelegate::sizeHint(option, index) + QSize(3,4); // 默认大小
    }
    // 获取样式表中的高度
    QStyleOptionViewItem opt = option;
    // 从样式中获取大小
    QSize size = treeView->style()->sizeFromContents(
                QStyle::CT_ItemViewItem, &opt, QSize(), treeView);

    return size;
}

如图示:
在这里插入图片描述
QSS:QTreeWidget::item{ background: #1d1f20; height:30px; }

修改控件

  • 添加标题允许勾选,添加设置按钮

添加标题勾选本质上是启用QTreeWidgetItem类的setCheckState方法,并且监控itemChanged(QTreeWidgetItem* ,int )事件响应,
添加按钮是在QTreeWidgetItem添加一个QPushButton按钮

修改源码:

  1. 修改qtpropertybrowser.cpp文件,修改QtPropertyPrivate 私有类
class QtPropertyPrivate
{
public:
    QtPropertyPrivate(QtAbstractPropertyManager *manager) : m_enabled(true), m_modified(false),m_ischecked(Qt::PartiallyChecked),m_ispushbutton(false), m_manager(manager) {}
    QtProperty *q_ptr;

    QSet<QtProperty *> m_parentItems;
    QList<QtProperty *> m_subItems;

    QString m_valueToolTip;
    QString m_descriptionToolTip;
    QString m_statusTip;
    QString m_whatsThis;
    QString m_name;
    bool m_enabled;
    bool m_modified;

    //! 是否可选
    Qt::CheckState m_ischecked;
    //! 是否作为一个按钮
    bool m_ispushbutton;

    QtAbstractPropertyManager * const m_manager;
};
  1. 修改 qtpropertybrowser.h文件,修改QtProperty类,添加修改属性方法
//追加属性
//! 因为使用的是 QTreeWidgetItem 的勾选事件,修改此属性不会触发信号
//!Qt::CheckState isChecked() const;
//!void setChecked(Qt::CheckState checked);
//! 设置作为一个button按钮 -后面的按钮使用
//!bool isPushbutton() const;
//!void SetPushbutton();

Qt::CheckState QtProperty::isChecked() const
{
    return  d_ptr->m_ischecked;
}
void QtProperty::setChecked(Qt::CheckState checked)
{
     d_ptr->m_ischecked=checked;
     //propertyChanged();
}

bool QtProperty::isPushbutton() const
{
    return  d_ptr->m_ispushbutton;
}
void QtProperty::SetPushbutton()
{
     d_ptr->m_ispushbutton=true;
     //propertyChanged();
}

在这里插入图片描述

  1. 修改qttreepropertybrowser.cpp文件 563行 修改QtTreePropertyBrowserPrivate类中的propertyInserted方法用于启用勾选,并根据状态判断是否插入按钮
void QtTreePropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex)
{
    //! 因为绑定了item修改事件 所以刚开始时禁止信号传递
    m_treeWidget->blockSignals(true);
    QTreeWidgetItem *afterItem = m_indexToItem.value(afterIndex);
    QTreeWidgetItem *parentItem = m_indexToItem.value(index->parent());

    QTreeWidgetItem *newItem = 0;
    if (parentItem) {
        newItem = new QTreeWidgetItem(parentItem, afterItem);
    } else {
        newItem = new QTreeWidgetItem(m_treeWidget, afterItem);
    }

    //add 是否启用勾选
    if(index->property()->isChecked()!=Qt::PartiallyChecked)
    {
        Qt::CheckState checkstate=index->property()->isChecked();
        newItem->setCheckState(0,checkstate);
    }

    m_itemToIndex[newItem] = index;
    m_indexToItem[index] = newItem;

    newItem->setFlags(newItem->flags() | Qt::ItemIsEditable);
    newItem->setExpanded(true);

    
    updateItem(newItem);

    //add pushbutton 插入一个按钮
    if(index->property()->isPushbutton())
    {
        newItem->setText(0,"");
        QPushButton* pubtton=new QPushButton();
        const QString descriptionToolTip  = index->property()->descriptionToolTip();
        const QString propertyName = index->property()->propertyName();

        pubtton->setToolTip(descriptionToolTip.isEmpty() ? propertyName : descriptionToolTip);
        pubtton->setStatusTip(index->property()->statusTip());
        pubtton->setWhatsThis(index->property()->whatsThis());
        pubtton->setText(propertyName);
        m_indexToPushButton.insert(pubtton,newItem);
        //! 绑定 点击信号槽
        QObject::connect(pubtton,&QPushButton::pressed,[=](){
            emit q_ptr->itemPressedupdate(m_itemToIndex[m_indexToPushButton[pubtton]]->property());
        });
        m_treeWidget->setItemWidget(newItem,1,pubtton);
    }

    m_treeWidget->blockSignals(false);
}

  1. 添加绑定itemChanged信号槽,用于监控勾选状态改变
    修改QtTreePropertyBrowser类和QtTreePropertyBrowserPrivate类
//! hpp
//! 追加内容
class QtTreePropertyBrowser : public QtAbstractPropertyBrowser
{

Q_SIGNALS:
    //! 选中状态改变
    void itemCheckStateupdate(QtProperty *item);
    //! 按钮按下 -某个按钮被按下触发事件
    void itemPressedupdate(QtProperty *item);
private:
    //! 获取勾选状态改变
    Q_PRIVATE_SLOT(d_func(), void slotitemChanged(QTreeWidgetItem* item,int col))

}

//! cpp
class QtTreePropertyBrowserPrivate
{
 //! 追加修改QtTreePropertyBrowserPrivate类
 
public:
    //! 勾选改变
    void slotitemChanged(QTreeWidgetItem *item, int column);
    
private:
    //! 创建一个变量 用于保存点击的属性项
    QMap<QPushButton *, QTreeWidgetItem *> m_indexToPushButton;
}

//! init 方法绑定信号
void QtTreePropertyBrowserPrivate::init(QWidget *parent)
{

    //! 添加-勾选改变状态事件
    QObject::connect(m_treeWidget, SIGNAL(itemChanged(QTreeWidgetItem* ,int )), q_ptr, SLOT(slotitemChanged(QTreeWidgetItem* ,int )));
}

//状态改变发送信号
void QtTreePropertyBrowserPrivate::slotitemChanged(QTreeWidgetItem *item, int column)
{
    if(column==0)
    {
        QtBrowserItem *browserItem = m_itemToIndex[item];
        if(browserItem && !browserItem->property()->hasValue())
        {
            if(browserItem->property()->isChecked() != Qt::PartiallyChecked)
            {
                browserItem->property()->setChecked(item->checkState(column));
                emit q_ptr->itemCheckStateupdate(browserItem->property());
            }
        }
    }
}

  • 设置修改自定义属性表 表头名称,获取QtTreeWidget控件等
QTreeWidget* QtTreePropertyBrowser::getPropertyTreeWidget()
{
    return d_ptr->treeWidget();
}

//! 修改标题
//! ui->widget_AttriTree->setHeaderLabels(QStringList()<<"属性"<<"业务值");
void QtTreePropertyBrowser::setHeaderLabels(QStringList Headers)
{
    return d_ptr->m_treeWidget->setHeaderLabels(Headers);
}
  • 根据鼠标坐标 获取选中属性
QtProperty * QtTreePropertyBrowser::getPropertybyPointf(QPoint pos)
{
   QTreeWidgetItem * item= d_ptr->m_treeWidget->itemAt(QPoint(pos.x(),pos.y()-d_ptr->m_treeWidget->header()->height()));
   if(item)
   {
        if(d_ptr->m_itemToIndex[item])
            return d_ptr->m_itemToIndex[item]->property();
        else
        {
            if(d_ptr->m_itemToIndex[item->parent()])
                 return d_ptr->m_itemToIndex[item->parent()]->property();
        }
   }
    return nullptr;
}

如图示:
在这里插入图片描述

调用示例

  • 绑定编辑工厂和属性节点管理
   //! 属性节点管理
   //! QtVariantPropertyManager *m_pVarMgrEdit;
   //! 修改数据类型 工厂
   //! QtVariantEditorFactory *m_pVarFactory;
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    m_pVarMgrEdit = new QtVariantPropertyManager(ui->widget_AttriTree);//关联factory,属性可以修改
    m_pVarFactory = new QtVariantEditorFactory(ui->widget_AttriTree);
    //connect(m_pVarMgrEdit,&QtVariantPropertyManager::valueChanged,this, &ProPertyWindow::onValueChanged);//绑定信号槽,当值改变的时候会发送信号

    //! LoadXml();
    //! qDebug()<<"XML 文件解析完毕! ! ! ";
    //将一个工厂与manger关联起来,即可修改内容。
    ui->widget_AttriTree->setFactoryForManager(m_pVarMgrEdit,m_pVarFactory);
//    ui->widget_AttriTree->setAlternatingRowColors(false);
    //! 修改标题
    ui->widget_AttriTree->setHeaderLabels(QStringList()<<"属性"<<"业务值");

    //! 默认事件
    connect(m_pVarMgrEdit,&QtVariantPropertyManager::valueChanged,this,[&](QtProperty *property, const QVariant &value){
        qDebug()<<"propertyName: "<< property->propertyName()<<" value: "<<value;
    });
    //! 勾选状态改变
    connect(ui->widget_AttriTree,&QtTreePropertyBrowser::itemCheckStateupdate,this,[&](QtProperty *item){

    });
    //! 点击事件
    connect(ui->widget_AttriTree,&QtTreePropertyBrowser::itemPressedupdate,this,[&](QtProperty *item){

    });
}
  • 绑定数据示例:
//QDomElement xml文件解析
void MainWindow::ParseItem(QDomElement ItemNode,QtVariantProperty *parent)
{
    if(!ItemNode.hasAttribute("name"))
        return;
    QString propertyName=ItemNode.attribute("name").trimmed();
    QString type=ItemNode.attribute("type","null").toLower().trimmed();
    QString check=ItemNode.attribute("check","null").toLower().trimmed();

    QtVariantProperty *item=nullptr;
    if(type=="float"||type=="double")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Double,propertyName);
        item->setValue(ItemNode.text().toDouble());
    }
    else if(type=="comboxlist")
    {
        QStringList enumNames;
        int CurrentIndex=0;
        QDomNodeList childnode= ItemNode.childNodes();
        for(int j=0;j<childnode.count();j++)
        {
            if (childnode.item(j).isElement()){
                QDomElement element = childnode.item(j).toElement();
                if(element.tagName().toLower()=="data")
                {
                    enumNames<<element.text();
                    if(element.attribute("isSelect","false").toLower()=="true")
                        CurrentIndex=enumNames.count()-1;
                }
            }
        }
        item = m_pVarMgrEdit->addProperty(QtVariantPropertyManager::enumTypeId(), propertyName);
        item ->setAttribute(QLatin1String("enumNames"), enumNames);
        item ->setValue(CurrentIndex);
    }
    else if(type=="int")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Int,propertyName);
        item->setValue(ItemNode.text().toInt());
    }
    else if(type=="datetime")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::DateTime,propertyName);
        item->setValue(QDateTime::fromString(ItemNode.text(),"yyyy/MM/dd hh:mm:ss"));
    }
    else if(type=="string")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::String,propertyName);
        item->setValue(ItemNode.text());
    }
    else if(type=="pointf")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::PointF,propertyName);
        QStringList poinfs=ItemNode.text().split(',');
        item->setValue(QPointF(poinfs[0].toDouble(),poinfs[1].toDouble()));
    }
    else if(type=="rectf")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::RectF,propertyName);
        QStringList poinfs=ItemNode.text().split(',');
        item->setValue(QRectF(poinfs[0].toDouble(),
                       poinfs[1].toDouble(),
                       poinfs[2].toDouble(),
                       poinfs[3].toDouble()));
    }
    else if(type=="bool")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Bool,propertyName);
        item->setValue(ItemNode.text()=="false"?false:true);
    }
    else if(type=="color")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Color,propertyName);
        item->setValue(QColor(ItemNode.text()));
    }
    else if(type=="pushbutton")
    {
        //! 添加按钮事件
        item = m_pVarMgrEdit->addProperty(QtVariantPropertyManager::groupTypeId(),propertyName);
        item->SetPushbutton();
    }
    else
        item = m_pVarMgrEdit->addProperty(QtVariantPropertyManager::groupTypeId(),propertyName);


    if(_ISNULL_(item))
        return;

    //! 添加首项勾选
    if(check!="null"){
//        qDebug()<<"propertyName: "<<propertyName<<" check: "<<check;
        item->setChecked(check.toLower().trimmed()=="true"?Qt::Checked:Qt::Unchecked);
    }

    if(!_ISNULL_(parent))
        parent->addSubProperty(item);
    else
        ui->widget_AttriTree->addProperty(item);

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2330934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开源模型应用落地-模型上下文协议(MCP)-从数据孤岛到万物互联(一)

一、前言 当开发者还在为每个AI工具编写臃肿的API适配器时&#xff0c;一场关于「连接」的技术革命已悄然降临。模型上下文协议&#xff08;MCP&#xff09;正在用一套全新的交互语法&#xff0c;重新定义人工智能与物理世界的对话方式。MCP协议如同为AI系统装上了“万能接口”…

【区块链安全 | 第三十八篇】合约审计之获取私有数据(二)

文章目录 前言漏洞代码代码审计攻击步骤修复建议审计思路 前言 在【区块链安全 | 第三十七篇】合约审计之获取私有数据&#xff08;一&#xff09;中&#xff0c;介绍了私有数据、访问私有数据实例、Solidity 中的数据存储方式等知识&#xff0c;本文通过分析具体合约代码进行…

mac 苍穹外卖 后端初始 SkyApplication 报错

报错内容 java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualid deepseek 解决 打开 File > Project Structure > Project SDK, 选择 JDK17。我没有 JDK17就下载了一…

Proximal Policy Optimization (PPO)

2.1 策略梯度方法 策略梯度方法计算策略梯度的估计值并将其插入到随机梯度上升算法中。最常用的梯度估计器的形式如下&#xff1a; g ^ E t [ ∇ θ log ⁡ π θ ( a t ∣ s t ) A ^ t ] (1) \hat{g} \mathbb{E}_t \left[ \nabla_{\theta} \log \pi_{\theta}(a_t | s_t) \h…

微信小程序:动态表格实现,表头单元格数据完全从data中获取,宽度自定义,自定义文本框,行勾选,样式效果,横向滚动表格(解决背景色不足的问题)等

一、样式效果 二、代码 1、wxml <view class"line flex flex-center"><view class"none" wx:if"{{info.length 0}}">暂无料号</view><view wx:else class"table-container"><!-- 动态生成表头 -->&…

python-Leetcode 65.搜索旋转排序数组

题目&#xff1a; 整数数组nums按升序排列&#xff0c;数组中的值互不相同 在传递给函数之前&#xff0c;nums在预先未知的某个小标K上进行了旋转&#xff0c;使数组变为[nums[k], nums[k1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]&#xff0c;小标从0开始计数。…

Django学习记录-1

Django学习记录-1 虽然网上教程都很多&#xff0c;但是感觉自己记录一下才属于自己&#xff0c;之后想找也方面一点&#xff0c;文采不佳看的不爽可绕道。 参考贴 从零开始的Django框架入门到实战教程(内含实战实例) - 01 创建项目与app、加入静态文件、模板语法介绍&#xff…

K8s私有仓库拉取镜像报错解决:x509 certificate signed by unknown authority

前言 在Kubernetes环境中使用自签名证书的私有Harbor镜像仓库时&#xff0c;常会遇到证书验证失败的问题。本文将详细讲解如何解决这个常见的证书问题。 环境信息&#xff1a; Kubernetes版本&#xff1a;1.28.2容器运行时&#xff1a;containerd 1.6.20私有仓库&#xff1a…

LabVIEW 长期项目开发

LabVIEW 凭借其图形化编程的独特优势&#xff0c;在工业自动化、测试测量等领域得到了广泛应用。对于长期运行、持续迭代的 LabVIEW 项目而言&#xff0c;其开发过程涵盖架构设计、代码管理、性能优化等多个关键环节&#xff0c;每个环节都对项目的成功起着至关重要的作用。下面…

蓝桥杯真题——好数、R格式

目录 蓝桥杯2024年第十五届省赛真题-好数 【模拟题】 题目描述 输入格式 输出格式 样例输入 样例输出 提示 代码1&#xff1a;有两个案例过不了&#xff0c;超时 蓝桥杯2024年第十五届省赛真题-R 格式 【vector容器的使用】 题目描述 输入格式 输出格式 样例输入…

Windows版-RabbitMQ自动化部署

一键完成Erlang环境变量配置&#xff08;ERLANG_HOME系统变量&#xff09;‌ 一键完成RabbitMQ环境变量配置&#xff08;RabbitMQ系统变量&#xff09;‌ 实现快速安装部署RabbitMQ PS&#xff1a; 需提前下载安装&#xff1a; - otp_win64_25.0.exe (Erlang) - rabbit…

openEuler24.03 LTS下安装Flink

目录 Flink的安装模式下载Flink安装Local模式前提条件解压安装包启动集群查看进程提交作业文件WordCount持续流WordCount 查看Web UI配置flink-conf.yaml简单使用 关闭集群 Standalone Session模式前提条件Flink集群规划解压安装包配置flink配置flink-conf.yaml配置workers配置…

从零开始学java--泛型(1)

泛型 学生成绩可能是数字类型&#xff0c;也可能是字符串类型&#xff0c;如何存放可能出现的两种类型呢&#xff1a; public class Score {String name;String id;Object value; //因为Object是所有类型的父类&#xff0c;因此既可以存放Integer也能存放Stringpublic Score…

【正点原子】STM32MP135去除SD卡引脚复用,出现 /dev/mmcblk1p5 not found!

如果在设备树中直接注释掉 sdmmc1 节点&#xff0c;就会导致系统启动时识别不到真正的 eMMC 设备&#xff0c;进而挂载失败&#xff0c;爆出 /dev/mmcblk1p5 not found 的问题。 正点原子STM32MP135开发板Linux核心板嵌入式ARM双千兆以太网CAN 正确操作是“放空”而不是“删光…

CrystalDiskInfo电脑硬盘监控工具 v9.6.0中文绿色便携版

前言 CrystalDiskInfo是一个不用花钱的硬盘小帮手软件&#xff0c;它可以帮你看看你的电脑硬盘工作得怎么样&#xff0c;健不健康。这个软件能显示硬盘的温度高不高、还有多少地方没用、传输东西快不快等等好多信息。用了它&#xff0c;你就能很容易地知道硬盘现在是什么情况&…

详解模型蒸馏,破解DeepSeek性能谜题

大家好&#xff0c;不少关注 DeepSeek 最新动态的朋友&#xff0c;想必都遇到过 “Distillation”&#xff08;蒸馏&#xff09;这一术语。本文将介绍模型蒸馏技术的原理&#xff0c;同时借助 TensorFlow 框架中的实例进行详细演示。通过本文&#xff0c;对模型蒸馏有更深的认识…

园区网拓扑作业

作业要求&#xff1a; 需求&#xff1a; 需求分析&#xff1a; 1.按照图示的VLAN及IP地址需求&#xff0c;完成相关配需&#xff1a;VLAN 2、3、20、30 已分配子网&#xff0c;需在交换机上创建 VLAN 并配置三层接口作为网关。确保各 VLAN 内设备能互通&#xff0c;跨 VLAN 通…

隔行换色总结

功能效果展示&#xff1a; 第一种思路&#xff1a; 使用数组&#xff0c;将数组的内容渲染到页面上&#xff0c;序号也就是将数组的下标输出到第一个td上&#xff0c;将数组的内容输出到第二个td上&#xff0c;&#xff08;使用拼接字符串&#xff09; 具体操作&#xff1a; …

使用Docker Desktop进行本地打包和推送

使用Docker Desktop进行本地打包和推送 一、Docker Desktop配置二、IDEA配置1.下载Docker插件2.在“Settings”中&#xff0c;配置“Docker”3.选择“Docker Registry”&#xff0c;配置远程仓库。 三、POM配置 一共有三个地方需要配置 一、Docker Desktop配置 在Docker Deskt…

Redis主从复制:告别单身Redis!

目录 一、 为什么需要主从复制&#xff1f;&#x1f914;二、 如何搭建主从架构&#xff1f;前提条件✅步骤&#x1f4c1; 创建工作目录&#x1f4dc; 创建 Docker Compose 配置文件&#x1f680; 启动所有 Redis&#x1f50d; 验证主从状态 &#x1f4a1; 重要提示和后续改进 …