QML give focus to a Component - qt

I have this code:
//main.qml
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
objectName: "Window"
onActiveFocusItemChanged: console.log("***** ACTIVE FOCUS:", activeFocusItem, "*****")
StackView {
anchors.fill: parent
initialItem: "qrc:/LoaderPage.qml"
objectName: "StackView"
onCurrentItemChanged: currentItem.forceActiveFocus()
}
}
//LoaderPage.qml
Item {
objectName: "ItemLoaderPage"
// Keys.forwardTo: loader
Loader {
id: loader
anchors.fill: parent
objectName: "Loader"
focus: true
sourceComponent: rect1
}
Component {
id: rect1
Rectangle {
Keys.onReleased: {
if(event.key === Qt.Key_Escape || event.key === Qt.Key_Back)
{
console.log("Esc or back pressed from", objectName)
event.accepted = true
}
}
objectName: "Rectangle"
focus: true
color: "blue"
}
}
}
I am trying to give the focus to the Rectangle in rect1 Component and catch key events, but with this code, the focus is always given to ItemLoaderPage and I am not able to catch key events. How can I solve that?

I find that maintaining keyboard focus is a big weakness in Qt. The docs make it all sound so straightforward, but in practice I am always ending up in situations where I can't even tell where the focus went.
I usually resort to manually calling forceActiveFocus() rather than depending on Qt to do the right thing automatically. It's a fragile solution, but it's at least one that I feel I have control over.
Loader {
sourceComponent: rect1
onLoaded: {
item.forceActiveFocus();
}
}

The Loader and the rectangle has requested the focus by your attribute:
focus: true
You should try to set the focus only once if I get the idea of the focus-attribute right.

Related

ContainsMouse gives incorrect value on parent change

In QML, the MouseArea's containsMouse property is supposed to return true when the mouse is currently inside the mouse area. Unfortunately, this is not always the case. In the following code, the red square turns blue when the MouseArea within it contains the mouse (ContainsMouse is true). However, if you hit the control key while the square is blue, when the square is reparented to the Window's contentItem, the containsMouse property is not updated (as indicated by the text in the middle of the square). The square will still be blue even though it doesn't contain the mouse anymore. Is there anyway to tell the MouseArea to refresh it's containsMouse property?
Here is the code:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 800
height: 500
visible: true
Rectangle {
id: square
width: 200
height: 200
focus: true
color: my_mouse_area.containsMouse ? "blue" : "red"
MouseArea {
id: my_mouse_area
anchors.fill: parent
hoverEnabled: true
onClicked: {
my_mouse_area.x = 200
}
}
Text {
anchors.centerIn: parent
text: my_mouse_area.containsMouse + ""
font.pixelSize: 20
}
Keys.onPressed: {
if(event.key === Qt.Key_Control){
second_window.show()
square.parent = second_window.contentItem
}
}
}
Window {
id: second_window
width: 400
height: 400
visible: false
}
}
I don't like my first solution, so I have made another, more sophisticated one, but this is not a pure QML solution. The trick is that on parent change you should call a C++ method where you send a mouse move event back to the mouse area, so it will re-evaluate the hovered aka containsMouse boolean. It is a nicer solution, but still a bit of a workaround.
Make sure you have a simple QObject derived class like MyObject with the following Q_INVOKABLE method:
class MyObject : public QObject
{
Q_OBJECT
//
// constuctor and whatnot
//
Q_INVOKABLE void sendMouseMoveEventTo(QObject* item)
{
QEvent* e = new QEvent(QEvent::MouseMove);
QCoreApplication::sendEvent(item, e);
}
};
Make an instance of it in main.cpp, and set as context property, so you can reach it from QML:
MyObject myObject;
engine.rootContext()->setContextProperty("myObject", &myObject);
And finally in the QML Rectangle add this:
onParentChanged: {
myObject.sendMouseMoveEventTo(my_mouse_area)
}
The solution I came up with uses Timer, but with zero interval, thus zero flickering. You can try setting the interval to higher value, to see what is going on. The trick is to set the rectangle visibility dependent of the timer running using "visible: !tmr.running", and start the timer immediately after the parent change of the rectangle.
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 800
height: 500
visible: true
Rectangle {
id: square
width: 200
height: 200
focus: true
color: my_mouse_area.containsMouse ? "blue" : "red"
visible: !tmr.running
Timer {
id: tmr
interval: 0
}
MouseArea {
id: my_mouse_area
anchors.fill: parent
hoverEnabled: true
onClicked: {
my_mouse_area.x = 200
}
}
Text {
anchors.centerIn: parent
text: my_mouse_area.containsMouse + ""
font.pixelSize: 20
}
Keys.onPressed: {
if(event.key === Qt.Key_Control){
second_window.show()
square.parent = second_window.contentItem
tmr.start()
}
}
}
Window {
id: second_window
width: 400
height: 400
visible: false
}
}

QML SplitView auto collapse on handlebar mouse release

I have a QML Controls 2 SplitView and a redefined handle, which works well, but I want detect mouse release event on the handler, so I could collapse the SplitView under a certain threshold of width. Adding a MouseArea on top of the existing handle will absorb drag events, so I'm unable to move the handlebar. Any idea how could I gather the mouse release event, or any other solution which solves this problem?
Alright, I have created an example application. As you can see in this example, my MouseArea is marked with yellow and collapses the right view programmatically when double clicked, which is nice, but I also want to drag the handlebar and upon mouse release under a certain width threshold I want to collapse the view as well. The black part of the handlebar where my MouseArea is not covering the handlebar, responds to drag, but since there is no signal I can gather from it, the width threshold already set shouldCollapse boolean property, so the view won't update. Probably I could solve this issue with a timer, but I need a more sophisticated solution.
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 800
height: 400
visible: true
SplitView {
id: splitView
anchors.fill: parent
orientation: Qt.Horizontal
function toggleCollapse() { collapsibleRect.shouldCollapse = !collapsibleRect.shouldCollapse }
handle: Rectangle {
implicitWidth: 20
implicitHeight: 20
color: "black"
MouseArea {
anchors.centerIn: parent
width: parent.width
height: parent.height / 2
onDoubleClicked: splitView.toggleCollapse()
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
anchors.centerIn: parent
text: "Double click to collapse"
rotation: 90
}
}
}
}
Rectangle {
id: mainRect
color: "green"
SplitView.fillWidth: true
Text {
anchors.centerIn: parent
font.pixelSize: 24
text: "Main scene"
}
}
Rectangle {
id: collapsibleRect
property bool shouldCollapse: false
SplitView.preferredWidth: shouldCollapse ? 0 : 300
color: "purple"
clip: true
onWidthChanged: {
if(width < 200) shouldCollapse = true
else shouldCollapse = false
}
Text {
anchors.centerIn: parent
rotation: parent.shouldCollapse ? 90 : 0
font.pixelSize: 24
text: parent.shouldCollapse ? "SHOULD BE COLLAPSED" : "NOT COLLAPSED"
Behavior on rotation { NumberAnimation { duration: 100 } }
}
}
}
}
I had a similar problem and was able to solve it thanks to the hint of #Ponzifex that the SplitView's resizing property will be set to true as soon as the handle is clicked. Using a Timer I managed to detect whether the handle was quickly pressed twice in a row.
SplitView {
id: view
...
handle: Rectangle {
...
}
//============================================================
// double click behavior
Timer {
id: doubleClickTimer
interval: 300 // number of ms between clicks that should be considered a double click
}
property bool doubleClicked: false
// `resizing` will be set to true even if the handle is just pressed
onResizingChanged: {
if (view.resizing) {
if (!doubleClickTimer.running) {
doubleClickTimer.start();
return;
}
view.doubleClicked = true;
} else {
if (view.doubleClicked) {
// do any manual resizing in here
view.doubleClicked = false;
}
}
}
}
It is important to note, however, that it is only possible to resize the contents of a SplitView when resizing is false. That's why I need to have the doubleClicked helper property.
Add this to MouseArea:
onPressed: {
mouse.accepted = (mouse.flags & Qt.MouseEventCreatedDoubleClick);
}
propagateComposedEvents: true
cursorShape: Qt.SplitHCursor

Loading a Rectangle upon mouse hover in QML

I have an item that consists of an image:
Item {
/* other stuff */
Image {
id: img
anchors.fill: parent
source: mySource
asynchronous: true
fillMode: Image.PreserveAspectFit
}
}
I want to overlay a (transparent) rectangle of the same size on the image when one hovers over it, so that I can do stuff like provide captions for the image etc.
How would one go about doing this?
My first try below: (I used console statements to verify that hovering on an image itself works)
import QtQuick 2.6
Item {
Image {
id: img
anchors.fill: parent
source: mySource
asynchronous: true
fillMode: Image.PreserveAspectFit
MouseArea {
enabled: img.status == Image.Ready
anchors.fill: parent
hoverEnabled: true
onEntered: {
console.log("Entering: ")
overlay
}
onExited: {
console.log("Exiting: ")
}
}
Rectangle {
id : overlay
anchors.fill: parent
color: "transparent"
Text {
id: imgcaption
text: "Caption"
anchors.bottom: overlay.bottom; anchors.verticalCenter: overlay.verticalCenter
font.pointSize : 14
}
}
}
}
When I do this I get something like this at all times, regardless of if I'm hovering over the image or not.
I also tried to put the Rectangle inside the onEntered handler itself, but when I do this the image doesn't display at all, and neither do the console statements so I'm not sure if this is valid QML.
It should be fairly obvious that I'm new to QML and don't really know what I'm doing, so I'd appreciate it if someone could point me in the right direction.
Try toggling the visibility based on the containsMouse property, like this:
Item {
Image {
id: img
anchors.fill: parent
source: mySource
asynchronous: true
fillMode: Image.PreserveAspectFit
MouseArea {
id: mouseArea
enabled: img.status == Image.Ready
anchors.fill: parent
hoverEnabled: true
}
Item {
id : overlay
anchors.fill: parent
visible: mouseArea.containsMouse
Text {
id: imgcaption
text: "Caption"
anchors.bottom: overlay.bottom; anchors.verticalCenter: overlay.verticalCenter
font.pointSize : 14
}
}
}
}
HoverHandler might be better. It's born to deal with hover events so you don't have to set hoverEnabled or anchors.
More importantly, it's transparent for mouse movement. This would be useful in more tricky scenarios, such as dealing with hover events for an item that already contains interactive controls (Button, Flickable, MouseArea, etc).
If you still use MouseArea in such cases, it may interrupt how these controls deal with hover events while HoverHandler won't. (MouseArea has a propagateComposedEvents property, which only controls whether clicked, doubleClicked, and pressAndHold would be propagated or not.)

Closing qml dialog properly

I've been playing around with dialogs and there is something that bothers me.
I have the following code:
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: dlgTest.open()
}
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: dlgTest.close()
text: "Close"
}
}
}
}
When I open it the first time I add some text to the TextField and then I close it. However, If I open it again, the text will still be there. What I want is to "reset" the dialog to it's original state when I opened it the first time (with an empty TextField). It seems that calling the method "close" is exactly the same as changing visible to false.
Is there a way of doing this "reset"?
I have an other dialog with a lot of controls and it's annoying having to restore everything manually.
In your code you create the Dialog once, as a child of the ApplicationWindow.
To 'reset' it, you have two options:
Have a reset-function, that you call, and restores everything. You can use this to set it up in the first place as well
Create a new Object with everything set in place.
For the latter you can either use JavaScript Dynamic Object Creation or a Loader.
JavaScript Dynamic Object Creation:
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
var d = diaComp.createObject(null)
d.open()
}
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
dlgTest.destroy()
}
text: "Close"
}
}
}
}
However, as you destroyed the Object, the contents of your properties are lost, and you can't access them anymore. So you need to make sure, to copy them (not bind them) to some property that is not destroyed, first.
With the Loader you have the posibility to unload the Dialog right before you load it again, which basically resets it. But until you unloaded it, you can still access it's values, as you can see in the Buttons onClicked-handler.
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
console.log((dlgLoad.status === Loader.Ready ? dlgLoad.item.value : 'was not loaded yet'))
dlgLoad.active = false
dlgLoad.active = true
dlgLoad.item.open()
}
}
Loader {
id: dlgLoad
sourceComponent: diaComp
active: false
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
property alias value: tfText.text
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
}
text: "Close"
}
}
}
}
Of course, you could also copy the values from the Loader's item as well, and then unload it earlier, to possible free the memory.
But if the Dialog is frequently (most of the time) shown, it might be the wisest to avoid the creation and destruction of the objects, by reusing it and resetting it manually.

Drag after long press

I want to drag my custom buttons QML after a long press over them. I've implemented that behaviour, however the problem is that after enabling drag, I need to press button once again to actually start dragging. How should I implement this mechanism if I want to move buttons without releasing after long press?
Here is my button code (onReleased and onLongPressed are my own signals):
ButtonWidget.SoftButtonUI
{
id:softButtonDelegate2
x:500
y:300
labelText: "button"
iconImageSource: path
isGrayedOut: false
Drag.active: dragArea2.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
onReleased:
{
console.log("onClicked")
}
onLongPressed:
{
console.log("onLongPressed")
dragArea2.enabled = true
}
MouseArea {
id: dragArea2
enabled: false
anchors.fill: parent
drag.target: parent
onReleased: parent.Drag.drop()
onClicked: {
console.log("MouseArea onClicked")
}
onPressAndHold: {
console.log("MouseArea onPressAndHold")
}
}
}
Any idea?
Generally speaking you can connect different signals and concatenate operations as discussed in this page. You should have a look at it since it is full of nice and useful information.
However, when it comes to mouse events, an interesting approach to events concatenation is given by MouseEvents acceptation. Documentation says about MouseEvent::accepted:
Setting accepted to true prevents the mouse event from being
propagated to items below this item. Generally, if the item acts on
the mouse event then it should be accepted so that items lower in the
stacking order do not also respond to the same event.
In this case we can take the opposite approach by not accepting the event. This way the pressed event can be used to both activate the drag and actually perform it. Then the MouseEvent can be accepted (implicitly) in the release event, occurring at the end of the drag.
Here is a simple example following this approach. As the mouse is pressed and hold the drag.target is set and drag can start, whereas when the mouse is released the drag.target is removed, removing the dragging behaviour. To test it, just press and hold the mouse over the rectangle and when it changes color just drag it.
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
width: 300
height: 300
visible: true
Rectangle {
id: item
border.width: 2
x: 100
y: 100
width: 100
height: 100
state: "BASE"
states: [
State {
name: "BASE"
PropertyChanges { target: mouseArea; drag.target: undefined}
PropertyChanges { target: item; color: "steelblue"}
},
State {
name: "DRAGGABLE"
PropertyChanges { target: mouseArea; drag.target: item}
PropertyChanges { target: item; color: "darkblue"}
}
]
MouseArea {
id: mouseArea
anchors.fill: parent
drag{
// target: NOT SET HERE
minimumX: 0
minimumY: 0
maximumX: parent.parent.width - parent.width
maximumY: parent.parent.height - parent.height
smoothed: true
}
onPressAndHold: {
item.state = "DRAGGABLE"
mouse.accepted = false // mouse event is USED but NOT CONSUMED...
}
onReleased: {
item.state = "BASE" // mouse event acceptation occurs here!
}
}
}
}
This simple approach should work perfectly also with your custom signals.

Resources