2013-03-25 65 views
14

Kết nối tín hiệu QML để C thường xuyên ++ khe rất dễ dàng:Connect QML tín hiệu C++ 11 khe lambda (Qt 5)

// QML 
Rectangle { signal foo(); } 

// C++ old-style 
QObject::connect(some_qml_container, SIGNAL(foo()), some_qobject, SLOT(fooSlot()); // works! 

Tuy nhiên, không có vấn đề gì tôi cố gắng, tôi dường như không thể để có thể để kết nối với khe chức năng lambda C++ 11.

// C++11 
QObject::connect(some_qml_container, SIGNAL(foo()), [=]() { /* response */ }); // fails... 
QObject::connect(some_qml_container, "foo()", [=]() { /* response */ }); // fails... 

Cả hai lần thử không thành công với lỗi chữ ký hàm (không QObject :: kết nối quá tải có thể chấp nhận các thông số này). Tuy nhiên, tài liệu Qt 5 ngụ ý rằng điều này có thể xảy ra.

Thật không may, Qt 5 ví dụ luôn luôn kết nối C++ tín hiệu đến một C++ khe lambda:

// C++11 
QObject::connect(some_qml_container, &QMLContainer::foo, [=]() { /* response */ }); // works! 

Cú pháp này không thể làm việc cho một tín hiệu QML, như QMLContainer :: chữ ký foo không biết tại thời gian biên dịch (và tuyên bố QMLContainer :: foo bằng tay đánh bại mục đích sử dụng QML ngay từ đầu.)

Tôi đang cố gắng làm điều gì? Nếu vậy, cú pháp chính xác cho cuộc gọi kết nối QObject :: là gì?

Trả lời

6

Lambdas v.v. chỉ hoạt động với cú pháp mới. Nếu bạn không thể tìm thấy một cách để cung cấp tín hiệu QML như một con trỏ, thì tôi nghĩ rằng nó không thể trực tiếp thực hiện được.

Nếu vậy, bạn có giải pháp thay thế: tạo phân lớp QObject định tuyến tín hiệu giả, chỉ có tín hiệu, một cho mọi tín hiệu QML bạn cần định tuyến. Sau đó, kết nối các tín hiệu QML với các tín hiệu tương ứng của một cá thể của lớp giả này, sử dụng cú pháp kết nối cũ.

Bây giờ bạn có các tín hiệu C++ bạn có thể sử dụng với cú pháp mới và kết nối với lambdas.

Lớp này cũng có thể có phương thức trợ giúp, để tự động kết nối từ QML với tín hiệu của lớp, sử dụng cơ chế phản chiếu QMetaObject và lược đồ đặt tên tín hiệu phù hợp, sử dụng nguyên tắc giống như sử dụng QMetaObject::connectSlotsByName. Ngoài ra, bạn có thể chỉ cần mã hóa cứng các kết nối tín hiệu QML-router nhưng vẫn ẩn chúng bên trong một phương thức của lớp router.

chưa được kiểm tra ...

+0

Cảm ơn câu trả lời, điều này mang lại cho tôi một hướng đi mới để tìm câu trả lời: có thể nhận con trỏ C++ tới tín hiệu QML không? Nếu vậy, tôi có thể liên kết một hàm std :: với tín hiệu và lambda vào khe. Thật không may, việc phản ánh mọi tín hiệu QML thành C++ QObject được cho là một thiết kế tồi tệ hơn chỉ xác định các khe trong QObjects (tức là cách tiếp cận trường cũ). Những gì tôi muốn làm là tránh sử dụng QObjects hoàn toàn, tận dụng lợi thế của giao diện Qt 5 mới (có thể hoặc có thể không được). –

+0

Vâng, có các kết nối tín hiệu tín hiệu xảy ra tự động có thể có nghĩa là nó chỉ là một dòng mã bổ sung, giống như dòng này sau khi khai báo trình xem QML: 'MyQMLSignalRouter qmlSignals (& myQmlView.rootObject());' và sau đó sử dụng 'qmlSignals' trong kiểu kết nối cuộc gọi. Các tín hiệu QML không tồn tại dưới dạng các hàm C++, chúng không thể (chúng là động, C++ là tĩnh) để nhận được một con trỏ phương thức trực tiếp đến chúng thậm chí không có lý thuyết, theo như tôi hiểu nó. – hyde

+0

Sự hoài nghi của tôi đối với phương pháp này nằm trong sự liên kết chặt chẽ giữa các tín hiệu QML và mã C++ mà nó giới thiệu, cũng như cách tiếp cận "siêu hạng" đơn lẻ (một lớp để khai báo tất cả các tín hiệu, ở khắp mọi nơi). Nó có mùi hôi! Bạn hoàn toàn đúng rằng các tín hiệu QML không có sẵn cho C++ tĩnh. Tuy nhiên, giải pháp động có thể tồn tại: QQuickItem :: metaObject() -> indexOfSignal ("foo()") trả về chính xác chỉ mục của tín hiệu đó. AFAICT, hệ thống ống nước để có được một wrapper có thể gọi được cũng tồn tại, nhưng ẩn bên trong không gian tên QtPrivate. Rất tiếc. –

2

Thay vì tạo ra các hàm lambda khi đang bay để đối phó với các tín hiệu khác nhau, bạn có thể muốn xem xét sử dụng một QSignalMapper để đánh chặn các tín hiệu và gửi chúng đến một khe cắm tĩnh xác định với một cuộc tranh cãi phụ thuộc vào nguồn. Hành vi của hàm sẽ phụ thuộc hoàn toàn vào nguồn gốc của tín hiệu gốc.

Việc cân bằng với QSignalMapper là bạn có được thông tin về nguồn của tín hiệu, nhưng bạn mất các đối số ban đầu. Nếu bạn không thể mất các đối số ban đầu hoặc nếu bạn không biết nguồn tín hiệu (như trường hợp với các tín hiệu QDBusConnection::connect()), thì việc sử dụng QSignalMapper.

không thực sự hợp lý Ví dụ của hyde sẽ đòi hỏi nhiều công việc hơn một chút, nhưng sẽ cho phép bạn thực hiện một phiên bản tốt hơn của QSignalMapper nơi bạn có thể thêm thông tin về tín hiệu nguồn vào các đối số khi kết nối tín hiệu với chức năng khe của bạn.

QSignalMapper tham chiếu lớp: http://qt-project.org/doc/qt-5.0/qtcore/qsignalmapper.html
Ví dụ: http://eli.thegreenplace.net/2011/07/09/passing-extra-arguments-to-qt-slots/

Dưới đây là một ví dụ loang một tín hiệu thông qua một ví dụ QSignalMapper kết nối với một ApplicationWindow dụ đầu với một objectName của "app_window":

for (auto app_window: engine.rootObjects()) { 
    if ("app_window" != app_window->objectName()) { 
    continue; 
    } 
    auto signal_mapper = new QSignalMapper(&app); 

    QObject::connect(
    app_window, 
    SIGNAL(pressureTesterSetup()), 
    signal_mapper, 
    SLOT(map())); 

    signal_mapper->setMapping(app_window, -1); 

    QObject::connect(
    signal_mapper, 
    // for next arg casting incantation, see http://stackoverflow.com/questions/28465862 
    static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), 
    [](int /*ignored in this case*/) { 
     FooSingleton::Inst().Bar(); 
    }); 
    break; 
} 
2

Bạn có thể sử dụng trợ giúp:

class LambdaHelper : public QObject { 
    Q_OBJECT 
    std::function<void()> m_fun; 
public: 
    LambdaHelper(std::function<void()> && fun, QObject * parent = {}) : 
    QObject(parent), 
    m_fun(std::move(fun)) {} 
    Q_SLOT void call() { m_fun(); } 
    static QMetaObject::Connection connect(
    QObject * sender, const char * signal, std::function<void()> && fun) 
    { 
    if (!sender) return {}; 
    return connect(sender, signal, 
        new LambdaHelper(std::move(fun), sender), SLOT(call())); 
    } 
}; 

Sau đó:

LambdaHelper::connect(sender, SIGNAL(mySignal()), [] { ... }); 

Các sender sở hữu đối tượng helper và sẽ làm sạch nó lên khi nó bị phá hủy.

+0

... và bây giờ bạn có một memleak – Teimpz

+0

@Teimpz Không có gì cả! –