QML propagate key events from ListView delegates - qt

main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
onActiveFocusItemChanged: console.log(activeFocusItem)
Item {
id: item
anchors.fill: parent
focus: true
ListView {
anchors.fill: parent
model: ["Test1", "Test2", "Test3", "Test4", "Test5"]
delegate: TextArea {
padding: 0
text: modelData
}
}
Keys.onReleased: {
if(event.key === Qt.Key_F3) {
console.log("F3 pressed")
event.accepted = true
}
}
}
}
I would like to intercept the click of the key F3 in the item but, when the focus is on the ListView delegates, they don't propagate the key event. If instead the focus is on the Item, everything works fine.
How can I solve that?

The TextArea must try to catch the F3 key itself, which is why it doesn't propogate. The way I would fix this is to catch the key both in the delegate and out of the delegate, and in both handlers call a common function to do the actual work that you want to do.
Item {
id: item
anchors.fill: parent
focus: true
ListView {
anchors.fill: parent
model: ["Test1", "Test2", "Test3", "Test4", "Test5"]
delegate: TextArea {
padding: 0
text: modelData
Keys.onReleased: {
if(event.key === Qt.Key_F3) {
// Just call the new function we created
item.f3Handler();
event.accepted = true
}
}
}
}
Keys.onReleased: {
if(event.key === Qt.Key_F3) {
// Just call the new function we created
f3Handler();
event.accepted = true
}
}
// This is where we do the actual work.
function f3Handler() {
console.log("F3 pressed")
}
}

Related

Why can't QML key events be intercepted?

I have the following code:
MyTest.qml
import QtQuick 2.0
FocusScope {
anchors.fill: parent
Keys.onReturnPressed: {
console.log("++++++++++")
}
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MyTest {
focus: true
Keys.onReturnPressed: {
console.log("==========")
event.accepted = true
}
}
}
The output is:
++++++++++
==========
What is event.accepted = true invalid?
I want to intercept keystroke events in Window and process the event on ly in Window (only output "=========="). How to do that?
You can't disconnect a method when you use onReturnPressed: {} definition.
You have to use Connections for that.
A quick example:
import QtQuick 2.9
import QtQuick.Controls 1.4
Item {
id: myItem
anchors.fill: parent
property bool acceptEvents: true
signal returnPressed()
Keys.onReturnPressed: returnPressed()
Connections {
id: connection
target: myItem
enabled: acceptEvents
onReturnPressed: {
console.log("++++++++++")
}
}
}
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
VolumeSlider {
id: obj
anchors.fill: parent
focus: true
Keys.onReturnPressed: {
console.log("==========")
event.accepted = true
}
Component.onCompleted: {
obj.acceptEvents = false; // Remove connections
}
}
}

Make effective property alias, using Connections, Binding or Qt.binding

Is it possible to make mutual connection (without loop issue), using Connections, Binding or Qt.binding()?
It is possible to connect, say, SwipeView.currentIndex to TabBar.currentIndex and vise versa. When I page through SwipeView, then current tab of TabBar is also changed and vice versa. There is no binding loop of properties.
How to achieve this in Repeater? When some item created by Repeater became selected (in some sense), then I want to rebind its properties to another standalone item, which operates like editor of the selected item. The state of elements in selected item should depend on the state of items into the editor. But on selection changing I need to initialize items in editor using values from newly selected item.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
ApplicationWindow {
id: root
property var currentDayOfWeek: { "enabled": false, "time": "08:00" }
visible: true
SystemPalette {
id: palette
}
RowLayout {
anchors.centerIn: parent
Column {
ButtonGroup { id: weekButtonGroup }
Repeater {
model: 7
RowLayout {
Settings {
id: dayOfWeekSettings
category: Qt.locale("C").standaloneDayName(index, Locale.LongFormat)
property bool enabled: false
property string time: "08:00"
}
Label {
text: dayOfWeekSettings.time
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
color: dayOfWeekSettings.enabled ? palette.highlight : palette.base
}
Layout.fillHeight: true
}
RadioButton {
text: Qt.locale().standaloneDayName(index, Locale.LongFormat)
onCheckedChanged: {
if (checked) {
root.currentDayOfWeek = dayOfWeekSettings
timeField.text = Qt.binding(function() { return dayOfWeekSettings.time }) // ???
enabledCheckBox.checked = Qt.binding(function() { return dayOfWeekSettings.enabled }) // ???
}
}
ButtonGroup.group: weekButtonGroup
Layout.fillHeight: true
}
}
}
Layout.fillHeight: true
}
Column {
// Editor
TextField {
id: timeField
text: currentDayOfWeek.time // ???
inputMask: "00:00;_"
inputMethodHints: Qt.ImhDigitsOnly
}
CheckBox {
id: enabledCheckBox
checked: currentDayOfWeek.enabled // ???
}
Layout.fillHeight: true
}
}
}
How to achieve this? Is there canonical way to do this? Above example is not the solution.
The following code works as I want:
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
ApplicationWindow {
id: root
//property var currentDayOfWeek: { "enabled": false, "time": "08:00" }
visible: true
SystemPalette {
id: palette
}
RowLayout {
anchors.centerIn: parent
Column {
ButtonGroup { id: weekButtonGroup }
Repeater {
model: 7
RowLayout {
Settings {
id: dayOfWeekSettings
category: Qt.locale("C").standaloneDayName(index, Locale.LongFormat)
property bool enabled: false
property string time: "08:00"
Binding on enabled {
when: dayOfWeekSettingsCheckBox.checked
value: enabledCheckBox.checked
}
Binding on time {
when: dayOfWeekSettingsCheckBox.checked
value: timeField.text
}
}
Label {
text: dayOfWeekSettings.time
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
color: dayOfWeekSettings.enabled ? palette.highlight : palette.base
}
Layout.fillHeight: true
}
RadioButton {
id: dayOfWeekSettingsCheckBox
text: Qt.locale().standaloneDayName(index, Locale.LongFormat)
onCheckedChanged: {
if (checked) {
//root.currentDayOfWeek = dayOfWeekSettings
timeField.text = dayOfWeekSettings.time
enabledCheckBox.checked = dayOfWeekSettings.enabled
}
}
ButtonGroup.group: weekButtonGroup
Layout.fillHeight: true
}
}
}
Layout.fillHeight: true
}
Column {
id: editor
TextField {
id: timeField
//text: currentDayOfWeek.time
inputMask: "00:00;_"
inputMethodHints: Qt.ImhDigitsOnly
}
CheckBox {
id: enabledCheckBox
//checked: currentDayOfWeek.enabled
}
Layout.fillHeight: true
}
}
}
but there is an issue: editor filled with values in onCheckedChanged when checked became true. But what if when: in Bindings shoot first? Is it possible? Should I use delayed?
If I comment out:
timeField.text = dayOfWeekSettings.time
enabledCheckBox.checked = dayOfWeekSettings.enabled
and uncomment all the commented in above solution, then also all works fine. But the suspicion still persist.

QML reset dialog with tabview

I was trying to implement a tabbed Dialog in QML with the means to reset it to the intial values.
Since tabs are dynamically instantiated, none of the straight forward methods seem to work. The parent Dialog can not reference the inner Combobox and the Combobox can not reference the outer Dialog. How can this be achieved?
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// RESET COMBOBOX TO DEFAULT
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
}
}
}
}
}
}
}
I am quite unsure, if I got your question right as you say, you can not reference the Dialog from within the Combobox. I can not see the reason why.
Assuming the example of yours contains indeed your problem and all you want to do is to reset the values (and you know the original values) once the reset button is pressed, this is how I would solve it.
Using the Connections-type to connect to the Dialog's reset() from within the Combobox
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// **DONT** RESET COMBOBOX TO DEFAULT **HERE**
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
/// *** INTERESTING PART HERE! ***
Connections {
target: dlg
onReset: {
// RESET COMBOBOX TO DEFAULT **HERE** INSTEAD
valueChooser.currentIndex = 0
}
}
}
}
}
}
}
}
}

QtQuick TableView delete row doesn't work

I am using QtQuick TableView to show data from a database through QSqlTableModel and QSortFilterProxyModel.
The remove row operation doesn't work as it should. I have implemented a method in a class derived from QSortFilterProxyModel to call removeRows methods of QSortFilterProxyModel.
Everything works correctly as long as I have a filter setted in QSortFilterProxyModel ( i set it through a text box ). But when the filter is empty, the TableView rowCount property doesn't decrement and, after each delete, the currentRow property is set to rowCount-2. Why? To me it looks like a bug. Why it works when the filter is not empty?
Q_INVOKABLE void eliminaCliente(int row) {
removeRows(row,1);
}
import QtQuick 2.6
import QtQuick.Controls 1.5
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import Material 0.2
import Material.ListItems 0.1
ApplicationWindow {
id: root
visible: true
width: 1024
height: 640
title: qsTr("assiBase")
Page {
id: pLayout
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
Toolbar {
id: aBar
Layout.fillWidth: true
page: pLayout
backgroundColor: "#eeeeee"
RowLayout {
anchors.fill: parent
ActionButton {
id: addButton
Layout.leftMargin: 10
iconName: "content/add_circle"
backgroundColor: "#4CAF50"
onClicked: modalDialog.show()
isMiniSize: true
}
ActionButton {
id: editButton
iconName: "content/create"
isMiniSize: true
}
ActionButton {
id: deleteButton
iconName: "action/delete"
isMiniSize: true
backgroundColor: "#FF0000"
onClicked: {
if (dataView.currentRow != -1) {
var r = dataView.currentRow
console.log(dataView.currentRow)
sqlSortedData.eliminaCliente(dataView.currentRow)
console.log(dataView.rowCount)
//dataView.currentRow = r
}
}
}
RowLayout {
Layout.alignment: Qt.AlignRight
Icon {
name: "action/search"
Layout.alignment: Qt.AlignBottom
}
TextField {
id: searchBox
Layout.rightMargin: 20
Layout.minimumWidth: 400
Layout.preferredWidth: 500
placeholderText: qsTr("cerca...")
onTextChanged: sqlSortedData.setFilterWildcard(searchBox.text)
font.capitalization: Font.MixedCase
}
}
}
}
TableView {
anchors.top: aBar.bottom
anchors.topMargin: 3
sortIndicatorVisible: true
frameVisible: false
Layout.fillWidth: true
Layout.fillHeight: true
onSortIndicatorColumnChanged: model.sort(sortIndicatorColumn, sortIndicatorOrder)
onSortIndicatorOrderChanged: model.sort(sortIndicatorColumn, sortIndicatorOrder)
id: dataView
TableViewColumn {
role: "ID"
visible: false
}
TableViewColumn {
role: "Nome"
title: "Nome"
width: 200
}
TableViewColumn {
role: "Residenza"
title: "Residenza"
width: 200
}
TableViewColumn {
role: "Assicurazione"
title: "Assicurazione"
width: 200
}
TableViewColumn {
width: 128
resizable: false
delegate: RowLayout {
anchors.fill: parent
clip: true
IconButton {
iconName: "content/create"
onClicked: console.log(styleData.row)
}
IconButton {
iconName: "action/delete"
onClicked: {
console.log(styleData.row)
sqlSortedData.eliminaCliente(styleData.row)
console.log(dataView.rowCount)
}
}
}
}
model: sqlSortedData
}
}
}
Take a look at here. There is an workaround suggestion.
It seems like QSortFilterProxyModel needs some love for a long time.

Get active Tab from TabView and change item property

In the context of a dual-pane file manager, I have two TabView items side by side, each contains multiple tabs of course, and each Tab loads a TableView showing the content of a specific directory using FolderListModel.
SplitView
TabView
Tab
Tab
TabView
Tab
My current task is to implement a toolbar button to toggle the showHidden property of the FolderListModel instance shown in the active tab. Therefore, I need a way to find out what the currently active tab is.
Next, once I get the active Tab, I need to change Tab.item.some_property, in particular, the property of interest is show_hidden, which is an alias to the showHidden property of the underlying FolderListModel. For example, a hard-coded scenario would be:
ToolButton {
onClicked: {
tab1.item.show_hidden = false;
tab1.destroy(); // need "refresh" instead
}
}
First I need to get tab1 based on whether it is active, and second, after I change show_hidden, the view doesn't refresh by itself, so I need to call some kind of reload function, but which? Or maybe reload isn't the best way to do it? Is it possible to do it using a custom signal handler? (Again I can only think conceptually without knowing how to implement it.)
As suggested I'm posting a running example below:
/* main.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 1280
height: 700
toolBar: ToolBar {
RowLayout {
anchors.fill: parent
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
tab1A.item.show_hidden = false;
// tab1A.destroy(); // fixme how to refresh the view?
}
}
}
}
Item {
anchors.fill: parent
SplitView {
id: splitView
anchors.fill: parent
TabView {
id: tabView1
width: splitView.width / 2
Tab {
id: tab1A
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
}
Tab {
title: qsTr("Folder")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
}
}
TabView {
id: tabView2
Tab {
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
}
}
}
}
}
/* dirview.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
TableView {
property alias folder_url: folderModel.folder
property alias show_hidden: folderModel.showHidden
id: tableView
anchors.fill: parent
TableViewColumn {
role: "fileName"
title: qsTr("Name")
width: tableView.width * 0.7
}
TableViewColumn {
role: "fileSize"
title: qsTr("Size")
width: tableView.width * 0.2
}
FolderListModel {
id: folderModel
nameFilters: ["*"]
showHidden: true
showDirsFirst: true
showDotAndDotDot: true
}
model: folderModel
}
Thank you.
Noticed something weird: Tab.item.folder_url has the right info, however, Tab.item.show_hidden is always false, even if I remove the line where I manually set it to false. This is hard to understand as I initially set FolderListModel.showHidden to true in dirview.qml.
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
var cur_tab_idx = tabView1.currentIndex;
console.log(tabView1.getTab(cur_tab_idx).item.folder_url);
console.log(tabView1.getTab(cur_tab_idx).item.show_hidden);
}
}
Here is an explanation of how I have got it to work.
I solved first problem using focus flag. When current Tab in TabView changes one Tab gains focus and the other one looses. So by using onFocusChanged() signal you can know exactly when one Tab becomes active or inactive.
The focus of Tab does not change when focus of whole TabView changes. Because of this I created an Array (named tabs in code) containing references to every TabView and Tab it contains. With this when TabView becomes inactive I can set focus of its Tab objects to false using simple for.
Second problem was more tricky. I see no other option of turning showHidden flag off than destroying and creating a new FolderListModel. We cannot (or I could not :) ) provide model to TableView dynamically so I made a ListModel. Advantage of regular ListModel compared to FolderListModel is that it can be cleared and refilled with data. Every time folder_url or show_hidden changes I destroy current FolderListModel and create a new one. After it is created I rewrite its data to the ListModel.
Here is the working code.
main.qml
/* main.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 1280
height: 700
property var tabs: [
[tabView1, [tab1A, tab1B]],
[tabView2, [tab2A]]
]
toolBar: ToolBar {
RowLayout {
anchors.fill: parent
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
tab1A.item.show_hidden = false;
// tab1A.destroy(); // fixme how to refresh the view?
}
}
}
}
Item {
anchors.fill: parent
SplitView {
id: splitView
anchors.fill: parent
TabView {
id: tabView1
width: splitView.width / 2
Tab {
id: tab1A
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
onFocusChanged: {
item.show_hidden = focus
}
}
onFocusChanged: {
if (!focus)
for (var i = 0 ; i < tabs[0][1].length ; i++)
tabs[0][1][i].focus = false
}
Tab {
id: tab1B
title: qsTr("Folder")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
onFocusChanged: {
item.show_hidden = focus
}
}
}
TabView {
id: tabView2
Tab {
id: tab2A
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
onFocusChanged: {
item.show_hidden = focus
}
}
onFocusChanged: {
if (!focus)
for (var i = 0 ; i < tabs[1][1].length ; i++)
tabs[1][1][i].focus = false
}
}
}
}
}
dirview.qml
/* dirview.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
TableView {
property string folder_url
property bool show_hidden
id: tableView
anchors.fill: parent
TableViewColumn {
role: "fileName"
title: qsTr("Name")
width: tableView.width * 0.7
}
TableViewColumn {
role: "fileSize"
title: qsTr("Size")
width: tableView.width * 0.2
}
ListModel {
id: secondListModel
}
property var fm
property int folderModelCount
onFolder_urlChanged: {
reloadFolderModel()
}
onShow_hiddenChanged: {
reloadFolderModel()
}
onFolderModelCountChanged: {
resetSecondListModel()
}
function reloadFolderModel() {
folderModelCount = 0
if (typeof(fm) !== "undefined")
fm.destroy()
var component = Qt.createComponent("foldermodel.qml")
if (component.status === Component.Ready)
fm = component.createObject(
tableView, {"folder_url": folder_url, "show_hidden": show_hidden})
else
console.error(component.errorString())
folderModelCount =
Qt.binding(function(){return fm.folderModel.count})
}
function resetSecondListModel() {
secondListModel.clear()
for (var i = 0 ; i < folderModelCount ; i++) {
secondListModel.append({
"fileName": fm.folderModel.get(i, "fileName"),
"filePath": fm.folderModel.get(i, "filePath"),
"fileURL": fm.folderModel.get(i, "fileURL"),
"fileBaseName": fm.folderModel.get(i, "fileBaseName"),
"fileSuffix": fm.folderModel.get(i, "fileSuffix"),
"fileSize": fm.folderModel.get(i, "fileSize"),
"fileModified": fm.folderModel.get(i, "fileModified"),
"fileAccessed": fm.folderModel.get(i, "fileAccessed"),
"fileIsDir": fm.folderModel.get(i, "fileIsDir")
})
}
}
model: secondListModel
}
foldermodel.qml (add this file)
import QtQuick 2.4
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
Item {
property string folder_url
property bool show_hidden
property alias folderModel: folderModelObject
FolderListModel {
id: folderModelObject
nameFilters: ["*"]
folder: folder_url
showHidden: show_hidden
showDirsFirst: true
showDotAndDotDot: true
}
}
Now you understand why QML is not very flexible. :)
Solution to finding the current Tab in the active TabView (pane): declare a property of SplitView to store the TabView that has activeFocus.
A StatusBar is added to demo the functionality.
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 1280
height: 700
toolBar: ToolBar {
RowLayout {
anchors.fill: parent
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
// Demo: get the current tab of the active pane
var active_pane = splitView.activePane;
var cur_tab_idx = active_pane.currentIndex;
var cur_tab_item = active_pane.getTab(cur_tab_idx).item;
testLabel.text = cur_tab_item.folder_url;
}
}
}
}
SplitView {
id: splitView
property TabView activePane: tabView1
anchors.fill: parent
TabView {
id: tabView1
width: splitView.width / 2
onActiveFocusChanged: {
if (activeFocus) {
splitView.activePane = tabView1;
}
}
Tab {
title: qsTr("tmp")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
}
Tab {
title: qsTr("home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
}
}
TabView {
id: tabView2
onActiveFocusChanged: {
if (activeFocus) {
splitView.activePane = tabView2;
}
}
Tab {
title: qsTr("bin")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///bin";
}
}
}
}
statusBar: StatusBar {
RowLayout {
Label {
text: (splitView.activePane === tabView1) ? "Pane 1" : "Pane 2"
}
Label {
id: testLabel
}
}
}
}

Resources