Spawn QtQuick UI file from a normal QML Window class - qt

How does one display a QtQuick UI fileset (ui.qml and .qml) from a normal QML Window?
To display other QML windows from my parent Window class, the call is, roughly:
var component = Qt.createComponent("SubWindow.qml")
var window = component.createObject(window)
window.show()
I've tried calling both the QtQuickUISubwindow.ui.qml and QtQuickUISubwindow.qml, but neither works.
Are QtQuickUI files not meant to be sub windows?

Window and ApplicationWindow are not of Type Item (or QQuickItem). This however is a requirement for being placed as a root item in a .ui.qml-file as stated in the documentation:
The following features are not supported:
[...]
Root items that are not derived from QQuickItem or Item
So the answer is:
No, you can't use QtQuickUI files neither as windows nor as sub windows.
You can however easily use them within a sub window
// main.qml
Window {
id: root
width: 800
height: 600
visible: true
Loader { // creates my sub window without JS
id: winLoader
active: false // Change to create the window.
sourceComponent: Window {
width: srcLoader.item ? srcLoader.item.width : 0
height: srcLoader.item ? srcLoader.item.height : 0
visible: srcLoader.item
Loader {
id: srcLoader
source: "QtQuickUISubwindow.ui.qml" // Your ui.qml-file *here*
active: true
}
onClosing: winLoader.active = false
}
}
Button {
text: "Show sub window!"
onClicked: winLoader.active = true
}
}
This code has not been tested by me. Maybe I'll do so later once I have access to a Qt machine. How to initialize multiple windows with a repeater and a ListModel you can find here: https://stackoverflow.com/a/47018205/2056452
You might pass the source of the srcLoader to the model, and read it from there if you want to open multiple different windows.
You can ofc modify the QtQuickUISubwindow.qml-file and add a Window or ApplicationWindow of the appropriate size as the root element. Then you can create everything as you used to do, using JS - and hope that the garbage collector plays nicely.

Related

How to inject data into QT item shown with loader

In my application I have a global system that handles navigation between "screens". In QML I can simply call something like:
appNavigation.show("MyScreen.qml", NavigationType.FADE)
this calls a C++ part of the code which handles the current stack of screens and uses signals to report back to QML to do the actual animation. At the end in QML some Loader will load the input qml ("MyScreen.qml" in this case) and show it as defined.
My issue here is how to inject data into newly loaded screen. Essentially I would like to do something like the following:
function showMyScreen() {
MyScreen screen = appNavigation.show("MyScreen.qml", NavigationType.FADE)
screen.someData = "some data here"
}
but is this possible? Could I somehow return the screen that is loaded by the loader?
I am guessing not so I would satisfy with sending the data with the navigation itself like:
function showMyScreen() {
MyScreen screen = appNavigation.show("MyScreen.qml", NavigationType.FADE, "some data here")
}
I could forward the data to the point where I set source to the loader but still what then? How or where would that specific screen that is going to be loaded get the data. To reduce is this is what I get:
function setNewItemWithData(newItem, data) {
loader.source = newItem
loader.concreteScreen.data = data // Not really doable
}
again I assume this is not doable and I need to forward the data down to loader and use onLoaded event. So what I would do is something like:
onLoaded: {
myLoadedScreen.data = data
}
I assume something like this is possible but how? What am I missing here, how do I get myLoadedScreen and how to access its properties?
What I am currently doing now is dumping the data in C++ part and then collecting it in the loaded QML. So like the following:
appNavigation.injectedData = "some data here"
and then in the newly loaded item:
property data = appNavigation.injectedData
It works but this seems like extremely poor coding. Any of the alternatives would be helpful.
Thank you for your patience.
Since the request for MCVE was made:
This is a general problem and I expect it to have multiple solutions. I would be looking forward to any of them. However the minimal example to produce this is creating a new project and adding a loader with another qml to which some property should be changed:
main:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Loader {
anchors.fill: parent; anchors.margins: 20
source: "MyScreen.qml"
// TODO: make screen green (loadedScreen.color = "green")
}
}
MyScreen:
import QtQuick 2.0
Rectangle {
color: "red"
}
Current result is seeing a red rectangle and desired result is to see a green one. The point being that the main screen needs to tell what color the loaded screen needs to use.
You have to use the item property of the Loader to get the object loaded:
Loader {
id: loader
anchors.fill: parent; anchors.margins: 20
source: "MyScreen.qml"
onLoaded: loader.item.color = "green"
}
To do that, you might as well use Component (If you use it when reacting to an event)
Component {
id: myScreenComponent
MyScreen {
anchors.fill: parent
}
}
function showMyScreen() {
myScreenComponent.createObject(this, {"color: "green"});
}
Alternatively, given your first code, I would recommend you to use StackView.
The push method seems to be similar to your appNavigation.show one.
You can give it an url, some properties, and a transition type (that you can customize).

Qt - QML Screen Mirroring in a dual monitor setup

I've developed an application, with the UI done using QML. What I was asked to do now is to make it so that when a second monitor is used, the second monitor shows everything that the program is doing. At first I thought of just telling the client to configure Windows to clone its screens. However when the applications uses some of its functionalities I need for the cloned screen to display certain indicators in the cloned screens but not on the original screen.
So my question is, How can I accomplish this. How can mirror what is happening in one screen, while maintaing enough control to draw in one and not in the other.
My only idea is to use a timer to take as screen shot at regular intervals and show that image in the second screen.
Is this doable?
Taking periodic screenshots, although perfectly doable, is undesired because of the impact to performance. Instead you should make use the main window's onFrameSwapped() signal, to grab images only when a new frame is generated.
Ideally, you'd want to make use of Layer or ShaderEffectSource, as suggested by #dtech, to read and re-render the frame straight from the GPU. Unfortunately, due to limitations in Qt Quick's Scene Graph, it is not possible to accomplish this across separate windows without destabilizing the source window's scene graph.
Update: Nevertheless, I've found a way to emulate the desired features of ShaderEffectSource using ShaderEffect instead. Your other options are to either copy frames through the CPU using Item's grabToImage function, or to re-implement QQuickView (your QML window) in C++ to grab images from the QML Engine when a new frame is generated.
Working Solution 1: Use ShaderEffect (Update: New Solution)
The new approach I've found consists of using ShaderEffect on a child Window and forcing it to update by calling that Window's update() method, which is inherited from QWindow. By using ShaderEffect, you guarantee the source image will be accessed only through the GPU, increasing performance by orders of magnitude over solution #2.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainWindow
title: qsTr("Main Window")
visible: true
width: 400
height: 200
color: "#0F0"
Rectangle {
id: viewport
color: "#00F"
width: parent.width/2
height: parent.height/2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
// Draw QML Item into an off screen layer
layer.enabled: true
// Performance tweaks
layer.smooth: false
layer.mipmap: false
}
onFrameSwapped: {
// Update mirror window manually with each new frame
projectionWindow.update()
}
Window {
id: projectionWindow
property int screenID: 1
transientParent: mainWindow
visible: true
x: Qt.application.screens[screenID].virtualX
y: Qt.application.screens[screenID].virtualY
width: Qt.application.screens[screenID].desktopAvailableWidth
height: Qt.application.screens[screenID].desktopAvailableHeight
flags: Qt.FramelessWindowHint
color: "#000"
visibility: Window.Maximized
ShaderEffect {
id: img
// Set source to copy visuals from
property variant source: viewport
// Setting shader to the same resolution as the source may improve performance
width: viewport.width
height: viewport.height
// Performance tweak
blending: false
}
}
}
Triggering update() manually has the side effect of preventing the child from updating on its own. Calling the update method on the source window from the child Window once whenever needed works around this limitation.
It is worth noting that this solution only works with Qt's basic renderer, which is single threaded. Qt prevents the texture from being accessed from a diferent rendering threads, giving the following error message and returning a null pointer:
QQuickShaderEffectSource::textureProvider: can only be queried on the rendering thread of an exposed window
The performance gained from this method is far greater than the performance loss of using the single threaded renderer. You can force use of the single threaded renderer by setting an environment variable at the start of your application. This can be done from inside the app by adding the following code at the very start of your main function:
#if defined(Q_OS_WINDOWS)
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
putenv("QSG_RENDER_LOOP=windows");
#else
putenv("QSG_RENDER_LOOP=basic");
#endif
#elif defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
setenv("QSG_RENDER_LOOP", "basic", 1);
#endif
Qt's documentation states that using the basic renderer reduces portability, but I can attest this approach works fine on Windows, macOS, and Linux and that it does not work on Haiku OS.
I've switched to this solution on the most recent versions of a project I develop. You can study the full implementation at:
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/kirigami_ui/PrompterPage.qml#L760
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/kirigami_ui/main.qml#L847
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/prompter/ProjectionsManager.qml#L261
Working Solution 2: Use Item's grabToImage (original answer)
Another easy approach is to grab frames from QML, using an Item's grabToImage() function. Since Window is not an item in itself, you'd have to grab the image from one of its elements. In this example, I grab the image from an item called viewport each time a frame is swapped on mainWindow, using the onFrameSwapped() signal. Then the path to that image in memory is set as the source for the image in the second window, named projectionWindow. The second window will open on the screen set by the screenID variable; it is also set to be a frame-less window with its visibility is set to either Maximized or FullScreen, such that it is the only window seen on the second screen.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainWindow
title: qsTr("Main Window")
visible: true
width: 400
height: 200
color: "#0F0"
Rectangle {
id: viewport
color: "#00F"
width: parent.width/2
height: parent.height/2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
onFrameSwapped: {
viewport.grabToImage(function(result) {
projectionWindow.frame = String(result.url);
});
}
Window {
id: projectionWindow
property int screenID: 1
property alias frame: img.source
transientParent: mainWindow
visible: true
x: Qt.application.screens[screenID].virtualX
y: Qt.application.screens[screenID].virtualY
width: Qt.application.screens[screenID].desktopAvailableWidth
height: Qt.application.screens[screenID].desktopAvailableHeight
flags: Qt.FramelessWindowHint
color: "#000"
visibility: Window.Maximized
Image {
id: img
anchors.fill: parent
fillMode: Image.PreserveAspectFit
// Performance tweaks
asynchronous: true
cache: false
}
}
}
I used to employed this solution on a project I develop. You can study the full implementation at:
https://github.com/Cuperino/QPrompt/blob/a873a2ec9b0619ec1bfdbde22fe90850e76969a5/src/kirigami_ui/main.qml#L828
https://github.com/Cuperino/QPrompt/blob/a873a2ec9b0619ec1bfdbde22fe90850e76969a5/src/prompter/ProjectionsManager.qml#L260
Alternate, incomplete, solution
The third, more manual, and unfinished solution consists of the following: You'd inherit from QQuickView or QQuickWindow, and then send the image to a second window on either the QQuickWindow::afterRendering() or the QQuickWindow::frameSwapped() signal. You would use a QQuickFramebufferObject or some other rendering pipeline such as DirectX, Metal or Vulkan, to render and grab the frame off screen. Using OpenGL as your renderer, means negating all the performance advantages that come from using the native rendering pipelines supported by Qt 6. You may need to implement the frame grab once per pipeline to get all performance benefits.
https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html
The following talk by Giuseppe D’Angelo shows how some of this is setup in Qt 5. It doesn't show how to copy the contents to another window/screen but it could aid in this regard.
https://www.youtube.com/watch?v=V_idc9BBRuI
https://www.youtube.com/watch?v=D-7fVGIBz6k
You can use particular QML elements as texture sources and easily duplicate them via trivial fragment shaders.
You definitely do not want to take screenshots and draw that image back, it is wildly inefficient.
Keep in mind that it will be just a visual duplicate, it will not take user input. If you want it to be interactive in both windows, then you should simply use a single data object and connect it to two individual GUI elements.
OK, here is the code, but unfortunately, it evidently uncovers a bug in QML, as the implementation doesn't seem to work across different windows:
Window {
id: mainw
visible: true
width: 640
height: 480
title: qsTr("main window")
Row {
spacing: 5
Rectangle {
id: source
width: 100
height: 100
color: ma.containsPress ? "red" : "cyan"
Text {
text: "adoy"
anchors.centerIn: parent
}
MouseArea {
id: ma
anchors.fill: parent
}
}
ShaderEffectSource {
width: source.width
height: source.height
live: true
sourceItem: source
}
}
Window {
visible: true
width: 640
height: 480
title: qsTr("another window")
x: mainw.x + width + 10
y: mainw.y
Row {
ShaderEffectSource {
width: source.width
height: source.height
live: true
sourceItem: source
}
Rectangle {
width: 100
height: 100
color: "blue"
}
}
}
}

Unable to set the parent of a dynamically created QML component

I am creating dynamic component in QML as follows:
var component = Qt.createComponent("PlayerWindow.qml")
if (component.status != component.errorString())
console.log(component.errorString())
var playerWin = component.createObject(rootWindow);
Here rootWindow is my main application window. Now, the PlayerWindow is quite simple as:
Window {
id: playerWindow
width: parent.width
height: parent.height
Component.onCompleted: {
console.log(parent.width)
console.log(rootWindow.height)
}
}
The thing is that the values for parent.width and rootWindow.width are really different and this is also evident when the window is displayed. However, rootWindow is set as the parent in the createObject call. So, I am not sure what is happening there but I wanted to know if this is the correct way to set the component parent when they are being dynamically created.
Try to add console.log(parent) in the code. You will see something like qml: QQuickRootItem(0x1e3e4e0). If you check the Qt doc you will find that Item.parent() returns Item but Windows is not Itemdescendant but QQuickWindow. Also from documentation:
A QQuickWindow always has a single invisible root item ...
So, in your case parent and rootWindow are different objects.
P.S. The dynamic object creation in your code can produce an error since component.createObject will be executed although Qt.createComponent returns error. Just copy the code from Qt documentation.

Properly reloading a QQmlApplicationEngine

I have a QML-based app that loads a main.qml file from the file system like this:
myEngine->load("main.qml");
This works fine, but I'd like to "reload" the engine in case the main.qml was replaced with a newer version.
What I tried so far was calling load() again, assuming that the engine will automatically reset itself like in other Qt classes.
Unfortunately this is not the case. If I call the method again, another window will appear with the contents of the updated qml file, while the original window stays open and continues to display the old qml file.
To fix this I tried to call load(QUrl()), followed by clearComponentCache() and a final load call for the new file. This results in the same effect.
Any ideas how I can "properly" reload a QML engine while the application is running?
Just saw this, but if you're still trying to figure this out - it's a three step process, and you have some of it.
You MUST close window created by the QQmlApplicationEngine first. In my case I pulled the first root object off the QQmlApplicationEngine and cast it to QQuickWindow, then call close().
Now you can call clearComponentCache on the QQmlApplicationEngine.
This is what my window closing code does (note that I gave my main window an objectName)
QObject* pRootObject = in_pQmlApplicationEngine->rootObjects().first();
Q_ASSERT( pRootObject != NULL );
Q_ASSERT( pRootObject->objectName() == "mainWindow" );
QQuickWindow* pMainWindow = qobject_cast<QQuickWindow*>(pRootObject);
Q_ASSERT( pMainWindow );
pMainWindow->close();
The third step is, of course, to load your QML.
Later, I moved to creating a QQuickView window instead of QQmlApplicationEngine, so that I could just call clearComponentCache and then setSource (I didn't like the user seeing the UI window vanish and then re-appear.)
I would try storing myEngine as a pointer on the heap, and deleting it after calling quit(). Then you can reconstruct it to get the new version of the QML file.
If you don't want to do this for some reason (e.g. because you want to keep the window around or whatever), you might try using a Loader and loading the QML file that way. Your main.qml would then look something like this:
import QtQuick 2.0
Loader {
source: "changing.qml"
}
Whenever you detect that changing.qml has changed, just toggle the active property of Loader to trigger a reload of the file.
Using a file watcher:
main.py
DEBUG = True
class EntryPoint(qtc.QObject):
if DEBUG:
qmlFileChanged = qtc.Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.qml_engine = qqml.QQmlApplicationEngine()
self.qml_entry = str(PATHS.playground.resolve())
self.qml_engine.load(self.qml_entry)
if DEBUG:
qml_files = []
for file in glob.iglob('**/*.qml', root_dir=PATHS.QML, recursive=True):
qml_files.append(str((PATHS.QML / file).resolve()))
self.file_watcher = QFileSystemWatcher(self)
self.file_watcher.addPaths(qml_files)
self.file_watcher.fileChanged.connect(self.on_qml_file_changed)
if DEBUG:
#slot
def on_qml_file_changed(self) -> None: # pragma: no cover
self.qml_engine.clearComponentCache()
window: QQuickItem = self.qml_engine.rootObjects()[0]
loader: QQuickItem = window.findChild(QQuickItem, 'debug_loader')
qtc.QEventLoop().processEvents(qtc.QEventLoop.ProcessEventsFlag.AllEvents, 1000)
prev = loader.property("source")
loader.setProperty('source', "")
loader.setProperty('source', prev)
playground.qml
import QtQuick
import QtQuick.Controls.Material
Window {
id: root
width: 1200
height: 900
visible: true
flags: Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint | Qt.CustomizeWindowHint | Qt.WindowTitleHint
Material.theme: Material.Dark
Material.accent: Material.Cyan
Pane {
anchors.fill: parent
objectName: "_rootRect"
Loader{id: loader
objectName: "debug_loader"
anchors.fill: parent;
source:"anything.qml"
}
}
}

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.

Resources