Text Selection in QtQuick - qt

I am writing a dialog box for a software plugin for Cura, a 3D printing slicer. When the user slices their file it brings up a dialog box to name the file before uploading it to the 3D printer. A python script generates the name in the format "print name - material-otherinformation.gcode" Right now, when the dialog loads, it highlights the whole text field except for the .gcode extension at the end. I would like it to only highlight a portion of that text field by default, namely the print name part. It is easy for me to return the length of that section as an integer and pass it to the QML file. I am definitely an amateur with QML, but it seems like the select function should be able to handle that but I can't figure out the usage. Any assistance or pointers would be much appreciated!
Here's a simplified version of the code. What I would like to do is add something to this so that just "word 1" is highlighted when the dialog box appears.
import QtQuick 2.1
import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.1
import UM 1.1 as UM
UM.Dialog
{
id: base;
minimumWidth: screenScaleFactor * 400
minimumHeight: screenScaleFactor * 120
Column {
anchors.fill: parent;
TextField {
objectName: "nameField";
id: nameField;
width: parent.width;
text: "word1 - word2 - word3.gcode";
maximumLength: 100;
}
}
}

It's just a matter of when to use the TextInput::select() method. This could be in Component.onCompleted: of either the dialog or the text field, for example:
UM.Dialog
{
...
property int selectionLength: 0
Component.onCompleted: nameField.select(0, selectionLength);
...
TextField {
id: nameField;
text: "word1 - word2 - word3.gcode";
}
...
}
If selectionLength could change after dialog is created, then I'd create a separate function which can be called from different events or even directly:
UM.Dialog
{
...
property int selectionLength: 0
Component.onCompleted: select(selectionLength);
onSelectionLengthChanged: select(selectionLength);
function select(len) { nameField.select(0, len); }
...
TextField {
id: nameField;
text: "word1 - word2 - word3.gcode";
}
...
}
Obviously if the selection isn't going to be from the first character then some adjustment to this strategy will be needed.

Related

Qt5-QML: How to handle change of color and text of a button after click event

I am writing a small application that is working as follows:
1) I launch the application and I select a robot to which I will connect. See print screen below of the small app:
2) That will lead me to another page where I can actually choose the robot to connect to as shown in the print screen below:
3) Finally after selecting the robot the application brings me back to the initial screen that will show me an additional Button showing the chosen robot.
The problem: I have is that after I choose the robot and I am back to the initial screen and I push the button the color of the button should turn into a (for example) green color and changing the text into (for example) Connecting...
The code I am using is the following for which I am only putting the related part:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls.Styles 1.4
Page {
property int dialogId: -1
signal selectDialog()
ColumnLayout {
anchors.fill: parent
spacing: 5
Button {
id: button1
text: "Select Robot"
onClicked: selectDialog()
Layout.fillWidth: true
font.pointSize: 20
}
Button {
id: dialogA
text: "FreddieMercury: Connect";
visible: dialogId === 1
Layout.fillWidth: true
font.pointSize: 20
function buttonClick()
{
console.log("Button "+ dialogA.text +" is clicked!")
}
Rectangle {
id: button
color: "red"
width: 96; height: 24; anchors.centerIn: parent
MouseArea {
id: region
anchors.fill: parent;
onClicked: console.log("clicked()")
onPressed: dialogA.color = "green"
onReleased: dialogA.color = "red"
}
Text {
id: st_text
anchors.centerIn: parent
text: "Connecting..."
font.bold: true
font.pointSize: 20
color: "green"
}
}
}
// Other Buttons
}
}
What I tried so far
I went through this source and also this post which I followed. As you can see from the point 3) I am close to the good functioning but there is clearly something I am not doing right.
Also this was useful and in fact I used the MouseArea option exactly from that post.
However I still don't see the whole color extended into the button.
Finally the text changed after the click event happened I included it in the Button as shown and thought that the property text: "Connecting..." was enough to overwrite the existing text but without success.
Please advise on what I am missing that is keeping me from a full working example.
I think the base issue is that you're trying to use examples for QtQuick Controls 1 with QtQuick Controls 2. They're completely different animals and you cannot style the v2 controls using QtQuick.Controls.Styles.
For customizing Controls 2 styles, like Button, see here. I also find it useful to look at the source code for the included controls (they're in your Qt library install folder inside /qml/QtQuick/Controls2/ directory). Though personally I find needing to re-create a whole new Button (or whatever) just to change a color or font is a bit much, especially if I want it to work across all the included QtQuick Controls2 Styles.
An alternative is to "hack" the properties of the built-in Control styles. This certainly has some drawbacks like if you want to be able to reset the control style back to default bindings, you'd have to save the original bindings and re-create them to reset the style. OTOH it beats creating customized controls for each style. YMMV.
Anyway here's an example of what i think you're looking for. This is based on our previous exercise with the buttons. :) Specifically, I just modified the Page1.qml code and the other 2 files are exactly the same as before. In this page I added buttonClick() handler and the Button::onClicked calls to trigger it from each button (and the button texts of course :).
Page1.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12 // for IconLabel
import QtQuick.Layouts 1.12
Page {
property int dialogId: -1;
signal selectDialog()
function buttonClick(button)
{
button.text = qsTr("Connecting to %1...").arg(button.text);
button.enabled = false; // prevent repeat clicks
// If Button has a background Rectangle object then we can set properties on it.
// note: `instanceof` was added in Qt 5.10
if (button.background && button.background instanceof Rectangle) {
button.background.color = "red"; // override style color
button.background.gradient = null; // some styles use a gradient
button.background.visible = true; // some styles may hide it in some situations
}
// Similar with the label element, IconLabel is used by all included QML styles.
if (button.contentItem && button.contentItem instanceof IconLabel) {
button.contentItem.color = "blue"; // override style color
button.contentItem.font.bold = true;
button.contentItem.font.pointSize = 20;
}
}
ColumnLayout {
anchors.fill: parent
spacing: 5
Button {
id: button1
text: "Select"
onClicked: selectDialog()
Layout.fillWidth: true
}
// These buttons should appear only after the user selects the choices on `Page2`
Button {
id: dialogA
text: "Freddie Mercury"
visible: dialogId === 1
Layout.fillWidth: true
onClicked: buttonClick(this)
}
Button {
id: dialogB
text: "David Gilmour"
visible: dialogId === 2
Layout.fillWidth: true
onClicked: buttonClick(this)
}
Button {
id: dialogC
text: "Mick Jagger"
visible: dialogId === 3
Layout.fillWidth: true
onClicked: buttonClick(this)
}
}
}
If you had a customized Button (like in the Qt docs example) then you could still do basically the same thing in buttonClick() but probably w/out worrying about the if (button.background ...) stuff (since you'd be sure your button has valid background/contentItem Items).
A better implementation of a "default" (Style-specific) Button but with custom colors/text properties would involve a subclass which uses Binding and/or Connections QML elements to control the properties and be able to reset them back to the current QtQuick Style defaults.

Can't set text field to fill width in QML

I am following this tutorial on YouTube and the person sets the TextField to fill the width of the RowLayout. However, it doesn't seem to work for me. I tried using Layout.fillWidth on the CheckBox and it seems to work perfectly fine but it doesn't seem to want to work on the TextField. Here is my code:
main.qml:
import QtQuick 2.9
import QtQuick.Controls 2.2
ApplicationWindow
{
visible: true;
width: 640;
height: 480;
title: qsTr("Tabs");
ToDoList
{
anchors.centerIn: parent;
}
}
ToDoList.qml:
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
Frame
{
ListView
{
// Using implicit width and height allows the frame to automatically scale to the size of the list view
implicitWidth: 250
implicitHeight: 250
clip: true
model: 100
delegate: RowLayout {
width: parent.width
CheckBox {}
TextField
{
Layout.fillWidth: true
}
}
}
}
Here is a screenshot of what mine looks like
What did I do wrong?
I don't know if this has anything to do with it but I made a "Qt Quick Application - Swipe" instead of "Qt Quick Controls 2 Application" as that option wasn't available to me. Thanks in advance for any help.
Edit: I have written step by step instructions to replicate the issue below.
File > New File or Project
From the new window make sure "Application" is selected then click "Qt Quick Application - Swipe" and press "Choose"
Set any name for the project and click "Next"
Set the build system to "qmake" and click "Next"
Set the minimal required Qt version to "Qt 5.9" and the Qt quick controls style to "Material Dark" and click "Next"
Select the "Desktop Qt 5.12.0 MSVC2017 64bit" as the kit and click "Next"
Set the options to have no version control and click "Finish"
Delete "Page1Form.ui.qml" and "Page2Form.ui.qml" from the "Projects" pane
Replace the contents of "main.qml" with:
import QtQuick 2.9
import QtQuick.Controls 2.2
ApplicationWindow
{
visible: true;
width: 640;
height: 480;
title: qsTr("Tabs");
ToDoList
{
anchors.centerIn: parent;
}
}
Right click on the root project file and click "Add New"
From the new window make sure "Qt" is selected then click "QML File (Qt Quick 2)" and press "Choose"
Name the file "ToDoList" and click "Next"
Add to project "qml.qrc Prefix: /" then set the options to have no version control and click "Finish"
Replace the contents of "ToDoList.qml" with:
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
Frame
{
ListView
{
// Using implicit width and height allows the frame to automatically scale to the size of the list view
implicitWidth: 250
implicitHeight: 250
clip: true
model: 100
delegate: RowLayout {
width: parent.width
CheckBox {}
TextField
{
Layout.fillWidth: true
}
}
}
}
Run the project
The width is set properly. The problem is with TextField style. You may check it by setting background like
TextField
{
Layout.fillWidth: true
background: Rectangle {
color: "red"
}
}
Or just start typing into those fields with and without Layout.fillWidth: true

Property contentItem in Qml

The following Qml code gives the following output (expected):
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.11
Window {
height: 200
width: 200
visible: true
Button {
id: root
text: "Text"
anchors.centerIn: parent
Item {
anchors.fill: parent
Text {
text: "Item.Text"
color: "red"
}
}
}
}
The following code (using contentItem) produces a different output:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.11
Window {
height: 200
width: 200
visible: true
Button {
id: root
text: "Text"
anchors.centerIn: parent
contentItem: Item {
anchors.fill: parent
Text {
text: "Item.Text"
color: "red"
}
}
}
}
The Qt documentation is not very clear, at least for me. The question is what does the property contentItem do? When it should be used?
Short answer: The contentItem is meant for customizing the control and replaces the existing implementation of the visual foreground element by your text.
Long answer:
A Quick Item has a so called "default property" - the data property. By definition, if you add an item as child of another item, it is instead assigned to the default property. Which means the following example:
Item {
Text { text: "test1"}
}
Is actually identical to:
Item {
data: [
Text { text: "test2"}
]
}
If you know look at your example, in the first variant, you simply append a child item to the root button. Since no further information is given, it is placed at the coordinates (0,0) within it's parent.
The contentItem property however is defined in the documentation as follows:
This property holds the visual content item.
In case of a Button it is an internally used Label to display the text property of the button. It exists to modify the appereance of the button.
In your second example, you "customize" the button by replacing the internal label with your custom Text - but without any code to properly position or fill the item. The correct way to declare a content item can be found in the customization guide:
Button {
id: control
text: qsTr("Button")
contentItem: Text {
text: control.text
font: control.font
opacity: enabled ? 1.0 : 0.3
color: control.down ? "#17a81a" : "#21be2b"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
// ...
}
You could either define it as part of a custom style, or create a MyButton.qml where do exactly this and can then use MyButton in other QML files, giving you a custom styled button whilest keeping the API intact (like beeing able to set the text via the text property etc.)
I hope this was sufficient enough to help you understand how it works.

Using variable as text in Text element immediately activates onTextChanged

If I have the following code:
import QtQuick 2.10
import QtQuick.Window 2.10
Window {
id: app
visible: true
width: 640
height: 480
property bool txt: false
Text {
text: app.txt
onTextChanged: { console.debug("Text changed") }
}
}
I get “Text changed” displayed in my console as soon as the app loads, however if I set the text manually to something like
Text {
text: "Some text"
onTextChanged: { console.debug("Text changed") }
}
I don’t get the “Text changed” display in my console unless I actually have something that changes the text after the app loads.
Is this normal behaviour? Is there a way to use the variable as the text but not have onTextChanged activate as soon as the app loads?
Yes, it's normal behaviour for qml, becuase your first text property is "" (nothing), and when you give it a variety it changes from "" to app.txt. If you set manually text, in this case, there is nothing to change.

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