How to outsource qml listview delegate - qt

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"}

Related

Qt Creator autocomplete values for a global qml object in a file which is loaded by Stackloader?

So in QML, I can declare a stackview like this:
ApplicationWindow {
Item{
id: globalObject
specialProperty: "xyz"
}
StackView{
id: sv
}
}
Then I have a button which loads a "Page1.qml" file into the StackView. When the program runs, I can access the globalObject from code in Page1.qml but qtCreator's autocomplete is oblivious to this connection. It is not very quick to go back and forth between Page1.qml and main.qml to find the name of special property especially when there are many objects that have to be shared between multiple pages.
In general, what I want to know, is there a way to statically link a response to a dynamically loaded object or leave a hint for the IDE that the following objects in this file are available to this other file?

Best Practices to implement a menu system in a QML game?

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()
}
}
}

How to change the transient parent of a QML Dialog/Window?

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.

[Flex mobile]About placing many instances of a custom component

Right now , my development of app is in the optimizing stage. I have a problem when adding one of my components: PlayerInfo, which is extended from Group and has some Labels and Images in it,I have to create 60 of this component and put'm all into a HGroup. But in the process of adding them into the hgroup, my app just stops responding for a few seconds,which is not tolerable. Can I achieve this with less memory usage?
I have read this page and thought if I can do it with any of my components,not only bitmaps.does anyone know how to do that?
here is how I did it:
class PlayerInfo extends Group{
private var name:Label;
private var age:Label;
private var photo:Image;
}
and in my list class:
public function addPlayers(arrPlayer:Array):void{
for(;;){
var player:PlayerInfo=new PlayerInfo();
HGroup.addElement(player);
}
}
Can I achieve this with less memory usage?
Yes! With your current approach; if you have 60 instances of your component, then the app creates 60 instances of the component, renders them all and puts them on the screen; even if they are not currently a view area.
You should make use of a class, such as a List or DataGroup, as #RiaStar suggests. Your custom component, PlayerInfo, should be used as the itemRenderer and the 'list' you are creating these components from should become the List's dataProvider.
Once you do this, then your app will make use of the List's renderer recycling. So, only the visual elements displayed on screen will be displayed to the user. So, if you have 10 items shown on screen from your list of 60; the app will generate 50 less an items. This will make better use of processing power and better use of device memory.
To change your class to a renderer, you will have to implement a dataChange() event handler so that the component updates whenever the list changes the data that the renderer should display.
I don't think we enough information to get a more detailed explanation. What is your dataProvider? What propeties need to be set on your custom component?

Flex and fake Mxml initialisation without actually showing the component, (more insise)

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.

Resources