Dynamically retranslate Qt Quick UI - qt

I want to dynamically retranslate Qt Quick GUI strings.
There is intrusive trick to retranslate affected string properties, whose notifications about changes cannot be centralized.
Is it possible to make qsTr (and others) to return string-like objects, which behaves exactly like string, but also behaves like global properties connected to common "valueChanged" signal (which I want to emit, when QEvent::LanguageChange in QCoreApplication occured).
I think I can use twitching of Loader's active property, which contains entire top level GUI element to make all the user-visible strings retranslated, but this approach results in lost of the state of all the items and components, connected to the Loader and not differs from complete application restart for me.
Is it possble to create such myQsTr function?

From Qt 5.10 onwards, you can call QQmlEngine::retranslate() after you have installed a new translator with QCoreApplication::installTranslator(), to ensure that your user-interface shows up-to-date translations.

You could opt to use your own, 100% QML solution like that:
// Tr.qml
// also put `singleton Tr Tr.qml` in the qmldir file
pragma Singleton
import QtQuick 2.7
QtObject {
function t(s) {
if (lang === eng) return s
var ts = lang[s]
return ts ? ts : s
}
property var lang: eng
readonly property var eng : {
"hello" : "hello",
"goodbye" : "goodbye"
}
readonly property var ger : {
"hello" : "hallo",
"goodbye" : "auf wiedersehen"
}
readonly property var esp : {
"hello" : "hola"
}
}
// test it out
import QtQuick 2.7
import QtQuick.Controls 2.1
import "." // same old singleton bug
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
color: "darkgray"
Column {
Text { text: Tr.t("hello") }
Text { text: Tr.t("goodbye") }
Button { text: "Eng"; onClicked: Tr.lang = Tr.eng }
Button { text: "Ger"; onClicked: Tr.lang = Tr.ger }
Button { text: "Esp"; onClicked: Tr.lang = Tr.esp }
}
}
The different language objects act like a map<string, string> and every time you change lang this will cause the binding expressions to reevaluate and refresh the value form the current language dictionary.
This solution will also fallback to the default language string if a translation is not found. You can easily customize the behavior and you don't rely on any external tooling. Clean, simple, self-contained and totally under your control.

Related

QML object and nest object onCompleted order

I have a QML file which contains an Item, the Item contains a object:
QtObject {
id: something
Component.onCompleted: {
console.log("A");
}
}
At the file Item I also have:
Component.onCompleted: {
console.log("B");
}
What I see in the Application Output is
B
A
What I want is for A to be processed first, is there anyway to do this or do I have to change use a function in the child object and call it from the onCompleted of the parent?
Well, AFAIK you can't change the "completed" order. But you could control when the QtObject loads. For example:
Item {
property QtObject something: null
property Component objectComponent: QtObject {
objectName: "something"
Component.onCompleted: console.log("A");
}
Component.onCompleted: {
something = objectComponent.createObject(this);
console.log("B", something);
}
}
qml: A
qml: B QObject(0x351d750, "something")
There's also the Loader type, which basically does the createObject() for you, though it's meant more for loading visual components on-demand.
Also the "something" QtObject could be in an external file, of course.
ADDED: Apparently this also works and loads in the desired order, though I'm not sure I'd prefer it myself (but it's more "declarative" I suppose :).
Item {
property QtObject something: objectComponent.createObject(this);
property Component objectComponent: QtObject {
objectName: "something"
Component.onCompleted: console.log("A");
}
Component.onCompleted: console.log("B", something);
}
As you can read from the official documentation (https://doc.qt.io/qt-5/qml-qtqml-component.html#completed-signal), "the order of running the onCompleted handlers is undefined". So you can not trust in your console output for know the object creation order.
If you want load components in a specific order, you can use the Loader item.

QML send signals between anonymous grandchildren

I'm trying to communicate between qml components in a tree structure. I have a main.qml component with and id of root. It has two children, and each of those children has an arbitrary number of children dynamically created from a repeater and a model.
When one of the grandchildren is clicked I would like the others to know, and be able to take action. So if I could send signals between the grandchildren that would be fine.
The problem is none of them have their id property set because they are made dynamically, and some of them are in different scopes. To communicate between them I have done the following:
Created a function in root, every grandchild can see that, and can call it with a message as parameter. The root function then emits a signal with the message as parameter. All the grandchildren can connect to the signal because they know the id of root.
What do people think of that? I'm getting the feeling that I've missed the point of signals in qml, feels like i've implemented a crude system and missed the whole point or something.
Also, I want to stay out of the C++ world, but do people think it would be best to use a C++ class so that I can use signals and slots.
What I'm aiming at is an MVC structure with very loose coupling, and a centralised Controller. What do people think about communicating between QML components in MVC.
The only similar questions I found here were about C++ or using hard-coded id's on components.
I don't think id's can be set dynamically, not even once at creation; am I wrong about that?
Also, the components are in different scopes, so id's can't be resolved; am I wrong about that?
I've written some code:
//qml.main
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
id: root
visible: true
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("&Open")
onTriggered: console.log("Open action triggered");
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
property string thisName: "root"
signal rootSays(string broadcastMessage)
function callRoot(message) {
var response = message
print("Root received: " + message)
print("Root broadcasting: " + response)
rootSays(response)
}
MajorComponent{//this is root's child A
property string thisName: "A"
thisModel: [{name:"First Grandchild of A", color:"red", y:0},
{name:"Second Grandchild of A", color:"green", y:80}]
}
MajorComponent{//this is root's child B
property string thisName: "B"
thisModel: [{name:"First Grandchild of B", color:"blue", y:210},
{name:"Second Grandchild of B", color:"yellow", y:290}]
}
}
//qml.MinorComponent
import QtQuick 2.0
Rectangle {
property string thisName: ""
property string thisColor: ""
color: thisColor
height: 50; width: 200
Text {
anchors.centerIn: parent
text: thisName
}
MouseArea {
anchors.fill: parent
onClicked: {
print(thisName + " clicked")
print("Root called with: " + thisName)
root.callRoot("Hello from " + thisName)
print("---")
}
}
}
//qml.MajorComponent
import QtQuick 2.0
Rectangle {
property var thisModel: []
Repeater {
model:thisModel
MinorComponent {
y: modelData.y
thisName: modelData.name
thisColor: modelData.color
function handleResponse(response) {
print(thisName + " received: " + response);
}
Connections {
target: root
onRootSays: handleResponse(broadcastMessage)
}
}
}
}
I don't think id's can be set dynamically, not even once at creation;
am I wrong about that?
ids are purely "compile" time construct. That being said, there is nothing preventing you from implementing and managing your own object registry system. A simple empty JS object would do, it can effectively be used as a QMap to lookup objects based on a key (the property name). If you set the map object as a property of the root object, it should be resolvable from every object in the tree because of dynamic scoping.
The approach with the signal is a sound one IMO. I've used something similar, combined with functors and capture by reference, allowing access to arbitrary and optionally existing objects in an arbitrary tree structure, filtering candidates by various criteria they must meet. You can do some very tricky stuff with this technique.
That being said, a practical example which illustrates what you actually want to achieve will be useful for providing a more specific answer.

Qt 5 QML app with lots of Windows or complex UIs

In QtQuick 2 using the QtQuick Controls you can create complex desktop apps. However it seems to me that the entire UI must be declared and create all at once at the start of the app. Any parts that you don't want to use yet (for example the File->Open dialog) must still be created but they are hidden, like this:
ApplicationWindow {
FileDialog {
id: fileOpenDialog
visible: false
// ...
}
FileDialog {
id: fileSaveDialog
visible: false
// ...
}
// And so on for every window in your app and every piece of UI.
Now, this may be fine for simple apps, but for complex ones or apps with many dialogs surely this is a crazy thing to do? In the traditional QtWidgets model you would dynamically create your dialog when needed.
I know there are some workarounds for this, e.g. you can use a Loader or even create QML objects dynamically directly in javascript, but they are very ugly and you lose all the benefits of the nice QML syntax. Also you can't really "unload" the components. Well Loader claims you can but I tried it and my app crashed.
Is there an elegant solution to this problem? Or do I simply have to bite the bullet and create all the potential UI for my app at once and then hide most of it?
Note: this page has information about using Loaders to get around this, but as you can see it is not a very nice solution.
Edit 1 - Why is Loader suboptimal?
Ok, to show you why Loader is not really that pleasant, consider this example which starts some complex task and waits for a result. Suppose that - unlike all the trivial examples people usually give - the task has many inputs and several outputs.
This is the Loader solution:
Window {
Loader {
id: task
source: "ComplexTask.qml"
active: false
}
TextField {
id: input1
}
TextField {
id: output1
}
Button {
text: "Begin complex task"
onClicked: {
// Show the task.
if (task.active === false)
{
task.active = true;
// Connect completed signal if it hasn't been already.
task.item.taskCompleted.connect(onTaskCompleted)
}
view.item.input1 = input1.text;
// And several more lines of that...
}
}
}
function onTaskCompleted()
{
output1.text = view.item.output1
// And several more lines...
// This actually causes a crash in my code:
// view.active = false;
}
}
If I was doing it without Loader, I could have something like this:
Window {
ComplexTask {
id: task
taskInput1: input1.text
componentLoaded: false
onCompleted: componentLoaded = false
}
TextField {
id: input1
}
TextField {
id: output1
text: task.taskOutput1
}
Button {
text: "Begin complex task"
onClicked: task.componentLoaded = true
}
}
That is obviously way simpler. What I clearly want is some way for the ComplexTask to be loaded and have all its declarative relationships activated when componentLoaded is set to true, and then have the relationships disconnected and unload the component when componentLoaded is set to false. I'm pretty sure there is no way to make something like this in Qt currently.
Creating QML components from JS dynamically is just as ugly as creating widgets from C++ dynamically (if not less so, as it is actually more flexible). There is nothing ugly about it, you can implement your QML components in separate files, use every assistance Creator provides in their creation, and instantiate those components wherever you need them as much as you need them. It is far uglier to have everything hidden from the get go, it is also a lot heavier and it could not possibly anticipate everything that might happen as well dynamic component instantiation can.
Here is a minimalistic self-contained example, it doesn't even use a loader, since the dialog is locally available QML file.
Dialog.qml
Rectangle {
id: dialog
anchors.fill: parent
color: "lightblue"
property var target : null
Column {
TextField {
id: name
text: "new name"
}
Button {
text: "OK"
onClicked: {
if (target) target.text = name.text
dialog.destroy()
}
}
Button {
text: "Cancel"
onClicked: dialog.destroy()
}
}
}
main.qml
ApplicationWindow {
visible: true
width: 200
height: 200
Button {
id: button
text: "rename me"
width: 200
onClicked: {
var component = Qt.createComponent("Dialog.qml")
var obj = component.createObject(overlay)
obj.target = button
}
}
Item {
id: overlay
anchors.fill: parent
}
}
Also, the above example is very barebone and just for the sake of illustration, consider using a stack view, either your own implementation or the available since 5.1 stock StackView.
Here's a slight alternative to ddriver's answer that doesn't call Qt.createComponent() every time you create an instance of that component (which will be quite slow):
// Message dialog box component.
Component {
id: messageBoxFactory
MessageDialog {
}
}
// Create and show a new message box.
function showMessage(text, title, modal)
{
if (typeof modal === 'undefined')
modal = true;
// mainWindow is the parent. We can also specify initial property values.
var messageDialog = messageBoxFactory.createObject(mainWindow, {
text: text,
title: title,
visible: true,
modality: modal ? Qt.ApplicationModal : Qt.NonModal
} );
messageDialog.accepted.connect(messageDialog.destroy);
messageDialog.rejected.connect(messageDialog.destroy);
}
I think loading and unloading elements is not actual any more because every user have more than 2GB RAM.
And do you think your app can take more than even 512 MB ram? I doubt it.
You should load qml elements and don't unload them, no crashes will happens, just store all pointers and manipulate qml frames.
If you just keep all your QML elements in RAM and store their states, it will works faster and looks better.
Example is my project that developed in that way: https://youtube.com/watch?v=UTMOd2s9Vkk
I have made base frame that inherited by all windows. This frame does have methods hide/show and resetState. Base window does contains all child frames, so via signal/slots other frames show/hide next required frame.

Does qml supports object oriented programming

I'm using QML for my project, I want to know if am instantiating a file in another file, is it like instantiating object for a c++ class?
File.qml
Rectangle {
id: idRect1
.
.
}
File2.qml
Rectangle {
id: idRect2
File1 {
id:idFile1
.
.
}
}
In File2.qml i have initialized File1, does it means i have created an object of type File1? Please share some knowledge(links) on how all this mechanism works. Thanks in Advance
In QML when creating a file with first letter uppercase, you're creating a component. Components are implemented using OOP aggregation (not subclassing). That mean if I write
// MyButton.qml
import QtQuick 2.0;
Rectangle {
id: base;
width: 120;
height: 40;
color: "lightgray";
Text {
text: "foobar";
anchors.centerIn: parent;
}
}
... I haven't subclassed Rectangle, I just created a component that contains a Rectangle as root object, and configurates it in a certain way, and adds a Text object in it.
As soon as a component is created, it can be instanciated by simply writing :
MyComponent { id: myNewInstance; }
Because that's a way it works in QML.
The component name is a kind of class (but not in the C++ or JS way to define it) and it can also be used as a type for a property :
property MyComponent theComponent : myNewInstance;
Then it can hold the ID of an object created with the given component, acting somewhat like a C/C++ pointer : the property holds a link to the actual object.
But because of the way QML was designed, even if it's more aggregating than subclassing, a property of the type of the root object of a custom component can also hold ID of a derived component, in my case :
property Rectangle theComponent : myNewInstance;
Will work, but if I try to put an ID of an Image or Text or something else, QML engine will throw incompatible types error.
I hope it helps you.

How to modify QML dynamic objects from JavaScript

I have QML file which describes button(moduleButton.qml):
import QtQuick 2.0
Rectangle {
id: button;
width: 100; height: 20
Text {
id: buttonText;
text: "Hello World";
}
}
From other QML form I load this button via Qt.createComponent method:
var moduleButton = Qt.createComponent("moduleButton.qml");
moduleButton.createObject(mainRect);
I tried to set/get width of moduleButton:
moduleButton.width = 30;
But received following error: Cannot assign to non-existent property "width"
How to access dynamic object attributes and child elements?
P.S. Qt.createQmlObject method perfectly works, but I need to load QML from file, not from string.
createObject() returns the new object. Your code should look like this:
var moduleButton = Qt.createComponent("moduleButton.qml");
var myButton = moduleButton.createObject(mainRect);
myButton.width = 40
The moduleButton is a component (a factory), used to instantiate the item.
Documentation:
http://qt-project.org/doc/qt-5/qtqml-javascript-dynamicobjectcreation.html

Resources