I can subscribe to the signal TextChanged in my QML code. But however i don't seem to find it in the documentation. I've searched the inherited ascending classes as well, none of them specify it. Have I missed something or is the documentation wrong?
I'm running QtQuick 2.15 and looking in the documentation for QtQuick 2.15 TextInput QML Type
TextInput {
id: firstNameInput
onTextChanged: {
console.log("First name: " + text)
}
}
I found the problem I've forgot that all the properties are assigned with handlers in QT.
import QtQuick 2.15
TextInput {
// Property
text: "Change this!"
// Signal handler
onTextChanged: console.log("Text has changed to:", text)
}
Related
I want to write a simple desktop application on Ubuntu and I thought that an easy way was to use Qt with QML as GUI and Python as the language for the logic, since I am somewhat familiar with Python.
Now I am trying for hours to somehow connect the GUI and the logic, but it is not working.
I managed the connection QML --> Python but not the other way around. I have Python classes which represent my data model and I added JSON encode and decode functions. So for now there is no SQL database involved. But maybe a direct connection between QML view and some database would make things easier?
So now some code.
QML --> Python
The QML file:
ApplicationWindow {
// main window
id: mainWindow
title: qsTr("Test")
width: 640
height: 480
signal tmsPrint(string text)
Page {
id: mainView
ColumnLayout {
id: mainLayout
Button {
text: qsTr("Say Hello!")
onClicked: tmsPrint("Hello!")
}
}
}
}
Then I have my slots.py:
from PySide2.QtCore import Slot
def connect_slots(win):
win.tmsPrint.connect(say_hello)
#Slot(str)
def say_hello(text):
print(text)
And finally my main.py:
import sys
from controller.slots import connect_slots
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
if __name__ == '__main__':
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load('view/main.qml')
win = engine.rootObjects()[0]
connect_slots(win)
# show the window
win.show()
sys.exit(app.exec_())
This works fine and I can print "Hello!". But is this the best way to do it or is it better to create a class with slots and use setContextProperty to be able to call them directly without adding additional signals?
Python --> QML
I cannot get this done. I tried different approaches, but none worked and I also don't know which one is the best to use. What I want to do is for example show a list of objects and offer means to manipulate data in the application etc.
include Javascript:
I added an additional file application.js with a function just to print something, but it could probably be used to set the context of a text field etc.
Then I tried to use QMetaObject and invokeMethod, but just got errors with wrong arguments etc.
Does this approach make any sense? Actually I don't know any javascript, so if it is not necessary, I would rather not use it.
ViewModel approach
I created a file viewmodel.py
from PySide2.QtCore import QStringListModel
class ListModel(QStringListModel):
def __init__(self):
self.textlines = ['hi', 'ho']
super().__init__()
And in the main.py I added:
model = ListModel()
engine.rootContext().setContextProperty('myModel', model)
and the ListView looks like this:
ListView {
width: 180; height: 200
model: myModel
delegate: Text {
text: model.textlines
}
}
I get an error "myModel is not defined", but I guess that it can't work anyway, since delegates only take one element and not a list.
Is this approach a good one? and if yes, how do I make it work?
Is there a totally different approach to manipulate data in a QML view?
I appreciate your help!
I know the Qt documentation but I am not happy with it. So maybe I am missing something. But PyQt seems to be way more popular than PySide2 (at least google searches seem to indicate that) and PySide references often use PySide1 or not the QML QtQuick way of doing things...
Your question has many aspects so I will try to be detailed in my answer and also this answer will be continuously updated because this type of questions are often asked but they are solutions for a specific case so I am going to take the liberty of giving it a general approach and be specific in the possible scenarios.
QML to Python:
Your method works because the type conversion in python is dynamic, in C++ it does not happen. It works for small tasks but it is not maintainable, the logic must be separated from the view so it should not be dependent. To be concrete, let's say that the printed text will be taken by the logic to perform some processing, then if you modify the name of the signal, or if the data does not depend on ApplicationWindow but on another element, etc. then you will have to change a lot connection code.
The recommended as you indicate is to create a class that is responsible for mapping the data you need your logic and embed it in QML, so if you change something in the view you just change the connection:
Example:
main.py
import sys
from PySide2.QtCore import QObject, Signal, Property, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
class Backend(QObject):
textChanged = Signal(str)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.m_text = ""
#Property(str, notify=textChanged)
def text(self):
return self.m_text
#text.setter
def setText(self, text):
if self.m_text == text:
return
self.m_text = text
self.textChanged.emit(self.m_text)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
backend = Backend()
backend.textChanged.connect(lambda text: print(text))
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("backend", backend)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
Column{
TextField{
id: tf
text: "Hello"
}
Button {
text: qsTr("Click Me")
onClicked: backend.text = tf.text
}
}
}
Now if you want the text to be provided by another element you just have to change the line: onClicked: backend.text = tf.text.
Python to QML:
I can not tell you what you did wrong with this method because you do not show any code, but I do indicate the disadvantages. The main disadvantage is that to use this method you must have access to the method and for that there are 2 possibilities, the first one is that it is a rootObjects as it is shown in your first example or searching through the objectName, but it happens that you initially look for the object, you get it and this is removed from QML, for example the Pages of a StackView are created and deleted every time you change pages so this method would not be correct.
The second method for me is the correct one but you have not used it correctly, unlike the QtWidgets that focus on the row and the column in QML the roles are used. First let's implement your code correctly.
First textlines is not accessible from QML since it is not a qproperty. As I said you must access through the roles, to see the roles of a model you can print the result of roleNames():
model = QStringListModel()
model.setStringList(["hi", "ho"])
print(model.roleNames())
output:
{
0: PySide2.QtCore.QByteArray('display'),
1: PySide2.QtCore.QByteArray('decoration'),
2: PySide2.QtCore.QByteArray('edit'),
3: PySide2.QtCore.QByteArray('toolTip'),
4: PySide2.QtCore.QByteArray('statusTip'),
5: PySide2.QtCore.QByteArray('whatsThis')
}
In the case that you want to obtain the text you must use the role Qt::DisplayRole, whose numerical value according to the docs is:
Qt::DisplayRole 0 The key data to be rendered in the form of text. (QString)
so in QML you should use model.display(or only display). so the correct code is as follows:
main.py
import sys
from PySide2.QtCore import QUrl, QStringListModel
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
model = QStringListModel()
model.setStringList(["hi", "ho"])
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("myModel", model)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
ListView{
model: myModel
anchors.fill: parent
delegate: Text { text: model.display }
}
}
If you want it to be editable you must use the model.display = foo:
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
ListView{
model: myModel
anchors.fill: parent
delegate:
Column{
Text{
text: model.display
}
TextField{
onTextChanged: {
model.display = text
}
}
}
}
}
There are many other methods to interact with Python/C++ with QML but the best methods involve embedding the objects created in Python/C++ through setContextProperty.
As you indicate the docs of PySide2 is not much, it is being implemented and you can see it through the following link. What exists most are many examples of PyQt5 so I recommend you understand what are the equivalences between both and make a translation, this translation is not hard since they are minimal changes.
Is there an equivalent of QMessageBox::aboutQt which can be used in QML? I didn't find anything suitable in the QtQuick.Dialogs module (https://doc.qt.io/qt-5/qtquickdialogs-index.html).
The closest match of QMessageBox::about with QML would be MessageDialog setup like this:
import QtQuick 2.2
import QtQuick.Dialogs 1.1
MessageDialog {
title: "Your title"
icon: StandardIcon.Information
text: "Your text"
standardButtons: StandardButton.Ok
Component.onCompleted: visible = true
}
As for QMessageBox::aboutQt, according to this topic, in the Qt forum, there is no QML equivalent and it is better to invoke qApp->aboutQt:
reading the docs seems there's no aboutQt for QML.
probably better to invoke standard qApp->aboutQt from C++ class.
I wanted to add custom key events to a TextEdit. But, it seems key events are not properly processed inside TextEdit.
For example, in the code below, I am trying to handle Space key events. Although the Space keypress is recognized by the signal handler function, the output text does not contain a space. It is the same for all other key events. How do I overcome this?
import QtQuick 2.15
import QtQuick.Controls 2.15
Item{
function processSpace(event){
event.accepted = true
console.log(xTextEdit.text)
}
TextEdit{
id: xTextEdit
height: parent.height
width: parent.width
Keys.onSpacePressed: processSpace(event)
}
}
You accept the event, and thus prevent the default handling.
Set event.accepted = false instead, so the event will be propagated.
Note that accepted is by default true (at least for key events), so not setting it will the accept the event
I have a Qt Quick Controls 2 Action with a shortcut:
Action {
id: myAction
shortcut: "Ctrl+D"
}
How can I get a platform native representation of the shortcut?
Here is what I already tried:
Using the shortcut right away like
ToolTip.text: myAction.shortcut
However, this returns the shortcut as defined (for example, "Ctrl+D") and not a platform native representation (for example, "⌘D"). It also returns incorrect results in case a StandardKey is used because it gives the integer value of the StandardKey and not the corresponding key combination.
Using a nativeText property like
ToolTip.text: myAction.shortcut.nativeText
But such a property doesn't exist.
There seems to be no straight forward way to get this.
A workaround is:
Create a disabled Shortcut item
This will give us the platform native representation via its nativeText property.
Derive from Action and add a custom property to hold the shortcut
This is necessary because the shortcut property of the Action will always return a string which will be interpreted incorrectly by the Shortcut item when using a StandardKey (the Shortcut item will interpret the integer value as the shortcut so you get a "3" instead of "Ctrl+O").
Bind the custom property to the Action shortcut and to the Shortcut sequence
So in code:
CustomAction.qml
import QtQuick 2.7
import QtQuick.Controls 2.4
Action {
/* Custom property */
property var keySequence
shortcut: keySequence
}
ToolTipButton.qml
import QtQuick 2.7
import QtQuick.Controls 2.4
Button {
/* Disabled Shortcut */
Shortcut {
id: dummyShortcut
enabled: false
sequence: action.keySequence
}
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.text: dummyShortcut.nativeText
}
Used like this:
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.4
ApplicationWindow {
visible: true
CustomAction {
id: myAction
keySequence: StandardKey.Open
}
ToolTipButton {
id: myButton
action: myAction
text: "Trigger my action"
}
}
Background story
So I recently decided that I should try out Qt. I started making a QtQuick Apllication. In my designer view I have one button and a mouse area.
What I want to do:
When I click the button, I want to display a message box with some text (like "Hello World").
My Question
How can I do that ?
Additional info
I tried googling it, i tried following this answer. But still nothing.
I know how to program in .Net (C# and VB), i have some knowage in C/C++, but Qt seems to hard for me
How about this:
import QtQuick 2.0
import QtQuick.Controls 1.0
import QtQuick.Dialogs 1.1
Rectangle {
width: 360
height: 360
MessageDialog {
id: msg
title: "Title"
text: "Button pressed"
onAccepted: visible = false
}
Button {
text: "press me"
onClicked: msg.visible = true
}
}
And if you prefer to have the dialog dynamically instantiated with arbitrary properties rather than have it "hardcoded", follow the first snippet from this answer. You can also set properties in createQmlObject() and instead of hiding the dialog just use destroy() to delete it.
You have to use signals and slots in order to trigger an event. You could use a QMessageBox that pops up to display Hello world.