I'm still quite new to Qt and I want to add a High Score system to my game. I found this file http://grip.espace-win.net/doc/apps/qt4/html/demos-declarative-snake-content-highscoremodel-qml.html which is a high score model qml element. I have have added it to my project but I am at an utter loss on how to implement it. I simply want to know how I can use it to show a High Score Table when my window goes to a certain state. I also want to know how to add scores and close it when the game is restarted. This my seem silly but I really can't figure out how to use it.
From the linked file above:
Use this component like this:
HighScoreModel {
id: highScores
game: "MyCoolGame"
}
Then ... use the model in a view:
ListView {
model: highScores
delegate: Component {
... player ... score ...
}
}
So by slightly altering the simpler of the two examples given in the QML ListView docs, we get:
import QtQuick 1.0
ListView {
width: 180; height: 200
model: highScores {}
delegate: Text {
text: player + ": " + score
}
}
Though if you want further control over the formatting of each element of the list, as suggested by the use of delegate: Component in the example quoted above from HighScoreModel.qml, the second usage example in the documentation shows you how.
You can also have a look at V-Play Engine for qt-based apps and games. It comes with many components to make mobile development easier.
You can also add leaderboards and user profiles to your application with a few lines of code:
import VPlay 2.0
import VPlayApps 1.0
import QtQuick 2.9
App {
// app navigation
Navigation {
NavigationItem {
title: "User Profile"
icon: IconType.user
NavigationStack {
initialPage: socialView.profilePage
}
}
NavigationItem {
title: "Leaderboard"
icon: IconType.flagcheckered
NavigationStack {
initialPage: socialView.leaderboardPage
}
}
}
// service configuration
VPlayGameNetwork {
id: gameNetwork
gameId: 285
secret: "AmazinglySecureGameSecret"
// increase leaderboard score by 1 for each app start
Component.onCompleted: gameNetwork.reportRelativeScore(1)
}
// social view setup
SocialView {
id: socialView
gameNetworkItem: gameNetwork
multiplayerItem: multiplayer
visible: false // we show the view pages on our custom app navigation
}
}
See here for more information: https://v-play.net/cross-platform-app-development/how-to-add-chat-service-and-cross-platform-leaderboard-with-user-profiles-to-your-ios-or-android-app#add-leaderboard-with-user-profiles
Related
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).
I'm a C++ developer, now studying about GUI development using QML in QtQuick.
In GUI creation, only one screen is visible to the user.
And based on user interaction, the screens are switched.
But what actually happens behind?
There are lot of info only on how to design a single screen, but very less resource for how to manage the transitions of their states.
Are all the screens and components loaded when starting the application and change the layer order to display once screen,
OR
after an user action, the new screen is built, loaded and old is destroyed ( only one screen is in memory at a time)
What is the term for this type of handling.
It would be so helpful to point to where i can find such information.
If you can't understand my question,please let me know. I will rewrite again!!
There is a convenient ready-made solution available: StackView. It provides built-in transitions for pages that slide/fade in and out.
StackView {
id: stack
initialItem: Page {
Button {
text: "Push"
anchors.centerIn: parent
onClicked: stack.push(Qt.resolvedUrl("OtherPage.qml"))
}
}
}
StackView allows you to push items, URLs and components. When pushing either of the latter two, StackView automatically creates and destroys the instance when appropriate. For example, if you push multiple URLs or components, it will only instantiate the top-most one that becomes the current item on the stack. Once you pop items off the stack, it creates an instance of the item underneath on demand once it becomes the current top-most item on the stack. StackView also allows you to replace one or more items in the stack. When popping or replacing dynamically created items off the stack, it automatically destroys the instances after the respective transitions are finished.
One of the possible options to switch between different screens using states:
ColumnLayout {
id: controls
states: [
State {
id: state1
name: "STATE1"
property list<Item> content: [
Loader {
...
},
MyItem {
...
}
]
PropertyChanges {
target: controls
children: state1.content
}
},
State {
id: state2
name: "STATE2"
property list<Item> content: [
MyHud {
...
}
]
PropertyChanges {
target: controls
children: state2.content
}
}
]
}
You can use Loader to load different qml-files or qml-components.
Example:
import QtQuick 2.0
Item {
width: 200; height: 200
Loader { id: pageLoader }
MouseArea {
anchors.fill: parent
onClicked: pageLoader.source = "Page1.qml"
}
}
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.
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.
I'm going crazy. I have a ListView inside a ScrollView, hooked up to a model that inherits QAbstractListModel. When objects are added to the model, the ListView shows them using a delegate. So far, so good.
But I really want the view to stay scrolled to the bottom (like a chat window), and I'm having a very difficult time making that happen. Here is the relevant QML code:
Rectangle {
ScrollView {
[anchor stuff]
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
}
}
TextField {
id: messageEditor
[anchor stuff]
onAccepted: {
controller.sendTextMessage(text)
text = ""
/* This works. */
//messageList.positionViewAtEnd();
}
}
Component {
id: messageDelegate
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
color: "white"
height: nameText.height + 4
Text {
id: nameText
wrapMode: Text.Wrap
text: "<b>" + authorName + " (" + authorId + ")</b> " + message
[anchor stuff]
}
ListView.onAdd: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
}
}
}
The really strange thing, is that messageList.positionViewAtEnd() (at the end of the file) actually jumps it to the beginning. Without the call, the view stays where it is, even as new entries appear in the list. And indeed, if you look at the Qt documentation for the ListView.positionViewAtEnd(), it says:
Positions the view at the beginning or end, taking into account ...
Is that a silly error in the documentation, or what? I've tried everything I can think of to make this work, particularly the positionViewAtIndex() method and using highlighters to force the scroll to happen. But nothing works. Note the /* This works. */ comment in the source code above. When that is enabled, it works totally fine! (except of course, it jumps to the ListView.count()-2 index, instead of the end of the list)
Does anyone have any idea what might be wrong here? Any examples I could try to prove that there's a terrible, terrible bug in QML?
I'm using Qt 5.3.1 with QtQuick 2.0 (or 2.1 or 2.2 fail too). I've tried many, many other configurations and code as well, so please ask if you need more info. I've completely exhausted my google-fu.
Thanks!
Edit 1
While the accepted answer does solve the above problem, it involves adding the Component.onCompleted to the delegate. This seems to cause problems when you scroll the list, because (I believe) the delegates are added to the view when you scroll up, causing the onCompleted trigger to be called even if the model item isn't new. This is highly undesirable. In fact, the application is freezing when I try to scroll up and then add new elements to the list.
It seems like I need a model.onAdd() signal instead of using the existence of a delegate instance to trigger the scroll. Any ideas?
Edit 2
And how does this NOT work?
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
onCountChanged: {
console.log("This prints properly.")
messageList.positionViewAtEnd()
}
}
The text "This prints properly" prints, so why doesn't it position? In fact, it appears to reset the position to the top. So I tried positionViewAtBeginning(), but that did the same thing.
I'm totally stumped. It feels like a bug.
You need to set the currentIndex as well.
testme.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 300
height: 240
ScrollView {
anchors.fill: parent
ListView {
anchors.fill: parent
id: messageList
model: messageModel
delegate: Text { text: mytextrole }
highlight: Rectangle { color: "red" }
highlightMoveDuration: 0
onCountChanged: {
var newIndex = count - 1 // last index
positionViewAtEnd()
currentIndex = newIndex
}
}
}
ListModel {
id: messageModel
ListElement { mytextrole: "Dog"; }
ListElement { mytextrole: "Cat"; }
}
Timer {
property int counter: 0
running: true
interval: 500
repeat: true
onTriggered: {
messageModel.append({"mytextrole": "Line" + (counter++)})
}
}
}
There is still some jumping to the first element and jumping back down for a fraction of a second.
There is a note in documentation:
Note: methods should only be called after the Component has completed. To position the view at startup, this method should be called by Component.onCompleted.
Change your ListView.onAdd: to
Component.onCompleted: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
And it works well.
In your case, the ListView emits add signal before the new delegate is created and completed. The ListView is still working on something behind the scene, so positionViewAtEnd cannot work as expected. And /* This works. */ because it is called after the new delegate is completed. However, don't assume this always works. Simply follow the note, call positionViewAtEnd in Component.onCompleted, in documentation.