I have added a minimal reproducible example for this question in relation to the last one I asked. I am using Python, Pyside6, and QML to create my application. I am trying to customize the amount of overall widgets created, as well as the custom components inside them. I am using a nested Repeater that you can see in widget.qml. I am able to use the outer repeater to instantiate 3 header elements; however, I am unable to control how many BasicContainer elements are created. Each header element should correspond with just 5 BasicComponents that get instantiated based off the length of one of the widget components, like item1Components in the main.py. Just like how the header widgets get instantiated, the model appends a row for each dictionary element that appears in a list I pass in.
For instance, under the first header element, you would see the list: widget1random, widget1random2, etc... under the second header element to the right: widget2random, widget2random2, etc...
In the current state, all 15 components get attached to each header widget which is not what I am trying to accomplish.
main.py
import os
import sys
from pathlib import Path
sys.path.append(os.path.join(os.path.dirname(sys.path[0]), ".."))
from PySide6.QtCore import Property, QUrl, QObject, Qt, QCoreApplication
from PySide6.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PySide6.QtQuick import QQuickView
CURRENT_DIRECTORY = Path(__file__).resolve().parent
# QstandardItem roles
SetText = Qt.UserRole+1
class ControlledSignalWidget(QObject):
def __init__(self):
super().__init__()
self._model = QStandardItemModel()
self._model.setItemRoleNames({Qt.DisplayRole: b"display"})
self._setpoints = QStandardItemModel()
self._setpoints.setItemRoleNames({SetText: b"textField"})
#Property(QObject, constant=True)
def model(self):
return self._model
#Property(QObject, constant=True)
def setpoints(self):
return self._setpoints
#create custom number of widgets
def create_widgets(self, widgets, allComponents):
counter = 1
# Iterates for the amount of widgets created
for general, widget in widgets.items():
#for key in widget:
print("Adding new widget")
item = QStandardItem(widget["Header"])
self._model.appendRow(item)
self.create_setpoints(allComponents["item" + str(counter)+"Components"])
print("Added widget")
counter +=1
def create_setpoints(self, component):
for subWidget in component:
print(subWidget)
item = QStandardItem()
item.setData(subWidget["title"], SetText)
self._setpoints.appendRow(item)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeViewToRootObject)
url = QUrl.fromLocalFile(os.fspath(CURRENT_DIRECTORY / "widget.qml"))
def handle_status_changed(status):
if status == QQuickView.Error:
QCoreApplication.exit(-1)
widgets = {'widget1':{'Header': "Header Text"},
'widget2':{'Header': "Header Text"}}
item1Components = [{'title': "widget1random"},
{'title': "widget1random2"}]
item2Components = [{'title': "widget2random"},
{'title': "widget2random2"}]
allComponents = {'item1Components': item1Components, 'item2Components': item2Components}
mainWidget = ControlledSignalWidget()
mainWidget.create_widgets(widgets, allComponents)
view.rootContext().setContextProperty("mainWidget", mainWidget)
view.statusChanged.connect(handle_status_changed, Qt.ConnectionType.QueuedConnection)
view.setSource(url)
view.show()
sys.exit(app.exec())
widget.qml
import QtQuick 2.0
import QtQuick.Controls.Material 2.15
import QtQuick.Layouts 1.12
Item {
id: root
width: 1000
height: 800
GridLayout{
columns: 3
Repeater{
id: repeater1
model: mainWidget.model
ColumnLayout{
Header{
title: model.display
}
Repeater{
id: repeater2
model: mainWidget.setpoints
ColumnLayout{
BasicContainer{
mainText: model.textField
}
}
}
}
}
}
}
Header.qml
import QtQuick 2.0
import QtQuick.Controls.Material 2.15
Item {
id: header
width: 300
height: 30
x: 177
y: 219
property alias title: widgetHeader.text
Rectangle {
id: boxRect
x: 193
y: 224
width: 408
height: 39
color: "#aaaaaa"
anchors.fill: parent
Text {
id: widgetHeader
color: "#ffffff"
text: "Default text"
font.pixelSize: 20
font.family: "Roboto"
textFormat: Text.AutoText
}
}
}
BasicContainer.qml
import QtQuick 2.12
import QtQuick.Controls.Material 2.15
Item {
width: 300
height: 30
visible: true
property alias mainText: textItem1.text
Rectangle {
id: rectangle
width: parent.width
Text {
id: textItem1
text: qsTr("Default Title")
font.pixelSize: 14
font.family: "Roboto"
}
}
}
Related
I have at the moment a simple PySide6 app which uses qml for image capturing. I want to transport the image to our app but cannot figure out how. I am not sure, if saving QML image is an attempt in the right direction.
My PySide App
import sys
from pathlib import Path
from PySide6.QtCore import QObject, Slot
from PySide6.QtGui import QGuiApplication, QImage
from PySide6.QtQml import QQmlApplicationEngine, QmlElement
QML_IMPORT_NAME = "io.qt.textproperties"
QML_IMPORT_MAJOR_VERSION = 1
#QmlElement
class Bridge(QObject):
#Slot(QImage)
def capture(self, preview):
# Do something with the preview
print(type(preview))
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
# Get the path of the current directory, and then add the name
# of the QML file, to load it.
qml_file = Path(__file__).parent / "simpleCam.qml"
engine.load(qml_file)
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
MY QML File
import QtQuick
import QtQuick.Controls
import QtMultimedia
import io.qt.textproperties
ApplicationWindow {
id: mainFrame
width: 640
height: 480
visible: true
title: qsTr("Cam Test")
Bridge {
id: bridge
}
Rectangle {
width: 640
height: 400
MediaDevices {
id: mediaDevices
}
CaptureSession {
imageCapture: ImageCapture {
id: capture
}
camera: Camera {
id: camera
}
videoOutput: output
}
VideoOutput {
id: output
anchors.fill: parent
}
Button {
id: startCamButton
text: "Start Cam"
anchors.top: output.bottom
anchors.left: output.left
onClicked: {
camera.start()
camImage.opacity = 0
}
}
Button {
id: takePicButton
text: "take pic"
anchors.top: output.bottom
anchors.left: startCamButton.right
onClicked: {
capture.capture()
camImage.opacity = 1
}
}
Image {
id: camImage
anchors.fill: parent
source: capture.preview
}
}
}
The Result
What is missing?
In the Button with id: takePicButton I want to transport the image to the PySide6 Bridge. If I add to theonClicked Signal bridge.capture(capture.preview) I got the Error:
TypeError: Passing incompatible arguments to C++ functions from JavaScript is not allowed.`
How can I solve this problem?
You should use the imageCaptured signal.
Change:
imageCapture: ImageCapture {
id: capture
}
To:
imageCapture: ImageCapture {
id: capture
onImageCaptured: function(req_id, preview){bridge.capture(req_id, preview)}
}
And change in main.py:
#Slot(QImage)
def capture(self, preview):
# Do something with the preview
print(type(preview))
To:
#Slot(int, QImage)
def capture(self,req_id, preview):
print(req_id)
print(type(preview))
Working example here.
You might want to look at this answer for other use cases.
Is it possible to pick between different types of sub-components dynamically (during instantiation)?
For example, some pseudocode (using Qt 5.9):
//MyComp.qml
import QtQuick 2.9
import QtQuick.Layouts 1.3
Item {
property bool useLayout: true
//Here I want to allow the user to choose
//whether a ColumnLayout or Column is used
//(e.g., by means of the useLayout property)
ColumnLayout { //Or Column
...
}
...
}
//main.qml
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.9
ApplicationWindow {
width: 640
height: 480
...
MyComp {
id: a
useLayout: false
...
}
}
I don't think there is a way to do exactly what you want, without a lot of javascript. The cleanest way to do this, that I can think of would be the following.
You could make the ColumnLayout invisible and set the Column as the parent of its children with something like this:
//MyComp.qml
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.5
Item {
property bool useLayout: true
ColumnLayout {
id: columnLayout
visible: useLayout
Component.onCompleted: {
if (!useLayout) {
butt1.parent = column;
butt2.parent = column;
butt3.parent = column;
}
}
Button {
id: butt1
text: "butt 1"
}
Button {
id: butt2
text: "butt 2"
}
Button {
id: butt3
text: "butt 3"
}
}
Column {
id: column
visible: !useLayout
}
}
I want to use some qml file(main.qml) but before that I need to get some authentication info from user(login, pass). So I want to do this in the second window(login.qml). I saw Qt.createComponent for opening second qml file, but I can't get any information from this type of window.
So how can I use some information from the second window in the first window?
Or how can I dynamically load these items(main.qml, login.qml) in the parent qml file?
So how can I use some information from the second window in the first
window?
This is just one way of doing it:
main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
ApplicationWindow {
width: 400
height: 400
visible: true
ColumnLayout {
id: logItems
height: 200
Button {
id: loginButton
onClicked: loginForm.visible = true
text: "Log in"
}
Login {
anchors.top: loginButton.bottom
id: loginForm
visible: false
onLoginInfo: {
logInfo.text = "User:" + user + " password: " + password
}
}
}
Text {
id: logInfo
anchors.top : logItems.bottom
}
}
Login.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
Item {
signal loginInfo(string user, string password)
ColumnLayout {
RowLayout {
TextField {
id: user
}
TextField {
id: password
}
}
Button {
text: "Submit"
onClicked: loginInfo(user.text, password.text)
}
}
}
How can I dynamically load QML items from separate files/resources in
another QML file?
Qt.CreateComponent allows you to use JavaScript to dynamically create and use QML code from other files.
I have the following custom Label:
import QtQuick 2.3
import QtQuick.Controls 1.4
Label
{
anchors.centerIn: parent
text: "DashboardLabel!"
font.pixelSize: 22
font.italic: true
color: "steelblue"
Rectangle
{
id: rectangle
}
}
I'm trying to change the position of the label by accessing the x and y variables from rectangle:
import QtQuick 2.3
import QtQuick.Controls 1.4
import CustomGraphics 1.0
Item
{
anchors.centerIn: parent
CustomLabel
{
id: customLabel
width: 100
height: 100
rectangle.x: 200
}
}
It doesn't seem to be working since my custom Label is not moved. Should I use the property feature? Here is the error I'm getting:
Cannot assign to non-existent property "rectangle"
EDIT: I've just tried to add property alias rect: rectangle in order to access x with rect.x. I do not get any errors but nothing appears on the window.
You can't access the private properties of the child element like that. You have to create alias in order for the subclass to access them. Try this
import QtQuick 2.3
import QtQuick.Controls 1.4
Label
{
property alias childRect: rectangle
anchors.centerIn: parent
text: "DashboardLabel!"
font.pixelSize: 22
font.italic: true
color: "steelblue"
Rectangle
{
id: rectangle
width: 100
height: 100
}
}
and then
import QtQuick 2.3
import QtQuick.Controls 1.4
import CustomGraphics 1.0
Item
{
anchors.centerIn: parent
CustomLabel
{
id: customLabel
width: 100
height: 100
childRec.x: 200
}
}
UPDATE as OP changed the description
You haven't set width and height properties for the rectangle. See my edit.
How can I move focus from one control to next one inside QML form?
By default it works with Tab button but I need to change it to Enter.
All the control are ordered with Gridlayout with 2 columns.
I've defined a new component, TextFieldMoveOnReturn.qml
import QtQuick 2.0
import QtQuick.Controls 1.1
TextField {
Keys.onReturnPressed: nextItemInFocusChain().forceActiveFocus()
}
If you use this one instead of TextField, you get the required behaviour
edit a better solution: define a new component GridLayoutNextOnReturn.qml
import QtQuick 2.0
import QtQuick.Layouts 1.1
GridLayout {
Keys.onReturnPressed: {
for (var i = 0; i < children.length; ++i)
if (children[i].focus) {
children[i].nextItemInFocusChain().forceActiveFocus()
break
}
}
}
and use normal TextField inside - works like a charm
In order to make it more robust and flexible, you should make same behaviour
for Tab and Enter/Return keys.
Handle keyPressed event and use KeyNavigation.tab instead of nextItemInFocusChain to focus next element as follow:
import QtQuick 2.12
import QtQuick.Controls 1.12
Column {
TextField {
id: field1
KeyNavigation.tab: field2
activeFocusOnTab: true
Keys.onReturnPressed: KeyNavigation.tab.forceActiveFocus();
}
TextField {
id: field2
KeyNavigation.tab: field3
activeFocusOnTab: true
Keys.onReturnPressed: KeyNavigation.tab.forceActiveFocus();
}
TextField {
id: field3
KeyNavigation.tab: field1
activeFocusOnTab: true
Keys.onReturnPressed: KeyNavigation.tab.forceActiveFocus();
}
}
So you controlled order of focus and users can use both tab and return keys interchangeably which results better UX.
Whenever you want to change order, just change KeyNavigation.tab values :)
Note: I extremely suggest you to avoid using nextItemInFocusChain because of future changes and flexibility
You can use onEditingFinished:
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
width: 400
height: 400
GridLayout {
anchors.fill: parent
columns: 2
Label {
text: "Name"
}
TextField {
onEditingFinished: addressEdit.focus = true
}
Label {
text: "Address"
}
TextField {
id: addressEdit
}
}
}
Use Keys.onReturnPressed and forceActiveFocus()
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
width: 400
height: 400
GridLayout {
anchors.fill: parent
columns: 2
Label {
text: "Name"
}
TextField {
Keys.onReturnPressed: organizationEdit.forceActiveFocus()
}
Label {
text: "Organization"
}
TextField {
id: organizationEdit
Keys.onReturnPressed: addressEdit.forceActiveFocus()
}
Label {
text: "Address"
}
TextField {
id: addressEdit
}
}
}