Qt Quick Controls 2 TextArea `tabChangesFocus`, how to use Tab key to change focus, not type Tab character - qml

QML TextArea from Qt Quick Controls 1.x (http://doc.qt.io/qt-5/qml-qtquick-controls-textarea.html) had a property called tabChangesFocus, which could be set to toggle the behaviour of the Tab key between two possible actions:
true: enter Tab character in the TextArea
false: move the focus to next item in the tab Chain
This property doesn't seem to exist for TextArea in Quick Controls 2.x (https://doc.qt.io/qt-5/qml-qtquick-controls2-textarea.html).
The default is the true behaviour, but I would like the false behaviour (focus change).
Does anyone know a simple way to achieve the same effect for Quick Controls 2?

Another way is to use Item::nextItemInFocusChain(). This way, you don't need to know the next item in focus chain:
import QtQuick 2.9
import QtQuick.Controls 2.2
ApplicationWindow {
id: window
width: 300
height: 300
visible: true
Column {
spacing: 20
TextArea {
id: textArea1
focus: true
text: "TextArea1"
Keys.onTabPressed: nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason)
}
TextArea {
id: textArea2
text: "TextArea2"
objectName: text
Keys.onTabPressed: nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason)
}
}
}

This should probably be made more convenient in the future, but you can setup tab navigation with QML KeyNavigation:
import QtQuick 2.9
import QtQuick.Controls 2.2
ApplicationWindow {
id: window
width: 300
height: 300
visible: true
Column {
spacing: 20
TextArea {
id: textArea1
focus: true
text: "TextArea1"
KeyNavigation.tab: textArea2
KeyNavigation.backtab: textArea2
KeyNavigation.priority: KeyNavigation.BeforeItem
}
TextArea {
id: textArea2
text: "TextArea2"
KeyNavigation.tab: textArea1
KeyNavigation.backtab: textArea1
KeyNavigation.priority: KeyNavigation.BeforeItem
}
}
}

Related

How do I go directly from FullScreen to Maximized in a QML Window?

Below is a small QML application. What I intended was for the application to start full screen, and on the Escape key, change it to maximized:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: topLevelWindow
width: 640
height: 480
visible: true
title: qsTr("Hello World")
visibility: Window.FullScreen
Rectangle {
id: rect
anchors.fill: parent
color: "lightBlue"
focus: true
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
rect.color = "lightGreen"
topLevelWindow.visibility = Window.Maximized
}
}
}
}
What actually happens, though, is that it starts full screen as intended, but pressing Escape makes it windowed but not maximized. Pressing Escape a second time actually maximizes it.
Is there a way to do this without making the user hit Escape twice?
It looks like this is indeed a bug. Seems like it's been lingering for a while, but (some) newer versions may have fixed it?
In the meantime, it's relatively easy to add a hacky workaround:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: topLevelWindow
width: 640
height: 480
visible: true
title: qsTr("Hello World")
visibility: Window.FullScreen
Rectangle {
id: rect
anchors.fill: parent
color: "lightBlue"
focus: true
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
rect.color = "lightGreen"
topLevelWindow.visibility = Window.Windowed
windowHackTimer.start()
}
}
}
Timer {
id: windowHackTimer
interval: 0
repeat: false
onTriggered: {
topLevelWindow.visibility = Window.Maximized
}
}
}
On the button press it becomes windowed, and then after it returns to the event loop (with a 0ms timer), it sets it to maximized. Just setting topLevelWindow.visibility multiple times in Keys.onPressed doesn't get the job done.
I think you should (1) make sure that the Escape key is accepted by your event handler (so that it is not passed down) and (2) schedule the outcome with Qt.callLater.
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
event.accepted = true;
Qt.callLater( greenRectAndMaximize );
}
}
function greenRectAndMaximize() {
rect.color = "lightGreen";
topLevelWindow.visibility = Window.Maximized;
}
The event.accept() is important to ensure that no other UI will handle the same event. That could be the cause of your problem. The Qt.callLater is important because it ensures that your action is added to the end of the windows messaging queue so that the UI/UX for the Escape key (and any other UI/UX events can complete) before we do our action.
Generally, whenever processing events, key presses, button clicks, and so forth, think about what needs to happen now and what needs to be queued for later.
In your case, you have a simple action, but, imagine something more complex such as a button click that triggers a page transition and a sort of 10000 records. The UI/UX is the user clicks the button and we want to see the button animate down and up fully before the action kicks off. If we did it during the event handler, the button may appear to be stuck in the down portion of the animation and the app will appear momentarily hang/crash. So, it is important to understand when we need to queue up actions.
Qt.callLater is one of the easiest ways to separate the UI/UX event and the user action and can improve the perceptual responsiveness of your application.

Why does my custom TabBar lose it's content children in this example?

I am trying to create a TabBar has preview images of the connected layout's children. However after adding several tabs (the exact number depends on the number of elements within the tabs) QML throws an error and the PreviewTabBar loses all its content children.
The following is a minimal working example:
My main.qml:
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
StackLayout {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
}
Timer {
interval: 50; running: true; repeat: true
onTriggered: addTab()
}
function addTab() {
console.log("add Tab")
var component = Qt.createComponent("qrc:/TabContent.qml")
if(component.status !== Component.Ready)
console.log("component not ready")
var item = component.createObject(swipeView)
tabBar.addTab(item)
tabBar.currentIndex = tabBar.contentChildren.length - 1
console.log("current index " + tabBar.currentIndex)
}
header: PreviewTabBar {
id: tabBar
currentIndex: swipeView.currentIndex
}
}
The PreviewTabBar.qml containing previews of the content:
import QtQuick 2.8
import QtQuick.Controls 2.1
TabBar {
signal closeCurrentTab
clip: true
background: Rectangle {
color: "white"
}
function addTab(imageSource) {
var component = Qt.createComponent("PreviewTabButton.qml")
if(component.status !== Component.Ready)
console.log("component not ready")
else {
var item = component.createObject()
item.setSource(imageSource)
addItem(item)
}
}
function closeTab() {
console.log("closeTab")
closeCurrentTab()
}
}
and last but not least the PreviewButton.qml using a ShaderEffectSource to render the preview:
import QtQuick 2.8
import QtQuick.Controls 2.1
TabButton {
height: 80
width: 140
function setSource(source) {
preview.sourceItem = source
}
contentItem: ShaderEffectSource {
id: preview
}
}
This example gets to about 80 tabs on my machine, after that the PreviewTabBar loses all its children (not so the StackLayout). However in the real life example with more complicated tab contents I only get up to around 8 tabs. What could I be doing wrong?
Here is the relevant part of the application output:
qml: current index 99
qml: add Tab
file:///usr/lib/qt/qml/QtQuick/Controls.2/TabButton.qml:65: TypeError: Cannot read property of null
qml: current index 100
qml: add Tab
qml: current index 1
I tried finishing the dynamic component creation in a callback as described here:
http://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html#creating-a-component-dynamically
however that brough no improvement.
Here is a link to the example project:
https://www.file-upload.net/download-12341284/tabtestshader.zip.html
The most probable cause is line 17 in PreviewTabBar.qml which reads:
var item = component.createObject()
As you have no parent set in the createObject()-function the GarbageCollector tends to run wild, and delete your object, even if it is still referenced.
Though not documented that way, you should always pass a parent object, to make sure it survives the GC.
A more stable way would be to generate the Tabs from a model, and add the according model entries in the addTab-functions.
As a little remark on the side: You create a new component everytime you call one of your addTab-functions. Why don't you declare it once like
Component {
id: myComp1
...
}
and create the objects from that?

How Can I Scroll the Images In QML Automatically?

I have a ListView Containing only Images. I assigned the orientation of ListView as horizontal Direction. How can I change, i.e. scroll, the images automatically with some time gap?
Use a Timer. When it is triggered, update the currentIndex of the ListView. This will scroll automatically with default animations. Finally, according to the documentation, positionViewAtIndex is
The correct way to bring an item into view is with positionViewAtIndex
Indeed the method provides a more fine-grained control over the appearance of Items via the PositionMode parameter. See the documentation for further details.
Minimal example:
import QtQuick 2.5
import QtQuick.Window 2.0
Window {
visible: true
width: 200
height: 15
ListView {
id: list
anchors.fill: parent
orientation: ListView.Horizontal
model: 10
delegate: Text {
width: 40
id: name
text: index
}
}
Timer {
interval: 500
repeat: true
running: true
onTriggered: {
//list.currentIndex += 1 // this...
//list.incrementCurrentIndex() // ...or this!
//list.positionViewAtIndex(list.currentIndex, ListView.Center)
}
}
}

Why does adding a MouseArea with anchors.fill: parent cause the rows to stack up on one another?

Given that this ListView works fine:
ListView {
id: myListView
model: myListModel
anchors.fill: parent
delegate: Row {
id: row
spacing: 5
Text {
text: id
width: 25
horizontalAlignment: Text.AlignHCenter
}
Text {
text: description
}
}
}
Why does adding a MouseArea with anchors.fill: parent cause the rows to stack up on one another? How do I get back the automatic vertical spacing that I had before adding MouseArea? I have already tried putting the Row in a Rectangle and also in a Component.
ListView {
id: myListView
model: myListModel
anchors.fill: parent
delegate: Row {
MouseArea {
anchors.fill: parent
onClicked: myListView.currentIndex = index
}
id: row
spacing: 5
Text {
text: id
width: 25
horizontalAlignment: Text.AlignHCenter
}
Text {
text: description
}
}
}
The items stack up for a simple reason: their height is not set. A delegate must always have an height set. Since you did not specify one, delegate height is zero and the enclosing text is rendered over the same y (zero), stacking up.
However, that's not the only problem here. You defined the MouseArea to be anchored. Rows, as well as Columns, force a specific arrangement for items inside themselves. Adding anchors can interfer with this automatic mechanism.
Also docs are clear about this. You can read that...
Row is a type that positions its child items along a single row. It can be used as a convenient way to horizontally position a series of items without using anchors.
...and also that...
[...]since a Row automatically positions its children horizontally, a
child item within a Row should not set its x position or horizontally
anchor itself using the left, right, anchors.horizontalCenter, fill or
centerIn anchors.
Probably, the anchoring error generates an inconsistent state such that Row does not inherit height from the enclosing text, as it did without anchoring items. This in turn results in the zero height and the stacking.
In this particular case, you can include the Row inside an Item and apply the filling MouseArea to the latter. The resulting code, with also the delegate height and width correctly set, would look similar to the following (mind you, I've removed roles and model in your code since the latter was not available in the provided code snippet):
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ApplicationWindow {
visible: true
width: 200
height: 300
ListView {
id: myListView
model: 20
anchors.fill: parent
delegate: Item {
width: myListView.width
height: text1.height // set the height!
Row {
id: row
anchors.fill: parent
spacing: 5
Text {
id: text1
text: "id"
width: 25
horizontalAlignment: Text.AlignHCenter
}
Text {
text: "description"
}
}
MouseArea { // fills the delegate Item, not the Row!
anchors.fill: parent
onClicked: {
myListView.currentIndex = index
console.info("Area clicked! Index: " + index)
}
}
}
}
}

How are implicit dimensions of QtQuick items propagated?

I am trying to implement a component which should by default (if no width was explicitly set) take up as much space as it needs (i.e. depending on its implicitWidth). And if width was set at definition it should shrink its contents to fit in the provided area.
Here's an example:
import QtQuick 2.2
import QtQuick.Window 2.0
Window {
width: 200
height: 100
visible: true
property bool restricted: false
Component {
id: external
FocusScope {
implicitWidth: column.implicitWidth
implicitHeight: column.implicitHeight
focus: true
Column {
id: column
width: parent.width > 0 ? parent.width : undefined
Text {
id: label
width: parent.width > 0 ? parent.width : undefined
elide: Text.ElideRight
font.pixelSize: 24
text: "1234567890"
}
}
Keys.onRightPressed: label.text += label.text
}
}
Loader {
width: restricted ? 100 : undefined
sourceComponent: external
focus: true
Keys.onReturnPressed: restricted = !restricted
}
}
In this sample two modes are controlled by auxiliary bool property, and I want it to support two forms of declaration:
Explicit width. Text should elide.
Loader {
width: 100
sourceComponent: external
focus: true
}
Loader width should be enough to fit the whole text without eliding.
Loader {
sourceComponent: external
focus: true
}
Motivation is that such a component will be defined in a separate file and is being designed to be placed in different parts of UI with both behaviors desired depending on current needs. This sample with inline component declaration is only for demonstration purpose.
UPDATE:
The following trick parent.width > 0 ? parent.width : undefined works, but only for initial setup. If component contents change and implicitWidth is updated (in an unrestricted mode) the width of the component does not change (i.e. Text remains elided).
For example, press right key just right after launching example. You should see that Text has become elided, but its width did not increased
twice regardless the fact that string was duplicated.