Get active Tab from TabView and change item property - qt

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

Related

QML propagate key events from ListView delegates

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

Load SwipeView pages dynamically

I have created the following MWE (Qt 5.13.0):
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 3;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Loader
{
sourceComponent: theSingleComp;
Component
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " +
(theSwipeView.currentIndex - 1);
}
}
}
}
}
}
}
In my program, I have an unique component (theSingleComp) and multiple components behind him (theMultiComp). As for now, I need to implement the following functionality:
In case the model used for theMultiComp has only 1 item, display only this item and not the theSingleComp. In case the are more theMultiComp items, display it like now. It seems to me that there is no possibility for this to work if I keep the items defined statically. But on the other hand, I don't know how to do this dynamically, since there is a case in which one of the components should not be displayed at all. I tried an approach like this:
sourceComponent: (itemsNo > 1) ? theSingleComp : null;
But then the page for this null component is still created.
Your problem is that Loader is an Item and SwipeView creates a page for it even if it doesn't have a source component.
To solve this problem you can use Repeater instead with a model of 1 (or 0 to disable it). Repeater is also an Item but it has some special code under the hood to be ignored by containers.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
id: window
property int itemsNo: 0
visible: true
width: 480
height: 480
SwipeView {
id: theSwipeView
anchors.fill: parent
Repeater {
model: window.itemsNo > 1 ? 1 : 0
Page {
Text {
text: "The single one"
}
}
}
Repeater {
model: window.itemsNo
Page {
Text {
text: "The multi one " + model.index
}
}
}
}
}
(I've simplified your code to remove the explicit Components and the Loaders)
I have come up with the following solution but I am not happy with it. It's very hacky and the user can see how the page index changes.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 2;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Component.onCompleted:
{
if (itemsNo > 1)
insertItem(0, theSingleComp);
set0IndexTimer.start();
}
Timer
{
id: set0IndexTimer;
interval: 1;
running: false;
repeat: false;
onTriggered: theSwipeView.setCurrentIndex(0);
}
onCurrentIndexChanged: console.log("page: ", currentIndex);
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " + theSwipeView.currentIndex;
}
}
}
}
}
}
Item
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
I am still seeking some other examples.

QML GridView hide one cell

It's possible to hide certain cell in GridView? I set delegate, but I still got empty place for this GridView element. It's possible to do this?
visible: false
width: 0
height: 0
As was said in the comment, you can indeed use a QSortFilterProxy model, but here is another solution. You could implement a pure-QML FilterProxyModel, using DelegateModel and DelegateModelGroup
import QtQuick 2.10
import QtQml.Models 2.3
DelegateModel {
property var filterAccepts: function(item) {
return true
}
onFilterAcceptsChanged: refilter()
function refilter() {
if(hidden.count>0)
hidden.setGroups(0, hidden.count, "default")
if(items.count>0)
items.setGroups(0, items.count, "default")
}
function filter() {
while (unsortedItems.count > 0) {
var item = unsortedItems.get(0)
if(filterAccepts(item.model))
item.groups = "items"
else
item.groups = "hidden"
}
}
items.includeByDefault: false
groups: [
DelegateModelGroup {
id: default
name: "default"
includeByDefault: true
onChanged: filter()
},
DelegateModelGroup {
id: hidden
name: "hidden"
}
]
}
Explanation: Every time an item is added to the model, it is added in the "default" group, which triggers the onChanged handler that will call filter().
Filter() will look for items in the default group, and move them either in the items group (which will make them visible) or to the hidden group, depending on the result of the filterAccepts function.
When filterAccept changes, the SortProxyModel will move every item to the default group to trigger a global refiltering.
You can then use your proxy model like this:
FilterProxyModel
{
id: filterProxyModel
model: <YourBaseModel>
delegate: <YourDelegate>
filterAccepts: function(item) {
// Eg: Only "small" items will be displayed
return item.size == "small"
}
}
GridView
{
anchors.fill: parent
model: filterProxyModel
cellHeight: 100
cellWidth: 100
}
Another simplified solution with QML only, based on hiding items.
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Layouts 1.2
Window {
id: window
title: "test"
visible: true
width: 400
height: 400
GridLayout {
id: layout
anchors.fill: parent
columns: 4
Repeater {
id: container
model: 20
Rectangle {
id: item
property int itemIndex: index
Layout.fillWidth: true
height: 60
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
Text {
anchors.centerIn: parent
text:item.itemIndex
}
MouseArea {
anchors.fill: parent
onClicked: {
item.visible = false;
layout.doIt(item.itemIndex);
}
}
}
}
function doIt(index)
{
var item = container.itemAt(index);
if(item)
item.visible = false;
for(var i = index - 1;i >= 0;i --)
{
var prev_item = container.itemAt(i);
if(prev_item.visible) {
prev_item.Layout.columnSpan ++;
break;
}
}
}
}
}

Adding TabButton dynamically to TabBar in QML

I am trying to add a tabButton to TabBar dynamically on pressing a button but i have spent a lot of time searching but i am not getting how to add, below is the code which i am working on :
MyTabButton.qml
import QtQuick 2.4
import QtQuick.Controls 2.2
Item
{
property int BtnWidth:0
property int BtnHeight:0
property string BtnText: ""
property bool isChecked : false
TabButton
{
id:tabBtn
text:BtnText
width:BtnWidth
height:BtnHeight
}
}
MainForm.qml
import QtQuick 2.4
import QtQuick.Controls 2.2
Rectangle
{
Button
{
id:button
width:100
height:100
anchors.top:parent.top
text:qStr("Add")
onClicked{
//How to add logic here to add tab in below tabBar.
}
}
TabBar
{
id:tabBar
anchors.top:button.bottom
width:500
height:500
}
}
Example:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: window
width: 360
height: 630
visible: true
header: TabBar {
id: tabBar
}
Component {
id: tabButton
TabButton { }
}
Button {
text: "Add"
anchors.centerIn: parent
onClicked: {
var tab = tabButton.createObject(tabBar, {text: "Tab " + tabBar.count})
tabBar.addItem(tab)
}
}
}
You need to have something like a Component that is a TabButton. Your file MyTabButton.qml won't result in a TabButton, but instead an Item containing a TabButton, but with this, your TabBar does not know what to do.
So your file will need to have TabButton as root element
//MyTabButton.qml
import QtQuick 2.4
import QtQuick.Controls 2.2
TabButton
{
id: tabBtn
// customize as you like
}
Then you create a Component of this in your file where you want to use it. (e.g. main.qml)
import QtQuick 2.4
import QtQuick.Controls 2.0
ApplicationWindow {
width: 800
height: 600
visible: true
TabBar {
id: tabBar
width: 800
height: 50
}
// The component is like a factory for MyTabButtons now.
// Use myTabButton.createObject(parent, jsobject-with-property-assignments) to create instances.
Component {
id: myTabButton
MyTabButton {
/// EDIT ACCORDING TO YOUR COMMENTS ***
Connections {
target: tabBar
onCurrentIndexChanged: doSomething()
}
/// EDIT OVER
}
}
Button {
anchors.centerIn: parent
// Create a object out of the component, and add it to the container
onClicked: tabBar.addItem(myTabButton.createObject(tabBar /*, { object to set properties }*/))
}
}
TabBar inherits Container, which has addItem().
Try it in Window
Row {
anchors.fill: parent
TabBar {
id: tabBar
currentIndex: 0
width: parent.width - addButton.width
TabButton { text: "TabButton" }
}
Component {
id: tabButton
TabButton { text: "TabButton" }
}
Button {
id: addButton
text: "+"
flat: true
onClicked: {
tabBar.addItem(tabButton.createObject(tabBar))
console.log("added:", tabBar.itemAt(tabBar.count - 1))
}
}
}

how to send text field values from one qml to another qml page in cascades blackberry

I am new to development on BB cascades. I've created two QML Pages. I want to pass data from one QML Page to another.
I want to pass the values phonenumber(id:phonenumber) and amount ( id:amount) from mobile.qml to payment.qml.
Please anyone help me out. Thank you in advance.
Mobile.qml:
import bb.cascades 1.4
import bb.data 1.0
Page {
onCreationCompleted: {
getData()
getCircle()
}
Container {
background: backgroundPaint.imagePaint
attachedObjects: [
ImagePaintDefinition {
id: backgroundPaint
imageSource: "asset:///images/background.png"
}
]
TextField {
id:phonenumber
hintText: "Enter Phone Number"
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
topMargin: ui.du(3)
// On text change the label text is updated.
input
{
keyLayout: KeyLayout.Text
}
}
TextField {
id:amount
hintText: "Enter Amount"
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
topMargin: ui.du(3)
// On text change the label text is updated.
input
{
keyLayout: KeyLayout.Text
}
}
Button {
id: newButton
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
topMargin: ui.du(3)
text: "Recharge"
appearance: ControlAppearance.Primary
color: Color.create("#F93249")
onClicked: {
var blogpage = goToWebView.createObject();
navigationPane.push(blogpage);
}
attachedObjects: ComponentDefinition {
id: goToWebView
source: "payment.qml"
}
}
}
attachedObjects: [
ComponentDefinition {
id: newOptionDef
Option {}
}
]
}
payment.qml:
import bb.cascades 1.4
Page {
Container {
background: backgroundPaint.imagePaint
attachedObjects: [
ImagePaintDefinition {
id: backgroundPaint
imageSource: "asset:///images/background.png"
}
]
}
}
Next time please post only code related to problem. As for your problem you can use parent as a proxy to access one item from another one.
For example, assume we have a component:
Page.qml
import QtQuick 2.4
import QtQuick.Controls 1.2
Item {
id:page
width: 200
height: 200
property int callee
function func() {
txt.text = txt.text + " (changed)"
}
Text {
id: txt
anchors.centerIn: parent
text: "click me"
MouseArea {
anchors.fill: parent
onClicked: {
page.parent.proxyFunction(page.callee);
}
}
}
}
and so Item contains several Pages:
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
width: 400
height: 200
Row {
anchors.fill: parent
function proxyFunction(page) {
children[page].func();
}
Page {callee: 1}
Page {callee: 0}
}
}
So here as you can see clicking Text in one of Pages triggers changing Text in another Page.
In your Page add these lines:
import bb.cascades 1.4
import bb.data 1.0
Page {
id: mobilePage // ADD THIS
property string m_amount // ADD THIS
property string m_phoneNumber // ADD THIS
// the rest of the code
onClicked: { // Buttons onClick
mobilePage.m_amount = amount.text // ADD THIS
mobilePage.m_phoneNumber = phonenumber.text // ADD THIS
var blogpage = goToWebView.createObject()
navigationPane.push(blogpage)
}
}
Now in payment.qml you can use this:
console.log(mobilePage.m_amount)
console.log(mobilePage.m_phoneNumber)
You have to create a property in payment.qml at page level.
Page{
Id: payment
Properly string phonenumber;
Properly string amount;
Container{
Label{
Id: lblphonenumber
text: phonenumber
}
Label{
Id: lblamout
text: amount
}
}
in your main.qml you have to do this:
onClicked: {
var blogpage = goToWebView.createObject();
blogpage.phonenumber = yourvalue;
blogpage.amount = yourvalue;
navigationPane.push(blogpage);
}
This is it :)

Resources