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

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

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.

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.

Add background and font colour to a button with material design

I am trying to design a login form with a material design on Qt which should look something like this:
However I can't figure out how to add colour to the button in QML and change the font colour of the button text. This is what I have got so far:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
Item {
property alias login: login
Pane {
id: pane
x: 144
y: 117
width: 353
height: 246
clip: false
font.strikeout: false
background: Rectangle {
color: "#ffffff"
}
ColumnLayout {
id: columnLayout
x: 139
y: -158
anchors.fill: parent
TextField {
id: username
Layout.fillWidth: true
placeholderText: qsTr("Username")
}
TextField {
id: password
Layout.fillWidth: true
placeholderText: qsTr("Password")
}
Button {
id: login
text: qsTr("Login")
spacing: -2
font.capitalization: Font.MixedCase
Layout.fillWidth: true
highlighted: false
// background: Rectangle {
// implicitWidth: 100
// implicitHeight: 40
// color: button.down ? "#d6d6d6" : "#f6f6f6"
// border.color: "#26282a"
// border.width: 1
// radius: 4
// }
}
}
}
}
As you can see (in the commented code) I tried to add colour using Rectangle with the background property but this removes the button features like shadow, highlight, darken on click and so on. Is there a simple way to accomplish this?
For reference here is the output of my code:
In order to theme a Material controls, you have to use the Material attached properties
In your case you want to use Material.background :
import QtQuick.Controls.Material 2.2
// ...
Button {
id: login
text: qsTr("Login")
Layout.fillWidth: true
Material.background: Material.Indigo
Material.foreground: "white"
}
Note that buttons should have upercased text, according to the material guidelines.
If you want to have a design that complies with the Google Materials design guidelines, the easiest way, is to use
QtQuick.Controls.Materials
To use them, it is sufficent to use any of the methods described here to activate them in your application. To try it out, I'd reccomend the command line argument. Just start your application with
-style material
If you want to have it fixed in your code, put it in the main.cpp:
QQuickStyle::setStyle("Material");
Note that the -style options is the very same option defined here for widgets and desktop os styles. Despite this quick styles and widget styles are totally different things and you cannot apply the former to the latter and vice versa. Widget
If now you already use the Material-style, but are not contempt with it and desire to change some of the definitions for selected controls, you can import
import QtQuick.Controls.Materials 2.x
where you need to adapt x to the most recent version installed. 0 is the right one for Qt5.7
Then you can alter specific aspects like
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
ApplicationWindow {
id: mainWindow
width: 800
height: 600
visible: true
Button {
id: login
text: qsTr("LOGIN")
Material.background: Material.Orange // Change the background
}
}
If you don't want to use the Material and only want to change a specific color of the Control you need to understand why it is not that easy to do, without messing it up.
I tried to add colour using Rectangle with the background property but this removes the button features like shadow, highlight, darken on click and so on. Is there a simple way to accomplish this?
You can't just change the color of the background, as there is not the color. There are various colors that are applied for different states. The expression might look like this:
color: (control.down ? 'darkgrey' : 'lightgrey')
So if you change the color to orange like this:
color: 'orange'
you messed up, as now the other state is not considered anymore.
Additionally, of course, you can't change the color of the background like background.color: 'green' from the beginning, as QML does not know about the property background.color. It expects an Item there, which has no color and the Rectangle is only created later. So what you need to do is
Be cautious to not override states
Wait until the property is available
example.qml
Button {
id: login
text: qsTr("LOGIN")
Binding {
target: login
property: "background.color"
value: 'red'
when: !login.pressed // Here comes the state
}
}
You can simply highlight a Button to make the button colorize its background in a style-independent way. The Material style fills the background with the accent color and makes the text light:
Button {
text: qsTr("Login")
highlighted: true
}
A highlighted Button is by far more efficient than a customized button. Customization should be done only if necessary. It is just a visual highlight. There can be multiple highlighted buttons. Highlighting a Button does not affect focus.

QML ListView greys out or removes text of element when adjacent element is selected

So in testing my program, I discovered the strangest thing.
So, I have a ListView element with a custom C++ model, and a fairly simple delegate. Each delegate is a MyButton class, which is simply a Text class(z:2), an Image class(z:1), and a MouseArea class. That Image class is the background, and contains a translucent image which becomes opaque when MouseArea is onPressed().
Now the strange part.
When the ListView has 4 elements, it operates normally -except- when the user selects entry #3, then entry #2 or #1.
When the selected entry goes from #3->#1, the text in entry #2 grays out as opposed to its normal white.
When the selected entry goes from #3->#2, the text in entry #2 completely disappears.
After hours of testing and banging head against desk, I've uncovered a bit more:
The opacity of MyButton or any of its children never changes.
The color of MyButton's text element never changes
The content of MyButton's test element never changes
After offsetting the text partially outside of MyButton, this abnormal behavior only affects the text remaining inside the bounds of MyButton's Image child.
The Z level of MyButton or any of its children never changes, though it appears as if MyButton's Image is being placed on top of its Text.
Another image is never placed on top of a MyButton element. If this was the case, when going from #3->#1 you would see the image of entry #2 become darker.
When the ListView is scrolled, everything returns to normal.
When ListView contains 4 elements, below are the abnormalities:
when #4->#1: #2 and #3 gray out
when #4->#2: #2 disappears
when #4->#3: #3 disappears
when #3->#2: #2 disappears
when #3->#1: #2 grays out
This is consistent to the image and text inside the MyButton class being re-ordered, placing the image a Z level above the text. However the z levels are forced in the MyButton definition, and an onZChanged signal is never created when these events happen.
Below is the relevant code:
//MyButton:
import QtQuick 2.0
Item {
id: button
property string source: ""
property string source_toggled: source
property string button_text_alias: ""
signal pressed
width: button_image.sourceSize.width
height: button_image.sourceSize.height
property bool toggled: false
Image{
id: button_image
z: 1
source: toggled ? parent.source_toggled : parent.source
}
MyText{
z: 2
text_alias: button_text_alias
anchors.centerIn: parent
}
MouseArea {
id: button_mouse
anchors.fill: parent
onPressed: button.pressed()
}
}
//ListView:
Component{
id: p_button
MyButton{
source: picture_path + "bar.png"
source_toggled: picture_path + "bar_selected.png"
toggled: model.isCurrent
onClicked: {
profile_model.setCurrent(model.index)
}
button_text_alias: model.display
}
}
ListView{
id: p_list
width: 623
height: count*74 -1
spacing: 1
interactive: false
model: p_model
delegate: p_button
}
I can't think of -anything- that could cause this behavior.. any ideas?
I was able to solve this error by breaking up my delegate into:
Component{
id: p_button
Item{
property bool toggled: model.isCurrent
width: button_image.sourceSize.width
height: button_image.sourceSize.height
Image{
id: button_image
visible: !toggled
source: picture_path + "bar.png"
}
Image{
visible: toggled
source: picture_path + "bar_selected.png"
}
MouseArea{
anchors.fill: parent
onClicked: p_model.setCurrent(model.index)
}
MyText{
text_alias: model.display
anchors.centerIn: parent
}
}
}
So instead of swapping the source of an object, there are two objects which become visible/invisible based on a boolean value. This prevented the issue, though I still don't know the cause.

Qt QML TextEdit component do no get a slidebar when the text is to large to fit

SDK: Qt Creator 2.4.1
Target: Nokia N9 and Windows 7
If I do the following in a qml file
import QtQuick 1.1
import com.nokia.meego 1.0
Page {
id: myShowChangeLogPage
TextEdit {
id: changeLogArea
anchors.top: titleBackground.bottom
width: parent.width
height: 300
text: "1\n1\n1\n1\n2\n1\n1\n1\n1\n1\n3\n1\n1\n1\n4\n1\n1\n1\n1\n5\n1\n1\n1\n6\n1\n1\n1\n7\n1\n1\n1\n8\n\n\n\n\n9"
font.pixelSize: 20
textFormat: TextEdit.AutoText
readOnly: true
wrapMode: TextEdit.WordWrap
}
}
The TextEdit area do not behave as I expected.
The String will be printed outside the size of the TextEdit area, that is,
it continues beneath the bottom screen edge.
There is no scrollbar/slider to the right
I was expecting that the the TextEdit element should automatically create a
scrollbar/slider if the string is to large to fit within the boundaries.
I have been told that TextEdit should do this and there is no need for a Flicker
or ScrollArea.
I have tried other type of components such as Text and TextEdit and also encapsulate
the TextEdit in a rectangle without any luck.
Regards
I read this right at the beginning of the documentation regarding the TextEdit element:
Note that the TextEdit does not implement scrolling, following the
cursor, or other behaviors specific to a look-and-feel.
There is also a complete example of how to implement scrolling for following the cursor.
TextEdit in qml doesn't implement scrolling. You must to use another one, such as TextArea (https://doc.qt.io/qt-5/qml-qtquick-controls2-textarea.html), then place it inside a ScrollView. Here's an example:
// Creating a scrollable TextEdit
ScrollView {
id: view
anchors.fill: parent
TextArea {
text: "TextArea\n...\n...\n...\n...\n...\n...\n"
}
}

Resources