QML - Dynamically create Timers when event occurs - javascript

I have a ListView whose delegate is a red button. When button's color changes, I want the program to dynamically create a timer (which is specific for that delegate), which sets again the color to red after 5 seconds. Then I want the program to destroy the timer. How can I do it?
Here is my actual code:
ListView {
id: myListView
model: myListModel
anchors.fill: parent
anchors.leftMargin: 20; anchors.rightMargin: 20
orientation: Qt.Vertical
clip: true
spacing: 8
delegate: Button {
id: myDelegate
property int myDelegateIndex: index + 1
width: 100; height: 50
text: "Push"
background: Rectangle {
id: myDelegateBackground
color: "red"
onColorChanged: {
myTimer.start();
}
}
Timer {
id: myTimer
interval: 5000
running: true
repeat: true
onTriggered: {
myDelegateBackground.color = "red";
}
}
}
}
Thank you all a lot!!!

You create a Component
SelfdestroyingTimer.qml
Timer {
property var action // Assing a function to this, that will be executed
running: true
onTriggered: {
action()
this.destroy() // If this timer is dynamically instantitated it will be destroyed when triggered
}
}
And have a function:
function createOneShotTimer(duration, action) {
var comp = Qt.createComponent('SelfdestroyingTimer.qml')
comp.createObject(root, { action: action, interval: duration })
}
Or declare the Component in the same file (so you do not need to create it each time you want an instance), and it looks like this:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: window
visible: true
width: 600
height: 600
Component {
id: singleShot
Timer {
property var action
running: true
onTriggered: {
if (action) action() // To check, whether it is a function, would be better.
this.destroy()
}
// This proves, it will be destroyed.
Component.onDestruction: console.log('Timer says bye bye!')
}
}
Button {
onClicked: {
singleShot.createObject(this, { action: function() { console.log('ACTION!!!') }, interval: 2000 })
}
}
}

There would appear to be no need to dynamically create timers. Create one Timer for each delegate and reuse it by calling restart().
See example below:
ListView {
id: myListView
model: 20
anchors.fill: parent
anchors.leftMargin: 20; anchors.rightMargin: 20
orientation: Qt.Vertical
clip: true
spacing: 8
delegate: Button {
id: myDelegate
property int myDelegateIndex: index + 1
width: 100; height: 50
text: "Push"
background: Rectangle {
id: myDelegateBackground
color: "red"
onColorChanged: {
myTimer.restart();
}
Timer {
id: myTimer
interval: 5000
running: false
repeat: false
onTriggered: {
myDelegateBackground.color = "red";
}
}
}
onClicked: {
background.color = "blue"
}
}
}
Regardless of what mechanism you choose to do this, you will have problems caused by the ListView destroying delegates which scroll out of the visible region of the ListView. When a delegate gets destroyed and recreated, it will have its original color and the timer will be in its default state.
There are two options to deal with this scenario:
Save the state of the delegates to a list (in a javascript var) outside the scope of the delegate.
Increase the Listview cacheBuffer so that the ListView does not destroy the delegates when they go out of the visible region.

Related

QML return NaN on attribute set by Javascript

I'm setting attributes of a QML object with java script on object creation.
It is working fine except on one parameter that needs a number.
this calls the script:
Component.onCompleted: CreateVerticalGaugeScript.createVerticalGauge(300,10,300,300,"MAP",Dashboard,"MAP");
The 300 before "MAP" is the needed number. Everything else is working.
var component;
var gauge;
function createVerticalGauge(setWidth,setX,setY,setMaxValue,setID,SetValueObject,SetValueProperty) {
component = Qt.createComponent("verticalbargauge.qml");
console.log(setMaxValue)
if (component.status == Component.Ready)
finishCreation(setWidth,setX,setY,setMaxValue,setID,SetValueObject,SetValueProperty);
else
component.statusChanged.connect(finishCreation);
}
function finishCreation(setWidth,setX,setY,setMaxValue,setID,SetValueObject,SetValueProperty) {
console.log(setMaxValue)
if (component.status == Component.Ready) {
gauge = component.createObject(adaptronicDash, {"id": setID, "gaugemaxvalue": setMaxValue,
"gaugetext": Qt.binding(function(){return SetValueObject[SetValueProperty]}),
"x": setX, "y": setY});
gauge.width = setWidth;
if (gauge == null) {
// Error Handling
//console.log("Error creating object");
}
} else if (component.status == Component.Error) {
// Error Handling
//console.log("Error loading component:", component.errorString());
}
}
Now when the object is created it has the value NaN in QML. In jas console.log shows me that the value 300 is set.
This is the QML thats created:
import QtQuick 2.8
import QtQuick.Controls.Styles 1.4
import QtQuick.Extras 1.4
import QtQml.Models 2.2
Rectangle {
id: initalID
width: 100
height: 80
color: "transparent"
antialiasing: false
Drag.active: dragArea.drag.active
property alias gaugetext: gaugetextfield.text
property alias gaugemaxvalue: gauge.maximumValue
MouseArea {
id: dragArea
width: parent.width
height: parent.height + 10 // easier to get
anchors.centerIn: parent
drag.target: parent
drag.axis: Drag.XAndYAxis
//onClicked: pieMenu.popup(mouseX, mouseY), console.log("clicked")
}
Gauge {
id: gauge
anchors.fill: parent
anchors.margins: 10
orientation : Qt.Horizontal
minorTickmarkCount: 4
tickmarkStepSize : 5000
//labelStepSize: 50
minimumValue: 0
maximumValue: 10000
//value: Dashboard.revs
Behavior on value {
NumberAnimation {
duration: 5
}
}
Text {
id: gaugetextfield
font.pixelSize: (parent.height / 3)
anchors.top : parent.top
font.bold: true
font.family: "Eurostile"
color: "white"
anchors.horizontalCenter: parent.horizontalCenter
}
style: GaugeStyle {
valueBar: Rectangle {
implicitWidth: rev.height /3
color: Qt.rgba(gauge.value / gauge.maximumValue, 0, 1 - gauge.value / gauge.maximumValue, 1)
}
}
}
}
I use property alias gaugemaxvalue to access the value. If I set a number directly in the Javascript it is working.
You are setting Gauge maximumValue to 300 and tickmarkStepSize is 5000.
Test by changing maximumValue to 30000 or commenting out tickmarkStepSize.

How to add items to a QML Grid after creation?

I've done a bit of searching but I've failed to find an answer which must be so simple it hasn't warranted a question thus far.
Anyway I'm new to QML and struggling to find a way to add items to a Grid dynamically post-creation.
Basically I have a Flickable with a Grid inside it containing (by default) four instances of a custom class named ImageTile, and when the MouseArea detects there's a click it wants to add 2 instances of ImageTile to that grid.
Here is a snippet of the code:
Flickable {
anchors.fill: parent
contentWidth: 400
contentHeight: 800
clip: true
MouseArea {
id: mouseArea
anchors.fill: parent
onclicked: { /* ADD TWO ImageTile TO imageGrid */ }
}
Grid {
id: imageGrid
columns: 2
spacing: 2
Repeater {
model: 4
ImageTile { }
}
}
}
As you can see I'm wondering what I should put inside the onClicked event to achieve adding two new instances of ImageTile to imageGrid.
Thanks in advance for any help!
So thanks to being pushed in the right direction by MrEricSir I've figured the issue out.
First I had to specify a proper Data Model for the Repeater to use, and then assign a delegate function to convert information in the data model to actual QML elements. Appending to the data model automatically triggered the Repeater to execute the delegate function.
Flickable {
anchors.fill: parent
contentWidth: 400
contentHeight: 800
clip: true
ListModel {
id: imageModel
ListElement { _id: "tile0" }
ListElement { _id: "tile1" }
ListElement { _id: "tile2" }
ListElement { _id: "tile3" }
}
MouseArea {
id: mouseArea
anchors.fill: parent
onclicked: {
imageModel.append({ _id: "tile" + imageModel.count })
}
}
Grid {
id: imageGrid
columns: 2
spacing: 2
Repeater {
model: imageModel
delegate: ImageTile { id: _id }
}
}
}

Prevent Click Event from document Level to child elements

I am create a hybrid app using Sencha frame work where I have a setting screen, when setting screen is visible in the screen I want to prevent all the touch event from the user to other components except specific views. I saw some of the post saying that event the stopProgation() and preventDefault() will the event further But i am not clear about It.
I tried to add click event to document to release my setting list from the view when user tap on the screen but if the user tap on other component in that screen it will trigger that button action also, how to prevent this.
Also I don't want to prevent the action of touch when user click on setting list or some specific component in my view.
Note: I not able to use jquery in this project because using jquery side by with Sencha may cause performance issue.
Code:
Ext.define('MyApp.view.ControlPanel.ControlPanel', {
extend: 'Ext.Container',
alias: "widget.controlpanel",
requires: [
'Ext.SegmentedButton'
],
config: {
layout: {
pack:'stretch'
},
docked:'bottom'
},
documentClickHandler:function(event){
console.log('Document Clicked');
document.removeEventListener('click', arguments.callee, false);
var settingListContainer = Ext.ComponentQuery.query("#setting-list-container")[0];
if (settingListContainer) {
var controlpanel = settingListContainer.up('controlpanel');
if (controlpanel) {
controlpanel.remove(settingListContainer, true);
var segmentButton = controlpanel.down("#control-segment-button");
if (segmentButton) {
segmentButton.setPressedButtons();
}
}
}
event.preventDefault();
return false;;
},
onSegmentToggled: function (container, button, pressed)
{
console.log("Toggle Event");
var index = container.getItems().indexOf(button);
if (index == 0) {
if (pressed) {
container.setPressedButtons();
var settingListContainer = this.down("#setting-list-container");
if (settingListContainer) {
this.remove(settingListContainer, true);
// close nav by touching the partial off-screen content
}
}
}a
else {
var settingListContainer = this.down("#setting-list-container");
if (!pressed&&settingListContainer) {
this.remove(settingListContainer, true);
}
else if (pressed) {
var existingList = Ext.ComponentQuery.query('settingList')[0];
if (existingList) {
this.add(existingList);
document.addEventListener('click', this.documentClickHandler, false);
}
else {
this.add({ xtype: "settingList", height: '349px', sortHandler: this.config.sortHandler, segmentControl: container });
document.addEventListener('click',this.documentClickHandler, false);
}
}
}
},
listeners: [
{
delegate: "#control-segment-button",
event: 'toggle',
fn: 'onSegmentToggled'
}
],
initialize: function () {
//Ext.require("");
var segmentedButton = Ext.create('Ext.SegmentedButton', {
layout: {
type: 'hbox',
pack: 'center',
align: 'stretchmax'
},
docked: 'bottom',
id:'control-segment-button',
allowMultiple: false,
allowDepress: true,
config: { flex: 1 },
items: [
{
iconCls: 'time',
width: '50%',
cls:'palin-segment',
style:"border-radius:0px"
},
{
iconCls: 'settings',
width: '50%',
cls: 'palin-segment',
style: "border-radius:0px"
}
]
});
this.add(segmentedButton);
}
});
event.preventDefault(); is what keeps the framework or the document from also handling this event.

Dynamic creation of QML ObjectModel elements

I'am trying to dynamically create bunch of QML ObjectModel elements such as simple rectangles and then show them in ListView. But when I build my application nothing is appear. Console log shows only the message: "Created graphical object was not placed in the graphics scene". Is there any way to do it right with this approach, or anything else?
UPD: code
main.qml
import "imgRectsCreation.js" as ImgRectsCreationScript
import QtQuick 2.0
import QtQml.Models 2.1
Rectangle {
id: root
ObjectModel{
id: itemModel
Component.onCompleted: ImgRectsCreationScript.createImgRects();
}
ListView {
id: view
clip: true
anchors { fill: root; bottomMargin: 30 }
model: itemModel
preferredHighlightBegin: 0; preferredHighlightEnd: 0
highlightRangeMode: ListView.StrictlyEnforceRange
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem; flickDeceleration: 2000
cacheBuffer: 200
}
Rectangle {
width: root.width; height: 30
x: 10
y: 330
color: "gray"
Row {
anchors.centerIn: parent
spacing: 20
Repeater { // little points at the bottom
model: itemModel.count
Rectangle {
width: 5; height: 5
radius: 3
color: view.currentIndex == index ? "sandybrown" : "white"
MouseArea {
width: 20; height: 20
anchors.centerIn: parent
onClicked: view.currentIndex = index
}
}
}
}
}
}
imgRectsCreation.js
var sprite;
var component;
function createImgRects() {
component = Qt.createComponent("ImgRectSprite.qml");
if (component.status === Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
for (var i = 0; i < 3; i++) {
if (component.status === Component.Ready) {
sprite = component.createObject(itemModel, {"x": 10, "y": 10});
if (sprite === null) { // Error Handling
console.log("Error creating object");
}
}
else if (component.status === Component.Error) { // Error Handling
console.log("Error loading component:", component.errorString());
}
}
}
and finally - ImgRectSprite.qml
import QtQuick 2.0
Rectangle {
width: 100; height: 100;
color: "red"
Image {
width: parent.width
height: parent.height
source: window.slotGetFileUrl()
}
}
I'm not a big fan of component creation from JS code - i tend to put QML code in .qml files, and "heavy" (well it's JS afterall) code inside .js files - .
Have you tried to dynamically create qml objects using the Loader object instead?
Well, okay, I've solved it by myself. But I'm not completely sure that it's a right decision.
I've decided to remove ObjectModel from my main.qml
... and replace it with ListModel
ListModel {
id: dataModel
dynamicRoles: true
Component.onCompleted: {
for (var i = 0; i < 3; i++)
{
append({ color: "orange" })
}
}
}
finally, I've added delegate to my ListView
delegate: Rectangle {
width: view.width
height: view.height
color: model.color
Image {
width: parent.width
height: parent.height
source: window.slotGetFileUrl() // includes logic to select images
}
???
PROFIT!
Thanks for your attention :)
Try to replace itemModel as a parent for dynamically created object with null or undefined value:
sprite = component.createObject(null, {"x": 10, "y": 10});

how to delay() qtip() tooltip to be loaded

I Load this way:
$('.selector').each(function(){
$(this).qtip({
content: { url: '/qtip.php?'+$(this).attr('rel')+' #'+$(this).attr('div'), text:'<center><img src="/images/loader.gif" alt="loading..." /></center>' },
show: { delay: 700, solo: true,effect: { length: 500 }},
hide: { fixed: true, delay: 200 },
position: {
corner: {
target: 'topRight',
tooltip: 'left'
}
},
show: {
// Show it on click
solo: true // And hide all other tooltips
},
style: {
name: 'light',
width: 730,border: {
width: 4,
radius: 3,
color: '#5588CC'
}
}
});
});
And that looks like if there is a thelay cause of the effect. but qtip.php it's loaded with no delay which is what I really want (to reduce unneeded requests)
Can I delay 300ms before loading qtip.php?
You could set it to use a custom event, then trigger the event after a timeout. The hoverIntent plugin might help, if you want to wait until the mouse stops.
Using hoverIntent:
$(selector).hoverIntent(function() {
$(this).trigger('show-qtip');
}, function() {
$(this).trigger('hide-qtip');
}).qtip({
// ...
show: {
when: { event: 'show-qtip' }
},
hide: {
when: { event: 'hide-qtip' }
}
});
If you want to make hoverIntent wait longer before triggering, you can give it a configuration object with an interval property:
$(selector).hoverIntent({
over: showFunction,
out: hideFunction,
interval: 300 // Don't trigger until the mouse is still for 300ms
});
Without a plugin (I haven't tested this):
(function() { // Create a private scope
var timer = null;
var delay = 300; // Set this to however long you want to wait
$(selector).hover(function() {
var $this = $(this);
timer = setTimeout(function() {
$this.trigger('show-qtip');
}, delay);
}, function() {
if (timer) {
clearTimeout(timer);
}
}).qtip({
// ...
show: {
when: { event: 'show-qtip' }
}
});
})();
Here I just created another param and it's more simple to use, I have tested this in qtip1(not sure about qtip2)
$('.qtip').qtip({
show: {
when: 'mouseover',
//customized param, when 'mouseout' the qtip will not shown and delay will clean
cancel : 'mouseout',
delay: 500
}
});
To add this param, you need to modify the code of function assignEvents() in qtip:
function assignEvents()
{
...
function showMethod(event)
{
if(self.status.disabled === true) return;
// If set, hide tooltip when inactive for delay period
if(self.options.hide.when.event == 'inactive')
{
// Assign each reset event
$(inactiveEvents).each(function()
{
hideTarget.bind(this+'.qtip-inactive', inactiveMethod);
self.elements.content.bind(this+'.qtip-inactive', inactiveMethod);
});
// Start the inactive timer
inactiveMethod();
};
// Clear hide timers
clearTimeout(self.timers.show);
clearTimeout(self.timers.hide);
// line : 1539
// Added code
--------------------------------------------
// Cancel show timers
if(self.options.show.cancel)
{
showTarget.bind(self.options.show.cancel,function(){
clearTimeout(self.timers.show);
});
}
--------------------------------------------
// Start show timer
self.timers.show = setTimeout(function(){ self.show(event); },self.options.show.delay);
};
For qtip2 there is parameter, called show while initializing the plugin, which represents time in milliseconds by which to delay the showing of the tooltip when the show.event is triggered on the show.target.
For example:
/*This tooltip will only show after hovering the `show.target` for 2000ms (2 seconds):*/
jQuery('.selector').qtip({
content: {
text: 'I have a longer delay then default qTips'
},
show: {
delay: 2000
}
});

Categories