How to resize QML TableView column to its contents on app start? - qt

I am using Qt 5.9.4 on Ubuntu 18.04.
When my application starts, I want to automatically adjust TableView's column size to its contents. A model have some data on start.
I know about the resizeColumnToContents function, but I have no idea where to call it.
onDataChange does not work in TableView: The QML engine says that this signal does not exist. But intellitype allows me to type it in the code.
How to accomplish that?

EDIT 18/09/18
If you use a StackView or else you can preload your TableView
// main.qml
Loader {
id: tableViewLoader
active: true
sourceComponent: TableView { id: tableView }
}
StackView {
id: stackView
initialItem: listViewLoader
function onContentReceived()
{
stackView.push(tableViewLoader);
tableViewLoader.item.resizeColumnsToContents()
}
function onContentClosed()
{
swipeView.pop()
}
}
EDIT 17/09/18
You are right Danil.
There is specified in TableView.qml that
Depending on how the model is populated, the model may not be ready when
TableView Component.onCompleted is called. In that case you may need to
delay the call to positionViewAtRow by using a \l {QtQml::Timer}{Timer}
For me this is working
Component.onCompleted: resizeColumnsToContentsTimer.start()
Timer {
id: resizeColumnsToContentsTimer
interval: 50
running: false
repeat: false
onTriggered: parent.resizeColumnsToContents()
}
You can also see this discussion about it
http://lists.qt-project.org/pipermail/interest/2016-June/023018.html
Maybe, you can call it in onModelChanged which called when you set your model (your model must be populated before).
onModelChanged: tableView.resizeColumnToContents()
Otherwise, you can use signals/slots when your data ready.
But beware with this function : if you have delegate you must specify implicitWidth in, or this is will not to work.
headerDelegate: Rectangle {
id: headerDelegate
height: 36
implicitWidth: textItem.implicitWidth + textItem.padding * 2
color: Style.lightColor
Text {
id: textItem
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
padding: 10
text: styleData.value
elide: Text.ElideRight
color: Style.darkColor
font.pixelSize: Style.bigFontPixelSize
}
}

Related

QML give focus to a Component

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.

MouseArea calling a FileDialog : know which element opened the FileDialog (QML)

Since my last issue with my code, I've come across a new one. Unfortunately, it's not really an implementation issue but much more an "conceptual" issue.
Well so let met introduce the case. I have a grid full of button and then to deal with their onClicked events I have a ButtonGroup
GridLayout {
id: gl
anchors.fill: parent
...
CustomButton{
id: btnMILA1
text: "PlayBook 1"
... //Layout stuff
}
CustomButton{
id: btnMILA2
text: "PlayBook 1"
... //Layout stuff
}
CustomButton{
id: btnMILAN
text: "PlayBook 1"
... //Layout stuff
}
}
Those are generated in a loop so no worries, I didn't wrote all 40 buttons ^^ So here is my ButtonGroup
ButtonGroup {
id: btnGroup
buttons: gl.children
onClicked: {
... //Do some stuff
}
}
As you may have seen, I have a CustomButton element which is used for two reasons :
Esthetics (custom design, round corners, etc...)
Add a MouseArea to each button and onRightclick, show a Menu element
So here is a simplified version of my code for CustomButton element:
import QtQuick 2.15
Button {
id: button
property string optionalConf //SEE LATER BELOW, THIS ITEM WILL BE USEFUL
text: qsTr("Button")
contentItem: Item{
Text {
id: name
text: button.text
font: button.font
color: "#ffffff"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
background: Rectangle{
color: internal.dynamicColor //Used to deal with Hovered/Pressed/Default states
radius: 10
}
MouseArea {
id:mouseHovered
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked:{
rightClickMenu.open()
}
hoverEnabled: true
}
Menu {
id: rightClickMenu
MenuItem {
text: qsTr("Choix du fichier de configuration...")
shortcut: StandardKey.Open
onTriggered: confOpen.open()
}
MenuItem {
text: qsTr("Choix du firmware...")
shortcut: "Ctrl+Shift+O"
onTriggered: firmwareOpen.open()
}
MenuSeparator{}
MenuItem {
text: qsTr("Console")
shortcut: StandardKey.AddTab
//onTriggered: zoomOut()
enabled: false
}
}
}
I don't really know the efficiency in generating a mouseArea for each element so let me know if you have a better way to have an independent onRightclick option for something like 20 or 30 elements.
My issue is the following. On the page, let's say main.qml where the CustomButton is implemented, I have two fileDialog items : one called confOpen and the other called firmwareOpen as you could expect given the code above. When the user uses the rightclick, the MenuItem shows at the exact place of the mouse, he can choose wherever option he wants. Then a called is made to either confOpen or firmwareOpen and the user is able to select one file.
FileDialog{
id: confOpen
title: "Please choose a conf file"
folder: shortcuts.desktop
selectMultiple: false
nameFilters: ["Conf file (*.conf)"]
onAccepted: {
console.log(fileUrl)
//I'd like to do something like this :
//ButtonUsedToOpenFileDialog.optionalConf : fileUrl
}
}
So here is the real issue, I'd like to store the file path into a property of my CustomButton. I have a property string optionalConf in order to do so. But I can't manage to which button made the call to the FileDialog, so I don't know which button should have his optionalConf property updated.
I hope I've been clear and it doesn't take to long to read but I wanted to be clear and precise. Let me know if you have better ways to do what I'm doing, I'm always listening to advice :)
Add a function to your FileDialog called openDialog and pass to it the button like this:
[...]
MenuItem {
text: qsTr("Choix du fichier de configuration...")
shortcut: StandardKey.Open
onTriggered: confOpen.openDialog(button)
}
[...]
FileDialog {
id: confOpen
property var button
function openDialog(button_) {
button = button_;
open();
}
onAccepted: {
button.optionalConf = "UPDATED";
}
}

Qt QML: Get reference to object emitting a signal

I have a screen with some rectangles which can contain text. The text content of these rectangles should be allowed to change through clicking on buttons in the screen where this component is used. The problem I am having is how to know in the screen which uses this component which instance is selected. I thought about solving this via emitting a signal, which transmits the id of the instance as reference, but it seems this does not work. How could this be accomplished? Here my custom rectangle component
Rectangle {
id: root
width: 50
height: 50
color: "#000000"
anchors.verticalCenter: parent.verticalCenter
border.color: "#555555"
property int value: 0
signal sendId(Item)
Text {
id: displayed_text
color: "#ffffff"
text: root.value
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 15
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
root.border.color="#222222"
root.sendId(root.id)
}
}
}
and here the file where other buttons should change the content of the custom component:
property Item selected: myRectangle
function changeSelected(value) {
selected.value=5
}
function setSelected(it) {
root.selected=it
}
MyRectangle {
id: myRectangle
Component.onCompleted: {
myRectangle.sendId.connect(tempNumber.setSelected)
}
}
MyRectangle {
id: myRectangle1
Component.onCompleted: {
myRectangle1.sendId.connect(tempNumber.setSelected)
}
}
MyRectangle {
id: myRectangle2
Component.onCompleted: {
myRectangle2.sendId.connect(tempNumber.setSelected)
}
}
root.sendId(root.id)
The id is not a regular property. Don't use it as such. The purpose of the id is to get you a reference, with which you can refer to a particular object, so all you really need is:
root.sendId(root)
And if root is your qml file root object, sendId(root) would work too as long as sendId is not shadowed, root members can be referenced directly, keep in mind this only applies to the root object, it won't work for a object that is a direct or indirect parent but not root.
It is recomended practice to abstain from giving everything an id - only use ids when you really need to reference a particular object and no other way exists.
Another thing you are missing is that unlike JS functions, you do have to provide some typing for a signal. You can still use var and pass anything, but usually it is more efficient to narrow down the scope. So you need type and identifier:
signal sendId(Item item)
This way you can access item in the signal handlers, so you can avoid the awkward imperative connection syntax, so instead you can simply:
MyRectangle {
onSendId: tempNumber.setSelected(item)
}
However, I'd say your design is not optional. Signals are supposed to be employed when you aim for generality and reuse. Your usage scenario is more specific, thus the usage of signals can be avoided altogether:
// Rect.qml
Rectangle {
width: 50
height: 50
color: manager.selected === this ? "red" : "blue"
MouseArea {
anchors.fill: parent
onClicked: manager.selected = parent
}
}
// main.qml
Window {
id: manager
visible: true
width: 600
height: 300
property Item selected: null
Row {
spacing: 2
Repeater {
model: 10
delegate: Rect {}
}
}
}
As the following example shows, you can directly access objects by id as long as they can be found down the object tree. The same applies to properties, however while the id will work for any object down the tree, properties will only work if they are declared in the root object of the particular qml file.

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.

Resources