Initializing Qt in C++ is done by creating an instance of
QCoreApplication
(or QGuiApplication
/QApplication
):
// main method in C++
int main(int argc, char* argv[]){
QApplication app(argc, argv);
return app.exec();
// finally, app is deleted when leaving scope
}
However, in Java you simply call the static methods initialize(args)
at the beginning of the main method and shutdown()
when leaving the
main method. Also exec()
is static.
// main method in Java
public static void main(String[] args) {
QApplication.initialize(args); // creating an instance of QApplication
QApplication.exec();
QApplication.shutdown(); // deleting instance of QApplication
}
If you want to use your custom subclass of QCoreApplication
(or
QGuiApplication
/QApplication
) call initialize(...)
with
constructor handle:
import io.qt.widgets.*;
public class MyApplication extends QApplication{
private MyApplication(String[] args){
super(args);
}
public static void main(String[] args) {
QApplication.initialize(args, MyApplication::new); // creating an instance of MyApplication
QApplication.exec();
QApplication.shutdown(); // deleting instance of MyApplication
}
}
Make sure to remove all top-level widgets of your application prior to
QApplication.shutdown();
either by removing all references (widget = null;
)
or by disposing the object (widget.dispose();
).
Almost all classes and namespaces declared by Qt API have counterparts in Java. The Java classes provide all methods of the native Qt classes with similar method signature.
C++ and Qt-specific primitive types are mapped to the corresponding Java primitive types. All unsigned types are used as signed Java types, as Java does not support unsigned integers.
native | java |
---|---|
int, qint32, quint32 | int |
short, qint16, quint16 | short |
char, qint8, quint8 | byte |
qint64, quint64, qintptr, quintptr | long |
bool, 1-bit field | byte |
QChar, char_16 | char |
float | float |
double | double |
void | void |
Qt provides two different categories of classes: object types and value types.
An instance of an object type is a unique entity. Entities cannot be copied and two entities are never equals.
In Qt's native API they are mostly represented as pointer argument types, e.g. QObject* object
.
In contrast, value types can be copied and compared. The unmodified copies of a value are always equal.
In Qt's native API they are mostly represented as const-reference-typed argument or copy argument, e.g. const QColor& color
.
If you submit null
as object type argument (showing a *
pointer in Qt API) it is translated to a native null pointer (nullptr
).
However, there are a few cases where null
is prohibited according to Qt's documentation.
If you submit null
as value type argument it is converted to the default value. For instance, calling widget.setWindowIcon(null);
is equivalent to widget.setWindowIcon(new QIcon());
(but faster).
Only in cases where the native Qt API expects a non-constant reference the corresponding Java method does not accept null
.
QtJambi API uses type annotations for arguments to show their nullness, i.e. whether their C++ representation is pointer-based (@Nullable
), value-based/const-reference-based (@NonNull
) or reference-based (@StrictNonNull
).
In Java, @Nullable
and @NonNull
have no further meaning. You can submit null
to @NonNull
-annotated method argument. Qt will use the default value as described above.
@StrictNonNull
annotated methods will throw NullPointerException
or IllegalArgumentException
when submitting null
. Nullness annotations are primarily used to specify type nullness for programming language Kotlin.
Java does not support constant types (const
), C-pointers (*
) or
C-references (&
). Corresponding function argument types are simply used as Java reference types:
const QSize& size
is QSize size
and QGraphicsItem* item
as well as
const QGraphicsItem* item
is QGraphicsItem item
in Java.
If a Qt function returns a constant reference to a value the corresponding Java method returns a copy of the value.
Sometimes, occurences of call-by-reference or call-by-value-pointer in Qt are
represented in QtJambi as wrapped return values.
For example, the QFormLayout
C++-method void getItemPosition(int index, int *rowPtr, QFormLayout::ItemRole *rolePtr) const
where rowPtr
and rolePtr
are pointers for storing the method output, is represented in Java by
public final ItemInfo getItemPosition(int index)
whereas
ItemInfo
provides role
and row
as public member fields.
In some cases, array pointer of primitive type are mapped to Java NIO
buffers. For instance, QSharedMemory.data()
, QImage.bits()
and
QUdpSocket.readDatagram(ByteBuffer,HostInfo)
. If the native Qt API
specifies constant pointers, the given Java buffer is read only.
In rare cases, void*
or const void*
maps to QNativePointer
as the most generic way to represent native pointers.
The Qt classes QString
, QLatin1String
, QStringView
,
QUtf8StringView
, QAnyStringView
and QStringRef
are mapped to
java.lang.String
. Also in many cases const char*
is used as Java
String.
The java class io.qt.core.QString
represents the mutable string type QString
.
However, this class is only available to provide data conversion and formatting features of QString
.
In nearly all cases you have to call toString()
to use QString
as string method parameter.
Enums declared by Qt are also available as Java enum. Java enums cannot
convert from or to integer values. Instead, all Qt enum Java classes
provide a value()
providing the enum value and a resolve(int)
method
for converting an int
to the corrensponding enum entry. In case of 64
Bit enums, the corresponding methods point to long
.
In most cases, Qt enumerator arguments don't accept null
.
Certain enum types in Qt are expected to be extensible, i.e. the
predefined set of enum entries can be extended by custom entries. This
is also supported by QtJambi. The resolve(int value)
method of an
extensible enum provides a new enum entry if the requested value is not
yet available. Alternatively, you can specify resolve(int value, String name)
to request a new entry with a specific enum name. If you want to
develop custom extensible enums use the annotation @QtExtensibleEnum
.
Using an extensible enum within a switch
statement may cause exceptions on Android
because the custom entries are unexpected.
On all other platforms, the meta object system adapts the code to allow custom entries.
So switch over extensible enums is save for gadgets and QObjects.
@Override
public boolean event(QEvent event){
switch(event.type()){ // throws ArrayIndexOutOfBoundsException on Android
case MouseButtonPress:
//...
break;
default:
break;
}
return super.event(event);
}
There are enums in Qt used as flags. This is also availale in Java by
providing a QFlags
type for the enum. For instance, the class
Qt.Edges
is available as flags type for enum Qt.Edge
.
In C++ you combine different enum entries to a flag value by inclusive
or operator (Qt::TopEdge | Qt::RightEdge
). In Java, there are multiple
ways available to create flags:
// three equivalent ways to create a flag
Qt.Edges flags1 = Qt.Edge.TopEdge.combined(Qt.Edge.RightEdge);
Qt.Edges flags2 = Qt.Edge.flags(Qt.Edge.TopEdge, Qt.Edge.RightEdge);
Qt.Edges flags3 = new Qt.Edges(Qt.Edge.TopEdge, Qt.Edge.RightEdge);
In most cases where methods take flags as argument, an overloaded method is provided taking the corresponding enum type as variadic argument, for instance:
QWindow.startSystemResize(io.qt.core.Qt.Edge ... edges)
for
QWindow.startSystemResize(io.qt.core.Qt.Edges edges)
In most cases, QFlags arguments don't accept null
.
Function pointers are made available as functional interfaces and can be used with lambda expressions.
Examples:
QEasingCurve ec = new QEasingCurve();
ec.setCustomType(value -> value < 0.5 ? 0.2 : 0.8);
Qt provides following container types:
QList
QQueue
QStack
QSet
QHash
QMap
QMultiHash
QMultiMap
QVector
(Qt5 only)QLinkedList
(Qt5 only)
These types are all available as Java class in QtJambi. However, when instantiating such a container class, you need to specify the element type in the constructor. Example:
// using containers in C++
QSet<int> intSet;
intSet << 1 << 2 << 3;
QList<double> doubleList{1.0, 2.0, 3.0};
QMap<QString,QList<int>> mapOfIntegers;
mapOfIntegers["first"] = QList<int>{1, 2, 3};
// using containers in Java
QSet<Integer> intSet = new QSet<>(int.class);
intSet.append(1);
intSet.append(2);
intSet.append(3);
QList<Double> doubleList = QList.of(1.0, 2.0, 3.0);
QMap<String,QList<Integer>> mapOfIntegers = new QMap<>(String.class,
QMetaType.fromType(
QList.class,
QMetaType.fromType(int.class)));
mapOfIntegers.insert("first", QList.of(1, 2, 3));
QtJambi container wrapper classes are fully compatible with Java
containers, for instance, QList
implements java.util.List
and QMap
implements java.util.Map
.
All Qt functions with container parameters accept lightweight Java
containers as well, for instance, QWidget::addActions(QList<QAction*>)
maps to QWidget.addActions(java.util.Collection<QAction>)
.
QtJambi container wrappers are significantly less performant than lightweight Java containers because every single access has Qt-Java interoperability and type conversion overhead.
A Java container is based on references to java.lang.Object
type.
The generic type parameter is just compile time information.
On the contrary, a Qt container is a data structure of actual value type (or key-value types)
as given in its constructor. When inserting anything to Qt container in Java,
the native counterpart of the Java object is copied into the native container.
In case of a type mismatch an exception is thrown:
// raw type list
QList list = new QList(QObject.class);
list.add("STRING"); // causes IllegalArgumentException
All of this makes Qt containers more expensive than Java containers.
However, when using a Java container as Qt method parameter (like in QWidget.addActions(java.util.Collection<QAction>)
) this effect is reversed.
Here, sending a Qt container is much faster than a Java container because the java container needs to be converted to native container entry by entry.
The generic Qt type QVariant
is directly mapped to java.lang.Object
as a method's argument or return type for instance in QAbstractItemModel.data(QModelIndex,int)
.
The internal QVariant
value is converted to the actual carried value type in Java.
For instance, a variant carrying QFont
(QVariant(QFont)
) is converted to io.qt.gui.QFont
,
QVariant(QString)
is converted to java.lang.String
, QVariant(int)
is converted to the boxed primitive type java.lang.Integer
and QVariant(QObject*)
is converted to io.qt.core.QObject
(which can also be null
).
The way Java objects are converted to native QVariant
values depends on wether the type is a (cloneable) value or object type.
Value types like io.qt.gui.QFont
are converted to QVariant(QFont)
, whereas the variant contains a copy of the value.
This also applies to boxed primitives and String
. All native object types like QObject
(i.e. types not copyable)
have a pointer-based QVariant
representation, e.g. QVariant(QObject*)
. Here, the conversion of a Java object to native QVariant
depends on wether the object is owned by Java or C++. An object is owned by Java if it has been created in Java and its life cycle
is managed by Java. A QObject
instance is owned by C++ if it has a parent because parents destroy all of their child objects when being deleted.
If a Java object of pointer-based type (e.g. QObject
) is converted to native QVariant
it uses the direct pointer representation (e.g. QVariant(QObject*)
).
However, if the object is managed by Java, the variant also keeps the reference of the Java object along with the native pointer.
This is represented by QVariant(JObjectWrapper)
in C++. The type automatically converts to QVariant(QObject*)
if necessary.
The Java null
always converts to invalid QVariant
. If you need to represent nullptr
as a result of e.g. QAbstractItemModel.data(QModelIndex,int)
there are different options:
- return
QVariant.NULL
being an alias for the nativeQVariant(Nullptr)
- filter a variable through
QVariant.nullable(object)
which never returns Javanull
but a nullalias instead if the given value isnull
.- If you need a typed variant use
QVariant.typedNullable<T>(object,Class,QMetaType...)
instead.
- If you need a typed variant use
- return an instance of
QVariant
boxing the given value byQVariant.fromValue(object)
. This method produces the nativeQVariant(Nullptr)
for javanull
. - return an instance of
QVariant
by constructor:
// --> (void*)nullptr
new QVariant(QMetaType.Type.VoidStar, null);
// --> (QObject*)nullptr
new QVariant(QMetaType.Type.ObjectStar, null);
// --> (QWidget*)nullptr
new QVariant(QMetaType.fromType(QWidget.class), null);
If you convert cloneable pure-Java objects to native QVariant
the original Java object is cloned.
In addition to Qt API, the Java class io.qt.core.QVariant
provides static methods for type check and conversion.
Since Java does not allow overloading operators, operator overloads in Qt are made available as methods with corresponding names in Java. For instance:
QMatrix4x4::operator+=(const QMatrix4x4&)
→QMatrix4x4.add(QMatrix4x4)
QPainterPath::operator&=(const QPainterPath&)
→QPainterPath.intersect(QPainterPath)
QPolygon::operator=(const QPolygon&)
→QPolygon.assign(QPolygon)
QBitArray::operator~()
→QBitArray.inverted()
QVector4D::operator/=(float)
→QVector4D.divide(float)
A Java object of any Qt type is actually a wrapper for an underlying C++ object. The native C++ object is created immediately when the Java object is created. The C++ object exists as long as the Java object exists unless it is deleted by Qt internal mechanisms. The Java object exists as long as there is no more reference available unless the native side does not own a global reference of the object. This is the case whenever the ownership of an argument is given to Qt as described in Qt API.
If the C++ object is deleted prior to the Java object, the Java object
is disposed, i.e. it does no longer provide a native resource. Calling
any method on the object will then throw a QNoNativeResourcesException
. You
can check if an object is disposed by isDisposed()
.
You can actively delete the native C++ object by calling dispose()
.
Usually, you don't have to care about object deletion because the Java
garbage collection cares for it.
However, QObject
parenthood avoids the garbage collection to delete the child
objects of a parent even if no more references to a child exist in Java.
You need to manage the life cycle of parented QObject
instances manually wherever the parent outlasts the child's life time.
Therfore, use dispose()
or disposeLater()
. This is especially required
for QDialog
because dialogs are usually created with the main window as parent
which avoids deletion even when Java has no more reference.
Calling dispose()
causes a native object to be deleted. Most types are not thread affine, thus, these objects are deleted instantly. Other types are thread affine.
In this case, the deletion is scheduled in the object's thread. All QObject
-derived types are thread affine but also some types that are associated to QObject
s such as QTextCursor
(it belongs to a QTextDocument
).
When calling dispose()
on QObject
in another thread the behavior is similar to disposeLater()
, i.e. a deleteion event is posted and handled by the event loop.
When calling dispose()
on an "owned non-QObject
" object (as QTextCursor
is) in another thread its deletion also takes place in the thread of the owner (here QTextDocument
) by posing a deletion event on a deletion handler object.
When calling dispose()
the Java object is instantly disconnected from its native counterpart even when the effective deletion takes place later through event handler.
When calling disposeLater()
the Java object stays connected to its native counterpart until the deletion event has been executed by event handler (which always takes place in the future).
Java GC always performs a cleanup similar to dispose()
, i.e. QObject
s are always deleted in their associated thread.
QPointer
is a strong reference to any Qt object whereasQPointer.get()
returnsnull
as soon as the referenced object is disposed.QWeakPointer
is a weak reference to any Qt object whereasQWeakPointer.get()
returnsnull
as soon as the referenced object is disposed.QScopedPointer
andQScopedArrayPointer
can be used in try-with-resource blocks to dispose an object or array of objects when leaving the scope:
int dialogCode;
try(QScopedPointer<QDialog> dialogPtr = QScopedPointer.disposing(new QDialog())){
dialogCode = dialogPtr.get().exec();
}
alternative implementation with lambda expression:
int dialogCode = QScopedPointer.performAndDispose(dialog->{
return dialog.exec();
}, new QDialog());
or simpler:
int dialogCode = QScopedPointer.performAndDispose(QDialog::exec, new QDialog());
In the very rare case where it is necessary to perform an operation when a Qt object is about to be disposed you can request the on-dispose signal and connect to it:
QColor color = ...
QtUtilities.getSignalOnDispose(color).connect( ()->{ System.out.println("Color is disposed."); } );
Be aware that disposed
is not identical to QObject
's deleted
signal.
The deleted
signal is emitted during an object's destructor, i.e. when the native component is deleted.
The disposed
signal (by QtUtilities.getSignalOnDispose()
) is emitted when the Java component is detached from its native component.
This can be by deleting the native component or by other reasons where the native object survives the Java wrapper.
Use QScope
to manage the object life time in a try-with-resource block. This is similar to QScopedPointer
but for many objects.
By subclassing a QObject
type QtJambi automatically creates the
corresponding QMetaObject
describing the object's properties, signals,
slots and invokable methods.
Basically, all public and non-static methods in a QObject
-based class are considered to be
invokable by Qt. This additionally applies to non-public void methods. If you want to make other members invokable
(i.e. static and non-public methods as well as constructors) use the @QtInvokable
annotation.
Likewise, you can avoid a method to be invokable by annotating with @QtUninvokable
.
public class Implementor extends QObject{
// invokable by default:
public String doSomething(int arg) {
...
}
// not invokable by default:
private int doSomething(double arg) {
...
}
// avoids being invokable by default:
@QtUninvokable
private void doSomething() {
...
}
// making private supplier invokable:
@QtInvokable
private int returnSomething() {
...
}
// making static method invokable:
@QtInvokable
public static void doSomethingStatic(int arg) {
...
}
// making constructor invokable:
@QtInvokable
public Implementor(){
super();
}
}
The signal-slot mechanism in Java has a different appearance as in C++ as faced below:
Signals in C++ are methods prefixed by the key word signals
.
// defining signals in C++
signals:
void stateChanged();
void textChanged(const QString& text);
void lengthChanged(int length, QPrivateSignal);
// defining slots
public slots:
void onStatechanged();
void onTextChanged(const QString& text);
void onLengthChanged(int length);
In Java, a signal is a final member variable of type SignalN
with N
=number of arguments (0-9). For private signals use the type
PrivateSignalN
. The arguments of the signal are given as generic type
arguments. Java does not allow primitive types (i.e. byte
, short
,
int
, long
, char
, float
, double
and boolean
) as generic type
arguments. If you want to specify a primitive type as signal argument
use the boxed Java type (one of Byte
, Short
, Integer
, Long
,
Character
, Float
, Double
and Boolean
) annotated with
@QtPrimitiveType
. @QtPrimitiveType
denies emmitting the signal with
null
parameter. On Android, type parameter annotations are ignored.
// defining signals in Java
public final Signal0 stateChanged = new Signal0();
public final Signal1<String> textChanged = new Signal1<>();
public final PrivateSignal1<@QtPrimitiveType Integer> lengthChanged = new PrivateSignal1<>();
// defining slots
void onStatechanged(){}
void onTextChanged(String text){}
void onLengthChanged(int length){}
If a signal is not declared final
QSignalDeclarationException
is
thrown at runtime.
Creating signal-slot connections in C++ is done with
QObject::connect(...)
:
// connecting signals in C++
QObject::connect(this, SIGNAL(statechanged()), this, SLOT(onStatechanged()));
QObject::connect(this, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
QObject::connect(this, SIGNAL(lengthChanged(int)), this, SLOT(onLengthChanged(int)));
// connecting signals in C++ with function pointers
QObject::connect(this, &ObjectType::statechanged, this, &ObjectType::onStatechanged);
QObject::connect(this, &ObjectType::textChanged, this, &ObjectType::onTextChanged);
QObject::connect(this, &ObjectType::lengthChanged, this, &ObjectType::onLengthChanged);
In Java, you can use the static connect(...)
method of QObject
or alternatively the
connect(...)
method of the signal directly:
// connecting signals in Java textual
QObject.connect(this, "statechanged()", this, "onStatechanged()");
QObject.connect(this, "textChanged(String)", this, "onTextChanged(String)");
QObject.connect(this, "lengthChanged(int)", this, "onLengthChanged(int)");
// alternatively
this.statechanged.connect(this, "onStatechanged()");
this.textChanged.connect(this, "onTextChanged(String)");
this.lengthChanged.connect(this, "onLengthChanged(int)");
// connecting signals in Java with method references
QObject.connect(this.statechanged, this, ObjectType::onStatechanged);
QObject.connect(this.textChanged, this, ObjectType::onTextChanged);
QObject.connect(this.lengthChanged, this, ObjectType::onLengthChanged);
// alternatively (1)
QObject.connect(this.statechanged, this::onStatechanged);
QObject.connect(this.textChanged, this::onTextChanged);
QObject.connect(this.lengthChanged, this::onLengthChanged);
// alternatively (2)
this.statechanged.connect(this, ObjectType::::onStatechanged);
this.textChanged.connect(this, ObjectType::::onTextChanged);
this.lengthChanged.connect(this, ObjectType::::onLengthChanged);
// alternatively (3)
this.statechanged.connect(this::onStatechanged);
this.textChanged.connect(this::onTextChanged);
this.lengthChanged.connect(this::onLengthChanged);
QNoSuchSignalException
is thrown if textually specified signal can not be found.QNoSuchSlotException
is thrown if textually specified slot can not be found.QUninvokableSlotException
is thrown if specified slot is not invokable.QMisfittingSignatureException
is thrown if signal and slot have incompatible arguments.- In rare cases, it might be necessary to define value type
arguments as pointer or reference to make signal and slot
signatures compatible. Therfore, use the annotations
@QtPointerType
and@QtReferenceType
(ignored on Android). Examples: Java methodvoid whatSize(@QtPointerType QSize size)
has the following C++ signature:void whatSize(QSize* size)
.
- In rare cases, it might be necessary to define value type
arguments as pointer or reference to make signal and slot
signatures compatible. Therfore, use the annotations
Disconnecting signals and slots works analogous with disconnect()
.
It is highly recommended to not subclassing slot interfaces. When using string-based or method-handle signal slot connections as shown above Qt monitors receiver's life cycle. If the receiver is deleted all connections are removed. This monitoring is not possible when connecting to custom slot type implementation.
Android is not able to resolve the corresponding QMetaMethod
from a method reference.
I.e. by connecting to method reference a lambda object is created and invoked at signal emitting.
Also, the same method reference at different positions will lead to unequal lambda objects.
For example, the following code connects signal statechanged
to a lambda object calling this.onStatechanged()
.
But the second line tries to disconect the signal from a second lambda object that has never been connected.
QObject.connect(this.statechanged, this::onStatechanged);
QObject.disconnect(this.statechanged, this::onStatechanged); //->false in Android
A possible solution is to use a variable to store the lambda object or the connection:
Slot0 slot = this::onStatechanged;
QObject.connect(this.statechanged, slot);
QObject.disconnect(this.statechanged, slot); //->true
QMetaObject.Connection connection = QObject.connect(this.statechanged, this::onStatechanged);
QObject.disconnect(connection); //->true
In any case, textual signal slot connections are resolved to QMetaMethod
s even in Android:
QObject.connect(this.statechanged, this, "onStatechanged()");
QObject.disconnect(this.statechanged, this, "onStatechanged()"); //->true in Android
CAUTION: Code obfuscation breaks the ability to resolve textual signal slot connections. If you intend to release your application with obfuscated byte code you should only use methodhandle-based or lambda-based connections.
Emitting a signal in Java is done by the signal's emit()
method.
Private methods can only be emitted within their declaring classes:
// emitting normal signal
this.statechanged.emit();
this.textChanged.emit("new text");
// emitting private signal
emit(this.lengthChanged, 5);
There are a couple of Qt classes providing overloaded signals, for
instance, QSpinBox
provides two "valueChanged" signals:
signals:
void valueChanged(int);
void valueChanged(const QString &);
In the Java type QSpinBox
there is only one signal valueChanged
.
When connecting, it determines the correct signal depending on the slot's arguments:
// given:
// void onValueChanged(int value)
QSpinBox spinBox = new QSpinBox();
spinBox.valueChanged.connect(this::onValueChanged);
Also, the signal provides overloaded emit methods:
spinBox.valueChanged.emit(1);
spinBox.valueChanged.emit("item");
In rare cases where connections are ambiguous or you need the individual signal object of a certain signal method use overload(...)
:
spinBox.valueChanged.overload(int.class); // returns Signal1<Integer> for valueChanged(int)
By adding the annotation @QtUninvokable
to a signal declaration it becomes lightweight, i.e.
the signal is not implemented by Qt's meta object system but purely in Java.
Lightweight signals are a Java-only feature not available in Qt and QML.
In contrast to native Qt, QtJambi allows to use the signal-slot
mechanism also in any other class not being subclass of QObject
.
Therefore, the custom class needs to implement the interfaces
QtSignalEmitterInterface
and QInstanceMemberSignals
:
public class NotifyingList<T> extends ArrayList<T>
implements QtSignalEmitterInterface, QInstanceMemberSignals{
public final Signal1<T> added = new Signal1<>(this);
public boolean add(T t){
if(super.add(t)){
added.emit(t);
return true;
}
return false;
}
}
If you want to define a static signal use the signal classes from
QStaticMemberSignals
:
public final static QStaticMemberSignals.Signal1<String> textChanged = new QStaticMemberSignals.Signal1<>();
public static void changeText(String text){
textChanged.emit(text);
}
If you want to use signals in a local context use the signal classes
from QDeclarableSignals
:
public void signalInsideMethod(){
QDeclarableSignals.Signal1<Integer, String> localSignal = new QDeclarableSignals.Signal1<>(String.class);
localSignal.connect(...);
localSignal.emit("test");
}
Non-QObject member signals as well as static and local signals do not use the underlying meta-object system but are based on lightweight Java implementation.
QtJambi automatically detects properties by looking for typical getters
and setters. For instance, if the class has two methods int getFoo()
and void setFoo(int)
the class is considered to have a property called
"foo".
All features supported by Qt properties are also available in QtJambi:
@QtPropertyNotify
public final Signal1<String> textChanged = new Signal1<>();
@QtPropertyReader
public final String text(){...}
@QtPropertyWriter
public final void setText(String text){...}
@QtPropertyResetter
public final void resetText(){...}
...creates a property "text" with reader, writer, resetter and notify signal.
In case the annotated getter/setter method does not reflect the initended property name
you can use the field name
of @QtPropertyReader
and/or @QtPropertyWriter
to specify the actual property name.
Further annotations reflect the corresponding features of Qt properties:
@QtPropertyMember
@QtPropertyRequired
@QtPropertyScriptable
@QtPropertyStored
@QtPropertyUser
@QtPropertyConstant
@QtPropertyDesignable
@QtPropertyBindable
(Qt6 only)
Qt6 provides QProperty
as bindable property member.
In QObject-derived classes, the appearance of void setFoo(int)
and int getFoo()
(or int foo()
) is auto-detected as property foo
.
Here, you don't need QtPropertyReader
and QtPropertyWriter
annotations. For an existing property "foo" an available method void resetFoo()
is considered to be the property's resetter.
Likewise, signal fooChanged
is auto-detected as corresponding notifier even without QtPropertyNotify
annotation and
method bindableFoo()
returning QBindable
is auto-detected as corresponding bindable even without QtPropertyBindable
annotation.
In QObject-derived classes, public final fields are considered to be constant (read-only) properties.
Additionally, a final QProperty<...> fooProperty
field is automatically considered to be property foo
. You don't need getter, setter and bindable.
Caution, if the member property's identifier ends with Property
this suffix is cut from the property name!
If your class declares a getter or setter but you don't intend to use it as Qt property you can annotate @QtPropertyReader(enabled=false)
.
The classes defined in Java are fully compatible with Qt's meta-object
system. All Java defined
subclasses of QObject
provide corresponding QMetaObject
s giving
access to signals, invokable methods and properties.
QMetaObject.forType(type)
provides meta-objects for any Java class
even for non-QObject
types.
By using meta-objects, it is also possible to access native objects whose classes are not public API.
QObject internalObject = ...
QMetaObject internalType = internalObject.metaObject();
// dynamically connecting to signal
internalType.findSignal(internalObject, "orientationChanged", Qt.Orientation.class)
.connect(this::onOrientationChanged);
// dynamically calling method
QMetaMethod changeOrientation = internalType.method("changeOrientation", Qt.Orientation.class);
changeOrientation.invoke(internalObject, Qt.Orientation.Horizontal);
// dynamically casting to interface type
if(internalObject.inherits(QPaintDevice.class)){
QPaintDevice paintDevice = internalType.cast(internalObject, QPaintDevice.class);
// this is even possible if (internalObject instanceof QPaintDevice)==false
// and (QPaintDevice)internalObject leads to ClassCastException
}
Basically, every Java class can be used as gadget, i.e. as meta-programmable type in Qt, so called gadgets. Gadgets are not QObject-based types with invokable methods and/or properties. In contrast to QObject a gadget class cannot declare native signals.
QtJambi does not auto-detect properties and invokable methods on non-QObject classes.
You explicitely need to specify @QtPropertyReader/Writer
annotation for every property as well as @QtInvokable
for every method.
Alternatively, you can enable auto-detection by annotating the gadget class with @QtAsGadget
.
If you want to use other classes as gadgets, e.g. from third-party library, you may specify QtUtilities.useAsGadget(class)
or QtUtilities.usePackageContentAsGadgets(package)
at startup.
Pure java objects which are prepared as Qt gadget can also be used in QML.
Like Java, Qt provides extensive thread
support. In QtJambi, both
perspectives on threads run in parallel. While originally in Java, every
thread is represented by an instance of java.lang.Thread
. You can
start a new thread by creating a new Thread
instance and call
start()
. Qt provides the QThread
class for thread management, thus,
by using Qtjambi you have basically the choice:
// creating a thread with Java originals:
Thread javaThread = new Thread(()->{ ... });
javaThread.start();
// creating a thread with Qt:
QThread qtThread = QThread.create(()->{ ... });
qtThread.start();
// get the corresponding QThread for a Java thread:
QThread javaThreadAsQt = QThread.thread(javaThread);
// get the corresponding Java thread for a QThread:
Thread qtThreadAsJava = qtThread.javaThread();
Remarks:
- The native method
QThread::wait(...)
has been renamed in Java toQThread.join(...)
. - The Java thread features uncaught exception handler, context class loader, thread group, daemon thread and thread name are made available in
QThread
. - Never call
dispose()
on a running thread!
For thread synchronization, Qt provides a number of classes for different scenarios all avaliable in Java:
QMutex
,QReadWriteLock
,QSemaphore
andQWaitCondition
However, Qtjambi does not provide the convenience classes
QMutexLocker
, QReadLocker
, QWriteLocker
and QSemaphoreReleaser
.
Use try-finally blocks instead:
QReadWriteLock lock = new QReadWriteLock();
try{
lock.lockForRead();
}finally{
lock.unlock();
}
QtJambi does not provide the Qt classes for atomic operations like
QAtomicInteger
and QAtomicPointer
. Please, use Java built-in atomic
classes.
QObjects are
thread-affine, i.e every
QObject
is associated to the QThread
it was created in (or moved to).
Signals and events of such an object can only run by the associated
thread. Thus, using a QObject
from outside its own thread may cause a
QThreadAffinityException
to be thrown.
If you need to use a method of a QObject
from another thread, use the
meta-object system instead:
QComboBox comboBox = ...
if(comboBox.thread() != QThread.currentThread())
QMetaObject.invokeMethod(comboBox::clear, Qt.ConnectionType.BlockingQueuedConnection);
You can switch on thread affinity checks with the Java start parameter
-Dqt.enable.thread.affinity.check=true
. This decreases performance
but leads to exceptions in case of thread affinity breaches. It is
recommended to test your application with enabled thread affinity checks
and to disable these checks in release/productive mode.
The Qt type system provides many interfaces, i.e. C++ classes with pure
virtual functions that are intended to be implemented in a
multi-inheritance context. For instance, the type QPaintDevice
is
implemented by the QObject
subclass QWidget
as well as by the value
type QPixmap
. QtJambi provides these Qt interface classes as Java
interfaces. You can use these interfaces in the same degree of freedom
as any other Java interface:
QRunnable runnable;
// creating annonymous class
runnable = new QRunnable(){
@Override
public void run() {}
};
// using lambda expression
runnable = ()->{};
// creating a custom class
class MyRunnable implements QRunnable{
@Override
public void run() {}
}
runnable = new MyRunnable();
// implementing the interface as subtype
class MyRunnableWidget extends QWidget implements QRunnable{
@Override
public void run() {}
}
runnable = new MyRunnableWidget();
In the example code above, the class MyRunnableWidget
combines a
QObject
subtype with an interface type. It is completely equivalent to
a custom C++ class:
class MyRunnableWidget : public QWidget, public QRunnable{
Q_OBJECT
public:
void run() override {}
};
Some Qt interfaces provide non-trivial constructors. Since Java interfaces cannot provide constructors, there is a workaround for constructing these interface instances:
public class MyLayoutItem implements QLayoutItem {
public MyLayoutItem(Qt.Alignment alignment){
QtUtilities.initializeNativeObject(this,
QtArgument.begin(QLayoutItem.class)
.add(alignment));
}
// implemented interface methods
// ...
}
Here, QtArgument
creates a stream of arguments subdivided by the
implemented interface types.
public class MyLayoutItemObject extends QObject implements QLayoutItem {
public MyLayoutItemObject(QObject parent, Qt.Alignment alignment){
QtUtilities.initializeNativeObject(this,
QtArgument.begin(QObject.class)
.add(parent)
.begin(QLayoutItem.class)
.add(alignment));
}
// implemented interface methods
// ...
}
In contrast to Qt, Java interfaces do not support protected methods in interfaces. There are some Qt interfaces providing protected methods to be used within subtypes. The Javadoc pages of these interfaces provide suitable code snippets to access the missing protected members.
class MyGraphicsLayoutItem implements QGraphicsLayoutItem{
@QtUninvokable
protected void setGraphicsItem(QGraphicsItem item){
QGraphicsLayoutItem.MemberAccess.of(this).setGraphicsItem(item);
}
@QtUninvokable
protected void setOwnedByLayout(boolean ownedByLayout){
QGraphicsLayoutItem.MemberAccess.of(this).setOwnedByLayout(ownedByLayout);
}
@QtUninvokable
protected QSizeF sizeHint(Qt.SizeHint which) {
return sizeHint(which, new QSizeF());
}
@QtUninvokable
protected QSizeF sizeHint(Qt.SizeHint which, QSizeF constraint){
return new QSizeF();
}
//...
}
In case, a protected (or even private) interface method is pure virtual in C++,
QMissingVirtualOverridingException
is thrown at runtime when the
method is missing in the custom implementation.
In contrast to Qt, methods in Java interfaces are never final and, thus,
can always be implemented by subclasses. Some Qt interfaces provide
non-virtual functions that are represented in the corresponding Java
interface by non-final default methods. If a custom implementation of
the interface overrides such a non-virtual functions,
QNonVirtualOverridingException
is thrown at runtime.
Some interfaces are not intended to be subclassed in Java at all, e.g.
the interface QSurface
. If you implement such an interface,
QInterfaceCannotBeSubclassedException
is thrown at runtime.
All interface types provide a nested default implementor class called
Impl
. Instead of implementing the Java interface, you can extend the
nested implementor class:
// creating a custom class from pre-implementation
class MyRunnable extends QRunnable.Impl{
@Override
public void run() {}
}
QRunnable runnable = new MyRunnable();
Qt has a resource system for storing icons and other resources in the application's executable or in libraries. You can use this mechanism also in Java, however, using resources in compiled-in manner is not possible. You should deliver your rcc resource files along with your Java program or pack it into your jar files.
Alternatively, you can store your icons (and other resources) directly in a Java package and access it from classpath:
QAction newFileAction = new QAction(new QIcon(":com/myapplication/icons/newFile.png"), "New File");
QFile cpResourceFile = new QFile(":com/myapplication/icons/data.dat");
If you want to specify an URL referring to classpath resource use qrc:
:
QUrl url = new QUrl("qrc:/com/myapplication/icons/data.dat");
If you want to use classpath resources in QML you always need to specify it this way.
Qt's
internationalization
mechanism is completely supported by QtJambi. Simply embed all UI texts
by tr(...)
as introduced
here.
QAction newFileAction = new QAction(new QIcon(":com/myapplication/icons/newFile.png"), tr("New File"));
Use Qt's lupdate
tool to extract all UI text from source code.
Therfore, create a *.pro file listing all Java source code paths as it
is exemplified below:
files.pro containing
SOURCES = com/myapplication/MyApplication.java \
com/myapplication/MainWindow.java \
com/myapplication/UserDialog.java
Then, call lupdate
:
lupdate files.pro -ts com/myapplication/translations/app_de.ts
The next step is to open the file
com/myapplication/translations/app_de.ts
in Qt Linguist and
translate the UI texts entry by entry. Thereafter, use lrelease
to
create the binary file containing the translations:
lrelease com/myapplication/translations/app_de.ts -qm com/myapplication/translations/app_de.qm
Install the required translation at runtime by loading the corresponding qm file from classpath:
QTranslator translator = new QTranslator();
translator.load(":com/myapplication/translations/app_de.qm");
QCoreApplication.installTranslator(translator);
Qt allows to create sophisticated user interfaces in graphical manner by using
Designer. Designer produces a *.ui
file
containing all components and properties of the designed user interface.
There are two ways to use these designed UIs in your QtJambi java application.
- ...by dynamically loading at runtime. Therefore, use the class
io.qt.widgets.tools.QUiLoader
from moduleqtjambi.uitools
:
QUiLoader loader = new QUiLoader();
QFile device = new QFile(":com/myapplication/widgets/mainwindow.ui");
device.open(QIODevice.OpenModeFlag.ReadOnly);
QWidget widget = loader.load(device);
device.close();
- ...by generating source code.
Therefore, use the tool UIC available in module
qtjambi.uic
. Download qtjambi-uic.jar from the release of your choice along with the correponding platform-dependent qtjambi-uic-native-X.jar and call:
java -Djava.library.path=<path to Qt libraries>
-p qtjambi-6.8.1.jar:qtjambi-uic-6.8.1.jar
-m qtjambi.uic --output=src --package=com.myapplication.widgets com/myapplication/widgets/mainwindow.ui
Alternative way to call it:
java -Djava.library.path=<path to Qt libraries>
-cp qtjambi-6.8.1.jar:qtjambi-uic-6.8.1.jar
io.qt.uic.Main --output=src --package=com.myapplication.widgets com/myapplication/widgets/mainwindow.ui
QtJambi UIC produces the widget class in output directory (-o
) and target package (-p
) as java source code file.
By specifying --generator=kotlin
you can generate Kotlin code.
Qt provides a number of classes and functions only available on specific platforms and/or for specific configurations. QtJambi provides these specific API components on all platforms and for all configurations. Thus, QtJambi is source and binary compatible for all cases.
For instance, QtJambi provides the class QSslConfiguration
and the
function QNetworkAccessManager.connectToHostEncrypted(...)
wether ssl
is available at compile time or not. However, if ssl is not available at
runtime, QNetworkAccessManager.connectToHostEncrypted(...)
and likwise
new QSslConfiguration(...)
throw a QNoImplementationException
.
QtJambi makes Java and QML fully interoperable. You can use Java-defined classes in QML and vice versa.
If your Java program uses QML for creating extended UI objects, it is recommended to first initialize all Qt modules you intend to use in Qt, e.g.:
QtUtilities.initializePackage("io.qt.network");
QtUtilities.initializePackage("io.qt.quick");
Also, it is necessary to load all Qt libraries not available as QtJambi module if you intend to use them in QML, e.g.:
QtUtilities.loadQtLibrary("QuickShapes");
QtUtilities.loadQtLibrary("QuickTemplates2");
QtUtilities.loadQtLibrary("QuickControls2");
QtUtilities.loadQtLibrary("QuickParticles");
Now, you can load qml code, for instance, by using QQuickView
:
QQuickView view = new QQuickView();
view.setSource(new QUrl("qrc:com/myapplication/qml/Main.qml"));
view.show();
In analogy to C++ you can make Java classes available to QML. Therefore, register the Java class as QML type:
QtQml.qmlRegisterType(Message.class, "com.mycompany.messaging", 1, 5, "Message");
There are two requirements for Java classes to run with QML:
- The Java class has to subclass
QObject
or a subclass ofQObject
. - The Java class has to provide a declarative constructor as exemplified below:
public class Message extends QObject{
private final String author;
private final QDateTime creationDate;
private Message(QDeclarativeConstructor dc) throws IllegalAccessException {
super(dc);
}
public String author() { return author; }
public void setAuthor(String author) { this.author = author; }
public QDateTime creationDate() { return creationDate; }
public void setCreationDate(QDateTime creationDate) { this.creationDate = creationDate; }
}
The QDeclarativeConstructor
parameter is an internal constructor
marker and needs to be passed through to the super constructor. It is
not allowed to call the declarative constructor of any QObject
class
from inside Java.
This allows you to use the Java-defined type in QML:
import com.mycompany.messaging 1.5
Message {
author: "Amelie"
creationDate: new Date()
}
The QtQml
class also provides methods for registering singleton types
or instances, uncreatable types, interface types, extended types,
attached properties and so on. Refer to
QML C++ integration
to read more about how to use custom types in QML.
Since Java does not support preprocessor macros, there is no automatic
type registration as it is enabled for C++ projects by CONFIG += qmltypes
.
You need to register all Java classes you want to use in QML
manual via QtQml.qmlRegister...
. Alternatively, you can use the class
QmlTypes
in the Qtjambi QML utilities and prepare entire packages to
be exported to QML.
QmlTypes.registerPackage("com.mycompany.messaging", 1);
You could also specify the package's version and qml classes by annotating the
package-info.java with QmlImport
:
@io.qt.qml.util.QmlImport(majorVersion=1, classes={Message.class})
package com.mycompany.messaging;
In this case, register the package without specifying a version:
QmlTypes.registerPackage("com.mycompany.messaging");
By registering an entire package, all Java classes annotated as QML type are registered. Additionally, all *.qml files located in the registered package are registered as QML type. Annotating as QML type works similar to C++ but instead of preprocessor macros use corresponding annotations:
QML_ELEMENT
→@QmlElement
QML_NAMED_ELEMENT
→@QmlNamedElement(name)
QML_ANONYMOUS
→@QmlAnonymous
QML_INTERFACE
→@QmlInterface
QML_UNCREATABLE
→@QmlUncreatable(reason)
QML_SINGLETON
→@QmlSingleton
QML_ADDED_IN_MINOR_VERSION
→@QmlAddedInMinorVersion(N)
Example:
package com.mycompany.messaging;
import io.qt.*;
import io.qt.core.*;
import io.qt.qml.*;
import io.qt.qml.util.*;
@QmlElement
@QmlAddedInMinorVersion(5)
public class Message extends QObject{
// constructor, properties, getters and setters see above
}
When transfering QObject
s from Java to QML be aware that QML engine might change the object's ownership as
introduced here. In Qt's terminology
"JavaScriptOwnership" means an object is owned by QML/JavaScript code. Do not mix up JavaScript with Java.
JavaScript is the runtime-interpreted script language used in QML when defining functions and bindings.
On the contrary, Java is a compiled programming language using the same type space and API as C++.
Thus, when using Qt API in Java "CppOwnership" means an object owned by Java code.
Be aware that object's ownership is transferred to QML when it is returned by a Java method (and jas no parent).
In this case, QML might delete the object although still used in Java. It is highly recommended to only submit
Java created QObject
s through properties to avoid this situation.
Finally, you can bundle your custom Java QML classes into a JAR file and
provide it as QML module. Therfore, QtJambi provides the jarimport
plugin to be found in the utilities
folder of the platform binaries.
Create a jar file containing the Java package to be provided for QML
import, e.g. com.mycompany.messaging
. Then, create a directory path in
the qml import location that matches your package subdirectories, e.g.
<QTPREFIX>/qml/com/mycompany/messaging
and place the jar file in it.
Additionally, copy the jarimport plugin library to the package
directory.
Now, create a file called qmldir
with following content:
module com.mycompany.messaging
plugin jarimport
The directory substructure should look like this:
qml
| com
| mycompany
| messaging
| com-mycompany-messaging.jar
| jarimport.dll
| qtdir
Since pure java objects can be used as Qt gadget they can also be used in QML/JavaScript.
There are basically two different types of Java-defined gadgets: cloneable values and unique entities.
A Java class represents a cloneable value if it has a standard constructor and a clone()
method
returning a copy of the instance. Alternatively, instead of clone()
method the class can
implement the interface java.lang.Cloneable
. By convention, the equals(Object)
function returns true
for each clone and the hashCode()
of clones returns the same value. All Java classes not meeting this
characteristic are types for unique entities.
Unique entities can only be created in Java. If a unique entity is transferred to QML for instance by a method call
the JavaScript variable stores a reference to the Java object. Actually, the reference is represented by a wrapper, thus,
you cannot successively compare to null
in JavaScript. therefore, you need to write an invokable Java method and do the null check there.
Item{
Component.onCompleted: {
// expecting a Java-defined singleton subclassing QObject...
var object = singleton.supplyJavaGadget();
// object now contains a reference to the Java object.
// The Java object has getText and setText methods.
object.text = "Hello World!"
// null test never succeeds:
if(object==null)...
// instead do the null check in Java:
if(singleton.checkForNull(object))...
}
}
In contrast to unique entities instances of cloneable value types are created in QML. Whenever they are assigned to another variable or used as function parameter a new clone of the object is created.
Cloneable types have to be registered as QML type with lower case typename:
qmlRegisterType(ValueType.class, "com.example.program", 1, 0, "myGadget");
Such a type can then be used in QML:
import com.example.program
QtObject{
property myGadget gadget
}
An object of type ValueType
is created when the QML object is loaded and assigned to the proeprty variable gadget
.
Optionally, a cloneable value type class can define a constructor taking one QJSValue
argument.�
If this is the case you can specify arguments for value creation in QML:
import com.example.program
QtObject{
property myGadget gadget: {"text": "Hello World!"}
}
You can implement custom Qt plugins in Java. These plugins can be either realized as application internal implementations or as jar library to be loaded automatically on demand.
Use the method QPluginLoader.qRegisterStaticPluginFunction(...)
to
register a plugin class or instance. The class inherits QObject
and
needs to implement an interface (or class) known as Qt plugin, for
instance:
public class CustomImageIOPlugin extends QImageIOPlugin {
@io.qt.QtUninvokable
public QImageIOHandler create(QIODevice device, QByteArray format){
return new CustomImageIOHandler(device, format);
}
}
Finally, register the plugin implementation anywhere in your application:
QPluginLoader.qRegisterStaticPluginFunction(CustomImageIOPlugin.class, Map.of("Keys", List.of("custom")));
If you want to provide a custom plugin as jar library you need to provide a platform-dependent loader library along with the jar file. Therefore, use the QtJambi plugin deployer tool to prepare the loader library. Download qtjambi-deployer.jar from the release of your choice along with the correponding platform-dependent qtjambi-deployer-native-X.jar. Call the plugin deployer as shown below. Make sure the library path points to the Qt and QtJambi libraries:
java -Djava.library.path=<path to Qt libraries>
-p qtjambi-6.8.1.jar:qtjambi-deployer-6.8.1.jar
-m qtjambi.deployer plugin
--class-name=my.company.CustomImageIOPlugin
--class-path=my-company-library.jar
--dir=<output directory>
--meta-data=metadata.json
Alternative way to call it:
java -Djava.library.path=<path to Qt libraries>
-cp qtjambi-6.8.1.jar:qtjambi-deployer-6.8.1.jar
io.qt.qtjambi.deployer.Main plugin
--class-name=my.company.CustomImageIOPlugin
--class-path=my-company-library.jar
--dir=<output directory>
--meta-data=metadata.json
The metadata.json file contains the keys of the plugin and additional meta data. Example:
{
"Keys": ["custom"]
}
QtJambi plugin deployer tool saves the prepared library and the jar file in the specified output directory.
Alternatively, deployer can generate source code for compiling the plugin library. This is especially necessary on macOS (arm64).
java -Djava.library.path=<path to Qt libraries>
-p qtjambi-6.8.1.jar:qtjambi-deployer-6.8.1.jar
-m qtjambi.deployer plugin
--class-name=my.company.CustomImageIOPlugin
--class-path=my-company-library.jar
--dir=<output directory>
--meta-data=metadata.json
--source
Now, output directory contains a source code project for the plugin library.
Call qmake
and make
to build the library.
QtJambi provides a JDBC plugin for using Java SQL capabilities in Qt.
QSqlDatabase db = QSqlDatabase.addDatabase("QJDBC");
db.setDatabaseName("jdbc:sqltech:192.168.178.155:4444");
QSqlQuery query = new QSqlQuery(db);
query.prepare("SELECT * FROM qtjambi");
query.exec();