QML - Capture all UI events of children objects - qt

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)
}
}

Related

QML Event before and after property change

Are there any signals I can bind to that allow me to execute functions directly before and directly after a property changes?
For example something like this:
Canvas {
id: canvas
anchors.fill: parent
property real lastX
property real lastY
property var lastContext
function cacheImage(){
lastContext = canvas.context
}
function loadImageFromCache(){
canvas.context = lastContext
canvas.requestPaint()
}
}
I want to call the cacheImage() function right before the canvas's size changes and the loadImageFromCache() right after the resize is completed.
If I understand correctly the onWidthChanged and onHeightChanged do the latter, but I need to save the image before it does that. Is there a simple way to do this?
But cacheImage() doesn't have to be called right before the size changes! It needs to be called any time after the most recent canvas contents change, and before the new canvas size takes effect. The window to cache the canvas is very long and extends between changes. The event "before n-th property change" is simply "after (n-1)-th property change" :)
This kind of functionality is not part of the "core design intent" of QML, so you are not getting it out of the box.
It is quite easy to do for C++ objects when you get to implement your own setter function.
But even in QML you can simply use an auxiliary setter function rather than using the property directly:
function setSomeProp(newValue) {
if (newValue === oldValue) return
stuffBeforeChange()
oldValue = newValue
stuffAfterChange() // optional, you can use the default provided signal too
}
If you insist on using a property based syntax, you can use an additional auxiliary property:
property type auxProp
onAuxPropChanged: setSomeProp(auxProp)
Which will allow you to use the = operator as well as do bindings.

QML Calendar : Programmatically call onClicked handler

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).

Dead QML elements receiving signals?

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.

Adobe Flex3: Keyboard shortcuts when a view is visible?

I have a quite large Flex application with a large set of views and I ceratain views I'd like to add shortcuts.
And i'm looking for something like:
<mx:Vbox>
<foo:Shortcut keys="ctrl+s" action="{bar();}"/>
....
</mx:VBox>
Is there any framwork or component already done that does something like this? I guess it should be too difficult to build? When building this I only want the shortcuts to be active when the view is visible. How do I detect this? What base class is best to inherit from when working with non visual components?
I don't know of any framework component that does that already, but the examples above should get you started if you try to build your own.
There's no need to inherit from any component for a non-visual component like the one you've described here (your "foo" class needs no parents.) There's nothing in the Flex framework you need to inherit from for this.
However you architect it, your foo class is going to have to take in and parse keyboard codes to listen for and accept one or more methods to call. All you have to do is figure out when to add and remove the event listeners that will call the passed-in methods.
To handle turning your keyboard events on and off based on visibility, just have your foo component bind to the "visible" property of it's parent and add/remove event listeners accordingly.
You might also consider having the listeners added when the component that foo is nested in is on the display list rather than just visible. To do this, simply added and remove your event listeners in one of the component lifecycle methods - probably commitProperties is the most appropriate.
I don't think this solution answer your question directly but anyway, to help solve your problem here is an example.
For instance, I've extended the TextArea component like so. This is the best I can do so far, it can definitely be improved upon. Like, I don't know how to make the cursor go to the end after the next shortcut is pressed.
public class TextArea extends mx.controls.TextArea
{
// the keysmap is an example dictionary holding keycodes
private var keysmap:*={
112 = "some text for F1"
,113 = "the text for F2!"
//etc, etc
}
public var handleKeyDown:Boolean =false;
public function TextArea(){
if(handleKeyDown ==true){
this.addEventListener(KeyboardEvent.KEY_DOWN,this.keydownHandler);
}
}
public function keydownHandler(e:KeyboardEvent):void{
if(e.keyCode >= 112 && e.keyCode <= 123){
e.currentTarget["text"] += String(keysmap[e.keyCode]) +" ";
}//focusManager.setFocus(this);
}
}
I can't give you a solution using MXML, however my first thought would involve a singleton static class with a Dictionary that contains a list of objects as its keys and dynamically created dictionaries as the value pairing that contain keys denoting the desired key press with a function reference as the value.
So, say you had a Sprite and you wanted to capture ctrl+s for save when focus is on that object, I would get the instance of that Singleton, and call a function such as registerKeyBinding passing in the Sprite, the keyCode you want, and your pre-defined callback:
private var registeredObjects:Dictionary = new Dictionary(true);
public function registerKeyBinding(targetObject:Object, keyCode:int, callback:Function) {
if (registeredObjects[targetObject]) {
Dictionary(registeredObjects[targetObject])[keyCode] = callback;
}
else {
registeredObjects[targetObject] = new Dictionary();
Dictionary(registeredObjects[targetObject])[keyCode] = callback;
targetObject.addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener);
}
}
private function keyDownListener(e:KeyboardEvent):void {
if (e.ctrlKey == true) {
//calls the function if that key exists.
Dictionary(registeredObjects[e.target])[e.keyCode];
}
}
Can't say I've tested this, but it was just the first thing that popped into my head. You could then setup functions to deregister and delete keys from the dictionaries, check states of the objects in addition to the keyCodes, remove old listeners, and delete entire dictionaries when there is no longer a need for them. Hopefully this is at least a tiny bit helpful.

Triggering a change event in a Flex tree control programmatically

I have a method to add an XML node structure to the currently selected tree node.
This appends the xml, and opens the parent node to display the newly added node.
I then select the node by setting the selectedItem of the tree.
I have an editing form that updates its values on the tree change event. When I set the selectedItem in this method, The node is selected correctly but the change event never fires (thus the editor doesnt update). I have tried to call it in a call later block to no avail.
Is there a way I can force the tree to dispatch a change event at this point?
public function addSelected(node:XML):void{
tree_expandItem(false);
var selectedItem:XML = tree.selectedItem as XML;
selectedItem.appendChild(node);
tree_expandItem(true);
callLater(function():void { tree.selectedItem = node; } );
}
To extend this question in a general sort of way - I would have thought that changing the selectedItem of the tree would result in a change event anyway? Or is a change only considered a change if the user makes it?
You could move the logic that is currently in your change event handler to a separate function, and then call that function directly:
private function changeHandler(event:ListEvent):void
{
doChangeLogic();
}
private function doChangeLogic():void
{
//statements
}
public function addSelected(node:XML):void
{
tree_expandItem(false);
var selectedItem:XML = tree.selectedItem as XML;
selectedItem.appendChild(node);
tree_expandItem(true);
callLater(function():void { tree.selectedItem = node; } );
doChangeLogic();
}
Is there a way I can force the tree to dispatch a change event at this point?
Use the dispatchEvent() method. Thanks James!

Resources