Add hint to QML TextField with maximum length? - qt

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
}
}

Related

How do you select a character from text in Qt?

I've uploaded a text file and parsed the content into a text object. Eventually I will be passing each character in the string to a rectangle of its own. I am trying to figure out how to pick a character from the string in qml?
For example:
//Assume the text is already parsed from the file and it is stored in the object below
Text {
id:myText
text: "The quick brown fox jumps over the lazy dog"
visible:false
}
// And now i would like to transfer the first letter into a rectangle
Rectangle{
id: firstRect
width:rectText.width
height:rectText.height
color: "yellow"
Text {
id:rectText
text: // How do I set this as the first character 'T'
font.pixelSize: 14
color: "black"
}
}
How can set the rectangle with the character from myText to rectText ?
Eventually I will be setting each character in a rectangle of its own.
It is close to impossible. At least not by using what little font metrics functionality is provided to QML. There is TextMetrics available since Qt 5.4, but for some reason it didn't report the text size accurately, at least for the fonts that I've been using. It may have to do with this issue. I ended up getting accurate results by appending the 字 character to the queried text, and I don't even want to go into detail how I figured that out.
And then, if that happens to work, you only have the text dimensions, but no way to determine the position of that rectangle, since QML text elements can only give you the cursor position, but not the position of any particular character. If you have a single line of text it could be doable - just calculate the width of the preceding text, but for multi line it is a no-go.
You will probably have to take a very different approach. Maybe implement an adapter that presents strings as list models, and you represent every individual character as a QML element in something like a flow view.
But having a discrete visual item for every character will be huge overhead for long text, so if you are going to have such text, you will also have to handle a model proxy that only displays a particular portion of the text.
I can currently think of no other way to get accurate information about the position and size of text character. The API simply doesn't have that functionality.
There is a simple example that is perfectly applicable for short text:
ApplicationWindow {
id: main
width: 640
height: 480
visible: true
property var charlist: textfield.text.split('')
property int currentChar: 0
Column {
TextField {
width: main.width
id: textfield
text: "example text"
}
Flow {
width: main.width
height: 200
Repeater {
model: charlist
delegate: Rectangle {
Text { id: tt; text: modelData; font.pointSize: 20 }
width: tt.width
height: tt.height
color: "red"
border.color: index === currentChar ? "black" : "red"
MouseArea {
anchors.fill: parent
onClicked: {
currentChar = index
var pos = mapToItem(main.contentItem, 0, 0)
info.text = "rectangle x y w h: " + pos.x + ", " + pos.y + ", " + width + ", " + height
}
}
}
}
}
Text {
id: info
}
}
}
You can enter arbitrary text in the text field, which will be represented by a model view that creates an item for every character. Clicking a character will "select" it and will also give you the rectangle values corresponding to its position in the application window.

QML textInput how to handle selection and escape

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()
}
}
}

Can I select text by mouse from a number of delegates in QML?

Lets imaging project for desktop which contains only one QML file:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
width: 500
height: 500
ListModel {
id: myModel
ListElement {
color: "red"
text: "some interesting information"
}
ListElement {
color: "blue"
text: "not so interesting information"
}
ListElement {
color: "green"
text: "and some more information"
}
}
ListView {
anchors.fill: parent
interactive: false
model: myModel
delegate: Rectangle {
width: parent.width
height: 30
color: model.color
TextEdit {
anchors.centerIn: parent
text: model.text
selectByMouse: true
}
}
}
}
With the selectByMouse property of TextEdit set to true I can select text in it. But how can I select text in multiple delegates at the same time? In multiple TextEdits? Is it even possible?
Since the other answers seem incomplete or don't answer what I believe VALOD9 was asking: "can you select text across multiple delegates as though their TextEdits are one element?"
This is not inherently possible, but can be crafted in QML with a lot of manual tracking of mouse presses and movement.
It could be accomplished by placing MouseArea over your ListView and delegates that each contain DropAreas. To track your text selection clicks/drags across your delegates, you could use an invisible MouseArea.drag.target that triggers the delegate DropAreas' onEntered and onPositionChanged events. Based on all this data, you can use TextEdit.positionAt() with your mouse coordinate results to get where your selections start and end, and use TextEdit.select() to programmatically select the text in each delegate. Since you are programmatically selecting text, your TextEdits would need to have selectByMouse: false.
You will need to store any necessary selection data in your model since you shouldn't store state in delegates in case they are removed from the ListView from automatic caching. You would then use this data to recreate the selection when they are re-loaded from cache using Component.OnCompleted. To do selection operations like copy, you could iterate over your model and pick up the saved selection data (especially if you save the selected text to the model using TextEdit.selectedText).
This would allow many TextEdit-based delegates to act as though they are one when selecting text across any of them.
You can set persistentSelection to true and each of your TextEdit will keep the text selected (http://doc.qt.io/qt-5/qml-qtquick-textedit.html#persistentSelection-prop)

Detect text content size in TextField

I'd like to reduce the font size in case user types a long text in a TextField. Is there a way to know how wide the current text is rendered?
As far as I know, it is not possible to do directly. You could, however, cheat by using an invisible item that contains the properties you need:
Text {
id: hiddenText
anchors.fill: tf
text: tf.text
font.pixelSize: tf.font.pixelSize
visible: false
}
TextField {
id: tf
width: 100
height: 60
font.pixelSize: 25
onTextChanged: {
while ((hiddenText.contentWidth > hiddenText.width) || (hiddenText.contentHeight > hiddenText.height)) {
font.pixelSize -= 1
}
}
}
You would have to do the same for scaling up the text, in case the user erases some input. The Text component does not contain borders, so you have to decrease it's width by a few pixels to make it the same size as the TextField as well.
Also, check out TextArea or TextInput. They may fit your need.

QML - Dynamically swap the visibility/opacity between overlapping Text and TextArea

I want to have a widget in QML which has combination of the following behaviors:
1) Multi line edit
2) Auto scroll the content as and when I hit newline. (The content on top of the page keeps going up as and when I enter new content at the bottom)
3) Have a placeholder text functionality.
As far as I know, only Text and TextField are having placeholder text property and only TextArea is having a multi line edit plus auto scroll. But If there is any such widget with all the combinations then, my question “Dynamically swap the visibility/opacity between overlapping Text and TextArea “ would be invalid.
In case there is no such widget (I wonder why), I am thinking to have a rectangle which has both Text and TextArea overlapping and based on the below logic I want to have the visibility/opacity/focus on one of them:
If the Text Area is empty (0 characters), then have the Text in the foreground with focus and with the placeholder text “Enter some text”. But as soon as the user starts typing, the Text should lose the focus, opacity and go to background and the TextArea should gain the focus and come to the foreground and start accepting multi line input. Similarly, when TextArea is in the foreground and is empty (0 characters) and when the user click on any other widget outside my Rectangle, the Text should again gain the focus, come to the foreground and display the placeholder again.
I tried to get inspiration from this code, but failed miserably, it would be helpful if anyone can help me with a few lines of code or give me some pointers on how to solve this.
You can implement placeholderText for TextArea the same way Qt does in TextField. The source can be found here: TextField.qml
When you remove all the comments and properties, you basically have a background and on top of that a MouseArea, the placeholderText Text and a TextInput. Since you need to have the placeholder visually below the TextArea, you must have a transparent background:
PlaceholderTextArea.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
property alias placeholderText: placeholder.text
id: background
width: 640
height: 480
color: "#c0c0c0"
Text {
id: placeholder
anchors.fill: parent
renderType: Text.NativeRendering
opacity: !textArea.text.length && !textArea.activeFocus ? 1 : 0
}
TextArea {
id: textArea
anchors.fill: parent
backgroundVisible: false
}
}
and use your component:
PlaceholderTextArea {
placeholderText: qsTr("Hello World")
anchors.fill: parent
}
Here's an alternative implementation, that works a bit better for me:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Item
{
property alias placeholderText: placeholder.text
property bool __shouldShowPlaceholderText:
!textArea.text.length && !textArea.activeFocus
// This only exists to get at the default TextFieldStyle.placeholderTextColor
// ...maybe there is a better way?
TextField
{
visible: false
style: TextFieldStyle
{
Component.onCompleted: placeholder.textColor = placeholderTextColor
}
}
TextArea
{
id: placeholder
anchors.fill: parent
visible: __shouldShowPlaceholderText
activeFocusOnTab: false
}
TextArea
{
id: textArea
anchors.fill: parent
backgroundVisible: !__shouldShowPlaceholderText
}
}

Resources