rightaway717
rightaway717

Reputation: 2831

How to simulate C-style "ifdef" macro in QML?

I need to switch off some parts of QML code, because this code has been done for demo purposes and it will be removed in final release. But the product will be used with these demo features for long, so I can't use a separate branch with demo features and constantly merge all new features to that branch - it's just not convenient. So it'd be nice to have this code running but easy to switch off and/or remove when needed. In C and C++ I use ifdef macro for that, but is it possible to do the same in QML?

Upvotes: 12

Views: 13233

Answers (7)

Reed Hedges
Reed Hedges

Reputation: 1650

In addition to some of the other answers here (dynamically adding QML code from javascript based on a property, or using file selectors), another approach that may work if what you want is to have different QML component implementations, or disable a QML component, based on a compile time condition or setting, is to configure CMake to include alternate QML files at build time, and set QML_RESOURCE_ALIAS to the desired QML component name (as referenced by other QML files) in the built executable's resources. For example:

  1. Create two versions of a QML component, e.g. MyComponent_enabled.qml which defines a component and MyComponent_disabled.qml which is an empty item (just import QtQuick ; Item {}.)

  2. In CMakeLists, choose which one to use. E.g.

option(ENABLE_MY_COMPONENT "Enable MyComponent")
if(ENABLE_MY_COMPONENT)
  set(MYCOMPONENT_QML_FILE MyComponent_enabled.qml)
else()
  set(MYCOMPONENT_QML_FILE MyComponent_disabled.qml)
endif()
set_source_file_properties(${MYCOMPONENT_QML_FILE} PROPERTIES QT_RESOURCE_ALIAS MyComponent.qml)
qml_add_qml_module(yourapp
  ... other stuff ...
  QML_FILES
    ... other QML files ...
    ${MYCOMPONENT_QML_FILE}
  ...
)
  1. Use MyComponent {...} to instantiate MyComponent in other QML files.

(This will of course baffle QtCreator a bit -- it will only show one or the other QML file in the file list etc. There may be ways to get QtCreator or other IDEs to know about both files but that's another topic.)

Upvotes: 0

rightaway717
rightaway717

Reputation: 2831

As @Mark suggested, context properties can be used in QML to decide in run time if some macro is enabled or not, but the example he provides does not address the case, when you need to instantiate an object based on this decision, but only covers case when inside a code block. So I will provide the full example of what I did.

In my case I exposed a class to QML only when a macro was defined during compile time:

main.cpp:

#ifdef SMTP_SUPPORT
qmlRegisterType<SmtpClientHelper>("com.some.plugin", 1, 0, "SmtpClient");
engine.rootContext()->setContextProperty("SMTP_SUPPORT", QVariant(true));
#else
engine.rootContext()->setContextProperty("SMTP_SUPPORT", QVariant(false));
#endif // SMTP_SUPPORT

Then on QML side I check this macro and decide if SmtpClient object must be created:

qml:

import QtQuick 2.0;
import com.some.plugin 1.0

Item {
    id: root
    // ...

    property var smtpClient // No inside any function, need to instantiate the object

    Component.onCompleted: {
        if (SMTP_SUPPORT) {
            smtpClient = Qt.createQmlObject('                                                 \
                import QtQuick 2.0;                                                           \
                import com.some.plugin 1.0;                                                   \
                SmtpClient {                                                                  \
                    id: smtpClient;                                                           \
                                                                                              \
                    function setSenderEmail(email) {                                          \
                        senderEmail = email;                                                  \
                        storage.save("common", "clientEmail", email);                         \
                    }                                                                         \
                }                                                                             \
            ', root, "SmtpClient");
        }
    }

    // Reference smtpClient normally, like if it was statically created
    TextInput {
        id: senderEmailLogin
        anchors.fill: parent
        font.pixelSize: Globals.defaultFontSize
        text: smtpClient ? smtpClient.senderEmail : ""
        onEditingFinished: if (smtpClient) smtpClient.setSenderEmail(text)
        activeFocusOnPress: true
    }
}

I think Loader should also do the job, so you could create a separate component, reference it in Loader's source or sourceComponent properties if C++ macro is defined, but I am not sure, because not sure if this component will be statically checked if (in my case) SmtpClient type is available

Upvotes: 8

Mark Ch
Mark Ch

Reputation: 3030

It's not the same as an #ifdef because the decision making all happens at runtime, but I sometimes use this approach, which gives a similar usage pattern.

main.cpp

    QQmlApplicationEngine engine;
#ifdef DEMO_MODE
    engine.rootContext()->setContextProperty("DEMO_MODE", QVariant(true));
#else
    engine.rootContext()->setContextProperty("DEMO_MODE", QVariant(false));
#endif
    engine.load(QUrl("qrc:/ui/MainQmlFile.qml"));

then, from any .qml file in the project

if (DEMO_MODE) {

} else {

}

Upvotes: 4

Mohammad Kanan
Mohammad Kanan

Reputation: 4592

QML is not compiled but interpreted at run time, so preprocessor directives can't be used directly by QML. But this is not a limitation, because it is possible to introduce properties to QML from c++ side, Thus directives can be placed on the C++ side to staff those properties, while decision making based on those properties, normal if/then, with Javascript can be used in QML side.

Upvotes: 0

SourabhKus
SourabhKus

Reputation: 828

You can also use class DebugC with function

Q_INVOKABLE bool isDebuggingEnable()
{
  #ifdef QT_DEBUG
    return true;
  #else
    return false;
  #endif
}

and then register this class in qml

viewer->rootContext()->setContextProperty("stName", DebugCObj)

now you can easily call stName.isDebuggingEnable() method for checking debugging is enable or not in your QML file. its just a trick

Upvotes: 1

Kevin Krammer
Kevin Krammer

Reputation: 5207

#ifdef is a preprocessor instructions, i.e. something processed at build time before the C or C++ compiler gets to see the code. You could do the same using your text manipulation language of choice, processing the QML files at build time before they get processed e.g. by the Qt resource compiler.

Upvotes: 2

Mitch
Mitch

Reputation: 24416

If you can refactor those parts into their own components, you have two options:

  1. File selectors
  2. Loader

The first option is perhaps a bit closer to a C-style #ifdef macro than using Loader, as it works at the file level. As long as you don't overuse Loader, though (e.g. as a delegate in a large view), they should both do the job fine.

Upvotes: 4

Related Questions