New Signal Slot Syntax/tr: Difference between revisions
(Revision 2) |
(Use native name for language) |
||
(One intermediate revision by the same user not shown) | |||
Line 1: | Line 1: | ||
[[Category:Ogreticiler]] | |||
= Yeni Signal Slot Sözdizimi (Qt5) = | = Yeni Signal Slot Sözdizimi (Qt5) = | ||
Latest revision as of 16:24, 25 November 2016
Yeni Signal Slot Sözdizimi (Qt5)
Aşağıdaki bağlantılar yeni signal-slot sözdizimi henüz geliştirilirken kaleme alınmıştır. Yeni signal-slot sözdizimi Qt5 ile birlikte kullanıma sunulmuştur.
- Yeni sözdizimini sunan blog yazısı
- Nasıl çalışır (gerçekleme detayları)
Not: Eski string tabanlı sözdizimi geçerliliğini korumaktadır.
Durum
- qtbase/master dalı ile birleştirilmiştir
Qt5'te Signal-Slot Bağlama
Qt5 signale bağlanmak için birden çok yöntem sunar.
Eski sözdizimi
Qt5, QObject veya QObject'ten türetilmiş (QWidget dahil) herhangi bir sınıfta tanımlanmış signal ve slotların birbirine bağlanması için eski string tabanlı sözdizimi kullanımına hala destek vermektedir.
connect(sender, SIGNAL (valueChanged(QString,QString)),
receiver, SLOT (updateValue(QString)) );
Yeni: QObject üyesine bağlanma
İşte Qt5 ile sunulan iki QObject'in birbirine bağlanmasını sağlayan yeni sözdizimi:
connect(sender, &Sender::valueChanged,
receiver, &Receiver::updateValue );
artıları
- Signal ve slotların varlığının ve tür uyuşmasının veya Q_OBJECT makrosunun eksik olup olmadığının kontrolünün derleme zamanında yapılması
- Argümanlar, typedef tanımlamaları veya farklı isim uzayı tanımlayıcıları olabilir.
- Örtük tür dönüşümü (implicit conversion) olduğu durumlarda otomatik olarak dönüşümün (casting) gerçekleştirilmesi (örn. QString türünün QVariant türüne dönüştürülmesi)
- QObject'in herhangi bir üye fonksiyonuna bağlanabilmesi, sadece slot fonksiyonlara bağlanmaması
eksileri
- Daha karmaşık sözdizimi (nesnenizin türünü belirtmek zorundasınız)
- Overload durumunda hangi fonksiyonun kullanılacağını belirtmek için çok daha karmaşık sözdiziminin gerekli olması (bakınız)
- Slot fonksiyondaki varsayılan argümanlar artık desteklenmiyor.
Yeni: basit bir fonksiyona bağlanma
Yeni sözdizimi sadece QObject'ten türetilen nesnelerin fonksiyonlarına değil sade fonksiyonlara da bağlanılabilir:
connect(sender, &Sender::valueChanged, someFunction);
artıları
- tr1::bind deyimi ile kullanılabilmesi:
connect(sender, &Sender::valueChanged,
tr1::bind(receiver, &Receiver::updateValue, "senderValue", tr1::placeholder::_1));
- c++11 lambda deyimleri ile kullanılabilmesi:
connect(sender, &Sender::valueChanged, [=](const QString &newValue) {
receiver->updateValue("senderValue", newValue);
});
eksileri
- 'Alıcı' yok edildiğinde (destroy) otomatik olarak signal slot bağlantısı koparılmaz çünkü 'alıcı' sadece QObject'ten türetilmeyen bir fonksiyon nesnesidir (functor). Buna rağmen, 5.2 sürümünden itibaren "bağlam nesnesi" (context object) ekleyen yeni bir overload sunulmuştur. Bağlam nesnesi yok edildiğinde (destroy) bağlantı kopar (bağlam nesnesi aynı zamanda thread benzeşimi -thread affinity- için de kullanılır: bağlam olarak kullanılan nesnenin olay döngüsünün (event loop) bulunduğu threadde çağrılacaktır).
Bağlantıyı koparma
Qt5 bağlantıların sonlandırılması konusunda da birtakım değişiklikler getirmiştir.
Eski yöntem
Aşağıdaki durumlar haricinde eski string tabanlı sözdizimini (SIGNAL, SLOT) kullanarak signal ve slot arasındaki bağlantıyı koparabilirsiniz:
- eski sözdizimi ile bağladıysanız veya
- bir signalden tüm slotları asterisk (*) karakterini kullanarak koparmak istediğinizde
Fonksiyon göstericileri (function pointers) simetrik olduğunda
disconnect(sender, &Sender::valueChanged,
receiver, &Receiver::updateValue );
Sadece fonksiyon göstericileri ile simetrik çağrı kullanarak bağladığınızda çalışır. Statik fonksiyonlar, fonksiyon nesneleri (functor) ve lambda fonksiyonları ile çalışmaz.
QMetaObject::Connection kullanma
QMetaObject::Connection m_connection;
//…
m_connection = QObject::connect(…);
//…
QObject::disconnect(m_connection);
Lambda fonksiyonlar ve fonksiyon nesneleri (functor) dahil tüm durumlarda çalışır.
Asenkronluk artık daha kolay
C11 ile kodu satıriçinde (inline) tutmak artık mümkündür.
void doYourStuff(const QByteArray &page)
{
QTcpSocket *socket = new QTcpSocket;
socket->connectToHost("qt.io", 80);
QObject::connect(socket, &QTcpSocket::connected, [socket, page] () {
socket->write(QByteArray("GET " + page + ""));
});
QObject::connect(socket, &QTcpSocket::readyRead, [socket] () {
qDebug()<< "GOT DATA "<< socket->readAll();
});
QObject::connect(socket, &QTcpSocket::disconnected, [socket] () {
qDebug()<< "DISCONNECTED ";
socket->deleteLater();
});
QObject::connect(socket, static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>
(&QAbstractSocket::error), [socket](QAbstractSocket::SocketError) {
qDebug()<< "ERROR " << socket->errorString();
socket->deleteLater();
});
}
İşte bu da kodu ait olduğu yerde tutarak, olay döngüsüne (event loop) tekrardan girmeyen bir QDialog örneğidir:
void Doc::saveDocument() {
QFileDialog *dlg = new QFileDialog();
dlg->open();
QObject::connect(dlg, &QDialog::finished, [dlg, this](int result) {
if (result) {
QFile file(dlg->selectedFiles().first());
// …
}
dlg->deleteLater();
});
}
QHttpServer sınıfı kullanan başka bir örnek: http://pastebin.com/pfbTMqUm
Hata raporu
GCC ile test edilmiştir.
Neyse ki Qt Creator gibi IDE'ler fonksiyon adlandırmayı kolaylaştırmaktadır.
Q_OBJECT makrosunu unutun
#include <QtCore>
class Goo : public QObject {
Goo() {
connect(this, &Goo::someSignal, this, &QObject::deleteLater);
}
signals:
void someSignal();
};
qobject.h: In member function 'void QObject::qt_check_for_QOBJECT_macro(const T&&) const [with T = Goo]':
qobject.h:535:9: instantiated from 'static typename QtPrivate::QEnableIf<((int)
(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename
QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*,
Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(), Func2 = void (QObject::*)(), typename
QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename
QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = QObject]'
main.cc:4:68: instantiated from here
qobject.h:353:5: error: void value not ignored as it ought to be
make: '''* [main.o] Error 1
Tür uyuşmazlığı
#include <QtCore>
class Goo : public QObject {
Q_OBJECT
public:
Goo() {
connect(this, &Goo::someSignal, this, &Goo::someSlot1); //hata
connect(this, &Goo::someSignal, this, &Goo::someSlot2); //çalışır
}
signals:
void someSignal(QString);
public:
void someSlot1(int);
void someSlot2(QVariant);
};
qobject.h: In static member function 'static typename QtPrivate::QEnableIf<((int)
(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename
QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*,
Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(QString), Func2 = void (Goo::*)(int), typename
QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename
QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = Goo]':
main.cc:6:62: instantiated from here
qobject.h:538:163: error: no type named 'IncompatibleSignalSlotArguments' in 'struct
QtPrivate::CheckCompatibleArguments<QtPrivate::List<QString, void>, QtPrivate::List<int, void>, true>'
qobject.h: In static member function 'static void QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::call
(QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function, Obj*, void*) [with Args = QtPrivate::List<QString, void>,
Obj = Goo, Ret = void, Arg1 = int, QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function = void (Goo::*)(int)]':
qobject.h:501:13: instantiated from 'void QObject::QSlotObject<Func, Args>::call(QObject*, void**) [with Func =
void (Goo::*)(int), Args = QtPrivate::List<QString, void>, QObject = QObject]'
main.cc:14:2: instantiated from here
qobject.h:109:13: error: cannot convert 'QtPrivate::RemoveRef<QString>::Type' to 'int' in argument passing
make: *** [main.o] Error 1
Açık Sorular
Slot fonksiyondaki varsayılan argümanlar
Aşağıdaki gibi bir kodunuz varsa:
class A : public QObject { Q_OBJECT
public slots:
void someSlot(int foo = 0);
};
Eski yöntem bu slotu argümanı olmayan signale bağlamanıza imkan tanırdı. Fakat template kodda varsayılan argüman olup olmadığını bilemem. Bu yüzden bu özellik iptal edildi.
Slotta signalden daha fazla argüman olduğunda eski yönteme geri dönen bir özellik gerçeklemiştik. Fakat bu özellikle hafif tutarsızdı çünkü eski yöntemde tür kontrolü ve tür dönüşümü yapılmıyordu. Bu özellik anadal ile birleştirilen yamadan çıkartıldı.
Overload
Asenkronluk artık daha kolay bölümündeki örnekte gördüğünüz üzere, QAbstractSocket::error signaline bağlanmak göze hoş gelmiyordu çünkü bu signal overloada sahipti ve overload edilmiş fonksiyonda açıkça argüman türlerinin belirtilmesi (explicit casting) gerekiyordu. Örneğin daha önceden signal-slot bağlantısını şu şekilde yapmıştık:
connect(mySpinBox, SIGNAL(valueChanged(int)), mySlider, SLOT(setValue(int));
ve bu kolaylıkla yeni sözdizimine çevrilemez:
connect(mySpinBox, &QSpinBox::valueChanged, mySlider, &QSlider::setValue);
...çünkü QSpinBox valueChanged() adında iki farklı argümana sahip bir signaldir. Yani yeni sözdizimi ile yapılan bağlantı şöyle olmalıdır:
connect(mySpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), mySlider, &QSlider::setValue);
Bazı makrolar bu sözdizimi kalabalığından kurtulmanıza yardımcı olabilir (c11 ile gelen typeof eklentisi)
Herhalde en iyi çözüm signal veya slotları overload etmemeniz olacaktır …
… fakat biz geçmişteki Qt sürümlerinde fonksiyonun adresinin bir kullanımı olmadığı için overload fonksiyonlar gerçeklemiştik. Şimdi kaynak kodu uyumluluğunu bozmadan bunu gerçeklemek imkansız olacağından ne yazık ki kullanmanız gereken durumlar olacaktır.
Bağlantıyı koparma
QMetaObject::Connection sınıfının disconnect() fonksiyonu olması gerekir mi?
Bir diğer problemde signal slot bağlantısı yaparken closure kullanıldığında closure içindeki bazı nesnelerin bağlantılarının otomatik olarak kopmamasıydı. Ya biri bağlantı koparılırken kullanılması için nesneleri listeye ekleyecekti (birinci yöntem) veya QMetaObject::Connection::require gibi yeni bir yöntem (ikinci yöntem) tanımlanacaktı:
auto c = connect(sender, &Sender::valueChanged, [=](const QString &newValue) {
receiver->updateValue("senderValue", newValue);
} , QList<QObject> { receiver } ); // birinci yöntem
c.require(receiver); // ikinci yöntem
İkinci yöntem hala geçerli mi? QMetaObject::Connection::require() gibisi yok.
Geri Çağrımlar (Callbacks)
QHostInfo::lookupHost, QTimer::singleShot veya QFileDialog::open fonksiyonlar QObject alıcısı ve char* slotu argüman olarak alır. Bu yeni yöntemde çalışmayacaktır. Eğer biri C++ yolu ile callback çalıştırmak istiyorsa std::function (veya tr1) kullanmalıdır. Fakat biz ABI (Application Binary Interface) arayüzünde STL türlerini kullanamayız. Bunu sağlamak için QFunction std::function kopyalayabilir. Bu durum QObject bağlantıları için geçerli değildir.