QML Flickable scrollbar change color fills the whole handle - qml

I have the following QML where I'm trying to change the scrollbar handle color. The color changes but it just fills the whole scrollbar and the handle does not move anymore while scrolling. Removing the color makes the scrollbar look correct again.
How can I change the color while keeping the handle moving
import QtQuick 2.15
import QtQuick.Controls
Flickable {
property bool scrollVisible;
property int columns : 3
id: slidesFrame
anchors.fill: parent
clip:true
flickDeceleration :10000
boundsMovement: Flickable.StopAtBounds
contentHeight: slidesFlowView.childrenRect.height + slidesFlowView.parent.anchors.margins
ScrollBar.vertical: ScrollBar {
id:control
onVisibleChanged: {
slidesFrame.scrollVisible = visible
}
contentItem: Rectangle {
implicitWidth: 10
color: control.pressed ? "#3C73AE" : "#424242"
}
background: Rectangle {
color:"#6D6D6D"
}
}
Rectangle{
color:"#1E1E1E"
anchors.fill: parent
anchors.margins: 12
Flow {
id: slidesFlowView
anchors.fill: parent
spacing: 14
Repeater{
model : GPresenter.slides
SlidePreview{
width: {
const scrollWidth = slidesFrame.scrollVisible ? control.width/slidesFrame.columns : 0
return (parent.width/slidesFrame.columns) - ((parent.spacing/slidesFrame.columns) * (slidesFrame.columns-1)) - scrollWidth
}
}
}
}
}
}
Here it is below without the color change looking correctly

Related

Visual feedback (color transition) on TextField?

I have an app with numerous numbers in text fields (in a GridLayout) and would like to visually highlight fields which get changed (something like color changing from the original to red and then back, so something similar).
I am new to animations/transitions and so on, so I would like to ask about what is the correct approach to this.
I was looking at tutorials in Qt Creator but they attach transitions to elements in the QML code already, whereas I would like to get the element by its id and say, now run the highlight transition, without adding something to its code. Is that possible?
You have to implement your own control, derived from Text and add inside all the animation logic you want.
For example:
MyItem.qml
import QtQuick 2.12
Text {
id: txt
property bool hightlight: false
property color textColor: color
property color hightlightColor: "red"
onHightlightChanged: {
if(hightlight)
anim.running = true;
}
SequentialAnimation
{
id: anim
running: false
PropertyAnimation {
target: txt
property: "color"
to: hightlightColor
duration: 500
}
PropertyAnimation {
target: txt
property: "color"
to: textColor
duration: 500
}
ScriptAction {
script: txt.hightlight = false;
}
}
}
Usage:
import QtQuick 2.12
import QtQuick.Controls 2.3
ApplicationWindow {
id: window
title: "Test"
visible: true
height: 250
width: 200
MyItem {
id: item
text: "Hello"
anchors.centerIn: parent
hightlightColor: "red"
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
item.hightlight = true;
}
}
}
}

How to rotate only the right or left part of an image

I want to simulate the closure of one page of a book.
Any suggestions on how to do that in qml?
Thanks in advance
Perhaps Flipable, in combination with the fillMode property of Image:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: window
width: 640
height: 480
visible: true
Image {
id: backgroundImage
source: "http://www.thebookdesigner.com/wp-content/uploads/2013/04/pages-vs-spreads.png"
anchors.centerIn: parent
Flipable {
id: flipable
anchors.fill: parent
anchors.leftMargin: parent.width / 2
property bool flipped: false
front: Image {
id: foldImage
source: backgroundImage.source
fillMode: Image.Pad
width: foldImage.implicitWidth / 2
horizontalAlignment: Image.AlignRight
}
back: Image {
source: backgroundImage.source
width: foldImage.implicitWidth / 2
fillMode: Image.Pad
horizontalAlignment: Image.AlignLeft
}
transform: Rotation {
id: rotation
origin.x: 0
origin.y: flipable.height / 2
axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis
angle: 0 // the default angle
}
states: State {
name: "back"
PropertyChanges {
target: rotation;
angle: -180
}
when: flipable.flipped
}
transitions: Transition {
NumberAnimation {
target: rotation
property: "angle"
duration: 1000
easing.type: Easing.InCubic
}
}
MouseArea {
anchors.fill: parent
onClicked: flipable.flipped = !flipable.flipped
}
}
}
}
Flipable does what its name suggests, and the fillMode property in combination with a width that is too small for the entire image allows you to "reposition" the contents of the image within the bounds of the item.
So, the front item contains the right side of the image, and the back item contains the left side. With an actual book that has many pages, you'd have to use the relevant pages instead of the same one.

(How) can i access relative position of qml element to main window

I have a qml element and want to show a (own) tooltip element as a new window right above this element. for this i need the absolute screen position to place the new window (AFAIK).
i got as far that the regular approach is to use "mapToItem" to get the relative position, but i cannot get to the "main window" - because the element in question is located within a "Loader" (which in this case is again located in another Loader).
So my question is: Is it possible to access the mainWindow from inside the dynamically loaded component, or is there maybe another easier way to anchor a new (tooltip) window right above an element ?
EDIT
mapToGlobal would probably work too, but i have to use qt 5.6.
i finally got it to work by setting the main window as a context property in c++:
this->qmlEngine->rootContext()->setContextProperty("mainWindow", this->root);
and in qml i can then access the main window position (on screen) and add the relative position the item has to the shown window like that:
tooltipWindow.setX(mainWindow.x +item1.mapToItem(item2,0,0).x )
The Window item has contentItem especially for that
[read-only] contentItem : Item
The invisible root item of the scene.
So you can refer to Window.contentItem as if it was Window:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: mainWindow
visible: true
width: 600
height: 300
Component {
id: testElement
Rectangle {
id: rect
width: 100
height: 100
color: "orange"
border { width: 1; color: "#999" }
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: tooltip.show(true);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mapToItem(mainWindow.contentItem,mouse.x, mouse.y));
}
}
}
Item {
x: 40
y: 50
Item {
x: 80
y: 60
Loader {
sourceComponent: testElement
}
}
}
Rectangle {
id: tooltip
visible: false
width: 100
height: 20
color: "lightgreen"
border { width: 1; color: "#999" }
Text {
anchors.centerIn: parent
text: "I'm here"
}
function show(isShow) {
tooltip.visible = isShow;
}
function setPosition(point) {
tooltip.x = point.x - tooltip.width / 2;
tooltip.y = point.y - tooltip.height;
}
}
}
As for me I would reparent tooltip Item to hovered item itself at MouseArea.onEntered and so you can avoid position recalculation etc.:
onEntered: tooltip.show(true, rect);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mouse.x, mouse.y);
...
function show(isShow, obj) {
obj = (typeof obj !== 'undefined' ? obj : null);
if(obj !== null) {
tooltip.parent = obj;
}
tooltip.visible = isShow;
}
function setPosition(x, y) {
tooltip.x = x - tooltip.width / 2;
tooltip.y = y - tooltip.height;
}

Change calendar style on button click

I need to change the Calendar style when clicking a Button. Currently, in the code below, the style change only works when the object is created for the first time but I need to do style change manually whenever the Button is clicked.
Below is the QML code:
import QtQuick 2.0
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0
import QtQuick.Controls.Styles 1.1
ApplicationWindow {
visible: true
width: 640
height: 400
minimumWidth: 400
minimumHeight: 300
color: "#f4f4f4"
id: root
Calendar {
id: cal_panel
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter;
frameVisible:false
style: CalendarStyle {
gridVisible: false
dayDelegate: Rectangle {
color: styleData.selected ? "#FF2E7BD2" : (styleData.visibleMonth && styleData.valid ? "#191919" : "#191919");
Text {
id:day_txt
text: styleData.date.getDate()
font.bold: true
anchors.centerIn: parent
color: {
var color = "#dddddd";
if (styleData.valid) {
color = styleData.visibleMonth ? "#bbb" : "#444";
var sel = root.getHiglightDates();
for(var i=0;i<sel.length;i++){
if(sel[i]===Qt.formatDateTime(styleData.date,"dd:MM:yyyy"))
color="red"
}
if (styleData.selected) {
color = "black";
}
}
color;
}
}
}
}
}
Button{
anchors.top:cal_panel.bottom
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
text:"Higlight"
onClicked: {
console.log("Higlight here....")
}
}
function getHighlightDates(){
var sel = ["10:11:2015","12:11:2015","11:11:2015","08:11:2015","09:11:2015"];
return sel;
}
}
Edit:
The return value of the function getHighlightDates() changes each time. In the snippet above I've just returned a predefined array for testing. In that case I am conduced how to edit style element which is already created.
Here is the screen shot:
As a simple solution, you can reassign the style on click event, forcing an under the hood refresh of the Calendar item.
To do that you can use
cal_panel.style=cal_panel.style
Be aware that this solution is not exactly performance friendly. :-)
Based on the comments in the question and in #folibis's answer, it looks the question might just revolve around how to get the calendar style to reflect the updated list of selected dates (from getHiglightDates()) after a user has updated the list by clicking a button.
What about just adding a new property selectedDates to store the selected dates (previously held in getHighlightDates()) like in the code below. By making use of property binding, the appearance of selected dates will automatically be updated whenever selectedDates changes. In the code below, the color of the "day_txt" Text is updated when selectedData is updated (which in turn is updated when selectedDates is updated).
import QtQuick 2.0
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.1
ApplicationWindow {
visible: true
width: 640
height: 400
minimumWidth: 400
minimumHeight: 300
color: "#f4f4f4"
id: root
property variant selectedDates : ["10:11:2015","12:11:2015","11:11:2015","08:11:2015","09:11:2015"]
Calendar {
id: cal_panel
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter;
frameVisible:false
style: CalendarStyle {
gridVisible: false
dayDelegate: Rectangle {
property bool selectedDate: selectedDates.indexOf(Qt.formatDateTime(styleData.date,"dd:MM:yyyy")) > -1
color: styleData.selected ? "#FF2E7BD2" : (styleData.visibleMonth && styleData.valid ? "#191919" : "#191919");
Text {
id:day_txt
text: styleData.date.getDate()
font.bold: true
anchors.centerIn: parent
color: selectedDate ? "red" : (styleData.selected ? "black" : (styleData.visibleMonth ? "#bbb" : "#444"));
}
}
}
}
Button{
anchors.top:cal_panel.bottom
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
text:"Higlight"
onClicked: {
var updatedDates = selectedDates
updatedDates.push(Qt.formatDateTime(cal_panel.selectedDate,"dd:MM:yyyy"))
selectedDates = updatedDates
# See http://stackoverflow.com/questions/19583234/qml-binding-to-an-array-element for why its done this way...
}
}
}
as #skypjack already suggested, you just can assign a new style on click. The style property is a Component so there is no problem to do something like this:
Component {
id: style1
CalendarStyle {
background: Rectangle { color: "lightyellow" }
}
}
Component {
id: style2
CalendarStyle {
background: Rectangle { color: "orange" }
}
}
Calendar {
id: calendar
anchors.fill: parent
style: style1
onClicked: {
calendar.style = style2;
}
}

QML Dialog is broken?

I have this code:
import QtQuick 2.3
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
Dialog {
standardButtons: StandardButton.Ok | StandardButton.Cancel
width: layout.implicitWidth
height: layout.implicitHeight
RowLayout {
id: layout
anchors.fill: parent
Item {
width: 10
height: 1
}
GridLayout {
columns: 2
rowSpacing: 10
Layout.fillHeight: true
Layout.fillWidth: true
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
Item {
width: 10
height: 1
}
}
}
When you run it it looks like this, and the dialog can be resized to any size. Also the RowLayout actually doesn't fill its parent as you can see.
How can I make it so that the dialog can't be resized below the minimum size of the layout, and so that the layout fills the dialog?
Unfortunately this is a bug in Qt. Currently the documentation is misleading and Dialog does not size itself correctly to the contents. Consider this working example, which I based on the DefaultFontDialog:
AbstractDialog {
title: "Hello"
id: root
// standardButtons: StandardButton.Ok | StandardButton.Cancel
modality: Qt.NonModal
Rectangle {
id: content
implicitWidth: mainLayout.implicitWidth + outerSpacing * 2
implicitHeight: mainLayout.implicitHeight + outerSpacing * 2
property real spacing: 6
property real outerSpacing: 12
color: "white"
GridLayout {
id: mainLayout
anchors { fill: parent; margins: content.outerSpacing }
rowSpacing: content.spacing
columnSpacing: content.spacing
columns: 5
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
}
}
}
This works exactly as expected, though of course you don't get the buttons.
If you just change it to a Dialog and uncomment the standardButtons, then it stops working - you can resize the dialog to clip its contents (width-wise at least), and the contents do not expand to the dialog size.
The reason for the minimum width not working becomes clear when we look at the source code for Dialog (in qtquickcontrols/src/dialogs/DefaultDialogWrapper.qml):
AbstractDialog {
id: root
default property alias data: defaultContentItem.data
onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus()
Rectangle {
id: content
property real spacing: 6
property real outerSpacing: 12
property real buttonsRowImplicitWidth: minimumWidth
property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth
property real minimumHeight: implicitHeight
property real minimumWidth: Screen.pixelDensity * 50
implicitHeight: defaultContentItem.implicitHeight + spacing + outerSpacing * 2 + buttonsRight.implicitHeight
implicitWidth: Math.min(root.__maximumDimension, Math.max(
defaultContentItem.implicitWidth, buttonsRowImplicitWidth, Screen.pixelDensity * 50) + outerSpacing * 2);
minimumWidth is hardcoded to Screen.pixelDensity * 50!! There was never any hope that it would match the dialog contents. minimumHeight does work better (though not perfect, I believe because the spacing isn't considered).
I'm not sure why the defaultContentItem does not expand correctly, but anyway. It looks like the only solution at the moment is to use AbstractDialog and implement the buttons and accepted()/rejected()/etc. signals yourself. Bit of a pain.
Edit / Solution
I did some further investigation.
The reason the defaultContentItem doesn't expand is because it's bottom anchor isn't tied to the top of the button row:
Item {
id: defaultContentItem
anchors {
left: parent.left
right: parent.right
top: parent.top
margins: content.outerSpacing
}
implicitHeight: childrenRect.height
}
Minimum sizes just don't work that well with anchor-based layouts. They do with GridLayout-based layouts.
Unfortunately childrenRect has no implicitWidth/Height so we have to actually have the child items go into a ColumnLayout rather than be the ColumnLayout.
...
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
// A Dialog that resizes properly. The defualt dialog doesn't work very well for this purpose.
AbstractDialog {
id: root
default property alias data: defaultContentItem.data
onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus()
Rectangle {
id: content
property real spacing: 6
property real outerSpacing: 12
property real buttonsRowImplicitWidth: minimumWidth
property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth
property real minimumHeight: implicitHeight
property real minimumWidth: implicitWidth // Don't hard-code this.
implicitWidth: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitWidth + outerSpacing * 2))
implicitHeight: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitHeight + outerSpacing * 2))
color: palette.window
Keys.onPressed: {
event.accepted = true
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
reject()
break
case Qt.Key_Enter:
case Qt.Key_Return:
accept()
break
default:
event.accepted = false
}
}
SystemPalette { id: palette }
// We use layouts rather than anchors because there are no minimum widths/heights
// with the anchor system.
ColumnLayout {
id: mainLayout
anchors { fill: parent; margins: content.outerSpacing }
spacing: content.spacing
// We have to embed another item so that children don't go after the buttons.
ColumnLayout {
id: defaultContentItem
Layout.fillWidth: true
Layout.fillHeight: true
}
Flow {
Layout.fillWidth: true
id: buttonsLeft
spacing: content.spacing
Repeater {
id: buttonsLeftRepeater
Button {
text: (buttonsLeftRepeater.model && buttonsLeftRepeater.model[index] ? buttonsLeftRepeater.model[index].text : index)
onClicked: root.click(buttonsLeftRepeater.model[index].standardButton)
}
}
Button {
id: moreButton
text: qsTr("Show Details...")
visible: false
}
}
Flow {
Layout.fillWidth: true
id: buttonsRight
spacing: content.spacing
layoutDirection: Qt.RightToLeft
Repeater {
id: buttonsRightRepeater
// TODO maybe: insert gaps if the button requires it (destructive buttons only)
Button {
text: (buttonsRightRepeater.model && buttonsRightRepeater.model[index] ? buttonsRightRepeater.model[index].text : index)
onClicked: root.click(buttonsRightRepeater.model[index].standardButton)
}
}
}
}
}
function setupButtons() {
buttonsLeftRepeater.model = root.__standardButtonsLeftModel()
buttonsRightRepeater.model = root.__standardButtonsRightModel()
if (!buttonsRightRepeater.model || buttonsRightRepeater.model.length < 2)
return;
var calcWidth = 0;
function calculateForButton(i, b) {
var buttonWidth = b.implicitWidth;
if (buttonWidth > 0) {
if (i > 0)
buttonWidth += content.spacing
calcWidth += buttonWidth
}
}
for (var i = 0; i < buttonsRight.visibleChildren.length; ++i)
calculateForButton(i, buttonsRight.visibleChildren[i])
content.minimumWidth = calcWidth + content.outerSpacing * 2
for (i = 0; i < buttonsLeft.visibleChildren.length; ++i)
calculateForButton(i, buttonsLeft.visibleChildren[i])
content.buttonsRowImplicitWidth = calcWidth + content.spacing
}
onStandardButtonsChanged: setupButtons()
Component.onCompleted: setupButtons()
}
You have to use it a bit differently to a normal Dialog. Just imagine it is a ColumnLayout (this is a slightly different example to the original question):
ColumnLayoutDialog {
id: dialog1
standardButtons: StandardButton.Ok | StandardButton.Cancel
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
// Spacer.
Item {
Layout.fillHeight: true;
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
By the way you could change the ColumnLayout to a GridLayout and expose the columns property if you want. That might make more sense.
A small issue
It turns out a QWindow's minimum width and height only ensure that the dialog isn't actively resized to be less than its content. It doesn't ensure that the dialog is never smaller than its content, because the content can grow after the dialog is created (e.g. extra items added). To workaround this I added this function to my ColumnLayoutDialog:
// The minimumWidth/Height values of content are accessed by the C++ class, but they
// only ensure that the window isn't resized to be smaller than its content. They
// don't ensure that if the content grows the window grows with it.
function ensureMinimumSize()
{
if (root.width < content.minimumWidth)
root.width = content.minimumWidth;
if (root.height < content.minimumHeight)
root.height = content.minimumHeight;
}
It has to be called manually when you change the dialog contents. Or to do it automatically you can add this to the content rectangle:
onMinimumHeightChanged: {
if (root.height < content.minimumHeight)
root.height = content.minimumHeight;
}
onMinimumWidthChanged: {
if (root.width < content.minimumWidth)
root.width = content.minimumWidth;
}
This is a bug in QT up to version 5.6.0. Most likely the bug number 49058. The code from the question works as expected in QT 5.6.1 and 5.7.0.
A partial workaround for the old versions is to remove the lines
width: layout.implicitWidth
height: layout.implicitHeight
and replace
anchors.fill: parent
with
anchors.right: parent.right
anchors.left: parent.left
The dialog then respects the minimum height and the contents expand horizontally.
Here is also a complete workaround, but it relies on undocumented implementation details of Dialog, so it should be used with caution. It works in 5.5.1, 5.6.0, 5.6.1 and 5.7.0. Note also that the second Item is changed to a red Rectangle to make the behavior more apparent.
import QtQuick 2.3
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
Dialog {
visible: true
standardButtons: StandardButton.Ok | StandardButton.Cancel
RowLayout {
id: layout
// In the horizontal direction, expansion and shrinking can be achieved with anchors.
anchors.left: parent.left
anchors.right: parent.right
// Used only for guessing the height of the Dialog's standard buttons.
Button {
id: hiddenButton
visible: false
}
// Repeats until the relevant parts of the dialog (parent of the parent of the RowLayout)
// are complete, then overwrites the minimum width and implicit height and stops repeating.
Timer {
id: timer
interval: 50; running: true; repeat: true;
onTriggered: {
if(layout.parent.parent) {
var lp = layout.parent
var lpp = layout.parent.parent
lpp.minimumWidth = layout.implicitWidth + 2 * lpp.outerSpacing
layout.buttonHeight = 2 * lpp.outerSpacing + hiddenButton.implicitHeight + lpp.spacing
lp.implicitHeight = layout.implicitHeight + 2 * lpp.outerSpacing
running = false
}
}
}
// The guessed space needed for the Dialog's buttons.
property int buttonHeight: 80
// Expand and shrink vertically when the dialog is resized.
height: parent.parent ? Math.max(parent.parent.height-buttonHeight, implicitHeight) : implicitHeight
Item {
width: 10
height: 1
}
GridLayout {
columns: 2
rowSpacing: 10
Layout.fillHeight: true
Layout.fillWidth: true
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
Rectangle {
Layout.fillHeight: true
color: 'red'
width: 10
}
}
}