效果演示
本次所做的工作主要是,由于物体属性的UI控件基本就那么几种类型,所以可以将UI生成、事件绑定等基本代码通过宏的形式来自动生成,可以减少开发时的编码量,达到快速迭代开发的目的。如上图右侧的面板,均通过半自动化生成。
得到上图最右边所示界面,仍然需要一些代码,但主要是描述内容性质的代码,如下:
// .h
// .cpp
以上描述性代码主要包括了三个部分:
(1) 注册部分:在头文件中注册用到的控件。不同类型的控件使用不同的宏,注册名字在同一类中不可重复。
(2) 初始化部分:在构造函数中做描述性初始化,如指定描述文本、取值范围一些基本设定。
(3) 值绑定部分(也可在初始化中绑定,也可分离):在合适的位置绑定数据,之后UI相关操作会自动修改绑定的值。
具体实现
实现的基本思路将可以重复的编码放到宏中,通过宏定义来完成一系列复杂操作。
如对于一个可以控制某一值的滑动条而言,我们在编写的时候就需要完成以下这些操作:
(1) 生成一个滑动条,一个描述文本,一个值文本。
(2) 设定这些UI的布局方式。
(3) 初始化UI的基本内容,如滑动条的滑块位置,取值范围等。
(4) 绑定滑动后值改变的信号与槽,并在槽中修改值、值文本等内容。
现在,所有的这些操作都封装在宏中,所以,我们只需要定义宏,而无需再去做以上这些重复操作了,如实例代码中的:
DECLARE_SLIDER(rough)
首先,我们需要定义一个基类,使得之后的面板从该类中继承,以定义一些基本的属性和操作:
class QVBoxLayout;
using namespace std;
class PropertyWidget : public QWidget
{
public:
PropertyWidget();
protected:
QVBoxLayout* vlayout;
void SetLabel(QString label);
};
#include "propertywidget.h"
#include <QVBoxLayout>
PropertyWidget::PropertyWidget()
{
vlayout = new QVBoxLayout;
setLayout(vlayout);
}
void PropertyWidget::SetLabel(QString label)
{
QLabel* l = new QLabel(label);
vlayout->addWidget(l);
}
之后,通过宏定义实现基本控件。由于基本控件数量比较多,目前我只实现了一部分控件,其余控件会随着我个人的需求完善后补充到文章中。
此处数据绑定的设计可优化,目前需要提供数据的裸指针,但是有时候我们无法访问到裸指针,而只能访问到对应的set,get方法,可改为传入set,get两个函数完成值的访问和修改:
slider控件
#define DECLARE_SLIDER(name)\
protected: \
QSlider* name##slider; \
QLabel* name##label; \
float name##min,name##max; \
float* name##data; \
public: \
void name##Init(string na,float mi,float ma,float* da = nullptr) \
{ \
name##min = mi,name##max = ma,name##data = da; \
QHBoxLayout* hlayout = new QHBoxLayout(); \
QLabel* text = new QLabel(QString::fromStdString(na)); \
name##slider = new QSlider(); \
\
name##slider->setOrientation(Qt::Horizontal); \
if(da) \
name##label = new QLabel(QString::number(static_cast<double>(*da))); \
else \
name##label = new QLabel(); \
hlayout->addWidget(text,1); \
hlayout->addWidget(name##slider,2); \
hlayout->addWidget(name##label,1); \
vlayout->addLayout(hlayout); \
name##slider->setMinimum(0); \
name##slider->setMaximum(100); \
connect(name##slider,&QSlider::valueChanged,this,[&](int value) { \
float t = static_cast<float>(value) / 100 *(name##max - name##min) + name##min; \
char buffer[100]; \
sprintf(buffer,"%.2f",static_cast<double>(t)); \
name##label->setText(QString(buffer)); \
if(name##data)*name##data = t;}); \
} \
void name##SetData(float* da) \
{ \
name##data = da; \
if(da) { \
char buffer[100]; \
sprintf(buffer,"%.2f",static_cast<double>(*da)); \
name##label->setText(QString(buffer)); \
name##slider->setValue(static_cast<int>((*da - name##min)/(name##max - name##min)*100)); \
} \
} \
checkbox控件
#define DECLARE_CHECKBOX(name) \
protected: \
QCheckBox* name##check; \
bool* name##data; \
public: \
void name##Init(string na, bool* da = nullptr) \
{ \
name##check = new QCheckBox(QString::fromStdString(na)); \
if(da) name##check->setChecked(*da); \
vlayout->addWidget(name##check); \
connect(name##check,&QCheckBox::stateChanged,this,[&](int value) { \
*name##data = value != 0; \
}); \
} \
void name##SetData(bool* da) \
{ \
name##data = da; \
if(da) { \
name##check->setChecked(*da); \
} \
}
image控件
#define DECLARE_IMAGE(name) \
protected: \
QImage* name##image; \
QLabel* name##label; \
QPushButton* name##btn; \
QString name##path; \
public: \
void name##Init(string na, QImage* image = nullptr) \
{ \
QLabel* label = new QLabel(QString::fromStdString(na)); \
name##image = image; \
QHBoxLayout* hlayout = new QHBoxLayout(); \
name##label = new QLabel(); \
name##btn = new QPushButton("."); \
name##btn->setFixedSize(20,20); \
hlayout->addWidget(label); \
hlayout->addWidget(name##label); \
hlayout->addWidget(name##btn); \
vlayout->addLayout(hlayout); \
\
connect(name##btn,&QPushButton::clicked,this,[&]() { \
QString fileName = QFileDialog::getOpenFileName(this, tr("File"), \
"D:",tr("img(*png *jpg *tga);;")); \
if(name##image && fileName!="") \
{ \
name##image->load(fileName); \
QPixmap pixmap = QPixmap::fromImage(*name##image); \
pixmap = pixmap.scaled(QSize(40,40)); \
name##label->setPixmap(pixmap); \
} \
}); \
} \
void name##SetData(QImage* image) \
{ \
name##image = image; \
if(image) name##label->setPixmap(QPixmap::fromImage(*image)); \
} \
color控件
#define DECLARE_COLOR(name) \
protected: \
QPushButton* name##colorBtn; \
QLabel* name##label; \
Vector3f* name##data; \
public: \
void name##Init(string na, Vector3f* da = nullptr) \
{ \
name##colorBtn = new QPushButton(); \
name##colorBtn->setFixedSize(40,20); \
name##label = new QLabel(QString::fromStdString(na)); \
QHBoxLayout* hlayout = new QHBoxLayout(); \
if(da) \
{ \
char buffer[500]; \
sprintf(buffer,"background: rgb(%d,%d,%d)",int(da->x*255),int(da->y*255), \
int(da->z*255)); \
name##colorBtn->setStyleSheet(buffer); \
} \
hlayout->addWidget(name##label); \
hlayout->addWidget(name##colorBtn); \
vlayout->addLayout(hlayout); \
connect(name##colorBtn,&QPushButton::clicked,this,[&]() { \
QColor color = QColorDialog::getColor(Qt::red, this,tr("Color"), \
QColorDialog::ShowAlphaChannel); \
if(name##data)*name##data = Vector3f(1.0f*color.red()/255,1.0f*color.green()/255, \
1.0f*color.blue()/255); \
char buffer[500]; \
sprintf(buffer,"background: rgb(%d,%d,%d)",color.red(),color.green(),color.blue()); \
name##colorBtn->setStyleSheet(buffer); \
}); \
} \
void name##SetData(Vector3f* da) \
{ \
name##data = da; \
if(da) \
{ \
char buffer[500]; \
sprintf(buffer,"background: rgb(%d,%d,%d)",int(da->x*255),int(da->y*255), \
int(da->z*255)); \
name##colorBtn->setStyleSheet(buffer); \
} \
} \