Check if a descendant has activeFocus == true in QML - qt

Inside a deeply nested QML GUI, is there an easy way to find out if any child or grandchild etc of an Item has activeFocus == true ?
Item {
id: intermediateItem
visible: anyDescendantHasActiveFocus(intermediateItem) ? true : false
Item {
Item {
Item {
id: hasActiveFocus
Component.onCompleted: hasActiveFocus.forceActiveFocus()
}
}
}
}

Instead of drilling down the hierarchy from your base ancestor, you can go up from the current activeFocusItem:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
function anyDescendantHasActiveFocus(ancestor) {
let item = ancestor.Window.activeFocusItem;
while (item) {
if (item === ancestor)
return true;
item = item.parent;
}
return false;
}
Row {
anchors.centerIn: parent
spacing: 10
Repeater {
model: 3
Rectangle {
width: 200
height: 100
border.width: 1
border.color: anyDescendantHasActiveFocus(this) ? "red" : "black"
Rectangle {
anchors.fill: parent; anchors.margins: 10
border.width: 1
Rectangle {
anchors.fill: parent; anchors.margins: 10
border.width: 1
Rectangle {
anchors.fill: parent; anchors.margins: 10
border.width: 1
Button {
anchors.centerIn: parent
text: "Focus me"
}
}
}
}
}
}
}
}

Related

QT QML how to create a reusable button component

I have some simulated buttons in on rectangle. These buttons I need at different places in the app. Is it possible to make a kind of component out of it?
This is how it is currently
RowLayout {
anchors.fill: parent
Rectangle {
id: button1
height: _buttonsHeight * 0.6
width: height
radius: height / 2
border.width: 1
border.color: "black"
Image {
anchors.centerIn: parent
source: "image://iconProvider/icons/128/button1.png"
sourceSize.height: parent.height * 0.8
}
MouseArea {
anchors.fill: parent
onPressed: btnHome.border.width = 2
onReleased: btnHome.border.width = 0
onClicked: userInputDevice.buttonClicked("button1")
}
}
Rectangle {
id: button2
height: _buttonsHeight * 0.6
width: height
radius: height / 2
border.width: 1
border.color: "black"
Image {
anchors.centerIn: parent
source: "image://iconProvider/icons/128/button2.png"
sourceSize.height: parent.height * 0.8
}
MouseArea {
anchors.fill: parent
onPressed: btnHome.border.width = 2
onReleased: btnHome.border.width = 0
onClicked: userInputDevice.buttonClicked("button2")
}
}
}
Something like this would be the goal
RowLayout {
anchors.fill: parent
Button {id = "button1", height = _buttonsHeight * 0.6, icon = "button1.png", command = "button1", parent = this}
Button {id = "button2", height = _buttonsHeight * 0.6, icon = "button2.png", command = "button2", parent = this}
}
Is something like this feasible ?
Thanks and best regards
Arne
You should read the documentation about QML defining types.
Don't use the assignment operator, but the colon to create bindings. No need to set any parent.
For the nested Image.source property you should create an alias property in the root of your component like so property alias imageSource: <imageID>.source to be able to set it from the outside.
import QtQuick
import QtQuick.Layouts
Window {
id: root
width: 320
height: 240
visible: true
property int btnHeight: 80
component CustomButton : Rectangle {
property alias icon: image.source
signal pressed
signal released
signal clicked
id: buttonRoot
height: 20
width: height
radius: height / 2
border.width: 1
border.color: "black"
Image {
id: image
anchors.centerIn: parent
source: "https://upload.wikimedia.org/wikipedia/commons/7/72/Variable_Resistor.svg"
fillMode: Image.PreserveAspectFit
width: buttonRoot.height / Math.sqrt(2)
height: image.width
}
MouseArea {
anchors.fill: parent
onPressed: buttonRoot.pressed()
onReleased: buttonRoot.released()
onClicked: buttonRoot.clicked()
}
}
RowLayout {
anchors.centerIn: parent
spacing: 20
CustomButton {
id: button1
height: root.btnHeight
onClicked: console.log("Button 1 clicked")
}
CustomButton {
id: button2
height: root.btnHeight
icon: "https://upload.wikimedia.org/wikipedia/commons/f/fd/Crystal_Clear_app_download_manager.svg"
onClicked: console.log("Button 2 clicked")
}
}
}
You can create a new qml file let's name it MyButton.qml
MyButton is a custom button with icon and text you can change it to fit your needs.
MyButton.qml:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.platform 1.0
import QtLocation 5.12
import QtQuick.Controls.Material 2.15
MouseArea {
id: control
width: control.w
height: control.h
property string icon_btn
property bool icon_visible: true
property string text
property int border: 3
property string borderColor: "white"
property string colorr: "#222222"
property int w: 50
property int h: 50
property bool text_visible: true
property bool clicked: false
property int radius: 12
property int contentLeftMargin: 0
property string buttonColor: enabled ? control.colorr : "grey"
cursorShape: Qt.PointingHandCursor
Rectangle {
border.color: control.borderColor
border.width: control.border
radius: control.radius
anchors.fill: parent
color: control.buttonColor
RowLayout{
anchors.fill: parent
Item { Layout.fillWidth: true }
Image{
id: img
visible: control.icon_visible
source: control.icon_btn
Layout.preferredWidth: 35
Layout.preferredHeight: 35
//Layout.leftMargin: control.contentLeftMargin
}
Text{
id: txt
text: control.text
font.pointSize: 17
font.bold: true
color: "white"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: control.text_visible ? x + width + 15 < control.width : 0
//Layout.leftMargin: control.contentLeftMargin
}
Item { Layout.fillWidth: true }
}
}
Layout.bottomMargin: 10
}
Then use it in your main qml file.
For example:
Window {
MyButton{
id: btn_refresh
icon_btn: "qrc:/Icons/outline_refresh_white_48pt_3x.png"
onClicked: {
//do something on click
}
}
}

QML ListView contentHeight property value is incorrect

I have a QML page with a GridLayout that contains the page title, ListView and close button:
GridLayout {
columns: 1
rows: 5
anchors.fill: parent
<page title item>....
ListView
{
id: list
spacing: 15
model: logModel
Layout.fillWidth: true
Layout.fillHeight: true
delegate: Item {
implicitWidth: parent.width
implicitHeight: grid.height
RowLayout
{
id: grid
spacing: 0
width: parent.width
height: commentLabel.implicitHeight
<icon>....
Label {
id: commentLabel
Layout.fillWidth: true
text: comment
wrapMode: Label.Wrap
}
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
<close button>...
}
Component.onCompleted:
{
console.log("list.contentHeight = ", list.contentHeight, "list.height = ", list.height)
}
when I bind ListView to a model (logModel) that contains items with comment property that contains relatively long multiline text and check contentHeight property of ListView from Component.onCompleted I get the value 161 that is obviously too small (actually it should be greater than 500, because ListView height property value is 461 and it is not enough for all the items).
So I cannot figure out what this 161 is. The only guess is that it is something close to the content height with single-line items (not multiline), but it is not clear what is the logic behind that.
My QT version is 5.13.2.
EDIT1:
The full source code:
ChangeLogPage.qml:
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4
Page
{
id: root
property alias model: list.model
background: Rectangle {
color: "transparent"
}
function close()
{
stack.pop()
}
SystemPalette {
id: palette
}
GroupBox
{
id: box
background: Rectangle {
color: palette.base
}
anchors.margins: 15
anchors.fill: parent
GridLayout {
columns: 1
rows: 5
anchors.fill: parent
ListView
{
id: list
Layout.fillWidth: true
Layout.fillHeight: true
//It is not clear why, but there is an enough left margin.
Layout.leftMargin: 0
Layout.rightMargin: 0
Layout.topMargin: 15
Layout.bottomMargin: 20
//Looks like it is only vertical spacing.
spacing: 15
clip: true
delegate: Item {
width: parent.width
height: grid.height
RowLayout
{
id: grid
spacing: 0
width: parent.width
//height: commentLabel.height
Label {
//id: commentLabel
Layout.fillWidth: true
text: comment //qsTr(comment)
wrapMode: Label.Wrap
}
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Close")
onClicked: close()
}
}
}
Component.onCompleted:
{
console.log("list.contentHeight = ", list.contentHeight, "list.height = ", list.height)
}
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import LinesGame 1.0
ApplicationWindow {
id: window
visible: true
width: 360
height: 640
title: "Test"
Component {
id: listModelComponent
ListModel {
id: model
}
}
StackView
{
anchors.fill: parent
id: stack
}
function showChangeLogPage(beginIndex, endIndex)
{
if (beginIndex < endIndex)
{
//var component = Qt.createComponent("ChangeLogModel.qml")
//var logModel = component.createObject(this)
var logModel = listModelComponent.createObject(this);
for (var i = beginIndex; i < endIndex; i++)
{
//i.toString()
logModel.append({comment: qsTr("The advertising was added, but it is shown only when you start a new game or load a saved game. Support the developers, tap on the advertising!")})
}
stack.push(Qt.resolvedUrl("ChangeLogPage.qml"), {model: logModel})
}
}
Component.onCompleted:
{
showChangeLogPage(0, 5)
}
}

How to have the components comunicate between them in an accordion like Component? [duplicate]

I'm trying to create accordion qml control like this.
First I thought that I can use combobox and customize it but now I think it is impossible.
Is there any standerd control that I can use? If not can you help me with controls structure?
Just playing with QML
PanelItem.qml
import QtQuick 2.7
import QtQuick.Layouts 1.2
Item {
default property var contentItem: null
property string title: "panel"
id: root
Layout.fillWidth: true
height: 30
Layout.fillHeight: current
property bool current: false
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
id: bar
Layout.fillWidth: true
height: 30
color: root.current ? "#81BEF7" : "#CEECF5"
Text {
anchors.fill: parent
anchors.margins: 10
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: root.title
}
Text {
anchors{
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 10
}
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: "^"
rotation: root.current ? "180" : 0
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.current = !root.current;
if(root.parent.currentItem !== null)
root.parent.currentItem.current = false;
root.parent.currentItem = root;
}
}
}
Rectangle {
id: container
Layout.fillWidth: true
anchors.top: bar.bottom
implicitHeight: root.height - bar.height
clip: true
Behavior on implicitHeight {
PropertyAnimation { duration: 100 }
}
}
Component.onCompleted: {
if(root.contentItem !== null)
root.contentItem.parent = container;
}
}
}
usage:
import QtQuick 2.7
import QtQuick.Layouts 1.2
import QtQuick.Window 2.0
Window {
visible: true
width: 400
height: 400
ColumnLayout {
anchors.fill: parent
spacing: 1
property var currentItem: null
PanelItem {
title: "Panel 1"
Rectangle {
color: "orange"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 2"
Rectangle {
color: "lightgreen"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 3"
Rectangle {
color: "lightblue"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 4"
Rectangle {
color: "yellow"
anchors.fill: parent
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
What about using this open source component which I did here
Accordion component and used here Accordion component example.
You only need to initialize:
Components.Accordion {
id: acordion
anchors.fill: parent
anchors.margins: 10
}
And create the data dynamically like this:
propertyAcordion.model = [
{
'menuTitle': value,
'children': [
{
'menuTitle': value,
'children': [
...
Updating the MouseArea click part as below with some added condition. Thanks to folibis for this qml accordian menu.
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.current = !root.current;
if(root.parent.currentItem !== null) {
if(root.parent.currentItem !== root)
root.parent.currentItem.current = false;
}
root.parent.currentItem = root;
}
}

How to get model in onCurrentItemChanged with QtQuick ListView

I use the listview like this:
ListView {
id: list
clip: true
spacing: 2
anchors.fill: parent
model: datas
delegate: listItem
onCurrentItemChanged: {
//I want get the part of the model which belong to currentItem
}
}
Component {
id: listItem
Rectangle {
height: 30
anchors.left: parent.left
anchors.right: parent.right
color: "green"
Label {
anchors.fill: parent
text: uuid
color: "white"
}
MouseArea{
anchors.fill: parent;
onClicked: {
console.log("study clicked")
console.log(uuid)
studyList.currentIndex=index
//component.selected(uuid)
}
}
}
}
I want get the part of the model which belong to currentItem in onCurrentItemChanged.
For example:
the model is
ListModel{
ListElement{uuid:aaaa}
ListElement{uuid:bbbb}
ListElement{uuid:cccc}
}
So there should be 3 item.
If I click the second one, I can get the ListElement which uuid is bbbb.
Is there any way?
You can do:
onCurrentIndexChanged: {
//I want get the part of the model which belong to currentItem
console.log(datas.get(currentIndex))
}
then the code will be
Item{
width: 800
height: 480
ListModel{
id: datas
ListElement{uuid:"aaaa"}
ListElement{uuid:"bbbb"}
ListElement{uuid:"cccc"}
}
ListView {
id: list
clip: true
spacing: 2
anchors.fill: parent
model: datas
delegate: listItem
onCurrentIndexChanged: {
//I want get the part of the model which belong to currentItem
console.log(datas.get(currentIndex).uuid)
}
}
Component {
id: listItem
Rectangle {
height: 30
anchors.left: parent.left
anchors.right: parent.right
color: "green"
Label {
anchors.fill: parent
text: uuid
color: "white"
}
MouseArea{
anchors.fill: parent;
onClicked: {
list.currentIndex=index
}
}
}
}
}
or you can try this approach, when button clicked emit a signal with parameters, the current element and the index or only the element .
example:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Item{
width: 800
height: 480
ListModel{
id: datas
ListElement{uuid:"aaaa"}
ListElement{uuid:"bbbb"}
ListElement{uuid:"cccc"}
}
ListView {
id: list
clip: true
spacing: 2
anchors.fill: parent
model: datas
delegate: listItem
signal sgnElementClicked(var element, var index)
// onCurrentIndexChanged: {
// //I want get the part of the model which belong to currentItem
// console.log(currentItem)
// }
onSgnElementClicked: console.log(element.uuid, index)
}
Component {
id: listItem
Rectangle {
height: 30
anchors.left: parent.left
anchors.right: parent.right
color: "green"
Label {
anchors.fill: parent
text: uuid
color: "white"
}
MouseArea{
anchors.fill: parent;
onClicked: {
// console.log("study clicked")
//console.log(uuid)
list.sgnElementClicked(datas.get(index), index)
/// studyList.currentIndex=index
//component.selected(uuid)
}
}
}
}
}

QT 5.3 / QML: Resizable StackView depending on currentItem

I'm working on a QML StackView that starts with a list of items to select from. Once selected I want to transition _.push(...) to a input form which has larger dimensions than the initialItem.
The only way I have trial-and-errored my way into a situation that works is by making the form Item a nested borderless window.
Q1. A nested window can't be the right type of concept to use for this... right ? there must be another way to do it. What is the right way ?
Q2. My goal after this is to have a transition animation that grows or shrinks between stacks of different sizes. Advice that doesn't preclude that would be best.
code
Main.qml :
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
ApplicationWindow {
property int itemHeight: 30
property int cornerRadius : 5
visible: true
color: "transparent"
flags: Qt.FramelessWindowHint
ListModel {
id: searchFacets
ListElement {
title: "Topics"
page: "content/TopicSearch.qml"
}
// ListElement {
// title: "Domains"
// }
}
StackView {
id: stackView
focus: true
initialItem: SearchFacets {
id: facets
}
delegate: StackViewDelegate {
pushTransition: StackViewTransition {
PropertyAnimation {
target: enterItem
property: "opacity"
from: 0
to: 1
}
}
}
}
}
Initial Item:
import QtQuick 2.3
Item {
height : listView.count * itemHeight
ListView {
id: listView
model: searchFacets
anchors.fill: parent
focus: true
highlightFollowsCurrentItem: false
highlight: Rectangle {
width: parent.width
height: itemHeight
radius : cornerRadius
color: "green"
opacity: 0.5
z:2
x: listView.currentItem.x;
y: listView.currentItem.y
Behavior on y {
SpringAnimation {
spring: 60
damping: 1.0
}
}
}
delegate: Component {
Item {
width: parent.width
height : itemHeight
Rectangle {
anchors.fill: parent
color: "#212126"
radius: cornerRadius
z:0
border.width: 2
border.color : "white"
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("clicked index: " + index)
listView.currentIndex = index
// listView.forceActiveFocus()
stackView.push(Qt.resolvedUrl(page))
}
}
Text {
text: title
font.pixelSize: 24
font.bold: true
z:1
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "white"
antialiasing: true
}
}
}
}
}
Input Form:
import QtQuick 2.3
import QtQuick.Window 2.0
Item {
Window {
width: 400
height: 400
visible: true
flags: Qt.FramelessWindowHint
Rectangle {
anchors.fill: parent
visible: true
color: "red"
}
}
}
One possible solution is to update the size of the dimensions of the StackView in the click handler that causes the transition. I do not know if that causes any problems with animating the transition.
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("clicked index: " + index)
listView.currentIndex = index
var component = Qt.createComponent(page)
var res = component.createObject(stackView)
stackView.height = res.height
stackView.width = res.width
stackView.push(res)
}
}

Resources