Take MenuItem as an example, normally in QML, specifying the handler for the triggered signal is simple:
MenuItem {
onTriggered: {
console.log("Hey");
}
}
Now if I want to do the same thing, but instead to a dynamically created MenuItem, e.g. via Menu.addItem(), then what is the syntax like to connect and specify the signal handler?
I didn't expect this to work, but here is a working solution:
function onTriggered() {
console.log("Hey");
}
var newItem = myMenu.addItem("Item 1");
newItem.triggered.connect(onTriggered);
Nevertheless is there a better way? Above I defined a custom function that happened to be named onTriggered, but it can be named anything, right? So this code piece doesn't make use of the built-in handler, that's why I'm wondering if there's a neater solution?
More importantly, later on I've noticed further problems with this approach: in a for loop, if there is a temporary variable used by the handler, things don't work any more:
for (var i = 0; i < myArray.length; i ++) {
var info = myArray[i];
var newItem = myMenu.addItem("Item " + i);
newItem.triggered.connect(function() {
console.log(info);
});
}
Here you'll see that console prints the last info in myArray for all added menu items when triggered. How can I properly set up independent handlers for each individual menu item?
In addition to the comments, you can easily make it "easier":
Menu {
id: myMenu
function add(text, handler) {
var newItem = addItem(text)
newItem.triggered.connect(handler)
}
}
And there you have it, problem solved, now you can simply myMeny.add("Item 1", onTriggered)
As for the result you get in the loop and functor, that's because of JS's scoping rules. Check the linked answer for details how to work around that.
So this code piece doesn't make use of the built-in handler
Don't think of onSignal as a handler, it is just a hook to attach a handler. Think of it as the declarative connection syntax. Sure, you can also use the Connection element in declarative, but it only makes sense when the situation actually merits it.
I think this confusion stems from some other language / framework which does generate handler methods for you. A onSignal is different from function onSignal() { expression } - the latter is a handler function, the former is handler hook, which just connects the signal to the bound expression.eval(). The Qt documentation too refers to onSignal as a handler, which IMO is technically and conceptually wrong, since the handler is the code which gets executed, the handler is whatever you bind to onSignal.
So you can rest easy, the code you are worried about does not result in any sort of redundancy or inefficiency and doesn't leave anything unused and is in fact the correct way to do things in QML.
All that being said, you can have "built in handlers", but it is a very different thing:
// SomeItem.qml
Item {
signal someSignal
onSomeSignal: console.log("I am a built in handler")
}
// main.qml
SomeItem {
onSomeSignal: console.log("I am another handler")
Component.onCompleted: {
someSignal.connect(function(){console.log("Yet another handler")})
someSignal()
}
}
And the output in the console will say:
qml: I am a built in handler
qml: I am another handler
qml: Yet another handler
As you see, it not really a handler, but a connection hook. There is no shadowing, no "replacing / not using the built in handler", there is just a signal with 3 connections to the evaluation of three expressions.
Using signal.connect() with a named function does come with one advantage, you can later signal.disconnect(namedFunction) if you need to remove a built in or another handler. I am not sure if you can do this if you use onSignal: expr since you don't have a way to reference that anonymous expression. Note that if you use onSignal: namedFunction() this will not work, you will not be able to signal.disconnect(namedFunction) because the signal is not directly connected to that function, but to an anonymous expression invoking it.
Related
I see the accept() somewhat similar to a return, so I've been putting it a the end of my slots with no code afterwards. That is, the accept() "finishes" the execution of the dialog.
Nevertheless, I came across the need to close a dialog and open a new one from a slot in the first one. Therefore, what I thought was moving the accept() to the beginning of the slot and initializing the second dialog after it. Something like the following:
void FirstDialog:slotFirstDialog()
{
accept();
// Setup second dialog arguments
// ...
SecondDialog *sd = new SecondDialog();
sd->exec();
}
Is this use of accept() valid? Is it good practice?
I'd avoid it. Calling accept() can trigger a delayed deletion of FirstDialog (say, if it has the Qt::WA_DeleteOnClose flag set)1; in that case, it would be deleted in one of the first events dispatched by the nested event loop (sd->exec()), which would lead to go on executing code in a method of an instance that has been deleted. This is just a sample problem on the top of my head, I'm sure others can be found.
I'd probably just hide the dialog before calling exec() on the other, and call accept() after the end of the nested event loop.
void FirstDialog:slotFirstDialog()
{
// Setup second dialog arguments
// ...
SecondDialog *sd = new SecondDialog();
hide();
sd->exec();
accept();
// NB are we leaking sd?
}
By the way:
SecondDialog *sd = new SecondDialog();
sd->exec();
here you are allocating on the heap a dialog without a parent, so either you set the Qt::WA_DeleteOnClose or explicitly call this->deleteLater() inside its code, or you are leaking the dialog instance.
Notes:
and it is explicitly remarked in the documentation
As with QWidget::close(), done() deletes the dialog if the Qt::WA_DeleteOnClose flag is set.
QDialog::accept calls QDialog::done with a dialog code Accepted. Here is how QDialog::done looks like:
void QDialog::done(int r)
{
Q_D(QDialog);
setResult(r);
hide();
d->close_helper(QWidgetPrivate::CloseNoEvent);
d->resetModalitySetByOpen();
emit finished(r);
if (r == Accepted)
emit accepted();
else if (r == Rejected)
emit rejected();
}
which, according to the documentation:
Hides the modal dialog and sets the result code to Accepted.
With this in mind, I think this is not a question of a good practice, but of what your application logic requires.
I use a unit of work pattern a lot in my flex projects. I'll have a class that might call a web service, put the data in a sqlite db, refresh a model with the data then raise an event.
I usually call these inline and pass in some singleton classes:
protected function CareerSynced():void
{
var process:ProcessWorkouts = new ProcessWorkouts(_dataModel, _trainerModel, _databaseCache, _database.Conn);
process.addEventListener("AllWorkoutsProcessed", AllWorkoutsProcessed);
process.UpdateAllUnprocessed();
}
I'll then get the response like this:
private function AllWorkoutsProcessed(event:DataReceivedEvent):void
{
//do something here
}
My question is, am I adding that event listener correctly? I think I might be causing a memory leak, but I'm not sure. I've also thought about using a weak reference. I'm confused about when to use them. Would this be one of those cases?
Should it be like this?
process.addEventListener("AllWorkoutsProcessed", AllWorkoutsProcessed,false, 0, true);
I would either go with the weak reference or just remove the listener:
private function AllWorkoutsProcessed(event:DataReceivedEvent):void
{
event.target.removeEventListener("AllWorksoutsProcessed",AllWorkoutsProcessed);
}
I could list out my reasons but I'll just point you to this.
I want to trace every event on every object, is there any way to do it?
Yes and no.
The one way is to simply override its dispatchEvent function:
override public function dispatchEvent(event:Event):Boolean
{
// Do something with event.
return super.dispatchEvent( event );
}
The problem, however, is that this does not always work -- sometimes dispatchEvent is not called if a child object does something. It also will not work if you are unwilling to create a special class for each instance.
Another alternative is to iterate through an array of different event types:
var evtTypes:Array = [ MouseEvent.CLICK, MouseEvent.ROLL_OVER,
MouseEvent.MOUSE_DOWN...
Event.ADDED, Event.ADDED_TO_STAGE... etc.];
for( var i:int = 0; i < evtTypes.length; i++ )
{
target.addEventListener( evtTypes[ i ], trace );
}
The problem with this method is that you'll not be able to capture custom events, only the events you have in your list. I would definitely recommend the second method for most learning and debugging problems.
I suppose a more important question, however, is "What do you want to do with these events?" Most of the documentation lists all of the events an object will dispatch: if you scroll down in the MovieClip documentation, you'll see an example.
You have to create your own registry and access it that way. So yes, there is a way to do it, but no, not easily.
In an AIR application I have the following code:
theDialog = PopUpManager.createPopUp( this, TheDialogClass, true ) as TheDialogClass;
theDialog.addEventListener(FlexEvent.CREATION_COMPLETE, cpuIntensiveCalc);
At the end of cpuIntensiveCalc the dialog is removed. The dialog informs the user that "something is going on, please stand by."
The problem is that cpuIntensiveCalc starts before the dialog draws. So the user experience is that the application freezes for 10 seconds with no indicator, then the modal dialog flashes quickly (less than a second) on screen.
The Adobe docs say this about creation_complete
Dispatched when the component has finished its construction,
property processing, measuring, layout, and drawing.
So this feels like the correct event.
In the name of completeness, I also tried
theDialog = PopUpManager.createPopUp( this, TheDialogClass, true ) as TheDialogClass;
cpuIntensiveCalc();
But had the same results.
TIA
The reason for this is that the Flash Player is single threaded, and so you are blocking the UI from reacting to the Dialog Popup until the maths chunk is finished.
Hacky fix time...
You have two options.
(This one should work, but is untested) Wrap the cpuIntensiveCalc() call in a callLater, so that the UI can finish rendering before you block the rendering.
Or
Use "Green Threads" to break up your processing so that you don't completely block the UI processing. Take a look.
(I just had the same issue => even if this thread is old, I just wanted to contribute my solution)
(disclaimer: this is a bit ugly, but they say that's ok in the UI layer... ;-) )
Flex is single threaded (at least from our developer's perspective, I think behind the scene threads are used by the VM)
=> you typically execute your code in the UI thread, after the user did some action on a widget. Any call to update a UI component (like setProgress or setLabel) will only be rendered on screen at the end of the render cycle (see again UiComponent life cycle).
=> In therory calling "cpuIntensiveCalc" in a callLater will let the framework display your popup before executing the method.
In practice though, I noticed you typically have to have for a couple of UI cylces before the popup be displayed, like this:
new MuchLaterRunner(popup, 7, cpuIntensiveCalc).runLater();
MuchLaterRunner being defined like this:
public class MuchLaterRunner
{
var uiComp:UIComponent;
var currentCounter = 0;
var cyclesToWaitBeforeExecution=0;
var command:Function;
public function MuchLaterRunner(uiComp:UIComponent, cyclesToWaitBeforeExecution:uint, command:Function)
{
this.uiComp = uiComp;
this.command = command;
this.cyclesToWaitBeforeExecution =cyclesToWaitBeforeExecution;
}
public function runLater() {
currentCounter ++;
if (currentCounter >= cyclesToWaitBeforeExecution) {
uiComp.callLater(command);
} else {
// wait one more cycle...
uiComp.callLater(runLater);
}
}
}
The issue is the same when calling setProgress afterward: we must divide cpuIntensiveCalc into small callable methods that can be ran at each UI cycle, otherwise the progressbar won't, err, progress.
Use enterFrame event on popup. Don't forget to remove the listener in the enterFrame event handler - otherwise the cpu intensive method will be called in each frame, crashing your app. If this doesn't work at first, use a private number as a counter and keep incrementing it in the enter frame handler - call cpu heavy method only when the counter reaches the appropriate value. Find the 'appropriate' value by trail and error.
theDialog = PopUpManager.createPopUp(this, TheDialogClass, true) as TheDialogClass;
theDialog.addEventListener(Event.ENTER_FRAME, onEnterFrame);
private function onEnterFrame(e:Event):void
{
//can use theDialog instead of e.currentTarget here.
(e.currentTarget).removeEventListener(Event.ENTER_FRAME, onEnterFrame);
cpuIntensiveCalc();
}
//in case the above method doesn't work, do it the following way:
theDialog.addEventListener(Event.ENTER_FRAME, onEnterFrame);
private var _frameCounter:Number = 0;
private function onEnterFrame(e:Event):void
{
_frameCounter++;
var desiredCount:Number = 1;//start with one - increment until it works.
if(_frameCounter < desiredCount)
return;
//can use theDialog instead of e.currentTarget here.
(e.currentTarget).removeEventListener(Event.ENTER_FRAME, onEnterFrame);
cpuIntensiveCalc();
}
I wonder how to achieve this in Flex.
Basically, I have enabled Drag and Drop in some of my list controls.
<mx:DataGrid id="dg1" width="100%" height="100%" dataProvider="{xmllcData}"
dropEnabled="true" dragDrop="dg1_dragDropHandler(event)">
</mx:DataGrid>
In the function dg1_dragDropHandler event, I have the following codes:
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
// Show Message to Confirm.
Alert.show('Proceed?', 'Title', Alert.YES | Alert.NO, null, handleAlert, null, Alert.YES);
}
private function handleAlert(evt:CloseEvent):void
{
if (evt.detail == Alert.YES)
{
// Perform the functions as necessary
}
else
{
// Execute the script to prevent the dropping of the object.
// How can I call the DragEvent.preventDefault(); function from here?
}
}
In the codes above, I want to call the preventDefault() on the alertHandler function since the other scripts after the call to the Alert.show in dg1_dragDropHandler event would be executed concurrently with the alert.show.
How would I be able to reference the DragEvent of the dg1_dragDropHandler event from the alertHandler event?
Instead of specifiying your listener function, handleAlert(), as a normal function, you can use an anonymous function. Write your code like this:
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
// Show Message to Confirm.
Alert.show('Proceed?', 'Title',
Alert.YES | Alert.NO,
null,
function(evt:CloseEvent) {
if (evt.detail == Alert.YES) {
// Perform the functions as necessary
}
else {
// Execute the script to prevent the dropping of the object.
// Now you have access to the evt:DragEvent!
}
},
null, Alert.YES);
}
}
When you use an anonymous function, you still have access to all the variables in your current scope. This means you can still access the evt:DragEvent variable. As Glenn said though, I don't know if this will solve your default action problem.
You probably want to store the details of the dropEvent in a local variable. Then when you want to do your "preventDefault" part, just access the event object and do your magic.
Not sure why you want to preventDefault though. I'm not quite understanding that part. Wouldn't all the other listeners of the event run to completion while the program is waiting for you to say YES/NO to the alert?
Which other parts of the callstack are operating here? You could stop anything else in the event chain from happening by calling event.stopImmediatePropergation(); on the first line of your dragDropHandler (assuming that the listener has a higher priority than others in the chain).
You would then need to manually replicate the drag and drop operations on confirm, which I'm not sure but you could achieve using the doDrag() method of the DragManager.
DragManager.doDrag() langauge reference
You're absolutely right that the Alert will be popped up asynchronously with respect to the original DragEvent dispatch.
Since you don't want the default datagrid behavior to kick in at that point, you need to call preventDefault() on receipt of the event, and then throw up your alert panel.
Then, in the success branch of your alert handler, you could try to rethrow ( throw a new) DragEvent. Use a local variable to keep track of the original event details so that you can clone() or simply create a new event with the same properties. Basically, you're intercepting and interrupting the event flow and then attempting to resume it later.
Haven't tried this myself, but that's what I'd explore first.
I have not tried this myself, but preventing default behavoiur immediately is the only way to stop the grid from performing the copy or move.
Try preventing the default behaviour and maintaining the drag event. Then, if you user hits no, you have already stopped the event. If the user hits yes, you can (this is the part i am unsure of) re-dispatch the drop event on the grid. Hopefully it will behave normally. To get the event into your Alert handler you can simply use the data property on the Event window to track it.
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
evt.preventDefault();
// Show Message to Confirm.
Alert.show('Proceed?', 'Title', Alert.YES | Alert.NO, null, handleAlert, null, Alert.YES).data = evt;
}
private function handleAlert(evt:CloseEvent):void
{
if (evt.detail == Alert.YES)
{
// Perform the functions as necessary
var dgEvt:DragEvent = Alert(evt.currentTartet).data;
var newDrag:DragEvent; //need a new event because the default behaviour on the old one is still prevented
//copy event values to new drag event
dg1.dispatchEvent(newDrag);
}
else
{
// Execute the script to prevent the dropping of the object.
// How can I call the DragEvent.preventDefault(); function from here?
}
Again, not entirely sure if it will work, just off the top of my head. Of course, you have to remove the custom dragDrop event handler from your grid before you redispatch the approved drag, otherwise your handler with prevent the default, then pop an alert and repeat over and over.