目录
- 写在前面
- 实现历程
- 传统定义方式
- 预想的方式(事实上有一点点区别)
- 例程
- mainwindow.h
- mainwindow.cpp
- main.cpp
- 执行结果
- 如上事实的使用方法
写在前面
上一篇写了pyqt如何更加便利地定义动态属性,关于C++版的其实在我刚接触Qt不久就想过并做了一些尝试,但死活通过不了Qt元对象编译,后来机缘巧合之下气急败坏地乱写一通竟然给我编过了😂,看代码怎么说呢,算是钻了元对象编译器地空子吧,有点歪门邪道的感觉了,但是确实是简化了,我是实用主义。
实现历程
传统定义方式
using type = int;
class Any: public QObject
{
Q_OBJECT
Q_PROPERTY(type name READ name WRITE setname NOTIFY nameChanged)
public:
void setname(const type& name)
{
if (m_name == name) return;
m_name = name;
emit nameChanged();
}
type name() const
{
return m_name;
}
signals:
void nameChanged();
private: type m_name;
}
关于Q_PROPERTY这个宏我就不赘述了,传统方式可以写好Q_PROPERTY后用qtcreator右键菜单重构选项生成实现代码,但是换一个编辑器或者要修改的时候就有点恼火了。
简化地思路其实很简单,相信很多人应该跟我有同样的想法,将传统定义的setter和getter以及信号一起打包到一个宏里边去就行了。
预想的方式(事实上有一点点区别)
#define NOTIFY_PROPERTY(type, name) \
Q_PROPERTY(type name READ name WRITE set##name NOTIFY name##Changed)\
public: void set##name(const type& value)\
{\
if (m_##name == value)\
{\
return;\
}\
emit name##Changed();\
}\
public: type name() const\
{\
return m_##name;\
}\
signals:\
void name##Changed();\
private: type m_##name;
using type = int;
class Any: public QObject
{
Q_OBJECT
NOTIFY_PROPERTY(type, name)
}
- 注意:这样是通不过编译的,原因可能在于元对象编译器在生成时没有找到”signals“这个关键字,但奇怪地是它能识别到Q_PROPERTY这个宏有大佬,知道原因可以在评论区解释一下,如果这个宏里不定义信号地话这么使用是OK的,但这不是我想要地最终结果。
于是乎有一段时间我就放弃了,直到前一阵子我脑子灵光一闪,如果我显示地把”signals“定义到宏外面,然后信号函数定义在宏里面是不是可行呢,虽然看上去很奇怪,但是试了确实可以,属于我是和元对象编译器各退一步了。
例程
mainwindow.h
#ifndef DEF_NOTIFY_CONNECT
#include <functional>
template<typename T>
struct ptr_traits;
template<typename className>
//!
//! \brief The ptr_traits struct
//! 可以像这样使用 T* p = new ptr_traits<T*>::class_name; 用于提取指针的类型
//!
struct ptr_traits<className*>
{
using class_name = className;
};
#define DEF_GETTER(type, name, ...)\
public: type name() const\
{\
__VA_ARGS__\
return m_##name;\
}
#define DEF_SETTER(type, name, ...)\
public: void set##name(type value)\
{\
if (m_##name == value)\
return;\
m_##name = value;\
__VA_ARGS__\
}
#define DEF_MEMBER(type, name, ...)\
private:\
int m_##name;\
__VA_ARGS__
#define DEF_PROPERTY(type, name, ...) \
DEF_MEMBER(type, name)\
DEF_GETTER(type, name)\
DEF_SETTER(type, name)\
__VA_ARGS__
#define DEF_NOTIFY_CONNECT(type, name)\
public:\
template<typename T>\
void on_##name##_changed(T slotFunc)\
{\
connect(this, &ptr_traits<decltype(this)>::class_name::name##Changed, slotFunc);\
}\
template<typename T>\
void on_##name##_changed(QObject* recever,T slotFunc)\
{\
connect(this, &ptr_traits<decltype(this)>::class_name::name##Changed, recever, slotFunc);\
}
#define DEF_NOTIFY_PROPERTY(type, name, ...) \
void name##Changed();\
DEF_MEMBER(type, name)\
DEF_GETTER(type, name)\
DEF_SETTER(type, name, emit this->name##Changed();)\
Q_PROPERTY(type name READ name WRITE set##name NOTIFY name##Changed)\
DEF_NOTIFY_CONNECT(type, name)\
__VA_ARGS__\
static_assert(true,"request a ';'")
#endif // DEF_NOTIFY_PROPERTY
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
signals: DEF_NOTIFY_PROPERTY(int, name);
signals: DEF_NOTIFY_PROPERTY(int, name2);
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
w.on_name_changed(&w, [&w]()//简化版的connect指定接收者
{
qDebug() << QStringLiteral("测试1:") << w.name();
});
w.on_name2_changed([&]()//简化版的connect不指定接收者
{
qDebug() << QStringLiteral("测试2:") << w.name2();
w.setProperty("name", 3);
});
w.setProperty("name", 1);
w.setProperty("name2", 2);
return a.exec();
}
执行结果
如上事实的使用方法
如上mainwindow.h中使用DEF_NOTIFY_PROPERTY宏定义了两个整形属性。