Software keyboard is pushing the root view up - qt

I'm struggling with software(iOS/Android) keyboard which is pushing whole view up. I could not find any solution to prevent this behavior and could not find a workaround.
The layout I have is pretty simple chat outline:
Page {
header: Toolbar {
}
ColumnLayoyt {
ListView {
}
Pane {
}
}
}
What I'm currently trying in a nutshell:
Page {
header: Toolbar {
/// #hack Counterflow push
topPadding: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.width : 0
}
ColumnLayoyt {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
ListView {
id: feedView
Layout.fillWidth: true
Layout.fillHeight: true
}
Pane {
Layout.fillWidth: true
Layout.prefferedHeight: textArea.implicitHeight
TextArea {
id: textArea
}
}
}
}
This eventually partially works but for some reason Qt.inputMethod.keyboardRectangle.y is changing when textArea gets bigger and header.topPadding is eventually growing(gap is visually getting higher).
Anyway it looks like a hack and I'm still looking for a solution. How to deal with it? I hope that I'm just bad at googling these days, because such ui task seems pretty straightforward to me.

Related

Qt / QML - Grouping and Reusing Elements

Could anyone point me in the right direction on how to best organise my QML?
Currently for common single components I make a new QML file and add it to a Common directory under my resources.
E.g. My Label.qml
Text{
width: parent.width * 0.5
height: parent.height * 0.1
color: "#ffffff"
font.underline: true
font.pointSize: 16
verticalAlignment: Text.AlignBottom
horizontalAlignment: Text.AlignLeft
}
And then in my Form.qml I can import and use it like this:
import "Common"
Page {
Label{
id: username_lbl
text: "Username"
anchors.topMargin: parent.height * 0.1
}
...
}
But how would I do the above if I want to group together multiple components and reference them for use with connections?
For example I'd like a pair of buttons that sit at the bottom of a page (below is just an example and doesn't work):
So I'd like to have a ButtonPair.qml which would like a bit like this:
Button {
id: left_btn
width: parent.width * 0.5
height: parent.height * 0.1
anchors.bottom: parent.bottom
anchors.right: parent.right
}
Button {
id: right_btn
width: parent.width * 0.5
height: parent.height * 0.1
anchors.bottom: parent.bottom
anchors.left: parent.left
}
And then in my Form.qml I'd like to use these buttons and add an event handler to each:
import "Common"
Page {
ButtonPair{id: back_forward_buttons}
Connections {
target: back_forward_buttons.left_btn
onClicked: {
stackView.pop();
}
Connections {
target: back_forward_buttons.right_btn
onClicked: {
stackView.push("AnotherPage.qml");
}
}
Do I need to wrap my ButtonPair in a Component and use a Loader on my page and if so how do I then get to the individual left/right buttons in order to bind to onClicked?
When a component is designed, it is considered a black box that has properties and signals that must be viewed from the outside.
For example in your case ButtonPair must expose 2 signals: one when the left button is pressed and another when the right button is pressed, another thing that I have added are 2 properties to be able to establish the name of the buttons.
I see that you have set the height of the buttons as 10% of the height of the father and should be in the lower part and if you want to use that same component on the top? I would have to create another topButtonPair component, and if I want them to be on the right, etc. For the size should be established when the component is created not in the implementation. In this case each button must occupy half of the parent item.
Using the above we obtain the following:
ButtonPair.qml
import QtQuick 2.0
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
Item{
// expose properties and signals
property string leftname: ""
property string rightname: ""
signal leftClicked()
signal rightClicked()
// internals
RowLayout{
anchors.fill: parent
spacing: 0
Button {
text: leftname
onClicked: leftClicked()
Layout.fillWidth: true
Layout.fillHeight: true
}
Button {
text: rightname
onClicked: rightClicked()
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
Note: The use of the layout is optional, you could use the anchors.
Now used on the page:
Page {
// other components
ButtonPair{
anchors.bottom: parent.bottom
height: 0.1*parent.height // <--- Here the height is established
anchors.left: parent.left
anchors.right: parent.right
leftname: "left text"
rightname: "right text"
onLeftClicked: console.log("left clicked")
onRightClicked: console.log("right clicked")
}
}
Generally, the black-box appraoch taken by #eyllanesc is the better one and should be preferred whenever possible. However, if you really need to access child items from the outside, you can:
ButtonPair.qml:
Item {
property alias leftButton: left_btn
property alias rightButton: right_btn
// … declarations of left_btn and right_btn as in your question
}
Usage:
ButtonPair {
leftButton {
onClicked: {
stackView.pop();
}
}
rightButton {
onClicked: {
stackView.push("AnotherPage.qml");
}
}
}
You can also use it in Connections. However, in 95% of cases you should forward properties and signals as in #eyllanesc’s approach, which leads to a much cleaner and readable interface.

Page Navigation in QML

I'm trying to implement the following GUI in QML and having trouble understanding how to properly navigate through different pages of the application.
There are 3 buttons in the main menu. When the user clicks on the 'actor' button the UI switches to 'actor view' where the user can toggle between Thumbnail view and List View. When the user clicks on one of the actors the UI switches to Actor Detail view: A view that has a movie view 'nested in it' which lists all the actors movies.
I'm trying to implement this using StackView.
So my StackView lives in the main menu screen (main.qml) when the user clicks one of the buttons the onClicked event pushes the correct view on to the stack.
ActorsView.qml consists of an internal StackView (Most likely a bad idea) and 2 buttons that switch between Thumb and Detail view. This is done by pushing either Thumb or Detail view onto the local stack.
DetailView.qml and ThumbView.qml function exactly the same though look different. Here is where I ran into trouble. I want the main view to be notified when a click event occurs in either Detail or Thumb view. So that it could (based on the event passed information) know what view push onto the main stack. For example when the user clicks on Actor1, the main menu could push 'actor detail view for actor 1' onto the stack.
Sadly I don't know how to 'catch' events that are firing in nested components in the parent element.
I've started playing around with QML and QT just a few weeks ago, and would be happy to hear that my approach is all wrong and that there is a much better way to achieve what I want. Sadly this is the only viable option I found this far.
main.qml:
ApplicationWindow {
title: qsTr("Hello World")
width: 1280
height: 720
visible: true
id: mainWindow
Component{
id: homeScreen
Rectangle{
height: 500
width: 500
color:"blue"
anchors.centerIn: mainWindow
Text {
anchors.centerIn: parent
text: qsTr("Home")
font.pixelSize: 40
}
}
}
Component{
id: actorsView
ActorsView{
view: stack
}
}
Component{
id: moviesView
MoviesView{
view: stack
}
}
ColumnLayout{
RowLayout{
Layout.fillWidth: true
Button{
text: "Back"
onClicked: stack.pop()
}
Button{
text: "actor view"
onClicked: stack.push(actorView)
}
Button{
text: "movie view"
onClicked: stack.push(moviesView)
}
}
StackView {
id: stack
initialItem: homeScreen
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ActorsView.qml:
Item {
property StackView view
Component {
id: actorDetailView
DetailView {
name: "actorDetailView"
text: "Actor"
}
}
Component {
id: actorThumbView
ThumbView {
name: "actorThumbView"
text: "Actor"
}
}
ColumnLayout {
RowLayout {
Text {
text: "Actor view"
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Button {
text: "Detail"
onClicked: internalStack.push(actorDetailView)
}
Button {
text: "Thumb"
onClicked: internalStack.push(actorThumbView)
}
Button {
text: "back"
onClicked: internalStack.pop()
}
Button {
text: "depth: " + internalStack.depth
}
}
StackView {
id: internalStack
initialItem: {
console.log(internalStack.depth)
internalStack.initialItem = actorThumbView
}
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ThumbView.qml:
Item {
property string name: "thumbView"
property string text
property int counter: 0
id:thumbView
signal thumbPressed (string pressedName)
GridLayout {
columnSpacing: 10
rowSpacing: 10
width: parent.width
Repeater {
model: 16
Rectangle {
width: 200
height: 300
color: "grey"
Text {
id: lable
text: text
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
var tag = lable.text
console.log("You have clicked " + tag)
thumbView.thumbPressed(tag)
}
}
Component.onCompleted: {
counter = counter + 1
lable.text = text + " " + counter
}
}
}
}
}
That's actually a common approach to structure a QML application, so no it's not all bad ! Nested StackViews are a powerful way to manage sub-content of a page, but surely add a level in your app structure. It's made easier by creating your own Page item, redefining the navigation and interaction as you wish.
There's different ways to handle signal in nested components. The easiest: call an identified item up in hierarchy. Local and parent elements in QML are accessible from their id directly, even if those are not in the same QML file. Which allowThis of course has the drawback of inducing coupling between your pages or components and the rest of your application.
ApplicationWindow {
id: mainWindow
function pushPage(page) {
stack.push(page)
}
function showActor(id) {
...
}
// ...
}
In your page simply...
MouseArea {
onClicked: {
mainWindow.showActor(index)
}
}
To achieve something more modular, you can rely StackView currentItem, signals, Connections and Binding elements to name a few, or implement an interface in QML and/or C++ to manage your navigation.
There's definitely a lot of possibilities depending on your goal architecture, trying & learning makes it perfect !

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.

How to auto-hide ApplicatioWindow menuBar?

I'd like to have an ApplicationWindow have an auto-hiding menuBar, which shows up when mouse cursor is positioned on the uppermost part of the window. Is this possible in QML?
PS: I'm using Qt 5.3.
Thanks in advance.
You can exploit internal properties, i.e. properties starting with "__". Since they are internal, functionality could break in future releases even if IMO it is unlikely in this case.
By using internal properties you can exploit __contentItem, the graphical container of the MenuBar and animate its properties to achieve the desired result. Here is a possible approach; it works with Qt 5.3/ Qt 5.4/ Qt 5.5 (the ones I could test it on):
ApplicationWindow {
id: app
visible: true
width: 400
height: 300
property real hideValue: 0
Behavior on hideValue {
NumberAnimation {duration: 200}
}
menuBar: MenuBar {
id: menu
//__contentItem.scale: value // (1)
//__contentItem.opacity: hideValue // (2)
__contentItem.transform: Scale {yScale: hideValue} // (3)
Menu {
id: m1
title: "File"
MenuItem { text: "Open..."
onTriggered: {
hideValue = 0 // hide the bar
}
}
MenuItem { text: "Close"
onTriggered: {
hideValue = 0 // hide the bar
}
}
}
}
Button{
id: b1
anchors.centerIn: parent
text: "CLICK ME!"
width: 100
height: 100
}
MouseArea {
id: ma1
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: menu.__contentItem.implicitHeight
hoverEnabled: true
onEntered: {
hideValue = 1 // show the bar
}
}
MouseArea {
id: ma2
anchors.fill: parent
hoverEnabled: true
z: -1
onEntered: {
m1.__dismissMenu()
hideValue = 0 // hide the bar
}
}
}
Summarizing the code:
Two MouseArea are defined: ma1 which covers the MenuBar and ma2 which fills the ApplicationWindow. The latter has a z negative to be positioned under any other element of the window, inclusing ma1: this way it cannot interfere with events related to any element added (such as the example button b1).
ma1 sets a property called hideValue to 1 whereas ma2 brings it back to 0. The property is used over a visual property of __contentItem (see (1), (2) and (3) in the code) to hide/show the MenuBar. A simple Behaviour over the hideValue property ensures that the transition is smooth.
Internal function __dismissMenu() is used to ensure that an opened Menu is closed when the MenuBar loses focus.
When a MenuItem is triggered the hideValue property is directly reset to ensure correct hiding.
I managed to get some results with this code:
ApplicationWindow {
id: app
MenuBar {
id: menu
Menu {
title: "Menu 1"
MenuItem {
text: "item 1"
}
MenuItem {
action: "item 2"
}
}
}
MouseArea {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 20
hoverEnabled: true
onEntered: {
if (app.menuBar === menu)
app.menuBar = null;
else
app.menuBar = menu;
}
}
}
The change however is abruptly and QML debugging report errors when trying to access null.__contentItem when the bar is hidden. And, of course, there's an absolute size in the code which could cause problems.

QML ListModel: are multiple ListModels allowed to live on screen?

GridView {
id: gridv
model: ListModel {
id: modelone;
}
delegate: componentId
}
Rectangle {
id: whattheproblem
color: red
ListView {
id: listv
model: ListModel {
id: modeltwo;
}
delegate: anotherComponentId
}
}
I can do gridv.model.append(element), it adds elements to displayed GridView.
But, I can't do listv.model.append(element), it doesn't draw anything (the component code is valid, though), but at the same time, modeltwo.count shows that element is added to model. Rectangle was added to check the layout (it's managed by RowLayout currently), and it seems to be working; other layout things (think anchor, x/y/z) do not help.
QT 5.3, QtQuick 2..
From my point of view, I can only think now, that modelone associates all the ListModel logics to GridView it's created from, so ListModel can't work with ListView anymore. Sounds illogical, but already spent two hours on this.
is there a necessity to create custom Model's, when dealing with multiple views?
I think the problem is with the delegate, since i tried your example with some modifications and it worked.
Following is the code:
Item {
width: 200
height: 200
GridView {
id: gridv
width: 200
height: 100
model: ListModel {
id: modelone;
}
delegate: Text { text: name }
}
Rectangle {
id: whattheproblem
anchors.top: gridv.bottom
ListView {
id: listv
model: ListModel {
id: modeltwo;
}
delegate: Text { text: name }
}
}
Button {
anchors.left: parent.left
anchors.bottom: parent.bottom
text: "Add to Grid"
onClicked: gridv.model.append({name: "grid"})
}
Button {
anchors.right: parent.right
anchors.bottom: parent.bottom
text: "Add to List"
onClicked: listv.model.append({name: "list"})
}
}
I tried it with Qt 5.3.1

Resources