I have QML Text elements in a ColumnLayout like so:
import QtQuick 2.0
import QtQuick.Layouts 1.1
Item {
Rectangle {
ColumnLayout {
anchors.fill: parent
anchors.bottomMargin: 5
Canvas {
Layout.fillHeight: true
Layout.fillWidth: true
}
Text {}
Text {}
Text {}
}
}
}
The canvas fill the top part of the column nicely and the Texts line up beneath it just fine. That anchors.bottomMargin sets a small margin only at the very bottom. But no matter what margins or anchors I set for the Texts, there is a lot of vertical empty space between them. The text is just numbers so there is no concern about characters that need to take up more room. How do I get rid of this space?
I've run into this issue as well, and there wasn't a solution. However, in Qt 5.4, the FontMetrics and TextMetrics QML types were added.
TextMetrics
FontMetrics has a comprehensive API which mirrors the C++ QFontMetricsF class, with some of it being imperative (functions). TextMetrics takes the functions in FontMetrics and makes them declarative (properties) for convenience, plus some extra properties for completeness.
Given some text string, TextMetrics will provide you with the tightBoundingRect property, which, as its name suggests, is a tight bounding rectangle around the string, without the extra space that you normally see. Take that height from the height of a string with only numbers in it, and you get the superfluous height than can be used as negative spacing:
import QtQuick 2.4
Item {
Rectangle {
anchors.fill: parent
TextMetrics {
id: metrics
text: "1"
}
Column {
anchors.fill: parent
anchors.bottomMargin: 5
spacing: -(metrics.height - metrics.tightBoundingRect.height)
Text { text: "123" }
Text { text: "123" }
Text { text: "123" }
}
}
}
Note the warning from the documentation:
Warning: Calling this method is very slow on Windows.
That shouldn't be a problem if you only set the text/font on the TextMetrics object once though, as it will only calculate it once.
Line height
An alternative, but sketchy approach is to basically guess a value for the lineHeight property of each Text item.
import QtQuick 2.0
Item {
Rectangle {
anchors.fill: parent
Column {
anchors.fill: parent
anchors.bottomMargin: 5
Text { text: "123"; lineHeight: 0.8 }
Text { text: "123"; lineHeight: 0.8 }
Text { text: "123"; lineHeight: 0.8 }
}
}
}
Negative spacing
As Shubhanga said, negative spacing will also work, but it's also not so great:
import QtQuick 2.0
Item {
Rectangle {
anchors.fill: parent
Column {
anchors.fill: parent
anchors.bottomMargin: 5
spacing: -4
Text { text: "123" }
Text { text: "123" }
Text { text: "123" }
}
}
}
Text height
Again, mentioned by Shubhanga, setting the text's height explicitly will work, but there's still guesswork involved. Like the two solutions above, you'll have to change the value you subtract from the height each time you change font size, and it won't scale between devices (low DPI desktop PC vs high DPI mobile):
import QtQuick 2.0
Item {
readonly property int heightAdjustment: 5
Rectangle {
anchors.fill: parent
Column {
anchors.fill: parent
anchors.bottomMargin: 5
Text {
text: "123";
height: implicitHeight - heightAdjustment
}
Text {
text: "123";
height: implicitHeight - heightAdjustment
}
Text {
text: "123";
height: implicitHeight - heightAdjustment
}
}
}
}
Have you tried using spacing property? This is used to set spacing between layout's contents. The default value is 5. Try setting it as 0.
Reference ColumnLayout spacing property
Related
I'm writing a QML application to draw brown blocks inside a blue Rectangle. The application performs this task using a ColumnLayout and a Repeater to draw an arbitrary number of blocks (4 by default):
I'm attempting to change the model of the Repeater dynamically when the user clicks on the screen to force the UI to draw a different amount of blocks. Whenever the desired amount of blocks is changed via blockCount, it triggers the recalculation of blockHeight, the height of each block, so that a smaller amount of blocks can occupy more space on the screen. At least that is the theory!
For debugging purposes, clicking on the screen sets blockCount to 2.
Here's a sample image with the expected result on the Left and the current result on the Right:
As you can see on the image above, when the click happens and rectId.blockCount = 2 is executed, it appears to trigger a sequence of calls that:
Ends up changing the model of the Repeater before blockHeight is recalculated;
Or the anchors for the ColumnLayout are reset, for some bizarre reason;
Or something else is going on;
I'm trying to understand what's causing this behavior and also looking for an approach that allows the application to change the number of blocks dynamically while being able to draw them correctly!
What am I missing?
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.15
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
if (numBlocks === undefined)
numBlocks = rectId.blockCount;
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (numBlocks-1))) / numBlocks;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.bottom: parent.bottom
anchors.bottomMargin: rectId.borderWidth + rectId.blocksSpace
anchors.left: rectId.left
anchors.leftMargin: rectId.borderWidth*2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
width: rectId.blockWidth
height: rectId.blockHeight
// Debug:
Component.onCompleted: {
print("Inner Rectangle")
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// since repId uses blockCount as the model, any change to it should automatically recreate the elements of the Repeater
// here we force blockHeight to be recalculated before the model is changed
rectId.blockHeight = rectId.updateBlockHeight(2)
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Why are you calculating the blockHeight and blockWidth while you can leverage the power of ColumnLayout?
Use the Layout.fillWidth and Layout.fillHeight properties to signal the ColumnLayout that the blocks should fill the entire width & height, evenly distributed. And then set the correct size to the ColumnLayout and it will do the calculations you try to program itself.
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
Layout.fillWidth: true
Layout.fillHeight: true
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
EDIT for keeping blockHeight calculation
If you insist on keeping the calculation since it is more difficult in the real world (fair enough), I would suggest to use implicitHeight and implicitWidth. This works because the Layout engine does not trigger on changes on width/height since it is supposed to set these himself, it does however monitor the implicit sizes:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (rectId.blockCount-1))) / rectId.blockCount;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
implicitWidth: rectId.blockWidth
implicitHeight: rectId.blockHeight
onXChanged: print("x[",index,"]=", x)
onYChanged: print("y[",index,"]=", y)
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Also, I refactored the updateBlockHeight function, it's not needed to explicitly set it, the QML engine is so smart it will even reevaluate the binding when one of the parameters in the function changes!
(From my comments)
With Qt 5.15.1 on Linux get the expected output. With Qt 5.15 get the output you indicate for what is probably a bug that has been fixed in Qt 5.15.1.
how can i get gridView.itemAtIndex(index).color?
what is i have tried:
contentrepeater.itemAt(5).gridView.model.color;
contentrepeater.itemAt(5).gridView.itemAtIndex(5).color;
But it doesn't work
Rectangle {
anchors.top: bar.bottom
Layout.fillHeight: true
Layout.fillWidth: true
Repeater {
id: contentrepeater
model: 11
Rectangle {
anchors.fill: parent
color: 'red'
visible: false
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 150
cellHeight: 170
clip: true
model: 11
delegate: Rectangle{
height: 160
width: 140
color: '#333333'
}
}
}
}
}
Ultimately you probably don't want to do it that way. It will be hackish and error-prone. For example GridView only provides item access based on position coordinates, not indexes. So you'd need to dive into its children which are going to be created dynamically... it's possible but very messy and not really supported API.
You are better off defining your item models first, then using the GridView (or whatever) to display them. That way you can manipulate objects in the models and changes will be reflected in the view (instead of the other way around like you're trying now).
This example (based on your posted code) creates 4 layouts with 11 squares each and animates the color in each square using a timed script. Note that we need separate instances of the models for each of the GridViews within contentrepeater (otherwise it is only shown in the last view). So the example is a bit more convoluted since the item models are being created dynamically.
I should add that in a "real" application I'd use a different method of tracking the created item models instead of looking them up in the display hierarchy like I have it here. The main point this is trying to demonstrate is to manipulate the displayed items (delegates) via changes to the model data.
import QtQuick 2.9
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQml.Models 2.3
Pane {
id: root
width: 400
height: 650
padding: 9
// rectangle items to create per model
property int itemsPerModel: 11
// prototype object model
property Component itemModel: ObjectModel {}
// prototype model item
property Component delegate: Rectangle {
height: 30
width: 30
color: '#333333'
}
// Creates a new ObjectModel with some Rectangle items as children
function newItemModel() {
var model = itemModel.createObject(root);
for (var i=0; i < itemsPerModel; ++i)
model.append(delegate.createObject(root));
return model;
}
SequentialAnimation {
id: animate
running: true
loops: Animation.Infinite
ScriptAction {
property string nextColor: "blue"
property int nextSet: 0
property int nextItem: 0
script: {
contentrepeater.itemAt(nextSet) // the Rectangle within the GridLayout
.children[0] // the gridView within the Rectangle
.model.get(nextItem) // the model's delegate item (a Rectangle)
.color = nextColor; // set the new color on it.
// advance to next item or set of items.
nextItem = (nextItem+1) % root.itemsPerModel;
if (!nextItem)
nextSet = (nextSet+1) % contentrepeater.count;
nextColor = (nextColor === "blue" ? "orange" : nextColor === "orange" ? "white" : "blue");
}
}
PauseAnimation { duration: 100 }
}
GridLayout {
columns: 2
anchors.fill: parent
Repeater {
id: contentrepeater
model: 4
Rectangle {
color: 'red'
width: 150
height: 170
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 40
cellHeight: 40
clip: true
// here we need a unique instance of the ObjectModel
model: root.newItemModel()
}
}
}
}
}
I'm creating a custom text input in QML. One of its configuration is that it's a field that should only accept digits I did it like this:
import QtQuick 2.6
Item {
property string vmFont: "Mono"
property string vmPlaceHolder: "Some text ..."
property bool vmNumbersOnly: false
// Qt Quick approach to make internal variables.
Item {
id: own
property string enteredText: ""
}
Rectangle {
id: lineEditRect
anchors.fill: parent
color: "#e4f1fd"
radius: 2
}
TextInput {
id: lineEdit
text: vmPlaceHolder
color: "#5499d5"
font.family: vmFont
font.pixelSize: 13
anchors.bottom: parent.bottom
//inputMethodHints: vmNumbersOnly ? Qt.ImhDigitsOnly : Qt.ImhNone
inputMethodHints: Qt.ImhDigitsOnly
verticalAlignment: TextInput.AlignVCenter
leftPadding: 10
width: lineEditRect.width
height: lineEditRect.height
onActiveFocusChanged: {
if (activeFocus){
if (own.enteredText === ""){
// Removing the placeholder
lineEdit.text = "";
}
}
}
onEditingFinished: {
own.enteredText = lineEdit.text;
if (lineEdit.text === ""){
lineEdit.text = vmPlaceHolder
}
}
}
}
However, even though that the inputMethodHits is set to Qt.ImhDigitsOnly, the text input still accepts all kinds of keypresses. What am I doing wrong?
I think that inputMethodHints is for virtual keyboards (e.g. mobile phone software keyboards, Qt Virtual Keyboard, etc.). For restricting input when a physical keyboard is in use (though it can also be used when a virtual keyboard is in use), you can use inputMask and validator. For example, the following code would allow only four digits from 0 to 9 to be entered:
TextInput {
inputMask: "9999"
}
Think of inputMethodHints as affecting what the virtual keyboard displays and how it behaves, and these properties as affecting what the TextInput itself allows as input.
it worked for me in any device:
TextInput {
validator: RegExpValidator{regExp: /[0-9]+/}
}
I am trying to figure out how to properly set focus in my application.
I have a component MyItem.qml, which I want to change its background when any of its children gets focus. I also have a MyDerivedItem.qml that derives from MyItem.qml that also should change the background of the base class if any of its children gets focus.
If I understood the documentation correctly, if a component gets focus the focus property of all its parents in the hierarchy are set to true (or until a FocusScope component is reached).
If this is true, then when I press any of the TextFields in MyItem.qml or MyDerivedItem.qml the myItem.focus property should change to true and the background change its color.
I have tried to make a small example of what I want to do, but it does not behave as I expect.
//main.qml
import QtQuick.Controls 2.0
ApplicationWindow {
height: 768
width: 1024
visible: true
MyDerivedItem {
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width / 2
}
MyDerivedItem {
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width / 2
}
}
//MyItem.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
Rectangle {
id: myItem
default property alias data: column.data
color: focus ? "red" : "green"
Column {
id: column
TextField {
placeholderText: "Input Text Here"
}
}
}
//MyDerivedItem.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
MyItem {
id: myDerivedItem
TextField {
placeholderText: "Derived Input Text Here"
}
TextField {
placeholderText: "Derived Input Text Here"
}
TextField {
placeholderText: "Derived Input Text Here"
}
TextField {
placeholderText: "Derived Input Text Here"
}
//...
}
This is somewhat doumented here. Accoding to that the propagation is: Qt -> QQuickWindow -> Item-with-focus.
There is no traversal of the object tree, but the focusing rather happens directly.
There is one exception to this rule, that is the FocusScope which acts as the focused Item towards the scene or a FocusScope in a higher hirarchy.
So basically you can say, in addition to the object-tree, there is a second focus-tree where each node is a FocusScope and all other Items are leaves.
Each FocusScope-Node might have one child that has focus.
Children of an Item in the object-tree might be siblings to their object-parents in the focus-tree.
The solution to my problem was a minor change. Adding FocusScope to MyItem.qml as follows:
//MyItem.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
FocusScope {
id: focusScope
default property alias data: column.data
Rectangle {
id: myItem
anchors.fill: parent
color: focusScope.focus ? "red" : "green"
Column {
id: column
anchors.fill: parent
TextField {
placeholderText: "Input Text Here"
}
}
}
}
I have rather strange scenario whereby if I launch a subwindow that contains a ListView with a moderately complex delegate and enough items to comfortably exceed the visible area, the entire subwindow will immediately close on launch.
Reducing the complexity of the delegate will allow the window to open, but then rapidly scrolling the ListView will forcibly close it.
This SSCCE triggers the effect on my laptop, but on a more powerful machine it may only do it whilst scrolling (or perhaps the delegate may need to be more complex):
import QtQuick 2.3
import QtQuick.Window 2.0
Window {
width: 300
height: 200
Component.onCompleted: {
win.createObject( null );
}
Component {
id: win
Window {
width: 600
height: 400
visible: true
ListView {
id: view
anchors.fill: parent
model: 100
boundsBehavior: Flickable.StopAtBounds
clip: true
delegate: Rectangle {
width: view.width
height: 24
property int debugLevel: index % 3
property int timestamp: index * 1000
property int message: index
color: "darkgray"
Row {
anchors.fill: parent
Repeater {
id: delegateRepeater
property list< QtObject > roleModel: [
QtObject {
property string label: timestamp
property int itemWidth: 100
},
QtObject {
property string label: debugLevel
property int itemWidth: 100
},
QtObject {
property string label: message
property int itemWidth: view.width - 100 - 100
}
]
model: roleModel
Item {
width: itemWidth
anchors {
top: parent.top
bottom: parent.bottom
}
Text {
anchors {
fill: parent
leftMargin: 4
}
verticalAlignment: Text.AlignVCenter
text: label
elide: Text.ElideRight
}
Rectangle {
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
}
width: 1
visible: index != ( delegateRepeater.count - 1 )
color: "white";
}
}
}
}
}
}
}
}
}
There doesn't seem to be any particular part of the code that is causing the problem, removing any of the objects in the delegate reduces the probability of the subwindow closing.
I've added the debugging tag because my main problem is that this effect produces no debug output. If I add a breakpoint into the subwindow's destruction handler (Component.onDestruction) then there is a single stack entry pointing at the model: roleModel statement - but removing the entire Repeater and replacing with a copy-and-pasted equivalent yields the same results minus the stack entry.
So I would be grateful is anyone knows of a way of getting more information from this pure QML example.
As noted by #BaCaRoZzo the changing of behaviour by modifying the delegate code seems to be an unrelated side-issue.
The real cause is because it turns out you cannot create new root contexts (i.e. top-level windows) from QML. This was hinted at being resolved when Qt Quick Components were released, but the blog post boasting of Window doesn't explicitly state this. Creating a new Window and passing null for the parent technically works but the result seems to be very unstable.
Thankfully in my circumstance I'm creating a QML/C++ application so I've solved the issue by creating new root contexts from Q_INVOKABLE methods on the C++ side. But if you're developing a pure QML application, it seems that you are out of luck.