组态设计是应一个客户要求新增加进去的,设计理念就是在提供一个组态设计的初级功能,比如读取自定义控件动态库,加载所有的控件放到控件栏(有点类似qtcreator的控件栏)用户拖曳对应的控件到画布上,自动生成控件,控件可以直接在画布上拉伸大小和拖动调整位置,设置一些控件本身的属性,比如各种颜色,同时还提供用户属性设置功能,用户可以自定义很多属性绑定在这个控件上,一般来说一个控件会定义一些Q_PROPERTY的东西来讲属性暴露出来进行设置,但是毕竟是有限的,大部分的时候用户希望的是除了这些自带的本身的属性以外,还能自定义一些用户的属性绑定到这个控件上,比如设备编号、设备名称等,而且这些信息要求可以保存的,下次自动加载并且应用进去。
弱属性机制在Qt中异常的强大,这种用户属性的处理必须要用到弱属性,弱属性要读取直接用widget->property(name)即可,要赋值直接widget->setProperty(name, value)即可,使用非常方便,支持多个,如果名字相当值取最后那个,会自动覆盖,所以想设备编号、设备名称这类的用户属性绑定到该控件以后,在程序中其他地方就可以直接用property函数取出来进行处理了。为此我专门做了个自定义控件属性设计器,支持中英文属性自动映射,有专门的一个系列的文章讲解那个属性设计器。
皮肤开源:https://gitee.com/feiyangqingyun/QWidgetDemo https://github.com/feiyangqingyun/QWidgetDemo
文件名称:styledemo
体验地址:https://gitee.com/feiyangqingyun/QWidgetExe https://github.com/feiyangqingyun/QWidgetExe
文件名称:bin_sams.zip
void frmConfigXml::initPlugin()
{
//载入默认的插件,debug则加载带d结尾的文件
QString appPath = qApp->applicationDirPath();
#ifdef QT_NO_DEBUG
QString str = "";
#else
QString str = "d";
#endif
#if defined(Q_OS_WIN)
QString fileName = QString("%1/quc%2.dll").arg(appPath).arg(str);
#elif defined(Q_OS_UNIX)
QString fileName = QString("%1/libquc%2.so").arg(appPath).arg(str);
#elif defined(Q_OS_MAC)
QString fileName = QString("%1/libquc%2.dylib").arg(appPath).arg(str);
#endif
//载入默认的自定义控件插件
loadPlugin(fileName);
//载入默认的控件xml数据
openFile(appPath + "/quc.xml");
//自动滚动条滚到指定位置
qApp->processEvents();
ui->listWidget->verticalScrollBar()->setValue(0);
}
void frmConfigXml::loadPlugin(const QString &fileName)
{
openPlugin(fileName);
}
void frmConfigXml::openPlugin(const QString &fileName)
{
//先清空原有的控件
qDeleteAll(listWidgets);
listWidgets.clear();
listNames.clear();
ui->listWidget->clear();
//加载自定义控件插件集合信息,包括获得类名+图标
QPluginLoader loader(fileName);
if (loader.load()) {
QObject *plugin = loader.instance();
//获取插件容器,然后逐个遍历容器找出单个插件
QDesignerCustomWidgetCollectionInterface *interfaces = qobject_cast(plugin);
if (interfaces) {
listWidgets = interfaces->customWidgets();
int count = listWidgets.count();
for (int i = 0; i < count; i++) {
//取出图标和类名
QIcon icon = listWidgets.at(i)->icon();
QString className = listWidgets.at(i)->name();
QListWidgetItem *item = new QListWidgetItem(ui->listWidget);
item->setText(className);
item->setIcon(icon);
listNames << className;
}
}
//获取所有插件的类名
const QObjectList objList = plugin->children();
foreach (QObject *obj, objList) {
QString className = obj->metaObject()->className();
//qDebug() << className;
}
}
}
void frmConfigXml::openFile(const QString &fileName)
{
//如果控件列表没有则不用继续
if (ui->listWidget->count() == 0) {
return;
}
//打开文件
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
return;
}
//将文件填充到dom容器
QDomDocument doc;
if (!doc.setContent(&file)) {
file.close();
return;
}
file.close();
listSelect.clear();
listUserProperty.clear();
xmlName = fileName;
//先清空原有控件
QList widgets = ui->centralwidget->findChildren();
qDeleteAll(widgets);
widgets.clear();
//先判断根元素是否正确
QDomElement docElem = doc.documentElement();
if (docElem.tagName() == "canvas") {
QDomNode node = docElem.firstChild();
QDomElement element = node.toElement();
while(!node.isNull()) {
//控件名称
QString name = element.tagName();
//取出当前控件在控件列表中的索引,如果不存在则意味着配置文件中的该控件不存在了
int index = listNames.indexOf(name);
if (index < 0) {
continue;
}
//存储控件的坐标位置和宽度高度
int x, y, width, height;
//存储自定义控件属性
QList > propertys;
//存储控件自定义属性
QStringList userProperty;
//节点名称不为空才继续
if (!name.isEmpty()) {
//遍历节点的属性名称和属性值
QDomNamedNodeMap attrs = element.attributes();
for (int i = 0; i < attrs.count(); i++) {
QDomNode node = attrs.item(i);
QString nodeName = node.nodeName();
QString nodeValue = node.nodeValue();
//qDebug() << name << nodeName << nodeValue;
//优先取出坐标+宽高属性,这几个属性不能通过设置弱属性实现
if (nodeName == "x") {
x = nodeValue.toInt();
} else if (nodeName == "y") {
y = nodeValue.toInt();
} else if (nodeName == "width") {
width = nodeValue.toInt();
} else if (nodeName == "height") {
height = nodeValue.toInt();
} else if (nodeName.startsWith("user-")) {
//取出user-开头的自定义属性
nodeName = nodeName.split("-").last();
userProperty << QString("%1|%2").arg(nodeName).arg(nodeValue);
} else {
QVariant value = QVariant(nodeValue);
//为了兼容Qt4,需要将颜色值的rgba分别取出来,因为Qt4不支持16进制字符串带透明度
//#6422a3a9 这种格式依次为 argb 带了透明度的才需要特殊处理
if (nodeValue.startsWith("#") && nodeValue.length() == 9) {
bool ok;
int alpha = nodeValue.mid(1, 2).toInt(&ok, 16);
int red = nodeValue.mid(3, 2).toInt(&ok, 16);
int green = nodeValue.mid(5, 2).toInt(&ok, 16);
int blue = nodeValue.mid(7, 2).toInt(&ok, 16);
value = QColor(red, green, blue, alpha);
}
propertys.append(qMakePair(nodeName, value));
}
}
}
//qDebug() << name << x << y << width << height;
//根据不同的控件类型实例化控件
int countWidget = listWidgets.count();
int countProperty = propertys.count();
for (int i = 0; i < countWidget; i++) {
QString className = listWidgets.at(i)->name();
if (name == className) {
//生成对应的控件
QWidget *widget = createWidget(i);
//逐个设置自定义控件的属性
for (int j = 0; j < countProperty; j++) {
QPair property = propertys.at(j);
QString name = property.first;
QVariant value = property.second;
widget->setProperty(name.toStdString().c_str(), value);
}
//设置控件坐标及宽高
widget->setGeometry(x, y, width, height);
//实例化选中窗体跟随控件一起
newSelect(widget, userProperty);
break;
}
}
//移动到下一个节点
node = node.nextSibling();
element = node.toElement();
}
}
}
void frmConfigXml::saveFile(const QString &fileName)
{
//如果控件列表没有则不用继续
if (ui->listWidget->count() == 0) {
return;
}
QFile file(fileName);
if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) {
return;
}
//以流的形式输出文件
QTextStream stream(&file);
//构建xml数据
QStringList list;
//添加固定头部数据
list << "";
//添加canvas主标签,保存宽高和背景图片,还可以自行添加其他属性
list << QString("";
//写入文件
QString data = list.join("\n");
stream << data;
file.close();
}