key handling in a StackView with multiple items - qml

I have a StackView with two items in it. Both items should handle some keys.
I would assume that, if the currentItem in a StackView does not handle a key, that the key would be forwarded to the lower layers but apparently, this is not the case.
The following example illustrates the problem. When pressing eg 'A', I see that the key is handled by layer1 and by the stackview itself but the key is not handled by layer0.
Note that layer0 remains visible after pushing layer1 on top of it due to the properties.exitItem.visible = true statement in transitionFinished
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: mainWindow
visible: true
width: 1280
height: 720
color: "black"
Component {
id: layer0
Rectangle {
focus:true
width:200;height:200;color:"red"
Keys.onPressed: console.log("layer0")
}
}
Component {
id: layer1
Rectangle {
focus:true
width:200;height:200;color:"#8000FF00"
Keys.onPressed: console.log("layer1")
}
}
StackView {
id: stack
width: parent.width
height: parent.height
focus: true
Component.onCompleted: {
stack.push(layer0)
stack.push(layer1).focus=true
}
Keys.onPressed: {
console.log("StackView.onPressed")
}
delegate: StackViewDelegate {
function transitionFinished(properties)
{
properties.exitItem.visible = true
properties.exitItem.focus = true
}
}
}
}

I would assume that, if the currentItem in a StackView does not handle a key, that the key would be forwarded to the lower layers but apparently, this is not the case.
Apparently according to Qt Documentation the key event propagation goes like this:
If the QQuickItem with active focus accepts the key event, propagation stops. Otherwise the event is sent to the Item's parent until the event is accepted, or the root item is reached.
If I understand that correctly, in your example the two items are siblings. Layer1 has focus, and it will propagate the event UP in the hierarchy, not horizontally or down. Moreover, those multiple focus: true won't have any effect, since the last item receiving the focus will get it, in this situation layer1 in Component.onCompleted
One way to work around this could be defining a new signal, say,
Window {
id: mainWindow
...
signal keyReceived(int key)
and then in StackView to fire that event on Keys.onPressed:
Keys.onPressed: {
console.log("StackView.onPressed")
keyReceived(event.key)
}
And finally catching the new signal in your Rectangles:
Component {
id: layer1
Rectangle {
Connections {
target: mainWindow
onKeyReceived: console.log("layer1")
}
}
}

Related

QML Flickable scrollbar change color fills the whole handle

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

How to block and do not propagate containsMouse property of MouseArea to parent?

You should keep parent-child relationship in order to propagate MouseArea's move events to other MouseAreas that overlaps and are lower in the visual stacking order. *
But how to do the opposite, that is how to block move signals if I do not want to share move events, so parent MouseArea doesn't containsMouse when child does (assuming both have hoverEnabled: true)?
Edit:
Here's little example app to illustrate what I'm talking about. Basically I'm looking for some elegant(qml only if possible, but any solution would be appreciated) way for outer MouseArea (ma1) to have containsMouse equal to false while mouse is over inner MouseArea.
import QtQuick 2.12
import QtQuick.Controls 2.5
ApplicationWindow{
width: 640
height: 480
visible: true
MouseArea {
id: ma1
anchors.fill: parent
hoverEnabled: true
MouseArea {
anchors.centerIn: parent
width: parent.width * Math.log(2)
height: parent.height * Math.log(2)
hoverEnabled: true
z: 1
// onPositionChanged: mouse.accepted = true;
// onMouseXChanged: mouse.accepted = true;
// onMouseYChanged: mouse.accepted = true;
// onEntered: { ma1.hoverEnabled = false; ma1.enabled = false; }
// onExited: ma1.hoverEnabled = true;
Component.onCompleted: bg.createObject(this, { "hovered": Qt.binding(function() { return containsMouse; }) } );
}
Component.onCompleted: bg.createObject(this, { "hovered": Qt.binding(function() { return containsMouse; }) } );
}
Component {
id: bg
Rectangle {
property bool hovered: false
anchors.fill: parent
color: hovered ? "green" : "red"
border.width: 1
Text {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
text: (!parent.hovered ? "un" : "") + "hovered"
color: "white"
}
}
}
}
Actually, requested feature should work out of the box. Each time you stuck on something, try to create separate project from the scratch.
Take a look at a bit simpler code I've prepared for you. It is pure QML. Also, since all is placed in logical order -- there is no need to provide stacking order (z parameter).
So, according to Qt Support, there is no way to do this with containsMouse property while keeping parent-child relations between two MouseAreas.
...the workaround for this situation can be breaking the parent-child
relationship or having another [custom] property...

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

How to send signals between tabs of TabView (Qt5)

I have TabView with two tabs. Each tab has Item element (which contains other stuff). I need to send signal from one tab and to catch (to handle) it in other tab.
If I try to send signal from one Tab (Item) to other - it doesn't work, without showing of any errors.
I found a way to send signal out from tab to TabView scope using Connections as written here (http://qt-project.org/doc/qt-5/qml-qtquick-loader.html).
declare in the first tab signal:
signal message()
Make a call in this tab (in onClicked handler? for example):
onClicked: {
nameOfItemInFirstTab.message()
}
In TabView scope I declared Connections:
Connections {
target: nameOfTheFirstTab.item
onMessage:
{
doSomething()
}
}
Using this way, it's possible to catch the signal out of the first tab. But how, now, to send signal to the second tab? Is there another way to do it?
You can do it like this:
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
id: myRect
width: 100
height: 100
TabView {
anchors.fill: parent
Component.onCompleted: {
tab1.tab1Signal.connect(tab2.onTab1Signal)
tab2.tab2Signal.connect(tab1.onTab2Signal)
}
Tab {
id: tab1
title: "Tab 1"
signal tab1Signal
function onTab2Signal() {
print("Tab 1 received Tab 2's signal")
}
Item {
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab1Signal()
}
}
}
Tab {
id: tab2
title: "Tab 2"
signal tab2Signal
function onTab1Signal() {
print("Tab 2 received Tab 1's signal")
}
Item {
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab2Signal()
}
}
}
}
}
The functions can of course be named anything; I only prefixed them with "on" because that's the convention for signal handler names.
For more information on connecting signals in QML, see Connecting Signals to Methods and Signals.
Is it possible to make a onTab1Signal() in Item? I need to send some information (String/int) to elements of Item of tab2.
Yes, but it's a bit trickier. First of all, remember that Tab is a Loader, so your Item declared inside it may not always be valid. In fact, if you look at the implementation of Tab, you'll see that it sets its active property to false, effectively meaning that tab content is loaded only after that tab has been made current (a design called lazy loading). A naive implementation (i.e my first attempt :p) will encounter strange errors, so you must either:
Set active to true in each Tab, or
Add an intermediate item that accounts for this
The first solution is the easiest:
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
id: myRect
width: 100
height: 100
TabView {
id: tabView
anchors.fill: parent
Component.onCompleted: {
tab1.tab1Signal.connect(tab2Item.onTab1Signal)
tab2.tab2Signal.connect(tab1Item.onTab2Signal)
}
// You can also use connections if you prefer:
// Connections {
// target: tab1
// onTab1Signal: tabView.tab2Item.onTab1Signal()
// }
// Connections {
// target: tab2
// onTab2Signal: tabView.tab1Item.onTab2Signal()
// }
property alias tab1Item: tab1.item
property alias tab2Item: tab2.item
Tab {
id: tab1
title: "Tab 1"
active: true
signal tab1Signal
Item {
id: tab1Item
function onTab2Signal() {
print("Tab 1 received Tab 2's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab1Signal()
}
}
}
Tab {
id: tab2
title: "Tab 2"
active: true
signal tab2Signal
Item {
function onTab1Signal() {
print("Tab 2 received Tab 1's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab2Signal()
}
}
}
}
}
The second solution is slightly more difficult, and comes with a caveat:
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
id: myRect
width: 100
height: 100
TabView {
id: tabView
anchors.fill: parent
QtObject {
id: signalManager
signal tab1Signal
signal tab2Signal
}
property alias tab1Item: tab1.item
property alias tab2Item: tab2.item
Tab {
id: tab1
title: "Tab 1"
signal tab1Signal
onLoaded: {
tab1Signal.connect(signalManager.tab1Signal)
signalManager.tab2Signal.connect(tab1.item.onTab2Signal)
}
Item {
id: tab1Item
function onTab2Signal() {
print("Tab 1 received Tab 2's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab1Signal()
}
}
}
Tab {
id: tab2
title: "Tab 2"
signal tab2Signal
onLoaded: {
tab2Signal.connect(signalManager.tab2Signal)
signalManager.tab1Signal.connect(tab2.item.onTab1Signal)
}
Item {
function onTab1Signal() {
print("Tab 2 received Tab 1's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab2Signal()
}
}
}
}
}
Because active isn't set to true, the second tab isn't loaded at start up, so it won't receive the first tab's signals until it's made current (i.e by clicking on it). Perhaps that's acceptable for you, so I documented this solution as well.