I'm working on a Qt (5.3) desktop application (C++ core with a QML ui) whose main window is an ApplicationWindow and that at some point launches Dialogs.
Since there are differences in the use of dialog modality between Windows and Mac OS X (e. g. An about dialog is rarely modal on Mac OS X but is almost always modal on Windows) and also in the way of presenting some dialogs’ content, I’ve changed the design to allow the implementation of platform specific versions of the dialogs.
For so I created the following DialogLoader:
Loader {
id: dialogFactory
property string dialogName
function platformFolder() {
if (Qt.platform.os === "osx")
return "osx"
return "win"
}
onDialogNameChanged: { source = platformFolder() + "/" + dialogName + ".qml" }
onStatusChanged: {
if (dialogFactory.status === Loader.Error)
console.log("DialogFactory: failed to load file: " + source);
else if (dialogFactory.status === Loader.Ready)
console.log("DialogFactory: file \"" + source + "\" loaded")
}
}
Which I use as follows:
ApplicationWindow {
// …
property alias aboutDialog: aboutDialogLoader.item
// …
DialogLoader { id: aboutDialogLoader; dialogName: "AboutDialog" }
// …
Action { text: qsTr("About..."); onTriggered: aboutDialog.show() }
// …
}
That approach is working fine and it suits my needs except for one thing:
Modal dialogs on Windows don’t behave as they do when I declare them in the ApplicationWindow directly. If the app looses focus when a modal windows is opened and the focus is granted again, the modal window appears behind the main window which causes the app to be inoperative.
After some research I’ve realized that the cause of the problem is that with the loader approach the ApplicationWindow is not acting as the transient parent of the Dialog.
I've found a work-around for this by manually bringing the Dialog to front when that happens:
MainWindow {
// ...
onActiveChanged: {
if (Qt.platform.os === "windows") {
if (active) {
// ...
if (aboutDialog.visible) {
aboutDialog.requestActivate()
}
// ...
}
}
}
// ...
}
but I think it would be better to avoid such work-arounds, if possible. After digging into the Qt documentation without any luck, I decided to post this question, which can be resumed as follows:
Is it possible to change the transient parent of a QML Window? If so, please would you point out how?
Suggestions about a different approach that could avoid the reparent stuff are welcome.
I guess it is impossible to do that. At least in Qt 5.4.
From documentation (default property "data" of QML type "Window")
data : list The data property allows you to freely mix visual
children, resources and other Windows in a Window.
If you assign another Window to the data list, the nested window will
become "transient for" the outer Window.
If you assign an Item to the data list, it becomes a child of the
Window's contentItem, so that it appears inside the window. The item's
parent will be the window's contentItem, which is the root of the Item
ownership tree within that Window.
If you assign any other object type, it is added as a resource.
It should not generally be necessary to refer to the data property, as
it is the default
property for Window and thus all child items are automatically
assigned to this property.
It seems that all dynamic creation of "Window" has wrong transient parent, because of QML Binding limitation. All QML list elements is not modifiable. Thus, it is not recommended to use lists with property bindings. You'll never get notify signal if it changes. So, I guess, QML Engine never knows that new QML element is "Window" type and it necessary to add it to Windows hierarchy.
From documentation (https://www.ics.com/files/qtdocs/qml-list.html):
A list property cannot be modified in any other way. Items cannot be
dynamically added to or removed from the list through JavaScript
operations; any push() operations on the list only modify a copy of
the list and not the actual list. (These current limitations are due
to restrictions on Property Binding where lists are involved.)
Maybe this is a bug, maybe feature. Anyway, for now it is impossible to load "window" type dynamically with proper transient parent.
The best workaround I've found - use modality: Qt.ApplicationModal for dialog.
Related
I'm relatively new to QML/QtQuick 2 and I quite enjoy it. To start mastering it I am implementing a small fullscreen game. My idea is to have the (simple) game and when the uses presses up the ESC key to pop-up a graphical menu with some settings.
Now I have all the basics running but I'm not completely sure which is the best Declarative way of managing the menu and passing keyboard focus back and forth.
Is it better to statically create the menu and make it hidden in my main file, like this:
MyMenuSystem {
visible: false
}
and then set it to "visible" when I need it? Or defer loading it and use Loader and create a new instance? I prefer to have it declared in my QML code for cleanness, but what is the best practice here? Or is there some component to switch between views that I'm completely missing? I could find very little information and examples on this topic. Thanks!
Your question is kinda broad and could lead to opinionated answers, but since it's a topic close to my heart, I thought I'd try to give some advice.
Loader
I tend to only use Loader when the item I need to load is only relevant under certain circumstances. For example, if a document is open that doesn't support a certain feature, then the UI element that represents that feature can be put into a Loader. A benefit of this (besides a (usually) very small increase in performance) is that it avoids the need to add lots of null checks for that feature. For example, if you don't use a Loader in this case, you might end up with a lot of code that looks like this:
text: optionalFeature.foo ? optionalFeature.foo.bar : ""
With a Loader, you control when the item is loaded, such that it's only loaded when the optional feature is enabled. Then the code becomes much nicer:
text: optionalFeature.foo.bar
Popup
For an in-game menu, I'd use a Popup. This is good if you want the user to be able to see the game in the background, though you can also obscure that with an overlay, if you wanted.
StackView
If you don't want to use a popup, then I'd suggest StackView. You should probably be using this for each screen in your game anyway, as it is a perfect fit for it.
Here's how I managed to do a Menu System:
I used Component to create a Component and createObject() to turn it into an instance of that component.
MyMenu.qml:
import QtQuick 2.9
Popup {
/* Menu Item 1 */
/* etc etc */
}
Game.qml:
Component {
id: myMenuId
MyMenu {
}
}
Game {
id: myGameId
property var dynamicItem
Keys.onPressed: {
if (event.key == Qt.Key_ESC) {
dynamicItem = myMenuId.createObject(myGameId);
dynamicItem.open()
}
}
}
My delegate grew pretty big. I want to put it in a dedicated file. What do I have to do to make this work? I need clearifications especially on how to import and instantiate the delegate. For future readers a complete howto would be fine.
You can have a property property Component delegateComponent : Qt.createComponent("file.qml") and use that as the delegate. Or directly delegate: Qt.createComponent("file.qml").
You can use a Loader for the delegate, and set its source property to the desired file. This extra level of abstraction allows different items in the list to instantiate different qml files, practically achieving different delegates.
Normally, you don't need to import anything, unless your component is registered as a part of an external module. QML files part of your projects will just work, no imports are needed.
You can click on the root object of your delegate component (not on the component but its single allowed child), go to "refactoring" and click "move component into separate file". This will result in a Component { TheNewQMLFile { } } where the TheNewQMLFile will replace the content of the object tree you promoted to a new source. It can also work in this form, without the need to use the first two techniques.
Because of QML's dynamic scoping, you can use the model roles from external QML files as well, they will be resolved as long as the object is instantiated in the correct context, i.e. as a delegate of a view with a model, providing those roles.
3 possible ways to do it:
delegate: Qt.createComponent("DelegateType.qml")
delegate: Component { DelegateType { } }
delegate: Component { Loader { source: "DelegateType.qml"} } // here source can come from a model role as well
Actually, it seems that explicitly wrapping in a Component is not even necessarily, it happens implicitly as well. So the last two will also work like this:
delegate: DelegateType { }
delegate: Loader { source: "DelegateType.qml"}
I use Qt 5.5.0 and Qt Creator 3.4.2 on Mac-book, I want to open second qml on button click, I want to develop application in which login page come and after login control move to next page.
I try suggestion of stackoverflow, but still I face problem to open next qml on button click.
I tried
button2.onClicked:{
var component = Qt.createComponent("test.qml")
var window = component.createObject(root)
window.show()
}
How can I create a new window from within QML?
In upper case error come related to root, ReferenceError: root is not defined. I gave root id in test.qml file.
I also tried many more example to solve to develop functionality. Please give brief idea or code to develop this functionality.
The code below works, Take note of the property status and the function errorString() – they will give you information in debugging (.e.g. "file not found" errors, components not ready, etc).
See these pages for background:
Dynamic QML Object Creation from JavaScript
Component QML Type
Window QML Type
function createWindows() {
var component = Qt.createComponent("Child.qml");
console.log("Component Status:", component.status, component.errorString());
var window = component.createObject(root, {"x": 100, "y": 300});
window.show();
}
For this type of ui I'd use [StackView][1] component that allows also animations between transitions. Other solution is to use a Loader component.
I have a (hopefully) quick question. I've got some stepper boxes. Though really this could apply to any interactive component. I want the selected box to lose focus when when I click anywhere else ( stage included ). Is there an easy way to do this? I can't seem to find an effective way to make it lose focus.
In case anyone else finds their way here seeking a solution to this problem, here is the answer:
private function onGlobalMouseUp(event : MouseEvent) : void {
var fm:FocusManager = new FocusManager(stage);
//Since Flash Player can set focus on subcomponents as well as on components themselves,
//findFocusManagerComponent is used to find the component that either has focus or contains the subcomponent
//that has focus. If, for example, a TextField contained within a TextArea component has focus, findFocusManagerComponent
//will return the TextArea component, and not the TextField. This being the case, we can definitely determine
//whether the target object of the MouseUp event is a component (or is part of a component). If the target
//object is NOT a component (nor contained within one), then we clear all component focus.
if(fm.findFocusManagerComponent(event.target as InteractiveObject) is UIComponent){
//target of MouseUp is either a UIComponent, or is contained within a UIComponent, so do nothing.
}else{
//target of MouseUp is neither a UIComponent, nor is it contained within a UIComponent, so set component focus to null.
fm.setFocus(null);
}
}
So here's the solution I've come up with that works very well. I have a function called add() which has been assigned to applicationComplete. In that function I include:
this.skin.addEventListener( MouseEvent.MOUSE_UP, loseFocus );
Which calls:
private function loseFocus( e : MouseEvent ) : void
{
if ( e.eventPhase == EventPhase.AT_TARGET )
{
this.focusManager.deactivate();
}
}
Simple enough, and does what I was looking for. "Phase" filter is necessary to keep other components from registering the clicks.
As an important note: this.skin needs to be the event target. The stage is never exposed to the mouse in a Flex application.
Example Application Code
If someone has a better solution, please suggest one!
Perhaps you should check out FocusManager.hideFocus().
Maybe tie it into the focusOut event of your UIComponent.
Flex API
I have a TitleWindow mxml class wich has several components, and listeners.
On its creationComplete and init state i add some listeners which listen for events on its gui.
This TitleWindow is only shown when the user click on a "button", i made TitleWindow a singleton with the following code:
public static function getInstance():MyWindow
{
if ( MyWindow.singleton )
{
return MyWindow.singleton;
}
else{
MyWindow.singleton = new MyWindow();
return MyWindow.singleton;
}
}
I needed a singleton because the user will call this window several times as much as he wants and i only need one.
The problem is the following on some special external events i need to "modify" some listeners (remove listeners and add new ones) on a button from MyWindow, before it was even shown once.
I still have MyWindow.getInstance() in memory when my application starts up.
However adding /removing listeners does not seem to have any effect if he actual rendering of the components did not happen, event when using the following code on app startup.
myWindow= MyWindow.getInstance();
myWindow.initialize();
Not suprisingly if i "show" ('render') the myWindow at least once then the events modifications on the myWindow instance works perfectly.
How can i fake the complete initialisation of this component without showing it on startup ?
Thanks !
Which sort of a container holds your button? If you are using a Multiple View Container you can try setting the creationPolicy to all. Single View Containers create all their children in one go and you shouldn't face this problem.
From Flex 3.0 docs I could retrieve this:
The default creation policy for all containers, except the Application container, is the policy of the parent container. The default policy for the Application container is auto.
This looks like the cause for all your troubles.
Update: I did not mention this earlier, since I thought this was to be expected :) Setting the creationPolicy to all makes your application load more slowly. So, read up on Ordered Creation -- this technique helps you to choose if the controls are displayed all in one go (which is the default behavior, after all of the controls have been created) or step-by-step, as and when they are created.