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
Related
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 have a QML application where I'm creating lists of elements received from JavaScript. Using the details from this answer I'm populating the model as a JS array, and it works great. However, I'd like it so that when properties of the JavaScript objects change that ListView items driven from them update live.
Here's a simple test app showing the problem. The ListView is properly populated with MyRow instances showing the correct id/title, but when the rand property is changed by the timer, the List rows are unchanged (they show 0 for the last item).
MyRow.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
property var obj
color:'#eeeeff'; height:20
RowLayout {
anchors.fill:parent
Text { text:obj.id }
Text { text:obj.title; Layout.fillWidth:true }
Text { text:obj.rand }
}
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id:app; visible:true; width:200; height:100
property var database: ({"17":"World","42":"Hello"})
property var objById: ({})
function getObj(id){
if (!objById[id]) objById[id] = { id:id, title:database[id], rand:0 };
return objById[id];
}
ListView {
id:mylist
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(mylist.model[index])
}
}
Timer { // Update every object's rand value every second
interval:1000; running:true; repeat:true
onTriggered: {
Object.keys(objById).forEach(function(id){
objById[id].rand = Math.random()*100<<0;
})
}
}
}
How can I get the delegate's Text items to update their text when properties of the objects change?
The easiest (only?) way to get property bindings to work properly is to create real Qt objects to hook the values to. If you don't want to use a ListModel (because you want to quickly populate a model with items from a master library), then you can use createObject() to generate objects and pass them to your delegate.
Here's an updated main.qml that works as desired:
Window {
// ...same as above...
Component { // Creates real Qt objects with bindable properties
id:objFactory
QtObject {
property int id
property string title
property int rand:0
}
}
function getObj(id){
if (!objById[id])
objById[id] = objFactory.createObject( app, {id:id,title:database[id]} );
return objById[id];
}
// ...same as above...
}
Additionally, you may wish to change the property var obj in MyRow.qml to a more specific property QtObject obj (or a more specific object type, depending on what you pass in).
Finally, note that it's slightly cleaner/simpler to use modelData instead of mylist.model[index]:
ListView {
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(modelData)
}
}
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.
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.
Hello,
I'm using Qt 5.2.1 for Android on windows
I've gone through the different drag and drop examples provided with Qt, but non of them address my problem, although I found This Example copy Text via drag and drop, but I want to copy a whole Item.
Note: I don't want external copy, just inside the application.
Thank you
Am a noop, this is just an idea,
Create a new Object ( same as your dragged Item )
Copy the Dragged item properties into the newly created object
Destroy the source
lets say you have and Item:
// DraggedItem.qml
import QtQuick 2.0
Item {
id:draggedItem
property string itemText
property string imageSource
Image {
id: img
source: imageSource
}
Text {
id: txt
text: qsTr("text")
}
}
Then you can have your drop area like this:
import QtQuick 2.0
Item {
id:dropItem
DropArea {
id: dropArea
anchors.fill: parent
onDropped: {
if (drop.source.parent !== draggedItem) {
// Create new Item
var newItem = createNewItem();
// Copy the source item properties into the new one
newItem.width = parent.width;
newItem.height = drop.source.height;
newItem.itemText = drop.source.itemText
newItem.imageSource = drop.source.imageSource
// destroy the source
drop.source.destroy();
}
}
}
function createNewItem() {
var component = Qt.createComponent("DraggedItem.qml");
if (component.status === Component.Ready)
{
var newItem = component.createObject(parent, {"x":0,
"y":0});
if (newItem === null)
console.log("Error creating new Item");
}
return newItem
}
}
This code is not tested. There should be a better way to do this