QML textInput how to handle selection and escape - qt

I am building a QML application and I have a TextInput component as follows:
TextInput {
id: editableTextInput
text: styleData.value
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
selectionColor: 'darkgray'
selectedTextColor: 'white'
font.pixelSize: 14
font.family: "AvantGarde-Medium"
onEditingFinished: {
// TO DO: Handle new text
}
MouseArea {
anchors.fill: parent
onClicked: {
editableTextInput.selectAll()
}
}
}
At the moment, the way it stands is when the user clicks on the text, it selects the whole text and then I can start typing and the whole text will be replaced but I want the user to give a bit more finer control. For one, initially the user selects the full text and then if they click again, it should basically put the cursor at the current position. Also, the escape key should basically restore the old text and cancel the whole operation.
These are standard ways for text input. I was wondering if I have to program all of this explicitly or is there a way to get this behaviour with the TextInput control.

Basically, you don't need to use MouseArea for this. Hook activeFocus to decide when to select the text (on initial click, activeFocus will become true), store the old text, and restore it when editing is finished if escape was pressed.
I think this gets you a good part of the way to what you want:
import QtQuick 2.6
TextInput {
text: "Hello world!" + index
font.pixelSize: 24
width: 300
height: 30
// Store the previous text for restoring it if we cancel
property string oldText
// Lets us know that the user is cancelling the save
property bool cancelling
Keys.onEscapePressed: {
// Cancel the save, and deselect the text input
cancelling = true
focus = false
}
onEditingFinished: {
if (cancelling) {
// When cancelling, restore the old text, and clear state.
text = oldText
oldText = ""
cancelling = false
} else {
// TO DO: Handle new text
}
}
onActiveFocusChanged: {
// When we first gain focus, save the old text and select everything for clearing.
if (activeFocus) {
oldText = text
selectAll()
}
}
}

Related

Add hint to QML TextField with maximum length?

I have a QML TextField where the user can input a limited amount of characters. Is there a way to add a simple hint like so? It can be inside the input too (as uneditable text, of course), or next to it.
ColumnLayout {
Label {
text: "Short description"
}
TextField {
Layout.fillWidth: true
maximumLength: 30
}
}

How to programmatically append text to a QML TextArea?

I am trying to pass log data to my QML front end, one line at a time, and have it append to the end of a TextArea. I've considered several approaches. The following is the most promising. I have created a QAbstractListModel (in Python) and pass this model into a repeater where it arrives as a single item (rowCount =1) which I append to the TextArea using the line
text: terminal_text.text + display
This works but I get this warning everytime the text is updated.
file://.../TextArea.qml:728:9: QML QQuickTextEdit*: Binding loop detected for property "text"
See below for the code of the repeater.
Repeater {
model: TerminalFeed { }
delegate: TextArea {
id: terminal_text
font.family: "Courier"
width: parent.width
height: parent.height
readOnly: true
selectByMouse: true
wrapMode: TextEdit.NoWrap
horizontalScrollBarPolicy: Qt.ScrollBarAsNeeded
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
text: terminal_text.text + display
}
}
How can I stop this happening? Alternatively does anyone have a better way of achieving the same result?
Technically, that is indeed a binding loop because text is dependent on its own value. If QML didn't detect it and break it, an infinite loop of updating would result.
Instead of using a binding, you can do something like this:
Repeater {
model: TerminalFeed { }
delegate: TextArea {
id: terminal_text
font.family: "Courier"
width: parent.width
height: parent.height
readOnly: true
selectByMouse: true
wrapMode: TextEdit.NoWrap
horizontalScrollBarPolicy: Qt.ScrollBarAsNeeded
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
onDisplayChanged: {
text = text + display;
}
}
}
With the original binding approach, it will try and update whenever either display or text changes. With this approach, it will only try and update whenever display changes – which is what you really want.
I had a similar problem where I wanted to show logged data in a QML window.
I use the insert() method, which is inherited from QML TextField. The insertion position is the length of the TextArea.
TextArea {
id: outputTextArea
}
Component.onCompleted: {
data = "dummyString"
outputTextArea.insert(outputTextArea.length, data)
}

QML several items with active focus / keyboard shortcut control

I'm trying to implement a keyboard shortcut control for my qml application. I know there's the possibility to do that with an Action element, but I don't want menus and toolbars which are then mandatory to use.
That's why I'm approaching this topic with keyboard events. For this, I need to have the element performing the action to be in focus. But my goal is a global shortcut control, so theoratically I'd need to have all the elements in question in focus.
I found the FocusScope type in the documentation, but I'm not sure if this is what I need.
Does it mean that the focus of nested FocusScopes 'slides' through to the last element that's not a FocusScope and acquiring focus manually with focus: true thus only this last element holding focus? Or do all the elements on the way down the slide that acquire focus have the activeFocus property set?
Is this the right approach or would I need something else?
Focus in Qt Quick is a mess in my opinion. It always confuses me and I end up hacking around it with forceActiveFocus(). I'd recommend the new Shortcut type:
Shortcut {
sequence: StandardKey.Quit
context: Qt.ApplicationShortcut
onActivated: Qt.quit()
}
With the context property, you can choose whether you want the shortcut to apply to the current window or the entire application.
The motivation for this type can be seen in the comments of patch set 5:
Shortcut aims to supersede Action. I want to kill the latter in the future because...
compare the actual user code: http://cutebin.fi/prwznhkbo
look at the amount of "action" related expressions all around BasicButton.qml
IMHO the whole concept doesn't quite fit mobile/embedded or QML
Action was a frequently requested feature. Now that they have it, the frequent questions are "how to use a different icon/text" or "how to know the source that triggered an action". Both are contradicting the sole purpose of Action, and neither "problem" would exist if they just wrote simpler QML code in the first place, as illustrated by the example snippet. :)
Evidently the most usable part of Action is the shortcut feature. Those who need shortcuts are not happy that they need to use Action, because "what's up with all this other stuff, I just want a shortcut".
Maybe there are different ways of achieving this, but the way I know is the following one.
The idea is to have an Item which controls the key events you need to handle.
I'll explain myself with an example. As you will see, if we have input widgets (i.e. TextInput) we have to implement a mechanism to return the input to our Item in order to process again the keyboard events. In this example, the Qt.Key_Escape key will be used to set the focus back.
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
id: mainwindow
title: qsTr("Hello")
width: 640
height: 480
visible: true
Item {
anchors.fill: parent
focus: true
Keys.onPressed: {
if ( (event.key === Qt.Key_Q) && (event.modifiers & Qt.ShiftModifier) ) {
rect.blue()
} else if ( (event.key === Qt.Key_W) && (event.modifiers & Qt.AltModifier) ) {
rect.red()
} else if ( (event.key === Qt.Key_E) && (event.modifiers & Qt.AltModifier) ) {
text.text = 'Key Alt+E was pressed'
}
}
Rectangle{
id: rect
width: 100
height: 100
color: "black"
function blue() {color = "blue"}
function red() {color = "red"}
}
Text {
id: text
anchors.centerIn: parent
font.pointSize: 20
}
TextInput {
id: textinput
anchors.top: text.bottom
text: "sample text"
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
console.log('Key Escape was pressed');
parent.focus = true;
}
}
}
}
}
Edit #1: #Mitch suggested to use the Shortcut QML Type. If you can use it (it's available since Qt 5.5), the code will be slightly different. Anyway, you need also to set the focus to the main app in some cases depending on the shortcut sequences implemented. For example, if we're typing text, Shift+Q doesn't have effect in this example. We need to press Escape first.
import QtQuick 2.5
import QtQuick.Controls 1.3
ApplicationWindow {
id: mainwindow
title: qsTr("Hello")
width: 640
height: 480
visible: true
Shortcut {
sequence: "Shift+Q"
onActivated: rect.blue()
context: Qt.ApplicationShortcut
}
Shortcut {
sequence: "Alt+W"
onActivated: rect.red()
context: Qt.ApplicationShortcut
}
Shortcut {
sequence: "Alt+E"
onActivated: text.text = 'Key Alt+E was pressed'
context: Qt.ApplicationShortcut
}
Item {
anchors.fill: parent
Rectangle{
id: rect
width: 100
height: 100
color: "black"
function blue() {color = "blue"}
function red() {color = "red"}
}
Text {
id: text
anchors.centerIn: parent
font.pointSize: 20
}
TextInput {
id: textinput
anchors.top: text.bottom
text: "sample text"
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
console.log('Key Escape was pressed');
parent.focus = true;
}
}
}
}
}
Much like Mitch, I found focus to be a mess in QML, much like many other aspects of it.
I ended up implementing my own "active focus / selection" scheme. Basically I keep a list of item pointers as my "active selection", I have the keyboard focus fixed at a single item acting as an event dispatcher, and it redirects keyboard events to all items in the active selection list. I still use QML's MouseArea to manage the selected items.

QML ListView method positionViewAtEnd() does exactly the opposite

I'm going crazy. I have a ListView inside a ScrollView, hooked up to a model that inherits QAbstractListModel. When objects are added to the model, the ListView shows them using a delegate. So far, so good.
But I really want the view to stay scrolled to the bottom (like a chat window), and I'm having a very difficult time making that happen. Here is the relevant QML code:
Rectangle {
ScrollView {
[anchor stuff]
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
}
}
TextField {
id: messageEditor
[anchor stuff]
onAccepted: {
controller.sendTextMessage(text)
text = ""
/* This works. */
//messageList.positionViewAtEnd();
}
}
Component {
id: messageDelegate
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
color: "white"
height: nameText.height + 4
Text {
id: nameText
wrapMode: Text.Wrap
text: "<b>" + authorName + " (" + authorId + ")</b> " + message
[anchor stuff]
}
ListView.onAdd: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
}
}
}
The really strange thing, is that messageList.positionViewAtEnd() (at the end of the file) actually jumps it to the beginning. Without the call, the view stays where it is, even as new entries appear in the list. And indeed, if you look at the Qt documentation for the ListView.positionViewAtEnd(), it says:
Positions the view at the beginning or end, taking into account ...
Is that a silly error in the documentation, or what? I've tried everything I can think of to make this work, particularly the positionViewAtIndex() method and using highlighters to force the scroll to happen. But nothing works. Note the /* This works. */ comment in the source code above. When that is enabled, it works totally fine! (except of course, it jumps to the ListView.count()-2 index, instead of the end of the list)
Does anyone have any idea what might be wrong here? Any examples I could try to prove that there's a terrible, terrible bug in QML?
I'm using Qt 5.3.1 with QtQuick 2.0 (or 2.1 or 2.2 fail too). I've tried many, many other configurations and code as well, so please ask if you need more info. I've completely exhausted my google-fu.
Thanks!
Edit 1
While the accepted answer does solve the above problem, it involves adding the Component.onCompleted to the delegate. This seems to cause problems when you scroll the list, because (I believe) the delegates are added to the view when you scroll up, causing the onCompleted trigger to be called even if the model item isn't new. This is highly undesirable. In fact, the application is freezing when I try to scroll up and then add new elements to the list.
It seems like I need a model.onAdd() signal instead of using the existence of a delegate instance to trigger the scroll. Any ideas?
Edit 2
And how does this NOT work?
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
onCountChanged: {
console.log("This prints properly.")
messageList.positionViewAtEnd()
}
}
The text "This prints properly" prints, so why doesn't it position? In fact, it appears to reset the position to the top. So I tried positionViewAtBeginning(), but that did the same thing.
I'm totally stumped. It feels like a bug.
You need to set the currentIndex as well.
testme.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 300
height: 240
ScrollView {
anchors.fill: parent
ListView {
anchors.fill: parent
id: messageList
model: messageModel
delegate: Text { text: mytextrole }
highlight: Rectangle { color: "red" }
highlightMoveDuration: 0
onCountChanged: {
var newIndex = count - 1 // last index
positionViewAtEnd()
currentIndex = newIndex
}
}
}
ListModel {
id: messageModel
ListElement { mytextrole: "Dog"; }
ListElement { mytextrole: "Cat"; }
}
Timer {
property int counter: 0
running: true
interval: 500
repeat: true
onTriggered: {
messageModel.append({"mytextrole": "Line" + (counter++)})
}
}
}
There is still some jumping to the first element and jumping back down for a fraction of a second.
There is a note in documentation:
Note: methods should only be called after the Component has completed. To position the view at startup, this method should be called by Component.onCompleted.
Change your ListView.onAdd: to
Component.onCompleted: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
And it works well.
In your case, the ListView emits add signal before the new delegate is created and completed. The ListView is still working on something behind the scene, so positionViewAtEnd cannot work as expected. And /* This works. */ because it is called after the new delegate is completed. However, don't assume this always works. Simply follow the note, call positionViewAtEnd in Component.onCompleted, in documentation.

QML signal executed twice

I'm new in QML and QML signals and I'm having this silly problem that I'm not being able to resolve by myself. I'm triggering an onTouch signal and is executing twice, generating a double response that crashes my app.
Here's my QML code:
//LabelKey.qml
import bb.cascades 1.0
Container {
property string labelText: "#"
property real width: 153.3
property real height: 102.5
property int labelPosX: 60
property int labelPosY: 25
property int labelTextFontWidth: 45
property string imgSrc: "asset:///images/keyboard_button.png"
layout: AbsoluteLayout {
}
preferredWidth: width
preferredHeight: height
objectName: "contTecla"
id: contTecla
ImageView {
objectName: "imgTecla"
id: imgTecla1
imageSource: imgSrc
preferredWidth: width
preferredHeight: height
onTouch: {
textFieldKey.text = textFieldKey.text + labelTecla.text;
}
}
Label {
objectName: "labelTecla"
id: labelTecla
text: labelText
textStyle {
color: Color.DarkYellow
size: labelTextFontWidth
}
layoutProperties: AbsoluteLayoutProperties {
positionX: labelPosX
positionY: labelPosY
}
}
}
I have this TextField whose id is textFieldKey in another QML where I'm including the one I post above. The main idea is simple, is a keyboard where each key is a component of the code above and it has to print the value of the key pressed in this TextField.
The problem is, as I said, the signals is being called twice, filling the TextField with two chars of the value each time.
Please help me I don't know if maybe I'm missing something in the proper way of using signals or something like that.
Thanks!
I figure it out. The touch signals has 4 differents states:
Down: Occurs when the user touches the screen.
Move: Occurs when the user moves a finger on the screen.
Up: Occurs when the user releases a finger.
Cancel: Occurs when an interaction is canceled.
Each one identify with a number from 0 to 3.
And when a touch signal is triggered two states are involved, Down and Up. You just need to make sure with wich one you want to work with and catch it inside the onTouch signal:
if (event.touchType == numberOfTheTouchState){
}
You want to use
ImageView
{
objectName: "imgTecla"
id: imgTecla1
imageSource: imgSrc
preferredWidth: width
preferredHeight: height
onTouch:
{
if(event.isDown())
{
textFieldKey.text = textFieldKey.text + labelTecla.text;
}
}
}
As was noted, without this you get both the up and down events

Resources