Controlling size of QML components from within style property - qt

I'd like to create my own styled RadioButton, but I'm having difficulty controlling the size of the components used in the RadioButtonStyle that I am using. I want to put my RadioButton in a GridLayout and have the RadioButton take up all the available space (as if I were setting Layout.fillWidth and Layout.fillHeight to true)
To start off with, I am using the RadioButtonStyle code from the Qt docs inside a GridLayout:
GridLayout {
columns: 3
anchors.fill: parent
RadioButton {
text: "Radio Button"
style: RadioButtonStyle {
indicator: Rectangle {
implicitWidth: 16
implicitHeight: 16
Rectangle {
anchors.fill: parent
visible: control.checked
color: "#555"
}
}
}
}
}
we can see that implicitWidth and implicitHeight of the indicator Rectangle are set int literals. I want to size the indicator Rectangle to fill the space provided by the layout.
I would like the width and height of the indicator Rectangle to track the width and height of the RadioButton, which in turn tracks the width and height of the GridLayout cell containing it.
== UPDATE ==
I have tried the following - setting Layout.maximumHeight/Width prevents Qml going into some kind of infinite loop
RadioButton {
id: rdbt
Layout.fillHeight: true
Layout.fillWidth: true
Layout.maximumWidth: 200
Layout.maximumHeight: 200
style: RadioButtonStyle {
id: ab
indicator: Rectangle {
color: "blue"
height: control.height
width: control.width //control.width

Related

How is ComboBox component able to determine if the popup needs to be above of control?

I've seen that with the following code:
Window {
width: 440
height: 280
visible: true
ComboBox {
id: control
model: ["First", "Second", "Third"]
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
delegate: ItemDelegate {
width: control.width
contentItem: Text {
text: modelData
color: "#21be2b"
font: control.font
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
highlighted: control.highlightedIndex === index
}
indicator: Canvas {
id: canvas
x: control.width - width - control.rightPadding
y: control.topPadding + (control.availableHeight - height) / 2
width: 12
height: 8
contextType: "2d"
Connections {
target: control
function onPressedChanged() { canvas.requestPaint(); }
}
onPaint: {
context.reset();
context.moveTo(0, 0);
context.lineTo(width, 0);
context.lineTo(width / 2, height);
context.closePath();
context.fillStyle = control.pressed ? "#17a81a" : "#21be2b";
context.fill();
}
}
contentItem: Text {
leftPadding: 0
rightPadding: control.indicator.width + control.spacing
text: control.displayText
font: control.font
color: control.pressed ? "#17a81a" : "#21be2b"
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
border.color: control.pressed ? "#17a81a" : "#21be2b"
border.width: control.visualFocus ? 2 : 1
radius: 2
}
popup: Popup {
y: control.height - 1
width: control.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: control.popup.visible ? control.delegateModel : null
currentIndex: control.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator { }
}
background: Rectangle {
border.color: "#21be2b"
radius: 2
}
}
}
}
(The ComboBox example from Qt documentation, at the bottom of the window)
If you click on the ComboBox, the popup its shown above the control (because its out of space below). I would like to know which signal or variable makes this automatic behaviour, so that i can capture it and trigger a different action.
I'm not sure I fully understand the question, but hopefully this answers it.
The code within popup uses a ternary to navigate the popup visibility. See this post regarding QML conditional bindings (ternary operators)
model: control.popup.visible ? control.delegateModel : null
"If popup visible, set model equal to delegate model. Else set popup model null"
Lets talk about signals and slots. If you want to easily view all of the signal/slots on a qml object type go within the block and type 'on'. Then view all of the code fillins from there. You can check the QT documentation as well.
If I were to implement this, I may have done it differently using the popup signals: open(), close(). It will add more lines of code, but improve readability and utilize the signal/slot mechanism. The current method creates very tight coupling between QML components.
Hey, thanks for your answer! Basically what I need to do is work with
popup y-coordinate. More specifically evaluate a condition to assign
the y property of popup, depending on how much space is left to open
it below the control... like this: popup.y = some_condition?
control.height - 1 : popup.implicitHeight + 1 QML already has some way
to know if the space is enough... and then readjust the popup
y-coordinate. I would like to know which inner-mechanism handles this.
Three ways to tackle it come to mind:
Use Layouts
Use Component attributes/member data
Use anchors
Layouts
Wrap all of your components inside of a column layout. Have your column layout fill up the space of both components combined. Then you can set minimum, preferred, and maximum width/heights of each component. In addition, you could set the preferred size for one component. Then call Layout.fill width/column to have it automatically take up the rest of the space.
Component attributes/member data
Mathmatically calculate the .y data using all of your other components.
popup.y = appWindow.y - componentWindow.y
or
popup.y = doMath(some property var of component X)
Anchors
Anchor your popup component to another component. So suppose you wanted a popup underneath some rectangle component.
Anchors.top = myRect.bottom
I'm a huge fan of using nested layouts to create dnyamic screens that always fill up spaces in the way I expect them to. It prevents tightly coupled components and lets Qt do the hard work.

Multiline text item is not clipped inside QML ListView

I have a QML page that with a GridLayout that contains the page title, ListView and close button:
GridLayout {
columns: 1
rows: 5
anchors.fill: parent
<page title item>....
ListView
{
spacing: 15
model: logModel
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>...
}
When I scroll the list, the first and the last visible item in the list may go beyond the list bounder and intersect the page title or close button:
How to prevent this and make the items clipped?
EDIT1:
I tried to add
clip: true
to ListView, delegate Item, RowLayout and Label, but with no success. According to docs, ListView with clip property set to true should clip its content, should not it?
I found a similar question where clip property is the answer, but it is not clear why it does not work with my ListView.
My QT version is 5.13.2.
Set clip:true in the ListView component.
ListView
{
clip:true
}

How I can dropdown options when I check on checkbox?

I have one Checkbox with onCheckedChanged handler and what I want is, when the Checkbox is checked, dropdown a menu with several texts and text fields. I have the following code:
CheckBox {
id: box
onCheckedChanged: {
// TODO here to dropdown a menu with settings
}
}
I have texts and text fields like the following:
Component {
id: label
Text {
color: "red"
antialiasing: true
smooth: true
}
}
I'm a newbie in QML so please be patient.
You didn't really say where this menu is located, if it's floating or if it is to just appear maybe displacing other elements on the view. Anyway, to anwser your question, you can achieve what you're asking by setting the height of your 'menu' to zero then, when the CheckBox is checked, setting it to however tall you want it to be. To make the menu grow smoothing you can use a NumberAnimation.
You can change your onCheckedChanged() slot to look like this:
onCheckedChanged: {
menu.height = checked ? 100 : 0
}
and add the following, as a child of your menu element:
Behavior on height { NumberAnimation {...} }
to make the menu's height grow from 0 to 100 over a period of time to make it grow smoothly.
Another approach, which I'd prefer, is to use States with a Transition (instead of a Behavior).
Here is an example of a 'menu' which, when the CheckBox is checked, will slide out from beneath the CheckBox:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
Rectangle {
id: checkboxContainer
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 100
color: "pink"
CheckBox {
id: menuCheckBox
anchors.centerIn: parent
text: qsTr("Click Me")
}
}
Rectangle {
id: menu
anchors.top: checkboxContainer.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 0 //This is the default value when the 'default' state is active. That is whenever we're not in the "openState"
clip: true // this hurts rendering performance a bit but is required to make sure child elements don't exceed the bounderies of this object (so when height is zero you don't see the text)
color: "lightblue"
states: [
State {
name: "openState"
when: menuCheckBox.checked // This state is only active when the check box is checked. When you uncheck the check box we move to the 'default' state (which sets the menu's hight back to zero)
PropertyChanges {
target: menu
height: 100
}
}
]
transitions: Transition {
NumberAnimation {
property: "height"
duration: 350 //This means when the height property is changed it will take 350ms to move from what its at to what your changing it to (i.e. 0 to 100 or 100 to 0).
easing.type: Easing.InOutQuad
}
}
Text {
anchors.centerIn: parent
color: "red"
antialiasing: true
smooth: true
text: qsTr("HELLO")
}
}
}
I hope this answers your question.

Building TabBar in QML - Loader doesn't show all the Rectangles

import QtQuick 2.4
import QtQuick.Window 2.2
Window
{
visible: true
height: 500
width: 500
property VisualItemModel contentToBeShownOnTabClick : visualItemModelDemo
property variant tabLabels : ["Navigation", "Payload", "System Control"]
VisualItemModel
{
id: visualItemModelDemo
Rectangle
{
id: navigationTab
color: "green"
height: 200
width: 200
}
Rectangle
{
id: navigationTab1
color: "darkgreen"
height: 200
width: 200
}
Rectangle
{
id: navigationTab2
color: "lightgreen"
height: 200
width: 200
}
}
MainForm
{
Component
{
id: tabsOnBottomComponent
Repeater
{
model: tabLabels
// The Tabs
Rectangle
{
id: tabsOnBottom
// This anchoring places the tabs on the outer top of the parent rectangle.
anchors.top: parent.bottom
anchors.topMargin: 180
color: "lightsteelblue"
border.color: "steelblue"
border.width: 2
implicitWidth: Math.max ((labelTabsBottom.width + 4), 80)
implicitHeight: 20
radius: 2
// Tabs Text/Label
Text
{
id: labelTabsBottom
anchors.centerIn: parent
color: "white"
rotation: 0
// With reference to mode: tabLabels
text: modelData
font.pointSize: 11
}
MouseArea
{
anchors.fill: parent
onClicked: bottomTabClicked (index);
}
}
}
}
Rectangle
{
// The things which get displayed on clicking of a tab will be shown in this rectangle.
id: areaForTabContents
border.color: "black"
border.width: 10
height: parent.height
width : parent.width
color : "pink"
// These are the tabs displayed in one row - horizontally.
Row
{
id: horizontalTabs
Loader
{
anchors.fill: parent
sourceComponent: tabsOnBottomComponent
}
}
}
anchors.fill: parent
}
}
This gets shown as follows:
whereas I want it to see 3 rectangles there side by side.
Loader is not a transparent type w.r.t. the containing type, Row in this case. I think this is an issue related to creation context and the way Repeater works. From the documentation of the latter:
Items instantiated by the Repeater are inserted, in order, as children of the Repeater's parent. The insertion starts immediately after the Repeater's position in its parent stacking list. This allows a Repeater to be used inside a layout.
The Rectangles are indeed added to the parent which is the Loader, they stack up - Loader does not provide a positioning policy - then they are added to the Row resulting in just one Item (the last one) to be visible.
You can tackle the problem with few different approaches, depending on the properties you want to maintain or not. I would get rid of anchoring in the Component and move it to the containing Row. A too specific anchoring inside a Component could be a pain in the neck when it is instanced and used all over a (not so small) project.
As a first approach you can re-parent the Repeater to the Row, i.e. you can rewrite code as:
Row
{
id: horizontalTabs
Loader
{
sourceComponent: tabsOnBottomComponent
onLoaded: item.parent = horizontalTabs
}
}
However this would result in warnings due to the Component anchoring references not working as expected any more.
If you still want to maintain the anchoring, as defined in the Component, and off-load the creation, you can go for the dynamic way (if the semantics fits in your use case), i.e. you can use createObject. This way you totally avoid the Loader and the related issue. For instance, you can create the Repeater once the Row has completed its creation:
Row
{
id: horizontalTabs
Component.onCompleted: tabsOnBottomComponent.createObject(horizontalTabs)
}
Clearly, the creation code can be move anywhere else, depending on your needs.

QML MouseArea onEntered hoverEnabled does not work

a pretty simple scenario, actually:
Text {
text: "Hover me!"
font.family: "Arial"
font.pointSize: 16
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.color = "#ffffff"
}
onExited: {
parent.color = "#000000"
}
}
}
As you can see, all i want is that the text color changes when i hover it. However, it works only when i'm holding down my left mouse button. Since i'm pretty new to Qt, i've no idea where the problem could be. I googled all day to find an answer, but everything leads me to the "set hoverEnabled: true" solution, which i'm already using.
I'm working on windows with Qt 2.4.1 including VPlay (doesn't think that VPlay matters here)
From your snippet, it looks like you're not assigning the Text element a size (either explicitly or via anchoring), so it has a width/height of (0,0), which means it will never contain the mouse cursor. Note that in QtQuick, the size of items is not defined by their contents or where they paint (an item can paint anywhere outside of its (position, size) rectangle). An item without explicit width/height attributes or anchoring will have a size of (0,0), no matter how it appears on screen.
In the following example, the mouse area has a size of 360, 360 inherited from its parent rectangle (via anchors.fill: parent):
import QtQuick 2.0
Rectangle {
width: 360
height: 360
color: "black"
Text {
anchors.centerIn: parent
text: qsTr("Hello World")
color: mouseArea.containsMouse ? "red" : "white"
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
}
}
}
I preferred the declarative approach using a property binding with containsMouse here, but onEntered/onExited would work as well.

Resources