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

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

界面

在这里插入图片描述

如上图所示:

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

如何定义xml文件

<em>
    <default></default>
    <editable>True</editable>
    <group>1</group>
    <help>类别说明1</help> 
    <index>0</index>
    <name>type2</name>
    <scope>
        <chosen0></chosen0>
    </scope>
    <type>str</type>
    <value></value>
    <widget>combo_box</widget>
</em>
  1. 控件名:<name>type2</name>
  2. 控件类型 <widget>combo_box</widget>
  3. 分组 <group>1</group>
  4. 排序 <index>0</index>
  5. 值类别 <type>str</type>
  6. 值是多少: <value></value>

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

解析xml

定义结构体

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

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

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

 <scope>
     <chosen0>value1</chosen0>
     <chosen1>value2</chosen1>
     <chosen2>value3</chosen2>
 </scope>

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

 <em>
     <default>0.001</default>
     <editable>True</editable>
     <group>1</group>
     <help>学习率</help>
     <index>8</index>
     <level>high</level>
     <name>rate</name>
     <scope>
         <max>0.1</max>
         <min>0.00001</min>
     </scope>
     <type>float</type>
     <value>0.001</value>
     <widget>line_edit</widget>
 </em>

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

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

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

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

解析

解析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));
	}
}

创建控件

排序

WidEleList 内部是根据加入的顺序排序的,我们在xml的 <index>0</index> 定义了该控件显示在第几个,所以首先对 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;
}

使用stl自带的排序算法:

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

分组

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

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

//cpp
void  SetGUI(QWidget* pWidget)
{
    
    
	m_pParent = pWidget;
}
//h
QWidget* m_pParent;//父窗口

在父窗口设置栅格布局

mainLayout = new QGridLayout;
m_pParent->setLayout(mainLayout);

首先创建第一个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;

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

然后我们遍历整个结构体

for(所有的控件)
{
    
    
获取控件类型
使用具体类型的创建函数
}
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();
}

创建具体控件

举例: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);
}

我们要怎么得知放在哪个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);
}

猜你喜欢

转载自blog.csdn.net/fuyouzhiyi/article/details/127567479