I have a QStringList property, and I basically want to turn that into a group of radio buttons dynamically, so that when the QStringList property changes the number of buttons and their labels is automatically updated.
I can sort of do it with a ListView, but it has problems:
It's not really a desktop widget so you have all the mobile bounciness.
I can't get the ListView selection and the radio button checks to interact nicely.
Here's my attempt anyway. I'd ideally like to do it without a ListView though:
ListView {
id: myList
orientation: ListView.Horizontal
ExclusiveGroup {
id: myListExclusiveGroup
}
Component {
id: myDelegate
RadioButton {
text: modelData
onCheckedChanged: {
if (checked)
myList.currentIndex = index
}
exclusiveGroup: myListExclusiveGroup
}
}
model: myListOfStrings
delegate: myDelegate
focus: true
}
Thanks to koopajah, I changed it to use Repeater and it works now. Note that it seems Repeater adds everything to the end of its parent's children, which means you can't rely on its position in a layout - you have to put it inside another layout, for example like this:
ExclusiveGroup {
id: myListExclusiveGroup
}
RowLayout {
Repeater {
id: myList
RadioButton {
text: modelData
exclusiveGroup: myListExclusiveGroup
}
model: myListOfStrings
focus: true
}
}
Related
Suppose I have a QML Window that contains a bunch of different controls:
Window {
...
TextEdit { ... }
CheckBox { ... }
Button { ... }
etc
}
Now I want my window to change "mode" on a certain event, and display a completely different set of controls.
Imagine its part of a multi-screen form. The current state is the first page of the form. When the user clicks "Next" button it goes to page 2 of the form. I want to add a new set of controls that represents page 2.
What is the correct way to organize this in QtQuick / QML ?
A common way to do that is with a StackView. The organization would be something like this:
Window {
StackView {
id: stackView
initialItem: page1
}
Item {
id: footerItem
// Maybe add other buttons here too
Button {
id: nextBtn
text: "Next"
onClicked: {
stackView.push(page2);
}
}
}
Component {
id: page1
Page1 {
// Define this in separate Page1.qml file
// This is where your page 1 controls go.
}
}
Component {
id: page2
Page2 {
// Define this in separate Page2.qml file
// This is where your page 2 controls go.
}
}
}
I would probably implement a mode changing view or form with "Back" and "Next" buttons using StackLayout. The StackLayout class provides a stack of items where only one item is visible at a time. You go to the next or previous mode by updating currentIndex.
StackLayout {
id: layout
anchors.fill: parent
currentIndex: 1
Rectangle {
color: 'teal'
implicitWidth: 200
implicitHeight: 200
}
Rectangle {
color: 'plum'
implicitWidth: 300
implicitHeight: 200
}
}
I'm following this tutorial (without the flickable content in each entry) for Qt 4.8 while using Qt 5.7 with QtQuick 2.0. The way the ListView there works is as follows:
User clicks on item in list
Alternative (detailed) view of item is displayed
User has to click on Close button in detailed view to reset the state of entry to its default compact view.
This leads to a clutter where at some point if the user clicks on all items in which case all will be shown in their full view. Having the user click on the Close button every time he/she opens a detailed view also is (omho) not that handy.
I've altered the entry to close when the user clicks on the view. I'm also trying to prevent this clutter and achieve a more (omho) flowing behaviour:
User clicks on item in list
Alternative view of item is displayed
User clicks on detailed view to reset state of entry to its default compact view OR
User clicks on another entry and all currently in detailed view entries are reset to their compact view
Currently I'm looping through my ListView's contentItem.children[loop_index] and setting the state to "" ("Details" = show detailed view | "" = show compact view). Due to the way ListView works (loading/unloading delegates on demand) this is quite unreliable and I often get an undefined reference when I try to access the state of other delegates. The following MouseArea, which I'm using to do all that, is part of every delegate:
// state is a QML `State` that is bound to the delegate (see below for the details on it)
MouseArea {
anchors.fill: background
onClicked: {
// Iterate through all other entries and close them
for (var entry = 0; entry < listView.count; ++entry) {
if(listView.contentItem.children[entry] !== gestureEntry) {
console.log("Hide other element");
listView.contentItem.children[entry].state = ""; // IT FAILS HERE (SOMETIMES)
}
}
// Change view of current entry
if(gestureEntry.state === "Details") {
gestureEntry.state = "";
console.log("Hiding details")
}
else {
gestureEntry.state = "Details";
console.log("Showing details");
}
}
}
with state being a delegate's state:
states: State {
name: "Details"
PropertyChanges { target: background; color: "white" }
PropertyChanges { target: gestureImage; width: 130; height: 130 } // Make picture bigger
PropertyChanges { target: gestureEntry; detailsOpacity: 1; x: 0; y: 0 } // Make details visible
PropertyChanges { target: gestureEntry; height: listView.height } // Fill the entire list area with the detailed view
}
I'm thinking that the state information can be stored inside the ListModel itself making it possible to iterate through the model's contents (which are always there unlike the contents of the delegates) however I don't know how to automatically update my list (and the currently visible/invisible delegates) when an entry changes in the model. From what I've found so far it seems not possible to do that since the ListView doesn't actively monitor its ListModel.
Is this indeed the case? If yes, then is it possible to go around this problem in a different way?
Why don't you use the currentIndex property of your ListView?
Just modify your delegate like this:
Item {
id: gestureEntry
...
state: ListView.isCurrentItem?"Details":""
...
MouseArea {
anchors.fill: background
onClicked: {
if(listView.currentIndex == index)
listView.currentIndex = -1
else
listView.currentIndex = index
}
}
}
EDIT:
The only issue with the solution above is that - upon loading - an entry in the ListView is preselected which automatically triggers the detailed view of that entry. In order to avoid that the following needs to be added to listView:
Component.onCompleted: {
listView.currentIndex = -1;
}
This ensures that no entry will be preselected.
guess it is an issue because you stored a state in your delegate. You should not do this as described in the delegate-property (Link), because the delegates get reused when they get out of view.
At least you should use a when: ListView.isCurrentItem in the State and depend on a value of the ListView. So only your current delegate is maximized. Then in the MouseArea only set `ListView.view.currentIndex = index'. Don't change the state manually in the function!
I ran in the same trouble, removed the states completely and just used the attached property ListView.isCurrentItem. But binding the state to a Value from the ListView should also work, because it's not stored in the delegate.
Minimal example:
import QtQuick 2.0
Item {
width: 800
height: 600
ListView {
id: view
anchors.fill: parent
model: 3
spacing: 5
currentIndex: -1
delegate: Rectangle {
id: delegate
color: ListView.isCurrentItem ? "lightblue" : "green" // directly change properties depending on isCurrentItem
height: 100
width: 100
states: State {
name: "maximized"
when: delegate.ListView.isCurrentItem // bind to isCurrentItem to set the state
PropertyChanges {
target: delegate
height: 200
}
}
MouseArea {
anchors.fill: parent
//onClicked: delegate.ListView.view.currentIndex = model.index // if only selection is wanted
onClicked: {
//console.debug("click");
if (delegate.ListView.isCurrentItem)
{
delegate.ListView.view.currentIndex = -1;
}
else
{
delegate.ListView.view.currentIndex = model.index;
}
}
}
Text {
anchors.centerIn: parent
text: index
}
}
Text {
text: "CurrentIndex: " + parent.currentIndex
}
}
}
I created a dummy ListModel and loaded it to the TableView:
ListModel {
id: testModel
ListElement {
status: false
}
}
function testFunction() {
for (var i = 0; i < 45; i++) {
testModel.append({});
}
}
TableView {
model: testModel
TableViewColumn {
role: "status"
title: "Activation On/Off"
delegate: Checkbox {
id: idDelegatedCheckbox
}
}
}
I pretend to use the Checkbox as delegate component to select the items in the TableView. I check the first item in the table in the following way:
First item selected
Then I scroll down and return to the top and the items that I had selected disappear and other item is selected:
Different item selected
Do you know why?
Thanks in advance.
TableView delegates are instantiated as needed and may be recycled or destroyed at any time. As with any Qt Quick item views (ListView, GridView, PathView, TableView...), state should never be stored in a delegate. Hence, your CheckBox delegate must store its "checked" state in the model:
delegate: Checkbox {
id: idDelegatedCheckbox
checked: model.checked // read from the model when created or recycled
onCheckedChanged: model.checked = checked // write to the model when checked or unchecked
}
I have a ListView including a lot of radio buttons. The list is bigger than the visible area. One of the radio buttons is checked. Sometimes, if the selected radio button is outside the visible area I want to scroll to it:
ScrollView {
anchors.fill:parent
ListView {
anchors.fill: parent
model: valuesList
delegate: RadioButton {
id: radioBtn
//check of value is index type and do the corresponding checked? test
checked: valueIsIndex ? (parseInt(valueFromParent) == index ? true : false) : (valueFromParent == valueString ? true : false)
onClicked: {
root.selected(valueString, index)
}
Component.onCompleted: {
if(checked)
//Here i want to scroll the list to display this radiobutton
}
}
}
}
Any ideas howto to get the list scrolled? I've played around a lot of with hightlights and contentY but nothing worked.
I've use the ScrollView around the ListView to automatically get the systems scrollbars on the desktop. On mobile devices i have just the flickable ListView.
EDIT
I get it on the way with the help of BaCaRoZzo. Here is my current working example:
ScrollView {
id: scrollView
anchors.fill:parent
property int yOfCheckedRadioButton: 0
ListView {
id:listView
anchors.fill: parent
spacing: Math.round(appWindow.height*0.05)
model: internalValuesList
delegate: RadioButton {
id: radioBtn
//check of value is index type and do the corresponding checked? test
checked: checktest()
style: MyRadioButtonStyle {
myRadioBtn: radioBtn
labelString: value
}
Component.onCompleted: {
//set the position of the checked RadioButton to scroll to it later onContentHeightChange
if(checked) {
var checkedRadioBtnPositionY = Math.round((radioBtn.height + listView.spacing) * index - radioBtn.height * 1.5)
if( checkedRadioBtnPositionY > 0)
scrollView.yOfCheckedRadioButton = checkedRadioBtnPositionY
else
scrollView.yOfCheckedRadioButton = 0
}
}
}
onContentHeightChanged: {
//scroll to the checked RadioButton
contentY = scrollView.yOfCheckedRadioButton
}
}
}
I recall problems with scroll before Qt 5.4 when I found a workaround like:
ScrollView {
anchors.fill: parent // mind how you stretch it
contentItem:
Flow {
id: flow
spacing: 10 // mind gaps
width: parent.parent.width - 20 // select proper width
// Put anything you would like to scroll in here
// Mind that Flow positions items one after another
// left to right, top to bottom
// You can also try containers other than Flow
// but whether it works or not may depend on Qt version
ExclusiveGroup { id: tabPositionGroup }
RadioButton {
text: "RB1"
checked: true
exclusiveGroup: tabPositionGroup
}
RadioButton {
text: "RB2"
exclusiveGroup: tabPositionGroup
}
}
}
Whether ScrollView needs an explicit contentItem or not is another matter and it certainly may not need it but that does not hurt if the SrollView needs to resolve what it actually scrolls.
I want to show a context menu when right-clicking on Qt5.5 qml TreeView item, but it has clicked signal. How to show a context menu on right click?
TreeView {
id: tree_view
anchors.fill: parent
model: tree_model
headerVisible: false
backgroundVisible: false
TableViewColumn {
role: "display"
}
onClicked: {
console.log("clicked", index)
}
onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index)
}
It's actually quite easy, you just need a MouseArea configured to accept only right click events, and it won't interfere with the mouse handling performed by the TreeView itself:
TreeView {
id: tree_view
anchors.fill: parent
model: tree_model
TableViewColumn {
role: "display"
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
var index = parent.indexAt(mouse.x, mouse.y)
if (index.valid) {
console.log("show context menu for row: " + index.row)
}
}
}
}
Can you simply define your menu somewhere and use the popup method to show it? That method open the menu near to the mouse cursor, so to the right position.
Of course, you have to define your itemDelegate as well and let the event flows out of your item if needed (do not consume it).
The documentation for the clicked signal of a TreeView explicitly refers to the item delegate to consume those events, so I guess this is the intended approach.