QML

Loading

We support three methods of loading a QML file: QML.QQmlApplicationEngine, QML.QQuickView and QQmlComponent. These behave equivalently to the corresponding Qt classes. They have different advantages:

QML modules

Since QML.jl version 0.2, only the QtDeclarative package is installed by default, which includes only a very limited set of QML modules.

Interaction

Interaction with Julia happens through the following mechanisms:

Note that Julia slots appear missing, but they are not needed since it is possible to directly connect a Julia function to a QML signal in the QML code, e.g. with QTimer.

Type conversion

Most fundamental types are converted implicitly. Mind that the default integer type in QML corresponds to Int32 in Julia.

We also convert QVariantMap, exposing the indexing operator [] to access element by a string key. This mostly to deal with arguments passed to the QML append function in list models.

Using QML.jl inside another Julia module

Because of precompilation and because the QML engine gets destroyed between runs, care needs to be taken to avoid storing invalid pointers and to call all @qmlfunction macros again before restarting the program. An example module that takes care of this could look like this:

module QmlModuleTest

using QML
# absolute path in case working dir is overridden
const qml_file = joinpath(dirname(@__FILE__), "qml", "frommodule.qml")

# Propertymap is a pointer, so it needs to be a function and not a const value
props() = JuliaPropertyMap(
    "hi" => "Hi",
)

world() = " world!"

# Convenience function to call all relevant function macros
function define_funcs()
  @qmlfunction world
end

function main()
  define_funcs()
  loadqml(qml_file; props=props())
  exec()
end

end # module QmlModuleTest

The referred frommodule.qml file is expected in a subdirectory called qml, with the following content:

import QtQuick
import QtQuick.Controls
import org.julialang

ApplicationWindow {
  id: mainWin
  title: "My Application"
  width: 100
  height: 100
  visible: true

  Rectangle {
    anchors.fill: parent
    color: "red"

    Text {
      anchors.centerIn: parent
      text: props.hi + Julia.world()
    }
  }
}

Interface

QML.QMLModule

Module for building Qt6 QML graphical user interfaces for Julia programs. Types starting with Q are equivalent of their Qt C++ counterpart, so, unless otherwise noted, they have no Julia docstring and we refer to the Qt documentation for details instead.

source
QML.JuliaDisplayType
struct JuliaDisplay

You can use display to send images to a JuliaDisplay. There is a corresponding QML block called JuliaDisplay. Of course the display can also be added using pushdisplay!, but passing by value can be more convenient when defining multiple displays in QML. See below for syntax.

julia> using QML

julia> using Colors

julia> function simple_image(julia_display::JuliaDisplay)
          display(julia_display, Gray.(rand(50,50)))
          nothing
        end;

julia> @qmlfunction simple_image

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          import QtQuick.Layouts
          import org.julialang
          ApplicationWindow {
            visible: true
            JuliaDisplay {
              id: julia_display
              width: 50
              height: 50
              Component.onCompleted: {
                Julia.simple_image(julia_display)
              }
            }
            Timer {
              interval: 2000; running: true; repeat: false
              onTriggered: Qt.exit(0)
            }
          }
          """)
          loadqml(path)
          exec()
        end;
source
QML.JuliaItemModelMethod
function JuliaItemModel(items::AbstractVector, addroles::Bool = true)

Constructor for a JuliaItemModel. The JuliaItemModel type allows using data in QML views such as ListView and Repeater, providing a two-way synchronization of the data. A JuliaItemModel is constructed from a 1D Julia array. To use the model from QML, it can be exposed as a context attribute.

Setter and getter "roles" based on the fieldnames of the eltype will be automatically created if addroles is true.

In Qt, each of the elements of a model has a series of roles, available as properties in the delegate that is used to display each item. The roles can be added using the addrole! function.

julia> using QML

julia> mutable struct Fruit
          name::String
          cost::Float64
        end

julia> fruits = JuliaItemModel([Fruit("apple", 1.0), Fruit("orange", 2.0)]);

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          import QtQuick.Layouts
          ApplicationWindow {
            visible: true
            ListView {
              model: fruits
              anchors.fill: parent
              delegate:
                Row {
                  Text {
                    text: name
                  }
                  Button {
                    text: "Sale"
                    onClicked: cost = cost / 2
                  }
                  Button {
                    text: "Duplicate"
                    onClicked: fruits.appendRow({"name": name, "cost": cost})
                  }
                  Timer {
                    running: true; repeat: false
                    onTriggered: Qt.exit(0)
                  }
                }
              }
            }
          """)
          loadqml(path; fruits)
          exec()
        end
source
QML.JuliaPropertyMapMethod
function JuliaPropertyMap(pairs...)

Store Julia values for access from QML. Observables are connected so they change on the QML side when updated from Julia and vice versa only when passed in a property map. Note that in the example below, if you run output[] = new_value from within Julia, the slider in QML will move.

julia> using QML

julia> using Observables: Observable, on

julia> output = Observable(0.0);

julia> on(println, output);

julia> mktempdir() do folder
         path = joinpath(folder, "main.qml")
         write(path, """
         import QtQuick
         import QtQuick.Controls
         ApplicationWindow {
           visible: true
           Slider {
             onValueChanged: {
               observables.output = value;
             }
           }
           Timer {
             running: true; repeat: false
             onTriggered: Qt.exit(0)
           }
         }
         """)
         loadqml(path; observables = JuliaPropertyMap("output" => output))
         exec()
       end
source
QML.QQmlApplicationEngineType
struct QQmlApplicationEngine

One of 3 ways to interact with QML (the others being QQuickView and QQmlComponent. You can load a QML file to create an engine with [load]. Use exec to execute a file after it's been loaded.

The lifetime of the QQmlApplicationEngine is managed from C++ and it gets cleaned up when the application quits. This means it is not necessary to keep a reference to the engine to prevent it from being garbage collected prematurely.

julia> using QML

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          ApplicationWindow {
            visible: true
            Text {
              text: greeting
            }
            Timer {
              running: true; repeat: false
              onTriggered: Qt.exit(0)
            }
          }
          """)
          loadqml(path; greeting = "Hello, World!")
          exec()
        end
source
QML.QQmlComponentType
struct QQmlComponent

One of 3 ways to interact with QML (the others being QQmlApplicationEngine and QQuickView. Make from an engine from e.g.init_qmlengine. Use set_data to set the QML code, create to create the window, and exec to fill the window.

julia> using QML

julia> component = QQmlComponent(init_qmlengine());

julia> set_data(component, QByteArray("""
          import QtQuick
          import QtQuick.Controls
          ApplicationWindow {
            visible: true
            Rectangle {
              Text {
                text: "Hello, World!"
              }
              Timer {
                running: true; repeat: false
                onTriggered: Qt.exit(0)
              }
            }
          }
        """), QUrl())

julia> create(component, qmlcontext())

julia> exec()
source
QML.QQuickViewType
struct QQuickView

One of 3 ways to interact with QML (the others being QQmlApplicationEngine and QQmlComponent. QQuickView creates a window, so it's not necessary to wrap the QML in ApplicationWindow. Use init_qquickview to create a quick view, set_source to set the source for the quick view, QML.show to view, and exec to execute.

julia> using QML

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          Rectangle {
            Text {
              text: "Hello, World!"
            }
            Timer {
              running: true; repeat: false
              onTriggered: parent.Window.window.close()
            }
          }
          """)
          quick_view = init_qquickview()
          set_source(quick_view, QUrlFromLocalFile(path))
          QML.show(quick_view)
          exec()
        end
source
QML.QTimerType
struct QTimer

You can use QTimer to simulate running Julia in the background. Note that QML provides the infrastructure to connect to the QTimer signal through the Connections item.

julia> using QML

julia> counter = Ref(0);

julia> increment() = counter[] += 1;

julia> @qmlfunction increment

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          import org.julialang
          ApplicationWindow {
              visible: true
              Connections {
                target: timer
                function onTimeout() {
                  Julia.increment()
                }
              }
              Button {
                  text: "Start counting"
                  onClicked: timer.start()
              }
              Timer { // unrelated, this is a timer to stop and continue testing
                running: true; repeat: false
                onTriggered: Qt.exit(0)
              }
          }
          """)
          loadqml(path, timer=QTimer())
          exec()
        end
source
Base.stringFunction
function string(data::QByteArray)

Equivalent to QByteArray::toString. Use to convert a QByteArray back to a string.

julia> using QML

julia> string(QByteArray("Hello, World!"))
"Hello, World!"
source
QML.addrole!Function
function addrole!(model::JuliaItemModel, name::String, getter, [setter])

Add your own getter (and optionally, setter) functions to a JuliaItemModel for use by QML. setter is optional, and if it is not provided the role will be read-only. getter will process an item before it is returned. The arguments of setter will be collection, new_value, index as in the standard setindex! function. If you would like to see the roles defined for a list, use roles.

julia> using QML

julia> items = ["A", "B"];

julia> array_model = JuliaItemModel(items, false);

julia> addrole!(array_model, "item", identity, setindex!)

julia> roles(array_model)[256]
"item"

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          import QtQuick.Layouts
          ApplicationWindow {
            visible: true
            ListView {
              model: array_model
              anchors.fill: parent
              delegate: TextField {
                placeholderText: item
                onTextChanged: item = text;
              }
            }
            Timer {
              running: true; repeat: false
              onTriggered: Qt.exit(0)
            }
          }
          """)
          loadqml(path; array_model = array_model)
          exec()
        end
source
QML.content_itemFunction
function content_item(quick_view::QQuickView)

Get the content item of a quick view. Equivalent to QQuickWindow::contentItem.

julia> using QML

julia> using CxxWrap.CxxWrapCore: CxxPtr

julia> quick_view = mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          Rectangle {
            Timer {
              running: true; repeat: false
              onTriggered: Qt.exit(0)
            }
          }
          """)
          quick_view = init_qquickview()
          set_source(quick_view, QUrlFromLocalFile(path))
          @assert content_item(quick_view) isa CxxPtr{QQuickItem}
          exec()
        end
source
QML.createFunction
function create(component::QQmlComponent, context::QQmlContext)

Equivalent to QQmlComponent::create. This creates a component defined by the QML code set using set_data. It also makes sure the newly created object is parented to the given context. See the example for set_data.

source
QML.engineFunction
function engine(quick_view::QQuickView)

Equivalent to QQuickView::engine. If you would like to modify the context of a QQuickView, use engine to get an engine from the window, and then root_context to get the context from the engine.

julia> using QML

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          Rectangle {
            Text {
              text: greeting
            }
            Timer {
              running: true; repeat: false
              onTriggered: parent.Window.window.close()
            }
          }
          """)
          quick_view = init_qquickview()
          context = root_context(engine(quick_view))
          set_context_property(context, "greeting", "Hello, World!")
          set_source(quick_view, QUrlFromLocalFile(path))
          QML.show(quick_view)
          exec()
        end
source
QML.exec_asyncFunction
function exec_async()

Similar to exec, but will not block the main process. This method keeps the REPL active and polls the QML interface periodically for events, using a timer in the Julia event loop.

source
QML.init_qmlengineFunction
function init_qmlengine()

Create a QML engine. You can modify the context of an engine using root_context. You can use an engine to create QQmlComponents. See the example for set_data. Note that you can also get the engine for a QQuickView using engine.

source
QML.qmlfunctionFunction
function qmlfunction(function_name::String, a_function)

Register a_function using function_name. If you want to register a function under it's own name, you can use @qmlfunction. Note, however, that you can't use the macro for registering functions from a non-exported module or registering functions wtih ! in the name.

source
QML.qt_prefix_pathFunction
function qt_prefix_path()

Equivalent to QLibraryInfo::location(QLibraryInfo::PrefixPath). Useful to check whether the intended Qt version is being used.

julia> using QML

julia> isdir(qt_prefix_path())
true
source
QML.root_contextFunction
root_context(an_engine::QQmlEngine)

Get the context of an_engine. Equivalent to QQmlEngine::rootContext. Use set_context_property to modify the context. Use context_property to get a particular property. Use to get the context of an engine created with init_qmlengine before using set_data or from engine.

julia> using QML

julia> an_engine = init_qmlengine();

julia> context = root_context(an_engine);

julia> set_context_property(context, "greeting", "Hello, World!");

julia> context_property(context, "greeting")
QVariant of type QString with value Hello, World!

julia> component = QQmlComponent(an_engine);

julia> set_data(component, QByteArray("""
          import QtQuick
          import QtQuick.Controls
          ApplicationWindow {
            visible: true
            Rectangle {
              Text {
                text: greeting
              }
              Timer {
                running: true; repeat: false
                onTriggered: Qt.exit(0)
              }
            }
          }
        """), QUrl())

julia> create(component, qmlcontext())

julia> exec()
source
QML.set_dataFunction
function set_data(component::QQmlComponent, data::QByteArray, file::QUrl)

Equivalent to QQmlComponent::setData. Use this to set the QML code for a QQmlComponent from a Julia string literal wrapped in a QByteArray. Also requires an empty QUrl. See QQmlComponent for an example.

source
QML.set_sourceFunction
function set_source(window::QQuickView, file::QUrl)

Equivalent to QQuickView::setSource. See the example for init_qquickview. The file path should be a path wrapped with QUrl.

source
QML.setheadergetter!Method

setheadergetter!(itemmodel::JuliaItemModel, f::Function)

Set f as function to call when getting header data. The signature of of should be:

  f(data, row_or_col, orientation, role)

Here, data is the internal data array stored in the model, row_or_col is the index of the row or column, orientation is either QML.Horizontal for column headers or QML.Vertical for row headers and role is an integer describing the role (e.g. QML.DisplayRole)

source
QML.setheadersetter!Method

setheadersetter!(itemmodel::JuliaItemModel, f::Function)

Set f as function to call when setting header data. The signature of of should be:

  f(data, row_or_col, orientation, value, role)

Here, value is the value for the given header item. The other arguments are the same as in setheadergetter!

source
QML.@emitMacro
@emit signal_name(arguments...)

Emit a signal from Julia to QML. Handle signals in QML using a JuliaSignals block. See the example below for syntax.

Warning

There must never be more than one JuliaSignals block in QML

julia> using QML

julia> duplicate(value) = @emit duplicateSignal(value);

julia> @qmlfunction duplicate

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import QtQuick
          import QtQuick.Controls
          import QtQuick.Layouts
          import org.julialang
          ApplicationWindow {
              visible: true
              Column {
                TextField {
                    id: input
                    onTextChanged: Julia.duplicate(text)
                }
                Text {
                    id: output
                }
                JuliaSignals {
                  signal duplicateSignal(var value)
                  onDuplicateSignal: output.text = value
                }
                Timer {
                  running: true; repeat: false
                  onTriggered: Qt.exit(0)
                }
              }
          }
          """)
          loadqml(path)
          exec()
        end
source
QML.@expand_dotsMacro
QML.@expand_dots object_.field_ func

Expand an expression of the form a.b.c to replace the dot operator by function calls.

julia> using QML

julia> @macroexpand QML.@expand_dots a.b.c.d f
:(f(f(f(a, "b"), "c"), "d"))
source
QML.@qmlfunctionMacro
@qmlfunction function_names...

Register Julia functions for access from QML under their own name. Function names must be valid in QML, e.g. they can't contain !. You can use your newly registered functions in QML by first importing org.julialang 1.0, and then calling them with Julia.function_name(arguments...). If you would like to register a function under a different name, use qmlfunction. This will be necessary for non-exported functions from a different module or in case the function contains a ! character.

julia> using QML

julia> greet() = "Hello, World!";

julia> @qmlfunction greet

julia> mktempdir() do folder
          path = joinpath(folder, "main.qml")
          write(path, """
          import org.julialang
          import QtQuick
          import QtQuick.Controls
          ApplicationWindow {
            visible: true
            Text {
              text: Julia.greet()
            }
            Timer {
              running: true; repeat: false
              onTriggered: Qt.exit(0)
            }
          }
          """)
          loadqml(path)
          exec()
        end
source