Creating a custom QML Button with native look and feel - qt

I would like to create a custom Button by defining my own QML type. This Button type should contain two text fields, one with a single character of a symbol font and the other one the actual button text.
That's simple, but how could I use the native colors, gradients, fonts and borders defined for the target system?
Is it possible to extend Button itself? And how would I disable the possibility to set an image when extending Button?
import QtQuick 2.5
Rectangle {
id:anyButton
property string image:"\ue43f"
property string text:"Button"
border.color : "black"
border.width: 1
radius: 5
Gradient {
id: lightGradient
GradientStop { position: 0.0; color: anyButton.color }
GradientStop { position: 1.0; color: Qt.darker(anyButton.color,1.5) }
}
Gradient {
id: darkGradient
GradientStop { position: 0.0; color: Qt.darker(anyButton.color,1.7) }
GradientStop { position: 1.0; color: Qt.darker(anyButton.color,1.7) }
}
Rectangle{
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
Text{
id:buttonImage
}
Text{
id: buttonLabel
font.pixelSize:20
text: anyButton.text
}
}
signal buttonClick()
MouseArea{
id: buttonMouseArea
anchors.fill: parent
onClicked: parent.buttonClick()
hoverEnabled: true
onEntered:{
parent.border.width= 2
}
onCanceled:{
parent.border.width= 1
}
onExited: {
parent.border.width= 1
}
}
gradient: buttonMouseArea.pressed ? darkGradient : lightGradient
}

It's quite simple in QML to create a button with custom style, text etc.
With ButtonStyle you can define custom background and label as you want. To get system colors use SystemPalette. Here you can find its application for real controls.
For example:
Button {
anchors.centerIn: parent
property string firstfield: "a"
property string secondfield: "sometext"
iconSource: "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
text: firstfield + " " + secondfield
style: ButtonStyle {
background: Rectangle {
id: bg
border.width: 1
border.color: palette.mid
radius: 3
gradient: Gradient {
GradientStop { position: 0.0; color: control.pressed ? palette.button : palette.light }
GradientStop { position: 0.5; color: palette.midlight }
GradientStop { position: 1.0; color: control.pressed ? palette.light : palette.button }
}
}
label: RowLayout {
id: row
spacing: 5
Image { source: control.iconSource }
Label {text: control.firstfield; font.family: "Symbol"; font.pixelSize: 18; color: palette.buttonText}
Label {text: control.secondfield; color: palette.buttonText}
}
}
}
SystemPalette { id: palette; colorGroup: SystemPalette.Active }
Sure, you can add a shadow etc. If you will drop the ButtonStyle it should look like a regular button

Related

How to handle press state from inside the button if contentItem is defined from the outside?

I have a custom button that shows a white overlay over the content and background area when pressed. The user of the button can specify how to align the content (label + text) by defining the contentItem.
The current implementation works, but I would like to control the press state inside the button. How can the button display the white overlay on top of the contentItem from within the button?
I prefer not to add a bunch of properties to the button to define the layout of the text/icon. Is there another way to accomplish this?
Working code so far:
ButtonOverlay {
id: btnOverlay
width: 100
height: 50
state: ""
contentItem: Rectangle {
color: "red"
Label {
text: "button"
}
Rectangle {
id: rectId
anchors.fill: parent
color: "white"
opacity: 0.5
visible: btnOverlay.state === "pressed"
}
}
onPressed: {
btnOverlay.state = "pressed"
}
onReleased: {
btnOverlay.state = ""
}
}
ButtonOverlay.qml
Button {
id: root
background: Rectangle {
radius: 10
color: "gray"
Rectangle {
id: overlay
anchors.fill: parent
radius: parent.radius
visible: root.state === "pressed"
color: "white"
opacity: 0.5
}
}
}
I've removed the overlay in the contentItem and moved the overlay from the background into the Button. Furthermore I've used the z property to change the drawing order.
component ButtonOverlay: Button {
id: btn
background: Rectangle {
radius: 10
color: "gray"
}
Rectangle {
id: overlay
anchors.fill: parent
radius: btn.background.radius
color: "white"
opacity: 0.5
visible: btn.pressed
z: 1
}
}
ButtonOverlay {
width: 100
height: 50
contentItem: Rectangle {
color: "red"
Label { text: "button" }
}
}
Perhaps you could use Frame for your ButtonOverlay. Frame works by automatically sizing to your inner contents but allows you to easily replace the borders via the background property. To use it, you will have to move the entire Button out:
import QtQuick
import QtQuick.Controls
Page {
ButtonOverlay {
id: btnOverlay
pressed: btn.pressed
Button {
id: btn
width: 100
height: 50
implicitWidth: width
implicitHeight: height
contentItem: Rectangle {
color: "red"
Label {
text: "button"
}
Rectangle {
id: rectId
anchors.fill: parent
color: "white"
opacity: 0.5
visible: btnOverlay.pressed
}
}
}
}
}
// ButtonOverlay.qml
import QtQuick
import QtQuick.Controls
Frame {
id: root
property bool pressed: false
background: Rectangle {
radius: 10
color: "gray"
Rectangle {
id: overlay
anchors.fill: parent
radius: parent.radius
visible: pressed
color: "white"
opacity: 0.5
}
}
}
You can Try it Online!

Invalid property "style" for Button

Why am I getting this error: invalid property "style" every time I use the style property?
Button {
text: "A button"
style: ButtonStyle {
background: Rectangle {
implicitWidth: 100
implicitHeight: 25
border.width: control.activeFocus ? 2 : 1
border.color: "#888"
radius: 4
gradient: Gradient {
GradientStop { position: 0 ; color: control.pressed ? "#ccc" : "#eee" }
GradientStop { position: 1 ; color: control.pressed ? "#aaa" : "#ccc" }
}
}
}
}
Assuming you are using QtQuick.Controls.Button, you can use the following:
Button {
id: control
text: "A button"
background: Rectangle {
implicitWidth: 100
implicitHeight: 25
border.width: control.activeFocus ? 2 : 1
border.color: "#888"
radius: 4
gradient: Gradient {
GradientStop { position: 0 ; color: control.pressed ? "#ccc" : "#eee" }
GradientStop { position: 1 ; color: control.pressed ? "#aaa" : "#ccc" }
}
}
}
If you are using this definition repeatedly, you can put it (with text: "A button") in a separate file, which you can call MyButton.qml, then you can use MyButton { text: "A Button" }
Gray is the default color of a button in qml. If you would like to style it this is an option I use frequently. Create a rectangle child component of button, set anchors.fill, and then set a color on the rectangle. This will be the color of your button now.
You can use Rectangle, Text and MouseArea Components to create button. By this way, there is no need style. You can do it like,
Rectangle{
implicitWidth: 100
implicitHeight: 25
border.width: control.activeFocus ? 2 : 1
border.color: "#888"
radius: 4
gradient: Gradient {
GradientStop { position: 0 ; color: control.pressed ? "#ccc" : "#eee" }
GradientStop { position: 1 ; color: control.pressed ? "#aaa" : "#ccc" }
}
Text{
anchors.centerIn: parent
text: qsTr("A button")
}
MouseArea{
id: control
anchors.fill: parent
onClicked: {
//Your Button Function
}
}
}

How to activate the keyboard in qml (qt)

I have a Button object defined in Button.qml file, and another qml file Page.qml uses the Button object defined in Button.qml. How do I activate the keyboard input so that I can use keyboard to navigate the buttons in Page.qml. for example enter key, space key and etc.
I've tried using "focus: true", "Keys.onPressed:{}" and "onVisibleChanged: if (visible) sendBbkButton.forceActiveFocus()" both in Button.qml and Page.qml. But it does not activate the focus and keyboard for the two buttons in Page.qml.
Thanks!
Page.qml:
Rectangle {
id: bp
// some code
Button {
id: aButton
text: qsTr("Abort test1")
tooltipText: qsTr("test1")
onClicked: {
// some coding
}
anchors { right: parent.left; bottom: parent.bottom; margins: 10 }
height: contentHeight + 20
width: contentWidth + 40
}
Button {
id: bButton
text: qsTr("Abort test2")
tooltipText: qsTr("test2")
onClicked: {
// some coding
}
anchors { right: aButton.left; bottom: parent.bottom; margins: 10 }
height: contentHeight + 20
width: contentWidth + 40
}
// some code
}
Button.qml:
BorderImage {
id: button
property alias text: label.text
property alias contentWidth: label.contentWidth
property alias contentHeight: label.contentHeight
property alias buttonColor: shade.color
property string tooltipText: ""
signal clicked()
source: Style.buttonBorderImage
border { left: 10; top: 15; right: 10; bottom: 10 }
Highlight { }
width: Math.max( sourceSize.width, label.contentWidth * 1.2)
Rectangle {
id: shade
anchors.fill: parent
radius: 10
}
Rectangle {
id: focusBorder
color: "transparent"
border.color: button.activeFocus ? Style.iRobotGreen : "transparent"
anchors { fill: parent; margins: -1 }
radius: 10
}
Layout.preferredWidth: Math.max( label.implicitWidth + button.border.left
+ button.border.right,
button.implicitWidth )
Text {
id: label
anchors.centerIn: parent
font: Style.refFont1.font
}
MouseArea {
id: mouse
anchors.fill: parent
enabled: true
onClicked: button.clicked()
hoverEnabled: button.tooltipText.length > 0
onEntered: {
if (button.tooltipText.length > 0)
tooltip.show(mouseX + 16, mouseY + 16)
}
onExited: {
tooltip.hide()
}
}
ToolTip {
id: tooltip
enabled: true
text: button.tooltipText
}
}

Qml slider style, assigning different groove color before and after the slider's handle

So I wanted to create a slider style, similar to the default one, and also more matching to my applications style, but I found it hard to assign a different color before and after the slider's handle.
Here's a simplified version of my code, without gradients, anchor points and other properties:
Slider{
id: widthSlider
style: SliderStyle {
groove: Rectangle {
height: widthSlider.height*0.17
width: widthSlider.width
color: "black"
radius: 8
}
handle: Rectangle {
color: "grey"
}
}
}
I tried a rough workaround, to put two rectangles in the slider, anchored to the handle position like this:
Slider{
id: widthSlider
Rectangle {
anchors.left: widthSlider.left
anchors.right: widthSlider.__handlePos
color: "black"
}
Rectangle {
anchors.left: widthSlider.__handlePos
anchors.right: widthSlider.right
color: "white"
}
...
}
but I cannot anchor to the handle's position since it's just a double(I get the error: Unable to assign double to QQuickAnchorLine).
Does anyone have an idea of how I could do this in Qml?
Something like this?
Slider {
anchors.centerIn: parent
value: 0.5
width: 400
style: SliderStyle {
groove: Item {
implicitHeight: 10
LinearGradient {
anchors.fill: parent
start: Qt.point(0, control.height / 2)
end: Qt.point(control.width, control.height / 2)
gradient: Gradient {
GradientStop { position: 0.0; color: "orange" }
GradientStop { position: control.value; color: "brown" }
GradientStop { position: 1.0; color: "orange" }
}
}
}
}
}
Or:
Slider {
anchors.centerIn: parent
value: 0.5
width: 400
style: SliderStyle {
groove: Rectangle {
implicitHeight: 10
color: "lightgrey"
border {
color: "#999"
width: 1
}
Rectangle {
implicitHeight: 10
color: "orange"
implicitWidth: control.value * parent.width
border {
color: "#999"
width: 1
}
}
}
}
}
I realize this is an old question. However for future reference the Rectangle that represents the colour before the handle should have a width of styleData.handlePosition. The below code is from folibis second solution with this change.
Slider {
anchors.centerIn: parent
value: 0.5
width: 400
style: SliderStyle {
groove: Rectangle {
implicitHeight: 10
color: "lightgrey"
border {
color: "#999"
width: 1
}
Rectangle {
implicitHeight: 10
color: "orange"
implicitWidth: styleData.handlePosition
border {
color: "#999"
width: 1
}
}
}
}
}

Change Text Color of the selected item in a ListView

Let me start by saying that I am pretty new to QML.
I have a ListView (with model and delegate), it works fine in my model but I would like to change the color (currently color: skin.gray) of the selected item to something else when the item is the currentIndex-item.
ListView {
id: menuBody_listview
width: parent.width
height: parent.height
currentIndex: 0
clip: true
highlight: highlighter
highlightFollowsCurrentItem: true
Behavior on opacity {
NumberAnimation { property: "opacity"; duration: 300; easing.type: Easing.InOutQuad }
}
anchors {
top: menuHeader_listview.bottom
bottom: parent.bottom
}
model: ListModel {
ListElement {
itemIconLeft: 'images/icons/menu/pause.png'
itemText: "Cancel"
itemIconRight: 'images/icons/menu/take-me-home.png'
}
ListElement {
itemIconLeft: 'images/icons/menu/pause.png'
itemText: "Mute"
itemIconRight: 'images/nill.png'
}
ListElement {
itemIconLeft: 'images/icons/menu/repeat.png'
itemText: "Repeate"
itemIconRight: 'images/nill.png'
}
}
delegate: MenuBodyItem {
width: menuBody_listview.width
anchors.horizontalCenter: parent.horizontalCenter
iconLeft: itemIconLeft
message: itemText
iconRight: itemIconRight
}
}
Following is the code for the item which is being populated, ManuBodyItem.qml.
Item {
width: 100
height: 50
property alias iconLeft: menuitem_icon_start.source
property alias message: menuitem_text.text
property alias iconRight: menuitem_icon_end.source
RowLayout {
spacing: 20
anchors.fill: parent
Image {
id: menuitem_icon_start
fillMode: Image.PreserveAspectCrop
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
}
Text {
id: menuitem_text
anchors {
left: menuitem_icon_start.right
verticalCenter: parent.verticalCenter
verticalCenterOffset: -2
leftMargin: 20
}
color: skin.gray
font {
family: "TBD"
}
}
Image {
id: menuitem_icon_end
fillMode: Image.PreserveAspectCrop
source: iconRight
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
}
}
}
Use ListView's isCurrentItem attached property:
Text {
id: menuitem_text
anchors {
left: menuitem_icon_start.right
verticalCenter: parent.verticalCenter
verticalCenterOffset: -2
leftMargin: 20
}
color: itemDelegate.ListView.isCurrentItem ? "red" : skin.gray
font {
family: "TBD"
}
}
Note that you have to give your root delegate item an ID in order to qualify the expression above:
Item {
id: itemDelegate
RowLayout {
// ...
}
// ...
}
You can see the same approach used in the example I linked to.
From your example:
color: skin.gray is used for the Text element which will change the color of the text and not it's background viz. i understood you want.
You can use a Rectangle element here which can act as a background component to set the background color.
So instead of Item root element in the delegate you can use Rectangle. So MenuBodyItem.qml will look as
Rectangle {
width: 100
height: 50
...
}
Now to set background color to the Rectangle if it is current one you can use ListView.isCurrentItem to check.
So,
Rectangle {
color: ListView.isCurrentItem ? "cyan" : "lightblue"
width: 100
height: 50
}
and now finally you will have to set the clicked item as the current one which can be done in the MouseArea of the Delegate Item
delegate: MenuBodyItem {
width: menuBody_listview.width
anchors.horizontalCenter: parent.horizontalCenter
iconLeft: itemIconLeft
message: itemText
iconRight: itemIconRight
MouseArea {
anchors.fill: parent
onClicked: menuBody_listview.currentIndex = index
}
}

Resources