Iterate over buttons in ButtonGroup QML - qt

I have a bunch on Button in my QML application.
Since there are many buttons on the same page it might be difficult for the user to see which button was pressed last!
So I would like to change the last Button font to indicate it was pressed, but later when another button gets pressed I would like to restore its font again.
I can not find a good way to do this.
I tried connecting all buttons to a ButtonGroup, but I need to iterate through all the buttons in order to set underline to false but however I try to do this I get different type of error.
Please see the example below:
Button {
text: qsTr("Open")
ButtonGroup.group: buttonGroup
font.pointSize: 20; font.family: "Courier"
onPressed: { /* some action here*/ }
onReleased: { /* some action here*/ }
}
Button {
text: qsTr("Close")
ButtonGroup.group: buttonGroup
font.pointSize: 20; font.family: "Courier"
onPressed: { /* some action here*/ }
onReleased: { /* some action here*/ }
}
ButtonGroup {
id: buttonGroup
function underlineButtons(underlined) {
for (var i = 0; i < buttons.length; ++i) {
children[i].font.underline = underlined;
}
}
onClicked: {
console.log(" -------------- buttonGroup Clicked: ", button.text)
underlineButtons(false) // restore old buttons
button.font.underline = true // mark the last button clicked
}
}
I am getting the following error what ever I do to try to access the children element.
qrc:/main.qml:65 ((null)): qrc:/main.qml:65: ReferenceError: children is not defined

Short answer
You can either
iterate on the children of the parent of your buttons
or iterate on the buttons of your ButtonGroup.
Long answer
Every QML Item has a children property, containing all Item-inherited children.
It means that you can iterate over the children of an item, like any other list of elements.
Item {
id: container
function setUnderlineOnChildren() {
for (var i = 0; i < children.length; ++i) {
children[i].font.underline = true;
}
}
Button {}
Button {}
Button {
onClicked: {
container.setUnderlineOnChildren();
}
}
}
ButtonGroup is used to create mutually exclusive checkable buttons (like radio buttons) as explained in the documentation. But contrary to most QML elements, because it does not represent a graphical element, it inherits QtObject instead of Item. So it won't have the children property of Items. But you should be able to do the same, relying on the buttons property by adding to the above
ButtonGroup {
id: myGroup
buttons: container.children
function underlineButtons(underlined) {
for (var i = 0; i < buttons.length; ++i) {
buttons[i].font.underline = underlined;
}
}
}
And then calling myGroup.underlineButtons(true).

You can also try using the activeFocus property and then in the font you can query whether or not the button is in active focus.
Button {
text: qsTr("Open")
font.pointSize: activeFocus ? 30 : 20
font.family: "Courier"
onPressed: {
activeFocus = true
/* some action here*/ }
onReleased: {/* some action here*/ }
}
Button {
text: qsTr("Close")
font.pointSize: activeFocus ? 30 : 20
font.family: "Courier"
onPressed: {
activeFocus = true
/* some action here*/ }
onReleased: {/* some action here*/ }
}

Related

QML Button - How to check that button is pressed with Control modifier?

How we can detect the QML button is clicked (checked) with "Control" key button pressed simultaniously?
AFAIK there's no way to check if the CTRL key is pressed while you're in an onPressed handler of a button, but you could work around that:
Let QML inform you when the CTRL key is pressed/released. Save that status in a property and use it in the onPressed handler of the button.
// Example code
Item {
id: rootItem
anchors.fill: parent
focus: true
property bool ctrlPressed: false
Keys.onPressed: {
if (event.key === Qt.Key_Control) {
ctrlPressed = true
}
}
Keys.onReleased: {
if (event.key === Qt.Key_Control) {
ctrlPressed = false
}
}
Button {
anchors.centerIn: parent
text: "Press me"
onPressed: {
if (rootItem.ctrlPressed)
console.log("Click with CTRL")
else
console.log("Click without CTRL")
}
}
}
This is just a 'workaround' and has some issues:
The rootItem has to have the focus
when the Application loses focus (ALT+TAB or minimize) while the CTRL button is pressed you might get unexpected behaviour
Another option is simply adding your own MouseArea on top of the button. You could use propogateComposedEvents to allow the Button's default mouse handling to still occur, or you could manually regenerate things like the clicked signal yourself. Neither option is ideal, but maybe one works for you.
Button {
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
if ((mouse.button == Qt.LeftButton) && (mouse.modifiers & Qt.ControlModifier)) {
doSomething();
}
}
}
}
I used #ParkerHalo's answer. The issue he noticed when the app loses focus with ctrl pressed can be avoided by placing this in the top level Window:
onActiveChanged: if( ! active ) rootItem.ctrlPressed = false;
One more solution is to use a TapHandler (a relatively new feature, see blog post).
There's an example in the docs for a new TreeViewDelegate which can be modified for your case with Button (they both inherit AbstractButton).
This way you won't have to remember any state or propagate events to underlying internal mouse area of the button:
Button {
text: "Test"
TapHandler {
acceptedModifiers: Qt.ControlModifier
onTapped: console.log("tapped with Qt.ControlModifier")
}
TapHandler {
acceptedModifiers: Qt.NoModifier
onTapped: console.log("tapped with Qt.NoModifier")
}
}

QML/QtQuick Binding delegate's property with ListView's currentIndex

Inside the delegate, I bind Image's source property to ListView's currentIndex which determines which image to load. This works great:
ListView {
id: answerListView
model: 5
currentIndex: -1
delegate: answerDelegate
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
source: answerListView.currentIndex === index
? "selected.png" : "not_selected.png"
}
MouseArea {
anchors.fill: parent
onClicked: {
answerListView.currentIndex = index
}
}
Component.onCompleted: {
answerListView.currentIndex = 1; // doesn't work!!
}
}
}
Since currentIndex: -1, it will always show not_selected.png. To show selected.png, I change currentIndex in Component.onLoaded inside delegate.
I was expecting image to load selected.png since currentIndex was updated.
What is the correct way and what am I misunderstanding here?
Ok, new guess:
You want to have the posibility to select multiple Items. As currentIndex only stores one value, which is the value you assigned it last, you can use it to mark only one Item.
Therefore you need to find another way to store your selection. You might for example have a property in the delegate: property bool selected: false which you set to true upon selection.
The problem with this solution is, that it only works if all Items are instantiated at all times. As soon as one Item will be destroyed again, the information will be lost, and uppon the next creation, the selection/unselection is undone.
The better way would be to introduce a role in your model, that stores the selection outside of the non-persistant delegates for you:
ListView {
id: answerListView
model: lm
delegate: answerDelegate
width: 100
height: 220
}
ListModel {
id: lm
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
anchors.fill: parent
source: model.selected ? "selected.png" : "notselected.png"
}
Text {
text: (model.selected ? 'selected ' : 'notselected ')
}
Component.onCompleted: {
model.selected = true // doesn't work!!
}
MouseArea {
anchors.fill: parent
onClicked: {
model.selected = !model.selected
}
}
}
}
Another option would probably be a ItemSelectionModel, but I don't know atm, how it works.
Otherwise your example works as expected:
The Item with index 1 is shown, and displays the Image selected.png. All other Items are not shown (for the ListView is to small) but if the would be shown, they would show notselected.png for the answerListView.currentIndex is not equal to their index.

QML - event.modifiers doesn't work when using Menu QML type

in QML when I used Menu QML type, event.modifiers didn't work in Keys.onPressed, but after commenting Menu type it worked. What am I doing wrong? is there any bug related to Menu QML type?
I am using Qt 5.4.0.
Rectangle {
id: main
width: 600
height: 300
Menu {
id: menu
title: "Edit";
MenuItem { text: "Copy";shortcut: "Ctrl+C" }
MenuItem { text: "Paste";shortcut: "Ctrl+V" }
MenuItem { text: "Select" }
MenuItem { text: "Select all";shortcut: "Ctrl+A" }
MenuSeparator { }
MenuItem { text: "Delete";shortcut: "Delete" }
MenuItem { text: "Delete all" }
MenuSeparator { }
MenuItem { text: "Auto arrange" }
}
Keys.onPressed: {
if((event.key === Qt.Key_C) && (event.modifiers & Qt.ControlModifier))
{
console.log("Ctrl+C is pressed")
}
}
MouseArea{
anchors.fill : parent;
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
main.focus = true;
if(mouse.button === Qt.RightButton){
menu.popup();
}
}
}
}
Menu is not a visual primitive (QQuickItem), and hence doesn't receive events:
All visual primitives support key handling via the Keys attached property.
The shortcut property that you're currently using is the correct way to provide shortcuts to menus. You can also assign an action.
To respond to a shortcut being triggered, use the triggered signal.

using loader in qml

I have multiple QML files. I just want to link them. Then I want to return to my home page from every page I navigate to. I am using loader in every page
Here is my code.
import QtQuick 1.1
Rectangle{
id:welcome
width:480
height:272
Loader{
id:loader
focus:false
anchors.fill: parent
}
gradient: Gradient {
GradientStop { position: 0.0; color: "light blue" }
GradientStop { position: 1.0; color: "blue" }
}
Text{
text:"\n\t\tPRESS ENTER"
font.bold:true
font.pointSize: 17
}
Button {
id: wel
height:30;
x:parent.width/2-30
y:parent.height/2-30
focus:true
border.color:"black"
opacity: activeFocus ? 1.0 : 0.5
Text{
text:"WELCOME"
anchors.horizontalCenter:wel.horizontalCenter;
anchors.verticalCenter:wel.verticalCenter;
}
Keys.onReturnPressed: {
wel.focus=false
loader.focus=true;
anchors.fill=parent
loader.source="Home.qml";
//welcome.visible=false;
}
}
}
My question is whenever I click on the button its loading new file. But the welcome page will not go. Both the file's will be overlapped. When I did visible=false complete UI will go. I get a white screen.
Can any one help me fix this problem?
How to load other file?
To load multiple pages you will need to use Connections element to handle signal from the page you have loaded.
Loader {
id: windowLoader
source: "Welcome.qml"
focus: true
property bool valid: item !== null
}
Button {
Keys.onReturnPressed: {
windowLoader.source = "Page1.qml"
}
}
Connections {
ignoreUnknownSignals: true
target: windowLoader.valid? windowLoader.item : null
onChangeToPage2: { windowLoader.source = "Page2.qml" }
onPageExit: { windowLoader.source = "Welcome.qml" }
}
And from the pages you load you will need to emit the "changeToPage2" and "pageExit" signals. The emitted signals will be handled by the Connections element.
Page1.qml:
Rectangle {
id: page1
signal changeToPage2
signal pageExit
Button {
id: exitButton
Keys.onReturnPressed: { page1.pageExit() }
}
}

how to imitate the button in QML

I want to imitate the Button in QML, but why does it only work with containsMouse and !containsMouse, it doesn't work with OnPressed. can you help me ? Thank you.
Item {
id: area
property string str_noPressed;
property string str_hover;
property string str_pressed;
Image {
id: background
width: 75; height: 75
source: userArea.containsMouse ? str_hover : str_noPressed
MouseArea {
id: userArea
anchors.fill: parent
onClicked: {}
onPressed: background.source = str_pressed
hoverEnabled: true
}
}
}
Obviously, in the button is pressed, it also contains the mouse.
I assume containsMouse takes precedence over onPressed. Try:
onContainsMouseChanged: {
if (containsMouse && !pressed) {
background.source = str_pressed
}
}

Resources