프로퍼티는 객체지향 프로그래밍에서 객체를 모델링하고 추상화 할 때 정의한다. 예를 들어 QWidget
이라면 그 폭( width )이나 높이( height ), QLabel
이면 그 문자열( text )등의 프로퍼티가 있다.
일반적으로 C++ 에서는 이러한 프로퍼티의 값은 getter 라고 하는 함수를 통해 얻고 setter 라고 하는 함수로 설정한다. 예를 들어 QLabel의 경우 QLabel::text()
가 getter 이고 QLabel::setText()
가 setter 가 된다. 이와 같이 C++ 에서는 클래스 메서드를 통해서 프로퍼티를 다루는 반면 QML 로 액세스 하는 경우에는 getter / setter 가 QML엔진측에 노출되고 자동적으로 사용되어 QML에서 속성은 변수처럼 사용한다.
Qt는 Meta-Object 시스템과 유기적으로 작동하는 프로퍼티를 선언하기 위해 특별한 매크로를 제공한다.
C++ 클래스에서 속성을 만들려면 Q_PROPERTY
매크로를 사용하면 된다. 최소한 필요한 것은 프로퍼티의 type 과 getter 이다. 읽기와 쓰기를 모두 허용하려면 setter 도 필요하다. QML로 바인딩하는 경우 값이 변경되었음을 알리는 SIGNAL도 선언되어야 한다. 기본 서식은 다음과 같다. 이 기본 형식은 Qt 5와 Qt 6에서 모두 공통이다.
Q_PROPERTY(type name READ name WRITE setname NOTIFY nameChanged)
Qt 5 에서 사용 할 수 있는 Q_PROPERTY
의 전체 옵션은 다음과 같다.
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL]
[REQUIRED])
다음은 Qt 6 에서 유효한 Q_PROPERTY
매크로다. BINDABLE
이 추가된 점을 빼면 Qt 5 와 큰 차이는 없다.
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int | REVISION(int[, int])]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[BINDABLE bindableProperty]
[CONSTANT]
[FINAL]
[REQUIRED])
예를 들어 int 형의 count 라고 하는 프로퍼티을 가지는 Counter 클래스를 생각해보자. getter 로서 count()
메서드를 선언하고 setter 로서 setCount()
, SIGNAL 에 countChanged()
를 사용하는 경우 다음 예제와 같이 작성할 것이다.
class Counter : public QObject
{
Q_OBJECT
Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
public:
Counter(QObject *parent = 0);
int count() const
{
return m_count;
}
void setCount(int value)
{
if (m_count != value) {
m_count = value;
emit countChanged();
}
}
singals:
void countChanged();
private:
int m_count;
};
프로퍼티명과 getter 의 이름은 같게 하는 것이 일반적이다. 또 NOTIFY
로 지정하는 SIGNAL 은 그 밖에도 동시에 변화하는 프로퍼티가 있으면 동일한 것을 공유해도 괜찮다. 속성 type은 QVariant
에서 지원하는 모든 유형이거나 사용자 정의 유형일 수 있다. 사용자 정의 유형은 해당 값을 QVariant 개체에 저장할 수 있도록 Q_DECLARE_METATYPE()
매크로를 사용하여 등록해야 한다.
이와 같이 Q_PROPERTY
매크로를 추가하고 관련된 메소드를 선언·정의해 프로퍼티로서 취급할 수 있게 된다. 이 프로퍼티 시스템은 QML 과 C++ 의 양쪽을 사용하는 애플리케이션에서 상호 작용하기 위해 거의 필수적인 편리한 기능이다. 다만, 프로퍼티의 수가 적으면 문제 없으나 수가 늘어나면 getter 나 setter, SIGNAL 의 생성이 매우 귀찮아져 온다. 내용도 고정되어 있어서 쓰기가 재미가 없는 경우가 많다. 최근 Qt Creator에서는 Q_PROPERTY
매크로에 커서를 놓고 리팩토링 기능으로 누락된 메서드와 멤버 변수를 생성할 수 있지만 어쨋든 귀찮은 일이다. 그래서 등장한 것이 MEMBER
지정자다.
Qt 5.1 에서 도입된 MEMBER
는 Q_PROPERTY
매크로의 기술시에 READ
나 WRITE
로 getter / setter 를 지정하는 대신에 대응하는 멤버 변수를 지정한다. 다음은 전의 Counter 클래스를 MEMBER
를 사용해 재작성한 것이다.
class Counter : public QObject
{
Q_OBJECT
Q_PROPERTY(int count MEMBER m_count NOTIFY countChanged)
public:
Counter(QObject *parent = 0);
singals:
void countChanged();
private:
int m_count;
};
MEMBER
를 사용하면 getter / setter 가 불필요하게 되어 메소드를 작성할 필요가 없어진다. 상당한 처리는 moc 가 생성해 메타 오브젝트 시스템에 짜 넣어진다. 값을 변경할 때 통지를 하고 싶은 경우에는 SIGNAL 의 작성과 NOTIFY
로의 지정은 필요하지만 SIGNAL 의 emit
자체는 역시 moc 가 담당한다.
MEMBER
는 READ
또는 WRITE
지정자와 함께 사용할 수도 있다. READ 와 MEMBER 를 지정하는 경우에는 getter 를 작성할 필요가 있지만 setter 는 moc 가 작성한다. 반대로 WRITE 와 MEMBER 를 지정하는 경우는 setter 를 작성할 필요가 있지만 getter 는 moc 가 작성한다. MEMBER
와 READ
, WRITE
의 모두를 지정하는 것은 의미가 없기 때문에 지원의 대상 밖이다. setter 로 유효성 체크를 실시하고 싶은 경우등에 setter 만 작성하고 getter 는 생략하고 MEMBER
를 이용하는 것이 주된 사용법이다.
속성의 이름을 제외하고는 소유 클래스에 대해 아는 것이 없어도(getter / setter 역할을 하는 메서드가 없어도) 일반 함수 QObject::property()
및 QObject::setProperty()
를 사용하여 속성을 읽고 쓸 수 있다.
QPushButton *button = new QPushButton;
button->setDown(true);
QObject *object = button;
object->setProperty("down", true);
WRITE 접근자를 통해 속성에 액세스하는 것이 더 빠르고 컴파일 시간에 더 나은 진단을 제공하기 때문에 더 낫지만 이 방법으로 속성을 설정하려면 컴파일 시간에 클래스에 대해 알아야 한다. 이름으로 속성에 액세스하면 컴파일 타임에 알지 못하는 클래스에 액세스할 수 있다. QObject, QMetaObject 및 QMetaProperties 를 쿼리하여 런타임에 클래스의 속성을 알 수 있다.
QObject *object = ...
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i=0; i<count; ++i) {
QMetaProperty metaproperty = metaobject->property(i);
const char *name = metaproperty.name();
QVariant value = object->property(name);
...
}
위의 스니펫에서 QMetaObject::property()
는 알 수 없는 클래스에 정의된 각 속성에 대한 메타데이터를 가져오는 데 사용된다. 속성 이름은 메타데이터에서 가져와 QObject::property()
에 전달되어 현재 객체의 속성 값을 가져온다. 다시 말하지만 property()
와 setProperty()
는 퍼포먼스적으로는 적절하게 생성된 getter / setter 보다 안 좋기 때문에 C++ 측으로부터 빈번하게 액세스 하는 프로퍼티에는 적합하지 않을 수도 있다.
QObject::setProperty()
를 사용하면 런타임에 클래스의 인스턴스에 새 속성을 추가할 수도 있다. 주어진 이름의 속성이 QObject에 존재하지 않는 경우(즉, Q_PROPERTY
로 선언되지 않은 경우), 주어진 이름과 값을 가진 새 속성을 자동으로 QObject에 추가한다.
MyClass myclass; // MyClass is Subclass of QObject.
myclass.setProperty("text", "Hello world");
qDebug() << myclass.property("text").toString();
물론 속성 이름과 잘못된 QVariant 값을 QObject::setProperty()
에 전달하여 인스턴스에서 속성을 제거할 수도 있다(QVariant의 기본 생성자는 잘못된 QVariant를 생성한다).
myClass.setProperty("text", QVariant());