• qt 根据xml文件动态显示参数配置界面(进阶)


    基础 qt 根据xml文件动态显示参数配置界面

    界面

    在这里插入图片描述

    如上图所示:

    1. 根据界面xml文件动态展示相关控件
    2. 实现控件分组
    3. 实现控件排序
    4. 实现保加载和保存文件

    如何定义xml文件

    <em>
        <default>default>
        <editable>Trueeditable>
        <group>1group>
        <help>类别说明1help> 
        <index>0index>
        <name>type2name>
        <scope>
            <chosen0>chosen0>
        scope>
        <type>strtype>
        <value>value>
        <widget>combo_boxwidget>
    em>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 控件名:type2
    2. 控件类型 combo_box
    3. 分组 1
    4. 排序 0
    5. 值类别 str
    6. 值是多少:

    每一个动态控件都由 em标记

    解析xml

    定义结构体

    把解析到的信息存到结构体中:
    首先这个结构体不能完全写死,不然之后增删改属性很麻烦。
    实际上控件的所有属性都是一个键值对,所以可以用 map存储

    typedef struct WidgetELEM  //控件详细信息
    {
    	string strType;//控件类型
    	map<string, string> m_simp;//控件元素
    	map<string, string> m_simpRange;//取值范围
    }WidElement;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    解释:所有的键值对都存放在 m_simp中。控件类型很重要单独领出来。
    如果我们要用下拉列表:combo_box,那么下拉列表的值这么表示:

     <scope>
         <chosen0>value1chosen0>
         <chosen1>value2chosen1>
         <chosen2>value3chosen2>
     scope>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果我们要输入浮点数,且限定在某个范围内

     <em>
         <default>0.001default>
         <editable>Trueeditable>
         <group>1group>
         <help>学习率help>
         <index>8index>
         <level>highlevel>
         <name>ratename>
         <scope>
             <max>0.1max>
             <min>0.00001min>
         scope>
         <type>floattype>
         <value>0.001value>
         <widget>line_editwidget>
     em>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    也就是说scope内的值根据 控件类型的不同有调整,所以我们也把它单独领出来。用m_simpRange表示

    最后所有的控件都保存到一个列表里

    typedef struct WidEmList //控件信息vector
    {
    	vector<pair<string, WidElement>> m_mapList; // 控件名字 , 控件信息
    }WidEleList;
    
    • 1
    • 2
    • 3
    • 4

    注意:控件的名字必须是独一无二的,用名字表示每个控件

    解析

    解析xml是指得到 WidEleList结构体 tagList

    void AnalyzeParamsXml(string strFile, WidEleList& tagList)
    {
    	tagList.m_mapList.clear();
    	QFile qFile(strFile.c_str());
    	if (!qFile.open(QIODevice::ReadOnly))
    	{
    		return;
    	}
    	QString str = qFile.readAll();
    	QDomDocument XMLdoc;
    	bool bLoad = XMLdoc.setContent(str);
    	if (!bLoad)
    	{
    		return;
    	}
    	QDomElement root = XMLdoc.documentElement();  //获取根节点元素 args
    	QDomElement configElement = root.firstChildElement();//根节点的子节点  em
    
    	bool bFind = false;
    	while (!configElement.isNull())
    	{
    		QString strParmName = "";
    		QString strControlType = "";
    		WidElement tEM;
    
    		QDomElement pElementNode = configElement.firstChildElement(); 
    		while (!pElementNode.isNull())
    		{
    			QString value = pElementNode.text();
    			QString text = pElementNode.nodeName();
    			if (text == "name")
    			{
    				strParmName = value;
    			}
    			if (text == "widget")
    			{
    				strControlType = value;
    			}
    			if (text == "scope")
    			{
    				map<string, string> mapList;
    				QDomElement pElementNodeChild = pElementNode.firstChildElement(); //ElementMeta
    				while (!pElementNodeChild.isNull())
    				{
    					QString valueChild = pElementNodeChild.text();
    					QString textChlid = pElementNodeChild.nodeName();
    					mapList[textChlid.toStdString()] = valueChild.toStdString();
    					pElementNodeChild = pElementNodeChild.nextSiblingElement();
    				}
    				tEM.m_simpRange = mapList;
    			}
    			tEM.m_simp[text.toStdString()] = value.toStdString();
    			pElementNode = pElementNode.nextSiblingElement(); //查找下一个兄弟节点的指针
    		}
    
    		configElement = configElement.nextSiblingElement();
    		tEM.strType = strControlType.toStdString();
    		tagList.m_mapList.push_back(make_pair(strParmName.toStdString(), tEM));
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    创建控件

    排序

    WidEleList 内部是根据加入的顺序排序的,我们在xml的 0 定义了该控件显示在第几个,所以首先对 WidEleList m_widElement; 进行排序

    排序的函数:

    bool  WidElementCompare(pair<string, WidElement>& ele1, pair<string, WidElement>& ele2)
    {
    	QString index1 = ele1.second.m_simp["index"].c_str();
    	QString index2 = ele2.second.m_simp["index"].c_str();
    	int i1 = index1.toInt();
    	int i2 = index2.toInt();
    	return i1 < i2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用stl自带的排序算法:

    sort(m_widElement.m_mapList.begin(), m_widElement.m_mapList.end(), WidElementCompare);
    
    • 1

    分组

    在xml中0设置组别:0就是第一组放在最上面:

    首先我们获取父窗口,得到m_pParent

    //cpp
    void  SetGUI(QWidget* pWidget)
    {
    	m_pParent = pWidget;
    }
    //h
    QWidget* m_pParent;//父窗口
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在父窗口设置栅格布局

    mainLayout = new QGridLayout;
    m_pParent->setLayout(mainLayout);
    
    • 1
    • 2

    首先创建第一个group,即默认group

    	//第一个局中局,放在总布局的第一行第一列
    	
    	QGridLayout *pGridLayout = new QGridLayout;
    	QFrame *pFrame = new QFrame();
    	//framComTitleBar
    	pFrame->setObjectName("frame");
    	pFrame->setStyleSheet("QFrame#frame{ background-color: rgb(255, 255, 255);border-radius: 10px;border:1px solid rgb(33, 150, 243)}");
    	pFrame->setLayout(pGridLayout);
    	mainLayout->addWidget(pFrame,0,0);
    	m_vecGridLayout[0] = pGridLayout;
    	m_vecFrame[0] = pFrame;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    遇到新的group,就创建新的QFrame加入到父窗口的布局中。

    然后我们遍历整个结构体

    for(所有的控件)
    {
    获取控件类型
    使用具体类型的创建函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    void  CreateUIFromXml(string strRoot, string strFile)
    {
    	ClearControl();
    	m_strRoot = strRoot;
    	m_strFile = strFile;
    	parameterAnalyze jcStru;
    	jcStru.AnalyzeParamsXml(strRoot + strFile, m_widElement);
    	sort(m_widElement.m_mapList.begin(), m_widElement.m_mapList.end(), WidElementCompare);
    	mainLayout = new QGridLayout;
    	m_pParent->setLayout(mainLayout);
    
    	//第一个局中局,放在总布局的第一行第一列
    	
    	QGridLayout *pGridLayout = new QGridLayout;
    	QFrame *pFrame = new QFrame();
    	//framComTitleBar
    	pFrame->setObjectName("frame");
    	pFrame->setStyleSheet("QFrame#frame{ background-color: rgb(255, 255, 255);border-radius: 10px;border:1px solid rgb(33, 150, 243)}");
    	pFrame->setLayout(pGridLayout);
    
    	mainLayout->addWidget(pFrame,0,0);
    	m_vecGridLayout[0] = pGridLayout;
    	m_vecFrame[0] = pFrame;
    	for (auto it = m_widElement.m_mapList.begin(); it != m_widElement.m_mapList.end(); it++)
    	{
    		if (it->second.m_simp.find("editable") == it->second.m_simp.end() ||
    			it->second.m_simp["editable"] == "false")//不需要画的
    		{
    			continue;
    		}
     
    		switch (m_mapControl[it->second.strType])
    		{
    		case TYPE_LINEEDIT:
    			CreateLineEdit(it->second);
    			break;
    		case TYPE_SPINBOX:
    			CreateSpinBix(it->second);
    			break;
    		case TYPE_DOUBLE_SPINBOX:
    			CreateDoubleSpinBox(it->second);
    			break;
    		case TYPE_DIRVIEW:
    			CreateDirView(it->second);
    			break;
    		case TYPE_FILEVIEW:
    			CreateFileView(it->second);
    			break;
    		case TYPE_COMBOBOX:
    			CreateCombox(it->second);
    			break;
    		case TYPE_CHECKBOX:
    			CreateCheckBox(it->second);
    			break;
    		case TYPE_CHECKBOXGROUP:
    			CreateCheckBoxGroup(it->second);
    			break;
    		default:
    			CreateName(it->second);
    			break;
    		}
    	}
    	CreateSaveAndEditBtn();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    创建具体控件

    举例:QLineEdit
    首先获取控件的名字,和group,得到要把这个控件放在哪个QFrame中的QGridLayout
    new一个QLineEdit,设置默认值,设置取值范围,最后把控件加入到布局中

    void CreateLineEdit(WidElement& ele)
    {
    	int nLayoutIndex = 0, row = 0;
    	CreateElementLabel(ele , nLayoutIndex , row);
    	QGridLayout *pGridLayout = m_vecGridLayout[nLayoutIndex];
    	pInfo->m_strKey = ele.m_simp["name"].c_str();
    	QLineEdit* pEdit =new QLineEdit(m_pParent);  
    	//设置默认值
    	QString strText;
    	if (ele.m_simp["value"] != "")
    		strText = ele.m_simp["value"].c_str();
    	else
    		strText = ele.m_simp["default"].c_str();
    	pEdit->setText(strText);
    	//设置取值范围
    	QString strInfoText;
    	if (ele.m_simpRange.find("min") != ele.m_simpRange.end())
    	{
    		strInfoText = "(";
    		strInfoText += ele.m_simpRange["min"].c_str();
    	}
    	if (ele.m_simpRange.find("max") != ele.m_simpRange.end())
    	{
    		strInfoText += "-";
    		strInfoText += ele.m_simpRange["max"].c_str();
    		strInfoText += ")";
    	}
    	pEdit->setToolTip(ele.m_simp["help"].c_str());
    
    	QLabel* pLabelInfo = new QLabel(m_pParent);
    	pLabelInfo->setText(strInfoText);
    	m_vecLineEdit[m_nCtrID++] = pEdit ;
    	control_info cInfo{ ele.m_simp["name"].c_str() ,TYPE_LINEEDIT , m_nCtrID - 1 };
    	m_vecControlInfo.push_back(cInfo);
    
    	ele.id = m_nCtrID - 1;
    	pEdit->setProperty("index", m_nCtrID - 1);
    	pGridLayout->addWidget(pEdit, row, 1);
    
    	m_vecLable[m_nCtrID++] = pLabelInfo;
    	pLabelInfo->setProperty("index", m_nCtrID - 1);
    	//把控件加入到布局中
    	pGridLayout->addWidget(pLabelInfo, row, 2);
    	control_info cInfo1{ ele.m_simp["name"].c_str() ,TYPE_LABEL , m_nCtrID - 1 };
    	m_vecControlInfo.push_back(cInfo1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    我们要怎么得知放在哪个QFrame中的QGridLayout
    首先获取 group,查看 该group的QFrame和QGridLayout 是否创建(m_vecGridLayout里面有没有)
    没有的话 new一个,并加入到 m_vecGridLayout中,把QFrame加到父界面的布局中
    返回QGridLayout 的指针

    void  CreateElementLabel(WidElement& ele, int & nLayoutIndex, int &row)
    {
    	QLabel* pLabel = new QLabel(m_pParent);
    	pLabel->setText(ele.m_simp["name"].c_str());
    	QString help = ele.m_simp["help"].c_str();
    	pLabel->setToolTip(help);
    	m_vecLable[m_nCtrID++] = pLabel;
    	pLabel->setProperty("index", m_nCtrID-1);
    	control_info cInfo{ ele.m_simp["name"].c_str() ,TYPE_LABEL , m_nCtrID - 1 };
    	m_vecControlInfo.push_back(cInfo);
    
    	QString groupIndex = ele.m_simp["group"].c_str();
    	bool ok;
    	nLayoutIndex = groupIndex.toInt(&ok);
    	if (!ok || nLayoutIndex<0)
    	{
    		nLayoutIndex = 0;
    	}
    	map<int, QGridLayout *>::iterator iter = m_vecGridLayout.find(nLayoutIndex);
    	if (iter == m_vecGridLayout.end())
    	{
    		QGridLayout *pGridLayout = new QGridLayout;
    		QFrame *pFrame = new QFrame();
    		pFrame->setObjectName("frame");
    		pFrame->setStyleSheet("QFrame#frame{ background-color: rgb(255, 255, 255);border-radius: 10px;border:1px solid rgb(33, 150, 243)}");
    		pFrame->setLayout(pGridLayout);
    		mainLayout->addWidget(pFrame, nLayoutIndex, 0);
    
    		m_vecGridLayout[nLayoutIndex] = pGridLayout;
    		m_vecFrame[nLayoutIndex] = pFrame;
    	}
    	QGridLayout *pGridLayout = m_vecGridLayout[nLayoutIndex];
    	 row = pGridLayout->rowCount();
    	pGridLayout->addWidget(pLabel, ++row, 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
  • 相关阅读:
    一款Java开源的SpringBoot即时通讯IM 聊天系统
    【无标题】element select下拉框下拉选项位置不对,显示到旁边,不显示到下拉框底部
    OpenCV 透视变换
    探索公共厕所的数字化治理,智慧公厕完善公共厕所智能化的治理体系
    51单片机太阳能十字路口交通灯
    第 2 章:FFmpeg简介
    etc-day30
    Vue组件中的生命周期函数执行流程
    python数学建模--sympy三维图像绘制
    Nginx+Keepalived+LVS集群实战
  • 原文地址:https://blog.csdn.net/fuyouzhiyi/article/details/127567479