how can I transfer Component.onCompleted:{} method from ui.qml to .qml? - qt

I am developing a project by seperating ui.qml files and .qml files . So, I am writing the functionality codes(javascript codes) into .qml file and I am writing design codes in ui.qml file . However I have a problem with using Component.onComplete functionality in .qml file.
for example :
MapDisplayForm.ui.qml
Item{
id:item1
property alias map1
Map{
id: map1
Component.OnCompleted : {
//this is the function that i should write in Map.qml
}
}
}
MapDisplay.qml
MapDisplayForm{
//it does not accept map1.oncompleted here
}

You can solve this problem using the QML Connections item.
As an example in my form1.qml I have
SlideToAction {
id: sosSlider
height: 80
Component.onCompleted: {
if (window.getHomeLocation()) {
normalBMargin = 0
} else {
normalBMargin = -80
}
}
}
To put this into the form1Form.ui.qml and form1.qml format:
form1Form.ui.qml:
property alias sosSlider:sosSlider
SlideToAction {
id: sosSlider
height: 80
}
The property alias sosSlider:sosSlider is like saying this property is public and won't work otherwise.
form1.qml:
Connections {
target: sosSlider
Component.onCompleted {
// Your code here
}
}
This solved the problem for me.
I like the idea of splitting up the UI and functionality. It reminds of the MVC style that Ionic/Angular and also Django views use. It makes it really easy to play around with the UI without worrying about dependencies or other qml restrictions.
Hope this helps!

You can do something like the following:
Item {
id:item1
signal mapCompleted() // <-- custom signal
property alias mapDisplay
Map {
id: map1
Component.OnCompleted : {
item1.mapCompleted(); // <-- emit custom signal
}
}
}
and so:
MapForm {
onMapCompleted: { // <-- handle custom signal
}
}

UI files are meant to be used ONLY with the designer (which does not work pretty well with imperative JS). The solutions can be:
Remove the .ui or use common .qml files
Use an property to handle the onCompleted event and use that property in your qml file to do what you want like the next example.
MapDisplayForm.ui.qml
Item {
id:item1
property bool propertyOnCompleted: false
Map {
id: map1
Component.onCompleted: propertyOnCompleted = true
}
MapDisplay.qml
MapDisplayForm {
onPropertyOnCompletedChanged: {
console.log("Map1 Completed")
}
}

Related

How to listen for property collection size changes

I want to automatically show/hide view depending on a property's collection size, here's the code:
QtObject {
property var controlWindow: Window {
property var collection: []
signal sigAddElement(var element)
onSigAddElement: {
collection.push(element)
}
signal sigEraseAllElements()
onSigEraseAllElements: {
collection.length = 0
}
onCollectionChanged: {
console.log("collection.len = " + collection.length)
}
Rectangle {
id: autoHidableView
visible: collection.length != 0
}
}
}
but visible property of autoHidableView evaluates only once on startup and never evaluates again
The onCollectionChanged handler is never get called which is understandable since collection object itself stays the same
So is it possible to listen for collection's size change event?
The problem is that the javascript array that you create with property var collection: [] does not have any signals (the onCollectionChanged is indeed when you would assign a new collection to it). You better use ListModel:
QtObject {
property var controlWindow: Window {
ListModel {
id: collection
}
signal sigAddElement(var element)
onSigAddElement: {
collection.append(element)
}
signal sigEraseAllElements()
onSigEraseAllElements: {
collection.clear()
}
Rectangle {
id: autoHidableView
visible: collection.count > 0
}
}
}
Note that you need to change the push to append
Listening to the array length is not enough.
See var QML Basic Type:
It is important to note that changes in regular properties of JavaScript objects assigned to a var property will not trigger updates of bindings that access them.
It is the same behavior when assigning an array to the var. The binding will only be reevaluated if the property is reassigned with an entire new object/array.
You have 2 ways to make your binding listen to the collection length change:
Reassign the entire array:
onSigAddElement: {
collection = collection.concat(element)
}
// ...
onSigEraseAllElements: {
collection = []
}
Trigger the change signal manually:
onSigAddElement: {
collection.push(element)
collectionChanged()
}
// ...
onSigEraseAllElements: {
collection.length = 0
collectionChanged()
}

QML: connect slot to several controls

I would like to get notification in main Qt app in case any control in QML (loaded via QQuickWidget) changes its value. There are CheckBox'es, ComboBox'es, SpinBox'es and TextEditor's.
My current approach is to declare a slot for every control (onCheckedChanged, onCurrentIndexChanged, onValueChanged and onTextChanged respectively) and call mainApp.notifyMe() from them, there mainApp is a link to parent Qt Widget propagated to QML with help of
QQmlEngine* engine = new QQmlEngine(this);
if (QQmlContext* cntx = engine->rootContext()) {
cntx->setContextProperty("mainApp", this);
}
and notifyMe() is a slot in it on C++ side.
But this requires dozens of functions with the same code bcs I have dozens of controls. It would be ideal to have one or 4 functions with notifyMe() in QML which could be connected to all controls value changes.
Is there a way in QML to create such slot and connect it to multiple objects property changes?
I've end up with following in my root item:
MySettingsForm {
signal notify()
onNotify: {
mainApp.notifyMe();
}
Component.onCompleted: {
var cbs = [Combobox1, Combobox2, Combobox3];
for (var i in cbs) {
cbs[i].checkedChanged.connect(notify);
}
var sbs = [SpinBox1, SpinBox2, SpinBox3];
for (i in sbs) {
sbs[i].valueChanged.connect(notify);
}
var tes = [TextField1, TextFiel2, TextField3];
for (i in tes) {
tes[i].textChanged.connect(notify);
}
var cxs = [ComboBox1, ComboBox2, ComboBox3];
for (i in cxs) {
cxs[i].currentIndexChanged.connect(notify);
}
}
...
}
The trick is in creation of custom signal notify and slot attached to it.

Getting around list<> properties being readonly

I have two custom components written in QML
//XOption.qml
Container {
id: xOption
property string title;
property bool active: false;
function makeActive() {active=true}
onActiveChanged {
//alter appearance of option to reflect whether active/not
}
onTouch {
if (touchEvent reflects a tap) {
//notify underlying c++ engine that I was tapped
//engine will notify parent control that I was tapped by setting the flag var
}
}
//label to display title and some other visual components
}
//XParent.qml
Container {
id: XParent;
property list<XOption> options;
property int selectedOption: 0;
property string flag: cppengine.flag;
onCreationCompleted {
for (var k = 0; k < children.length; ++k) {
if (k==selectedOption)
options[k].makeActive()
}
cppengine.declareParentage(options);
}
onFlagChanged {
if (flag indicates that one of my child options was tapped) {
//determine from flag which option was tapped
tappedOption.makeActive()
//make other options inactive
}
}
}
But now I want to use XParent in another QML document and assign any number of different XOptions to it like so:
Container {
XParent {
options: [
XOption {
title: "title1";
},
XOption {
title: "title2";
}
]
}
}
However, when doing so, I get the error:
Invalid property assignment: "options" is a read-only property
Is there any way I could get around this? I've tried making options a string array type variant, that would contain the title of every child option to create, and then adding a ComponentDefinition for XOption to XParent and creating one for every title that was specified, but if I do that I am unable to call XOption's makeActive(), which is absolutely necessary.

How to override signal handler of superclass component

I have a base class item something like this:
Base.qml:
Item {
Thing {
id: theThing;
onMySignal: { console.log("The signal"); }
}
}
And I'm trying to make a derived item - Derived.qml.
How can I override the onMySignal handler of theThing? I have tried stuff like this...
Derived.qml:
Base {
theThing.onMySignal: { console.log("Do different things with theThing in Derived") }
}
but I can't find anything to tell me how to express this syntactically correctly, or whether/how to actually go about it!
You can define the code of the signal handler as a property in superclass and override it in the derived item:
Item {
property var handlerCode: function () { console.log("In Superclass") };
Thing {
id: theThing;
onMySignal: handlerCode()
}
}
Overriding :
Base {
handlerCode: function () { console.log("In Derived Item!") };
}

Is it possible to disconnect all slots from a signal in Qt5 QML?

In QML it is impossible to call .disconnect() without arguments for a signal:
file:mainwindow.qml:107: Error: Function.prototype.disconnect: no arguments given
So how can I disconnect ALL slots without specifying each of them?
Or maybe it is possible by passing signal object to C++ and disconnect it somehow there?
Or maybe any workaround exists?
The goal I want to reach is to change behavior of an object by connecting different slots to it's signal. For example:
object.disconnect() // disconnect all slots
object.connect(one_super_slot)
object.disconnect() // disconnect all slots
object.connect(another_super_slot)
No. I looked at the source code in qv4objectwrapper.cpp, and you can see this code:
void QObjectWrapper::initializeBindings(ExecutionEngine *engine)
{
engine->functionClass->prototype->defineDefaultProperty(QStringLiteral("connect"), method_connect);
engine->functionClass->prototype->defineDefaultProperty(QStringLiteral("disconnect"), method_disconnect);
}
Those are the only two methods that are added. If you look at the source code for method_disconnect() you can see that it always requires one or two parameters, including the name of the slot to disconnect.
There is no disconnectAll() unfortunately.
Okay, 5 minutes after my question I've made a workaround: connect only once to one signal that calls jsobject from inside:
Item {
property var fire
// Any qml object. In this example it is ActionExecutor which emits actionRequest
ActionExecutor {
//signal actionRequest(int actionType)
onActionRequest: fire(actionType)
}
Action {
shortcut: "Ctrl+S"
text: "One action"
onTriggered: {
parent.fire = function(actionType) {
console.log('one slot')
}
}
}
Action {
shortcut: "Ctrl+X"
text: "Another action"
onTriggered: {
parent.fire = function(actionType) {
console.log('Another slot')
}
}
}
}
So that js object can be reassigned many times as you want so you may change your behavior by reassigning this object. If you want to disconnect all simple assign undefined to fire. Also you can make a chain of "slots" by modifying code to something like:
Item {
property var fire
property var slots: [
function(actionType) {
console.log('1: ' + actionType)
},
function() {
console.log('2: ' + actionType)
},
function() {
console.log('3: ' + actionType)
}
]
// Any qml object. In this example it is ActionExecutor which emits actionRequest
ActionExecutor {
//signal actionRequest(int actionType)
onActionRequest: fire(actionType)
}
Action {
shortcut: "Ctrl+S"
text: "One action"
onTriggered: {
parent.fire = function(actionType) {
console.log('calling all custom JS-slots')
for (var i in slots) {
slots[i](actionType)
}
}
}
}
}
So anyone can implement own signal-slot architecture in qml as a simple javascript observer pattern.
Enjoy.

Resources