Suppose we have a QML file like the following:
Window {
Component.onCompleted: rect.color ="green"
TabView {
Tab {
Rectangle {
id: rect
color: "white"
}
}
}
}
When I run this code, for some reason, it gives me this error:
ReferenceError: rect is not defined
Somebody can say it's a scope problem but the following code works fine:
Window {
Component.onCompleted: rect.color ="green"
Item {
Item {
Rectangle {
id: rect
color: "white"
}
}
}
}
In my case I have a big form with tabs and controls inside it and I pass all the controls to several functions to validate the form, i.e. some code like this:
function onClose() {
validate(control1);
validate(control2);
// etc
}
but while accessing controls by id I get the above error.
How about just binding the rectangle to a color parameter instead of a hardcoded value?
This wil separate your Model and View code further to make it more readable elsewhere in your project as well...try:
Window {
property string myColor: "White"
Component.onCompleted: myColor = "Green"
TabView {
Tab {
Rectangle {
id: rect
color: myColor
}
}
}
}
To view the QML object tree, you need to start your project in debug mode. Then split your code window so that the "Locals and Expressions" view is showing (checkbox ticked on the right side). You will see your root item and all the other controls nested in a tree and now you can directly edit the values of their properties, and the changes will render immediately. There is a youtube video tutorial on debugging: https://youtu.be/mPXn6L2Wftc?t=19m55s
I'm not sure if the tree would give you access to the controls at runtime, but it might help you with debugging.
Ok, since Tab item cannot be accessed from outside I think it can be done in this way:
TabView {
id: tabView
Tab {
title: "tab1"
function validate() { /* validate all the controls related to tab1 only */ }
Item { id: item1 }
Item { id: item2 }
}
Tab {
title: "tab2"
function validate() { /* validate all the controls related to tab2 only */ }
Item { id: item3 }
Item { id: item4 }
}
function validateTabs() {
for(var i = 0; i < tabView.count;i ++) {
var tab = tabView.getTab(i);
if(tab && tab.active && tab.item.validate) {
if(!tab.item.validate())
return false;
}
}
return true;
}
}
The good point that if some Tab wasn't opened and so not changed it will not be validated.
Related
How can component inform parents when certain action happens? I mean something like event.buble in JavaScript. https://www.w3docs.com/learn-javascript/bubbling-and-capturing.html
For example some elements in dialog can send a "Ok" or "Cancel" action.
Parent item does not know all the child items items in advance. I would add something like:
Widget {
signal cancel
signal ok
...
}
ParentItem {
id: myParentItem
onCancel { ... }
onOk { ... }
Widget {
id: first
}
Widget {
id: second
}
// no connection section needed. Auto-connect signals by name.
}
}
Edit:
Note: adding separate Widget and then connection is a bit impractical. Some one can forget to add one or other, moreover when deleting and renaming one can remove only one part or rename one part incorrectly.
Calling parent.functionName is impractival too because then such Widget can be used only in parents having functionName.
One idea is to search through all the children and check their type. If they are the Widget type, then connect their signals to some ParentItem function. I haven't tested this code, but something like it should work.
ParentItem {
id: myParentItem
function doCancel() { ... }
function doOk() { ... }
Component.onCompleted: {
for (var i = 0; i < children.length; i++) {
if (children[i] instanceOf Widget) {
children[i].onOk.connect(doOk);
children[i].onCancel.connect(doCancel);
}
}
}
Widget {
id: first
}
Widget {
id: second
}
}
I am trying to use a property of the top-most component in the QML file inside a javascript function within some deeply nested structure, but it I'll get an error stating the property/variable name is undefined.
The confusing part is, that access to that very variable works in the line before.
Here's a part of the QML:
Page {
id: page
property var modelParam
property var albumNameParam
SilicaGridView {
id: grid
header: PageHeader { title: albumNameParam }
cellWidth: width / 3
cellHeight: width / 3
anchors.fill: parent
model: page.modelParam
delegate: GridItem {
id: gridItem
menu: ContextMenu {
MenuItem {
text: qsTr("Delete" )
onClicked: {
var dialog = pageStack.push(...)
dialog.accepted.connect(function() {
var res = modelParam.deleteSelected(); // works
modelParam.clearSelection(); // gives error
})
}
}
One solution I have found to this is:
onClicked: {
var theModel = modelParam
var dialog = pageStack.push(...)
dialog.accepted.connect(function() {
var res = theModel.deleteSelected(); // works
theModel.clearSelection(); // works
})
}
In this case, probably, the variable gets captured in the JS closure and is thus available inside the callback function.
However I don't understand how the first example can work partially? What is the proper approach in this case?
Try:
page.modelParam.clearSelection();
I am using Qt 5.9.3. I have a QML Item like so:
Item {
id: root
width : 400
height: 700
}
How do I track when the Item was first time visible on screen of my application window? onVisibilityChanged?
How do I do track the same for a MyQuickItem which is something like this:
// C++ definition
class MyQuickItem : public QQuickitem {
}
// QML side
MyQuickItem {
id: some_id
}
I guess the complete code suppose like:
ApplicationWindow{
visible: true
onVisibilityChanged: {
console.log("ApplicationWindow is ", visibility)
}
Component.onCompleted: {
console.log("ApplicationWindow is onCompleted")
}
Item {
id: root
width : 400
height: 700
onVisibleChanged: {
console.log("Item is ", visible)
}
Component.onCompleted: {
console.log("Item is onCompleted")
}
}
}
Obviously the onVisibilityChanged of ApplicationWindow will trigger when the visibility property is changed, so it triggers when you minimize and reshow the window. As you mention the first time visible, so I think you are looking for Component.onCompleted.
Is there any way to override ComboBox MouseArea to ignore wheel event instead of changing current index? ComboBox itself has no option to change wheel focus behaviour. So far I've tried to override onWheel from CB MouseArea with code like this:
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
console.log(combobox_ctrl.children[i])
console.log(combobox_ctrl.children[i].hasOwnProperty('onWheel'))
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')) {
console.log(combobox_ctrl.children[i]['onWheel'])
combobox_ctrl.children[i]['onWheel'] = function() { console.log("CB on wheel!") }
//combobox_ctrl.children[i]onWheel = function() { console.log("CB on wheel!")
//combobox_ctrl.children[i].destroy()
}
}
}
}
But I get
TypeError: Cannot assign to read-only property "wheel"
Did anyone was able to disable wheel events on ComboBox in Qml?
// EDIT
for example in Slider control I was able to remove wheel event handling like this:
Slider {
Component.onCompleted: {
for (var i = 0; i < slider.children.length; ++i) {
console.log(slider.children[i])
if (slider.children[i].hasOwnProperty("onVerticalWheelMoved") && slider.children[i].hasOwnProperty("onHorizontalWheelMoved")) {
console.log("Found wheel area!")
slider.children[i].destroy()
}
}
}
}
But in slider WheelArea is not responsible for handling "click" events.
You can place MouseArea over ComboBox and steel wheel event.
ComboBox {
anchors.centerIn: parent
model: [ "Banana", "Apple", "Coconut" ]
MouseArea {
anchors.fill: parent
onWheel: {
// do nothing
}
onPressed: {
// propogate to ComboBox
mouse.accepted = false;
}
onReleased: {
// propogate to ComboBox
mouse.accepted = false;
}
}
}
It's not currently possible, as ComboBox is not derived from MouseArea, but FocusScope, which has no support for these kinds of events.
A similar problem was mentioned in a suggestion recently:
Disable mouse wheel scroll event on QtQuick.Controls
If you're after a hacky way of doing it, it seems like the only option you have left is to apply a patch to ComboBox.qml that removes the onWheel handler:
diff --git a/src/controls/ComboBox.qml b/src/controls/ComboBox.qml
index 4e29dfe..3413cac 100644
--- a/src/controls/ComboBox.qml
+++ b/src/controls/ComboBox.qml
## -407,13 +407,6 ## Control {
popup.toggleShow()
overridePressed = false
}
- onWheel: {
- if (wheel.angleDelta.y > 0) {
- __selectPrevItem();
- } else if (wheel.angleDelta.y < 0){
- __selectNextItem();
- }
- }
}
Another alternative that doesn't involve modifying Qt code would be to add an intermediate MouseArea above ComboBox's, and then somehow only forward specific events through to ComboBox's MouseArea. Or, create a custom C++ item that does the equivalent. You may have more control that way.
Ok. After hacking around I've managed to come with solution that is acceptable for me but may introduce some regressions in some situations. pressed and hovered properties are no longer usable
import QtQuick.Controls.Private 1.0
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel') && combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
if (!Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
if (Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
}
}
This way we can mimic mouse area that was originaly inside the ComboBox. Popup is shown as it was (at least I didn't see any regresion in it yet). However two properties are inaccesible right now
I created a separate file called NonScrollingComboBox.qml with the following code following this post: https://stackoverflow.com/a/33080217/969016
Now I can just use NonScrollingComboBox as a component instead of ComboBox on places where I don't want the mouse scroll to change the value
import QtQuick 2.0
import QtQuick.Controls 1.4
ComboBox {
id: combobox_ctrl
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')
&& combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
combobox_ctrl.__popup.toggleShow()
}
}
}
usage:
NonScrollingComboBox {
anchors.verticalCenter: parent.verticalCenter
model: ["item one", "item 2"]
}
This seems to apply only to Qt Quick Controls 1 ComboBox. On Qt Quick Controls 2 ComboBox the wheel mouse event is not enabled by default and can be enabled manually by setting to true the property wheelEnabled (documented in the base class Control). Also the combobox won't keep a "focus" on mouse events so you can freely use the wheel on other mouse areas by just entering them.
I want to create MenuItem dynamically and add onTriggered callback for it.
var m = backContextMenu.insertItem(0,text)
m.onTriggered = ..? //function(x) { console.log('asd') }
It gives me error Cannot assign to read-only property "onTriggered". What can I do? Maybe I should create my menu using Qt.createQmlObject('qml code...')? Or maybe I should create MenuItem template declaratively and clone this object somehow?
P.S. I use MenuItem from QtQuick.Controls in Qt 5.2
You can use Connections QML item to create dynamic connections using createQmlObject function :
var item = menuContext.insertItem(0, "menu item")
Qt.createQmlObject("import QtQuick 2.0;Connections{onTriggered:foo()}",item)
Simply, you can create also direct connection :
item.onTriggered.connect(foo)
(MenuItem is necessarily Qt Quick 2 and Qt 5.1)
I was lucky to find another way to add Menu items dynamically: via Instantiator.
Menu {
id: recentFilesMenu
Instantiator {
model: recentFilesModel
MenuItem {
text: model.fileName
}
onObjectAdded: recentFilesMenu.insertItem(index, object)
onObjectRemoved: recentFilesMenu.removeItem(object)
}
MenuSeparator {
visible: recentFilesModel.count > 0
}
MenuItem {
text: "Clear menu"
enabled: recentFilesModel.count > 0
onTriggered: recentFilesModel.clear()
}
}
Sample code will explain everything:
Menu {
id: suggestionsMenu
property var suggestions: []
Instantiator {
model: suggestionsMenu.suggestions
onObjectAdded: suggestionsMenu.insertItem(index, object)
onObjectRemoved: suggestionsMenu.removeItem(object)
delegate: MenuItem {
text: suggestionsMenu.suggestions[index]
onTriggered: {
console.log(index + " : " + suggestionsMenu.suggestions[index])
}
}
}
}
Now in code you only need to call such 3 lines:
onShowSuggestions: {
console.log("Showing suggestions")
console.log(suggestions)
suggestionsMenu.clear()
suggestionsMenu.suggestions = []
suggestionsMenu.suggestions = suggestions
suggestionsMenu.popup()
}
Links:
Menu QML
Instantiator QML Type
Article #1