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:
- You can simply load a QML file using the
loadqmlfunction as aQML.QQmlApplicationEngine. QML.QQuickViewcreates a window, so it's not necessary to wrap the QML inApplicationWindow.- You can run QML code contained in a string with
QQmlComponent.
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:
- Call Julia functions from QML, e.g. with
@qmlfunction. - Read and set context properties from Julia and QML, e.g. with keywords to
loadqmlorset_context_property. - Emit signals from Julia to QML, e.g. with
@emit. - Use data models, e.g.
JuliaItemModelorJuliaPropertyMap.
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 QmlModuleTestThe referred frommodule.qml file is expected in a subdirectory called qml, with the following content:
import QtQuick
import QtQuick.Controls
import jlqml
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()
}
}
}Makie support
Make support is provided by the separate QMLMakie package, which can be installed as usual using add QMLMakie.
Interface
QML.QMLQML.JuliaDisplayQML.JuliaItemModelQML.JuliaPropertyMapQML.QByteArrayQML.QQmlApplicationEngineQML.QQmlComponentQML.QQuickViewQML.QTimerQML.QUrlQML.addrole!QML.content_itemQML.context_propertyQML.createQML.engineQML.execQML.exec_asyncQML.init_qmlengineQML.init_qquickviewQML.loadqmlQML.qmlcontextQML.qmlfunctionQML.qt_prefix_pathQML.rolesQML.root_contextQML.set_context_propertyQML.set_dataQML.set_sourceQML.setheadergetter!QML.setheadersetter!QML.showQML.@emitQML.@expand_dotsQML.@qmlfunction
QML.QML — ModuleModule 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.
QML.JuliaDisplay — Typestruct JuliaDisplayYou 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 jlqml
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;
QML.JuliaItemModel — Methodfunction 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()
endQML.JuliaPropertyMap — Methodfunction 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
import jlqml
ApplicationWindow {
visible: true
Slider {
value: observables.output
onValueChanged: {
observables.output = value;
}
}
// Timer only needed for doctests
Timer {
running: !Julia.isinteractive(); repeat: false
onTriggered: {
observables.output = 0.5
Qt.exit(0)
}
}
}
""")
@qmlfunction isinteractive # For doctest
loadqml(path; observables = JuliaPropertyMap("output" => output))
if isinteractive()
exec_async()
else
exec() # for doctest
end
end
0.5QML.QByteArray — Typefunction QByteArray(a_string::String)Use to pass text to set_data.
QML.QQmlApplicationEngine — Typestruct QQmlApplicationEngineOne 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()
endQML.QQmlComponent — Typestruct QQmlComponentOne 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()QML.QQuickView — Typestruct QQuickViewOne 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()
endQML.QTimer — Typestruct QTimerYou 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 jlqml
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()
endQML.QUrl — Typestruct QUrl([filename::String])Used to pass filenames to set_source. Pass an empty url (no arguments) to set_data.
Base.string — Functionfunction 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!"QML.addrole! — Functionfunction 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()
endQML.content_item — Functionfunction 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()
endQML.context_property — Functionfunction context_property(context::QQmlContext, item::AbstractString)Get a context property. See the example for root_context.
QML.create — Functionfunction 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.
QML.engine — Functionfunction 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()
endQML.exec — Functionfunction exec()Fill out a window. Use with a QQmlApplicationEngine, QQuickView, or QQmlComponent. Note that after calling exec, you will need to reregister functions, e.g. with [@qmlfunction], if you want to exec again.
QML.exec_async — Functionfunction 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.
QML.init_qmlengine — Functionfunction 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.
QML.init_qquickview — Functionfunction init_qquickview()Create a QQuickView.
QML.loadqml — Methodfunction loadqml(qmlfilename; properties...)Load a QML file, creating a QML.QQmlApplicationEngine, and setting the context properties supplied in the keyword arguments. Will create and return a QQmlApplicationEngine. See the example for QML.QQmlApplicationEngine.
QML.qmlcontext — Functionfunction qmlcontext()Create an empty context for QML. Required for create.
QML.qmlfunction — Functionfunction 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.
QML.qt_prefix_path — Functionfunction 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())
trueQML.roles — Methodfunction roles(model::JuliaItemModel)See all roles defined for a JuliaItemModel. See the example for addrole!.
QML.root_context — Functionroot_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()QML.set_context_property — Functionset_context_property(context::QQmlContext, name::String, value::Any)Set properties. See root_context for an example.
QML.set_data — Functionfunction 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.
QML.set_source — Functionfunction 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.
QML.setheadergetter! — Methodsetheadergetter!(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)
QML.setheadersetter! — Methodsetheadersetter!(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!
QML.show — Function function QML.show()Equivalent to QQuickView::show. See example for QQuickView.
QML.@emit — Macro@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.
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 jlqml
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()
endQML.@expand_dots — MacroQML.@expand_dots object_.field_ funcExpand 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"))QML.@qmlfunction — Macro@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 jlqml, 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 jlqml
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