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
loadqml
function as aQML.QQmlApplicationEngine
. QML.QQuickView
creates 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
loadqml
orset_context_property
. - Emit signals from Julia to QML, e.g. with
@emit
. - Use data models, e.g.
JuliaItemModel
orJuliaPropertyMap
.
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.QML
QML.JuliaDisplay
QML.JuliaItemModel
QML.JuliaPropertyMap
QML.QByteArray
QML.QQmlApplicationEngine
QML.QQmlComponent
QML.QQuickView
QML.QTimer
QML.QUrl
QML.addrole!
QML.content_item
QML.context_property
QML.create
QML.engine
QML.exec
QML.exec_async
QML.init_qmlengine
QML.init_qquickview
QML.loadqml
QML.qmlcontext
QML.qmlfunction
QML.qt_prefix_path
QML.roles
QML.root_context
QML.set_context_property
QML.set_data
QML.set_source
QML.setheadergetter!
QML.setheadersetter!
QML.show
QML.@emit
QML.@expand_dots
QML.@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 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;
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()
end
QML.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
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
QML.QByteArray
— Typefunction QByteArray(a_string::String)
Use to pass text to set_data
.
QML.QQmlApplicationEngine
— Typestruct 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
QML.QQmlComponent
— Typestruct 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()
QML.QQuickView
— Typestruct 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
QML.QTimer
— Typestruct 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
QML.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()
end
QML.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()
end
QML.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()
end
QML.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 QQmlComponent
s. 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())
true
QML.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.
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
QML.@expand_dots
— MacroQML.@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"))
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 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