We have a gui with multiple pages. On each page there are custom text components with a data coupling. When a page is shown/loaded the text components update their data binding using the Component.oncompleted and onVisibleChanged signals. To finalize this a call must be made to the communication module after each child has done its updates.
When pages are changed this can be done in the "onVisibleChanged" of the page. The problem is finding a point to do this when the page is created. The Component.onCompleted is performed before the one of the children.
Do you know any signal to use for this?
As a workaround I have an empty NumberAnimation with a Component.onCompleted call. This apperently runs after the children but feels like a hack.
Here goes the example code
DataText
DataText
{
property string address: ""
text: name + Model.getSingleItems().get(address).value //property binding here
onVisibleChanged: { Model.getSingleItems().setSubscribeStatus(address, visible);} //Stages the address for (un)subscribe
Component.onCompleted:{ Model.getSingleItems().setSubscribeStatus(address, visible);} //Stages the address for (un)subscribe
}
An example page
Rectangle
{
DataText
{
address: "datakey"
}
//This code must be executed after all setSubscribeStatus calls to
//finish the subscriptions.
Component.onCompleted:{ Model.getSingleItems().finialize();}
onVisibleChanged: { Model.getSingleItems().finialize();}
}
I'm looking for a way to subscribe to a batch of data adresses in the communication module. We can't keep all the data up to date due to limitations in our bandwith with the actual device.
Related
I have an item of some type that consists of several QML editing controls:
Column {
id: inputItem
SpinBox {
}
TextInput {
}
ComboBox {
}
Button {
id: enableMeButton // this needs to be enabled if
text: "Apply" // anything was changed above
enabled: false
}
}
This item [Column as example] can be potentially inserted to some list view as "polymorphic" editing item so that we don't know beforehand what edit fields to handle (say we would want to move Apply outside). Or we want to use a common type with Apply to develop different editing delegates (which is the story here).
How can we detect that any of the data handled in this form was changed? Is there a generic way to do so or there is some trick to accomplish it?
In my QML project, I need an object to capture all the UI events of its children objects. So, if any of its children register a click or something, the parent object needs to know about it. The issue here is that all of the children objects are pre-defined classes such as MyButton or MyComboBox. These classes all have defined MouseAreas and onClicked() functions that can't be overridden. Therefore, I need the parent object to capture all the events of its children WITHOUT modifying the MouseAreas of the children. Please let me know the best way to accomplish this.
You can crawl the object tree, connecting a function to every onClicked-signal.
For this we need three parts:
The signal that shall be connected
The function that does the crawling
A function that creates another function to call the signal with custom arguments.
I chose, that my signal shall have to arguments: sender and arguments. Sender is the object, that I clicked on, and arguments are the arguments of the clicked-signal of the clicked object. This is empty for QtQuick.Controls 2.x Buttons and contains one entry (mouse) for MouseAreas.
signal somethingWasClicked(var sender, var arguments)
As this signal has not the same signature as clicked for every clickable object, we can't connect it directly to this signal. We need a intermediary function that calls the signal for us, has the needed arguments in it's scope and has no arguments. We need to build this dynamically for each object we spy on.
function createSpyFunction(object) {
return function() { somethingWasClicked(object, arguments) }
}
And lastly for the crawl-function. We need to store the function we create somewhere. For this I utilize the fact that all QtObjects are JS-Objects in some sense, so I can use Object.defineProperty to dynamically add JS-properties to them. Here I can store our function, without the need of modyfing the sourcecode of the components them selves.
After creating this function, I connect it to the onClicked-handler.
function crawlChildren(obj) {
if (obj.onClicked) {
Object.defineProperty(obj, '__clickedFunction', { value: createSpyFunction(obj) })
obj.onClicked.connect(obj.__clickedFunction)
}
if (obj.children) {
var i = 0
for (; i < obj.children.length; i++) {
crawlChildren(obj.children[i])
}
}
}
This function I finally call in
Component.onCompleted: crawlObj(this.contentItem)
of the root ApplicationWindow of my programm.
As I only call it in the Component.onCompleted-handler, objects that will be added after this, won't be spied uppon. To change this, we will also need to spy on onChildrenChanged, and crawl the newly added objects as well. The process there is almost similar, so this is left as an exercise to the user, or might be subject to a new question.
You can try to overlay your items with a MouseArea. In the event handlers you can check the position and call the event handlers of the underlying items.
Item {
MyButton { id: mybutton
/* set the anchors */
}
MyMouseComboBox { id: myMouseComboBox
/* set the anchors */
}
MouseArea {
anchros.fill: parent
onClicked: {
// mouse.accepted = false
// Check whether clicked point is within mybutton
// on true, call mybutton.doSomething()
// or call mybotton.onPressed(mouse)
}
}
My QML application is displaying a Calendar element.
When the selected date is changed (clicked), I need to update an other element (an image).
I have something like this:
Calendar {
id: calCalendar
onClicked: {
// update other QML element
}
}
It works fine when the user click with the mouse on the calendar: the other element (the image) is correctly updated.
My problem is initialization : when my app is started, the calendar displays the current date by default, and I'd like to programmatically call the onClicked handler, to make sure the image is up to date.
I don't know how to do that.
If you want to do something when a QML component is done initializing you can use the Component.onCompleted : slot.
Calendar {
id: calCalendar
onClicked: {
// update other QML element
}
Component.onCompleted: {
// Do stuff for initialization.
// you could do this here : calCalendar.Clicked()
// if you want to use the same code for initialization and for user input handling later on.
}
}
The point is the following : onXXX : { declares a slot to handle the singal XXX. Here the signal is Clicked. You can trigger the signal programmatically as you say, just by invoking it like a function. You'll need to know a valid overload for the arguments (if any).
I have code similar to the following:
...
id: myComponent
signal updateState()
property variant modelList: []
Repeater {
model: modelList
MyButton {
...
Connection {
target: myComponent
onUpdateState: {
...
}
}
}
}
I assign a value to modelList and then issue myComponent.updateState() to update the MyButton components in the repeater. At this point I get a lot of warnings about non existent properties
It seems like the signal gets passed to the MyButton(s) that doesn't exist anymore (since the repeater will rerun when I change modelList).
Is there a way of avoiding this or should I simply ignore the warnings?
I had a similar problem when destroying QML components connected to C++ signals. The problem was solved by adding a handler for disconnecting the signals when the components were destroyed. In the dynamically generated components you could try manually connecting the signals, in order to manually disconnect them on destruction. In my case the code looks something like this:
MyComponent {
Component.onCompleted: signal.connect(callback_function)
Component.onDestruction: signal.disconnect(callback_function)
function callback_function() {
// process signal
}
}
It might be, there is a better solution not needing manually connecting and disconnecting the signals, but this worked for me. If you add a console.log("Destroying...") to the onDestruction handler you can check whether or not the components are disconnecting the signal, thus actually being destroyed.
How can I open a synchronous dialog in Flex? I need to call a function from an External Interface (JavaScript) that will open a simple dialog in the Flex application and returns an value according to the button the user has clicked (OK/Cancel).
So it should by a synchronous call to a dialog, i.e. the call waits until the user has closed the dialog like this.
//This function is called by JavaScript
function onApplicationUnload():Boolean
{
var result:Boolean;
result = showDialogAndWaitForResult();
return result
}
Does anybody know how I can do this? I could write a loop that waits until the dialog has set a flag and then reads the result to return it, but there must be something that is way more elegant and reusable for waiting of the completion of other asynchronous calls.
EDIT:
Unfortunately a callback does not work as the JavaScript function that calls onApplicationUnload() itself has to return a value (similar to the onApplicationUnload() function in Flex). This JavaScript function has a fixed signature as it is called by a framework and I cannot change it. Or in other words: The call from JavaScript to Flex must also be synchronous.
Flex doesn't work in a synchronous fashion, as it is a single thread application and so needs your code to hand execution back to the "core" in order to handle user input etc.
The way to do it is to make your dialogue's behaviour asynchronous:
function onApplicationUnload():void
{
showDialog(resultMethod);
}
function resultMethod(result:Boolean):void
{
ExternalInterface.call("javaScriptCallback", [result]);
}
You can't do that in Flex. As David mentioned, Flex is single-threaded, so you can't have your function block while the dialog is being processed.
Your best bet might be to use a Javascript popup. You'll have a lot less control over the window, but it should behave the way you want (blocking the function until it's been closed).
Have your Flex code use an event to wait for the dialog. In the main thread, register an event handler that waits for the dialog to close. On OK in the dialog, dispatch the dialog complete event.
With Cairngorm, this is something like:
In the main thread:
CairngormEventDispatcher.getInstance().addEventListener(ClosingDialogCompleteEvent.DIALOG_COMPLETE, onClosingDialogComplete);
(if you want to avoid returning until complete, loop on a timer and global variable.)
In the dialog closing handler:
CairngormEventDispatcher.dispatchEvent(new ClosingDialogCompleteEvent(<parameters>));
The event handler:
public function onClosingDialogComplete (e: ClosingDialogCompleteEvent):void
{
param1 = e.param1;
param2 = e.param2;
// etc.
// Continue processing or set the global variable that signals the main thread to continue.
}
For this to work, the class ClosingDialogCompleteEvent has to be defined. Partial code for the class is:
package com. ... .event // You define where the event lives.
{
import com.adobe.cairngorm.control.CairngormEvent;
public class ClosingDialogCompleteEvent extends CairngormEvent
{
// Event type.
public static const DIALOG_COMPLETE:String = "dialogComplete";
public var param1:String;
public var param2:String;
public function ClosingDialogCompleteEvent(param1:String, param2:String)
{
super(DIALOG_COMPLETE);
this.param1 = param1;
this.param2 = param2;
}
}
}
Waiting on an event is the best way to synchronize in Flex. It works well for startup dialogs too. In a flex-only application it works especially well.
I have explained a workaround to create synchronous alert in flex
http://reallypseudorandom.blogspot.com/2010/05/flash-asynchronous-alert-and-pause.html
OK... after all I found a possible solution. But I guess hardly everybody is going to do that seriously :-(
The solution focuses around using a while loop to check for a result and then return the function that is being called by JavaScript. However we need a way to sleep in the while loop, while we are waiting for the result. However calls to JavaScript are synchronous. Now the trick is to make a sleep in JavaScript, which is also not directly available here, but can be done using a synchronous XML Http Request like described on this blog.
As I said - I won't recommend this only as last resort. For my problem I have resorted to ugly JavaScript popups.
Have your dialog call another function in flex to process the result of the user selection:
private function deleteFileCheck():void
{
Alert.show("Are you sure you want to delete this file?",
"Confirm Delete",
Alert.YES| Alert.NO,
this, deleteFileHandler, null, Alert.NO);
}
private function deleteFileHandler(event:CloseEvent):void
{
if (event.detail == Alert.YES)
{
...do your processing here
}
}
You can fake a synchronous dialog in flex by popping up a dialog then disabling everything in the background. You can see this in action if you do Alert.show("Hello World"); in an application. The background will grey out and the user won't be able to click on any UI in the background. The app will "wait" until the user clicks the OK button.