Storing a user's code in a combo box that calls out for its name when selected - qml

I want to make two input text fields (let's call them X and Y) that are in page A,
X: code of the person, Y: name of the person,
Page A contains two text fields for input, and a combo box for selection (for delete purpose), an adding button, and a delete button,
Once the X and Y are filled in the text fields they are inputed using an 'add' button, I want them to be stored when the button is pressed so I can call them back in the page B, or in the combo box in page A,
Page B contains a combo box and a casual text output next to it, the X goes to the combo box, and the Y goes to the text that both are in page B
When I select in page B one X of the many X that already have been inputed, I want the text to change with what have been selected accordingly,
if I want to delete one of the X, I do so by selecting the combobox that exists in Page A and proceed pressing the delete button,
how to store both the X and Y in variables and insert them in comboboxes and the texts, and make a bond between them, connecting each name with its own code ?
PageA
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.VirtualKeyboard 2.14
Item {
id:pageA
width: 1920
height: 1080
property alias codeIn: code.text
property alias nameIn: name.text
TextField{
id:code
y: 0
width: 200
height: 50
placeholderText: qsTr("Type the code here")
}
TextField{
id:name
x: 0
y: 65
width: 200
height: 50
placeholderText: qsTr("Type the name here")
}
ComboBox{
id:selectionForDelete
x: 320
y: 65
width: 200
height: 50
}
Button{
id:adding
x: 29
y:155
text: "add"
onClicked: {
code.clear()
name.clear()
}
}
Button{
id:deleting
x: 355
y: 155
text: "delete"
}
}
PageB
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.VirtualKeyboard 2.14
Item {
id:pageB
width: 1920
height: 1080
ComboBox{
id:codeList
}
Text {
id: relatedName
y:70
text: qsTr("")
}
}

One very simple solution is to declare a ListModel in your main app and when you instantiate either PageA or PageB they will have read/write access to the ListModel.
//MainApp.qml
Page {
ListModel { id: listModel }
PageA { }
PageB { }
}
//PageA.qml
Page {
}
//PageB.qml
Page {
}
As an example, PageA gives the ability to add new users as well as delete existing users, whereas PageB gives the ability to search for existing users:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Page {
RowLayout {
anchors.fill: parent
PageA {
Layout.fillWidth: true
Layout.preferredWidth: 200
Layout.fillHeight: true
}
PageB {
Layout.fillWidth: true
Layout.preferredWidth: 200
Layout.fillHeight: true
}
}
ListModel {
id: listModel
function appendUser(code, name) { append( { code, name } ); }
}
}
//PageA.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Page {
ColumnLayout {
anchors.fill: parent
Frame {
Layout.fillWidth: true
Text { text: qsTr("PageA") }
}
Label { text: qsTr("Users") }
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: listModel
delegate: Frame {
width: ListView.view.width
RowLayout {
width: parent.width
Text {
Layout.fillWidth: true
text: model.code + " " + model.name
}
Button {
text: qsTr("Delete")
onClicked: listModel.remove(model.index)
}
}
}
}
Label { text: qsTr("Create new user") }
TextField {
id: code
Layout.fillWidth: true
placeholderText: qsTr("Code")
}
TextField {
id: name
Layout.fillWidth: true
placeholderText: qsTr("Text")
}
Button {
enabled: code.text && name.text
text: qsTr("Add")
onClicked: listModel.appendUser(code.text, name.text)
}
}
}
//PageB.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Page {
ColumnLayout {
anchors.fill: parent
Frame {
Layout.fillWidth: true
Text { text: qsTr("PageB") }
}
Label { text: qsTr("Select Code") }
ComboBox {
id: comboBox
model: listModel
textRole: "code"
valueRole: "name"
}
Label { text: qsTr("Selected Name") }
Text {
text: comboBox.currentValue ?? ""
}
Item {
Layout.fillHeight: true
}
}
}
You can Try it Online!

Related

TableView is invisible

I'm trying to set something up, where there's a a HorizontalHeaderView that spans the entire width of the window, and a TableView that also spans the entire width of the window, along with stretching down to the bottom of the window. I also wanted a TextField and Button side by side, to sit on top of all this.
So far, I've gotten the TextField and Button to sit at the top, and part of the HorizontalHeaderView to be visible, but the TableView is completely invisible. I've tried fiddling around with height, Layout.preferredHeight, etc. but nothing makes it stretch to the bottom and fill the width of the window.
Here's what I've got so far:
import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0
ApplicationWindow {
title: "Portmod"
width: 640
height: 480
visible: true
header: TabBar {
id: mainTabBar
width: parent.width
TabButton {
text: "Manage"
width: implicitWidth
}
TabButton {
text: "Search"
width: implicitWidth
}
}
StackLayout {
id: mainStackLayout
currentIndex: mainTabBar.currentIndex
ColumnLayout {
id: manageTab
RowLayout {
TextField {
placeholderText: "Filter packages..."
}
Button {
text: "Filter"
}
}
HorizontalHeaderView {
id: installedPackagesHeader
syncView: installedPackagesTable
}
TableView {
id: installedPackagesTable
columnSpacing: 1
rowSpacing: 1
clip: true
model: installedPackagesModel
Keys.onUpPressed: installedPackagesTableVerticalScrollBar.decrease()
Keys.onDownPressed: installedPackagesTableVerticalScrollBar.increase()
Keys.onLeftPressed: installedPackagesTableHorizontalScrollBar.decrease()
Keys.onRightPressed: installedPackagesTableHorizontalScrollBar.increase()
ScrollBar.vertical: ScrollBar {
id: installedPackagesTableVerticalScrollBar
parent: installedPackagesTable
}
ScrollBar.horizontal: ScrollBar {
id: installedPackagesTableHorizontalScrollBar
parent: installedPackagesTable
}
delegate: Rectangle {
implicitWidth: 300
implicitHeight: 25
Text {
text: display
anchors.left: parent.left
}
}
}
}
Rectangle {
id: searchTab
width: parent.parent.width
height: parent.parent.height
}
}
}
Here's my custom QSortFilterProxyModel and QAbstractItemModel:
class InstalledPackagesProxyModel(QSortFilterProxyModel):
def __init__(self, data: list[list]) -> None:
super().__init__()
self.realModel = InstalledPackagesModel(data)
self.setSourceModel(self.realModel)
def get_atom(self, index: QModelIndex) -> Atom:
"""
Returns a reference to the source index instead of proxy index, to handle the sorted view.
"""
mapped_index = self.mapToSource(index)
data = [tuple(pkg_data_list) for pkg_data_list in self.realModel._data]
return Atom(f"{data[mapped_index.row()][2]}/{data[mapped_index.row()][0]}")
class InstalledPackagesModel(QAbstractTableModel):
def __init__(self, data: list[list]):
super(InstalledPackagesModel, self).__init__()
self._data = data
self.header_labels = ["Name", "Use Flags", "Category", "Version"]
def data(self, index, role):
if role == Qt.DisplayRole: # type: ignore
value = self._data[index.row()][index.column()]
if isinstance(value, Atom):
return str(value)
return value
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
try:
return len(self._data[0])
# If there are no installed mods in the prefix
except IndexError:
return 1
def headerData(self, section, orientation, role=Qt.DisplayRole): # type: ignore
if role == Qt.DisplayRole and orientation == Qt.Horizontal: # type: ignore
return self.header_labels[section]
return QAbstractTableModel.headerData(self, section, orientation, role)
This is what it looks like:
You need to use anchors.fill: parent on the StackView so it fills out its parent and has a proper size. Furthermore the HorizontalHeaderView needs to get a implicitHeight and the TableView should set Layout.fillWidth and Layout.fillHeight to true.
You need to use Layout.fillWidth and Layout.fillHeight to span to the full width and height of layouts.
import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0
import Qt.labs.qmlmodels 1.0
ApplicationWindow {
title: "Portmod"
width: 640
height: 480
visible: true
header: TabBar {
id: mainTabBar
width: parent.width
TabButton { text: "Manage" }
TabButton { text: "Search" }
}
StackLayout {
id: mainStackLayout
currentIndex: mainTabBar.currentIndex
anchors.fill: parent
ColumnLayout {
id: manageTab
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
TextField {
Layout.fillWidth: true
placeholderText: "Filter packages..."
}
Button {
Layout.fillWidth: true
text: "Filter"
}
}
HorizontalHeaderView {
id: installedPackagesHeader
syncView: tableView
implicitHeight: 30
Layout.fillWidth: true
}
TableView {
id: tableView
Layout.fillWidth: true
Layout.fillHeight: true
columnSpacing: 1
rowSpacing: 1
clip: true
columnWidthProvider: function (column) {
return tableView.model ? tableView.width / tableView.model.columnCount : 0
}
rowHeightProvider: function (row) { return 30 }
onWidthChanged: tableView.forceLayout()
model: TableModel {
TableModelColumn { display: "name" }
TableModelColumn { display: "color" }
rows: [
{ "name": "cat", "color": "black" },
{ "name": "dog", "color": "brown" },
{ "name": "bird", "color": "white" }
]
}
delegate: Rectangle {
Text {
text: display
anchors.left: parent.left
}
}
}
}
Rectangle {
id: searchTab
Layout.fillWidth: true
Layout.fillHeight: true
color: "red"
}
}
}
I figured out that I needed to set the columnWidthProvider to this:
return Window.width / installedPackagesTableView.model.columnCount()
This is my full code for the TableView:
TableView {
id: installedPackagesTableView
property int selectedRow: 0
focus: true
columnSpacing: 1
rowSpacing: 1
clip: true
model: installedPackagesModel
columnWidthProvider: function () {
return Window.width / installedPackagesTableView.model.columnCount()
}
rowHeightProvider: function (row) {
return 30;
}
onWidthChanged: installedPackagesTableView.forceLayout()
Layout.fillWidth: true
Layout.fillHeight: true
Keys.onUpPressed: function () {
if (selectedRow != 0) {
selectedRow -= 1;
}
// Move scrollbar up if the selectedRow is going to be invisible
if (selectedRow == topRow) {
installedPackagesTableVerticalScrollBar.decrease()
}
}
Keys.onDownPressed: function () {
if (selectedRow != installedPackagesModel.rowCount() - 1) {
selectedRow += 1;
// Move scrollbar down if the selectedRow is going to be invisible
if (selectedRow == bottomRow) {
installedPackagesTableVerticalScrollBar.increase()
}
}
}
Keys.onReturnPressed: installedPackagesModel.getAtom(selectedRow)
ScrollBar.vertical: ScrollBar {
id: installedPackagesTableVerticalScrollBar
}
ScrollBar.horizontal: ScrollBar {
id: installedPackagesTableHorizontalScrollBar
}
delegate: ItemDelegate {
highlighted: row == installedPackagesTableView.selectedRow
onClicked: installedPackagesTableView.selectedRow = row
onDoubleClicked: installedPackagesModel.getAtom(row)
text: model.display
}
}

How to show/hide KDE plasmoid's tooltip programatically?

Is there a way to make a plasmoid tooltip to show/hide programatically?
I tried by setting a ToolTipArea over the compact representation, and trying to trigger it with a Timer - it does not work (the regular tooltip keeps showing but only when hovering the plasmoid icon (aka compactRepresentation):
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
Item {
Layout.preferredWidth: 200
Layout.preferredHeight: 300
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation
Plasmoid.compactRepresentation: Item
{
anchors.fill: parent
MouseArea
{
onClicked:
{
plasmoid.expanded = !plasmoid.expanded
}
}
PlasmaCore.ToolTipArea
{
id: toolTip
width: parent.width
height: parent.height
anchors.fill: parent
mainItem: tooltipContentItem
active: false
interactive: true
}
Timer
{
interval: 3000
running: true
repeat: true
onTriggered:
{
if (tooltipContentItem.active == false)
{
toolTip.showToolTip()
toolTip.active == true
}
else
{
toolTip.hideToolTip()
toolTip.active == false
}
}
}
}
Item
{
id: tooltipContentItem
implicitWidth: 300
implicitHeight: 200
ColumnLayout
{
id: mainLayout
anchors
{
left: parent.left
top: parent.top
margins: PlasmaCore.Units.gridUnit / 2
}
PlasmaExtras.Heading
{
id: tooltipMaintext
level: 3
Layout.minimumWidth: Math.min(implicitWidth, preferredTextWidth)
Layout.maximumWidth: preferredTextWidth
elide: Text.ElideRight
text: "Test"
}
PlasmaComponents.Label
{
id: tooltipSubtext
Layout.minimumWidth: Math.min(implicitWidth, preferredTextWidth)
Layout.maximumWidth: preferredTextWidth
text: "Testing text"
opacity: 0.6
}
}
}
}
There's the toolTipItem QQuickItem too, but I cannot figure out if it is possible to make it show or hide on command (this bit was borrowed from KDE's digitalclock plasmoid:
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
Item {
Layout.preferredWidth: 200
Layout.preferredHeight: 300
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation
Plasmoid.compactRepresentation: Item
{
anchors.fill: parent
MouseArea
{
onClicked:
{
plasmoid.expanded = !plasmoid.expanded
}
}
}
plasmoid.toolTipItem: Loader
{
id: toolTipLoader
source: "Tooltip.qml" // Just holds the tooltip contents
}
}

Changing model doesn't affect ComboBox

Suppose, we have the following code:
import QtQuick 2.4
import QtQuick.Window 2.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Window {
id: win
width: 800
height: 600
ListModel {
id: listModel
ListElement { name: "element1" }
ListElement { name: "element2" }
ListElement { name: "element3" }
}
ColumnLayout {
anchors.centerIn: parent
width: 200
height: 200
ComboBox {
model: listModel
currentIndex: 1
Layout.fillWidth: true
}
ListView {
model: listModel
delegate: Text {
text: name
}
Layout.fillHeight: true
Layout.fillWidth: true
}
Button {
text: "Change model"
onClicked: {
listModel.get(1).name = "changed text";
//listModel.setProperty(1,"name","changed text"); this line not works too
}
}
}
}
So clicking the button have to change model's element with index 1. But changing the model affects only ListView. The ComboBox remains unchanged.
Why that happens? Is it bug or feature? Is there a way to update ComboBox after changing its model?
I had a similar problem, I used a workaround. In onClicked function of button, create copy of model, change it as you want and then assign it again to ListViews model:
ListView {
id: listView
...
}
Button {
onClicked: {
var copy = listView.model;
copy.get(1).name = "changed text";
listView.model = copy; }
}
}

How to highlight the clicked (by mouse) element of a delegate w.r.t FolderListModel?

import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate: Text { id: h; text: fileName }
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
This works only when I use keyboard. How to make it work on mouse clicks?
To react on mouse events you need to place MouseArea item.
In the sample below (being an expanded version of the code you provided) I have added a MouseArea to the delegate item that upon being clicked sets the ListView's currentIndex to the delegate's index (a special property visible in the ListView's delegate).
import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate:
Text {
id: h;
text: fileName
MouseArea {
anchors.fill: parent
onClicked: list.currentIndex = index
}
}
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
As an alternative approach you might try placing a single MouseArea filling the whole ListView and use ListView's indexAt(int x, int y) method to check which delegate was clicked. However, you would need to care about more edge-conditions in such case.

QML Window is not visible

I have got QML application and there should be a lot of dialogs. When user press ToolButton appropriate dialog should be visible so that user can modify contols of that dialog. Here is minimum code for that:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0
ApplicationWindow {
visible: true
property variant dialog: Loader{sourceComponent: wind}
toolBar: ToolBar {
Row {
anchors.fill: parent
ToolButton {
iconSource: "1.png"
checkable: true
checked: false
onClicked: dialog.visible=checked
}
}
}
Component {
id: wind
Window{
visible: false
flags: Qt.Dialog
Button{
text: "hello"
}
}
}
}
However when I press ToolButton dialog is not visible. What is wrong?
property variant dialog: Loader{sourceComponent: wind} - that is wrong, don't expect the element to show when declared as a property, it has to be a child of its parent component.
onClicked: dialog.visible=checked - this is wrong, you need to use the item property of the dialog to refer to the object the loader instantiates
Code that works:
ApplicationWindow {
visible: true
toolBar: ToolBar {
Row {
anchors.fill: parent
ToolButton {
checkable: true
checked: false
onClicked: dialog.item.visible = checked
}
}
}
Loader {
id: dialog
sourceComponent: wind
}
Component {
id: wind
Window {
width: 100
height: 100
Button {
text: "hello"
}
}
}
}