I have a DataGrid on my main page that is full of Employees. Each Employee has an "in" status - this is modified based on whether the Employee is currently in the building or not.
I need to consistently update this DataGrid as people will be entering or leaving the building throughout the day. This means I will be sending MySQL queries somewhere between every 1-5 seconds until the user clicks on a button to navigate to another page.
I have tried the most simple and obvious solution, a While loop however this freezes and locks the UI. How can I create a loop that runs and queries MySQL without locking the UI?
EDIT: Attempted Answer
public void PeriodicCall()
{
var employeeDS = new EmployeeDataService();
int i = 1;
Timer timer = new Timer(
state => {
Employees = employeeDS.HandleEmployeeSelect();
FilteredView = CollectionViewSource.GetDefaultView(Employees);
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
dataGrid.ItemsSource = FilteredView;
testLabel.Content = "Who's Who " + i;
i++;
}));
},
null, //no object as Callback parameter
TimeSpan.FromSeconds(0), //start in x millisec
TimeSpan.FromSeconds(1)); //time between call
}
You can use a Backgroundworker, hook to its .completed and there render (using
BackgroundWoker worker = new BackgroundWorker();
worker.DoWork += functioToDo
worker.Completed += functionWhere you update the UI
worker.runAsynchronous(); //actually starts the thread
In the function that updates the UI don't forget to use the dispatcher to access the UI thread:
dispatcher.invoke((Action) delegate { here your code})
to access the UI thread for elsewhere.
Other cooler approach is to take care of the propertyChanged and bind the changes to the UI. But this is a bit more complex and I don't know the details by head.
You can use a timer to start the whole thing every few seconds.
A third approach would be to have a trigger when you update the data base. We would need more details to know how to attach there. But from there you can also call some updates in the UI and then you don't need to use CPU to check every seconds (you know, work by interruption instead of by polling).
You should use a timer to periodically retrieve data from your DB and update data in your datagrid using the Dispatcher.BeginInvoke function.
Timer timer = new Timer(
state => {
//Callback function
Object yourData = GetDataFromDB();
Application.Current.Dispatcher.BeginInvoke(new Action(()=> {
YourUIProperty = yourData;
}));
},
null, //no object as Callback parameter
TimeSpan.FromSeconds(1), //start in x millisec
TimeSpan.FromSeconds(5) //time between call
);
Related
Help turning multiple functions into a a few functions.
var myNumberOne = 10;
var myNumberTwo = 100;
function one would create a timer or interval that fire 2 times every second, Traceing the word "food", It would when finished, "myNumberOne += 10" making myNumberOne = 20;
function two would create a timer or interval that fire 5 times every half-second, Traceing the word "ben", It would when finished, "myNumberTwo += 50" making myNumberTwo = 250;
For two functions this is fine, but if I have 100s of possible combinations, I cannot think on this should be done, without intervals , timers, functions etc... interfering with each-other, and passing arguments through time.
Thanks for any help.
for clarification: I waant to call a function like this
setTimeFunction("myTimeOne", myNumberOne, 2,1000,10, "ben");
setTimeFunction("myTimeTwo", myNumberTwo, 5,500,50,"food");
Well, first, you need to compose a generic method that would perform a number of similar actions. Tracing is easy, but you cannot pass a variable to change directly because you'll pass a value, not a reference to variable. In order to do as you want you need to pass it as a pair "container object" and "variable name" to use the square bracket notation.
function myownDothings(target:Object, varname:String, adiff:int, totrace:String):void
{
// Use square bracket notation to change the targeted variable.
target[varname] += adiff;
// Trace the given argument.
trace(totrace);
}
Ok, now the simple complicated part. There's a setTimeout(...) function that calls the given method many times with a given timeout, but it's official documentation officially advises the use of Timer class.
I hope you know how to work with classes, because the thing you want calls for OOP and fitting it into the frame scripts will result in something ugly. So, you need to compose a class that remembers function to call, timeout settings and a bunch of arguments as well.
package
{
import flash.utils.Timer;
import flash.events.TimerEvent;
public class Ticker
{
// You need to keep the references to the things you use,
// or else Garbage Collector might think you don't need it.
static private var list:Array = new Array;
// Instead of static method you can use the "constructor" way,
// but I find it more stylish and it's one more thing for
// you to google and learn of, which I totally approve.
// The ... construction allows to pass a random number
// of arguments (after fixed arguments) as an Array.
static public function create(handler:Function, timeout:int, ...args:Array):void
{
var aTicker:Ticker;
// Brackets () are not mandatory with the "new" operator
// if there are no mandatory constructor arguments.
aTicker = new Ticker;
// Store all the necessary data in the new instance. That's the
// point of OOP scripting here: you want to make 100 different
// tickers and you need each of them to keep some custom data.
aTicker.timeout = timeout;
aTicker.handler = handler;
aTicker.args = args;
// Finally, run the ticker.
aTicker.start();
// Store the created instance into the keeper list
// to prevent Garbage Collector from destroying it.
list.push(aTicker);
}
// Again, fear the Garbage Collector.
private var clock:Timer;
// Keep in mind that timeout is not exactly accurate
// as it aligns to the SWF's frame rate. Setting it up to call
// more times a second than FPS will pose to be a meaningless act.
private var timeout:int;
// The reference to the method to call.
private var handler:Function;
// The list of arguments to pass to the method above.
private var args:Array;
// This method is called from the "create" method
// to finalize things and start ticking.
private function start():void
{
// Create a Timer instance with a given timeout.
clock = new Timer(timeout);
// Subscribe the listener to the Timer.
clock.addEventListener(TimerEvent.TIMER, onTick);
// Start the Timer.
clock.start();
}
// The Timer instance will trigger this method
// (approximately) every given timeout of milliseconds.
private function onTick(e:TimerEvent):void
{
// Now the idea is to call the given method
// passing the list of given arguments to it.
// Normally you don't need to pass the "this" object
// to a method unless you use unnamed unbound closures.
// (which I personally consider a heresy and don't recommend to use)
// So you just pass "null" as the first argument and everything is fine.
handler.apply(null, args);
}
}
}
Now, the usage. It's where all the horrors above finally shine.
import Ticker;
var myNumberOne = 10;
var myNumberTwo = 100;
// Fire 2 times every second, increase "myNumberOne" by 10, trace the word "ben".
// So, 2 times a second it will call: myownDothings(this, "myNumberOne", 10, "ben");
Ticker.create(myownDothings, 1000 / 2, this, "myNumberOne", 10, "ben");
// Fire 5 times every half a second, increase "myNumberTwo" by 50, trace the word "food".
// So, 10 times a second it will call: myownDothings(this, "myNumberTwo", 50, "food");
Ticker.create(myownDothings, 500 / 5, this, "myNumberTwo", 50, "food");
I have a function in Adobe Flex 4 (ActionScript 3) that accepts an object and returns an ArrayCollection...
If a certain global variable is set to true, I want the function to delay itself for 3 seconds before running. Otherwise I want the function to run as normal.
The problem is, if I use a Timer, that timer calls a separate function, and that function cannot return anything to my calling function, nor can the function it calls accept any parameters, so it's not like I can call my own function recursively after the TimerComplete event fires... And a recursive call wouldn't work anyway, because it would return the ArrayCollection to the timer-result function, not to the original calling function...
I need a delay within the function, not a delay that causes me to go outside that function. But I cannot figure out how to do it.
Something like this is what I need to do:
private function createArrayCollection(myObject:Object):ArrayCollection {
var myArrayCollection:ArrayCollection = new ArrayCollection();
if (globalWaitBoolean) {
//delay here for 3 seconds, somehow
}
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
}
So... Any ideas on how to accomplish this without calling an external Timer function that cannot return an object back to my original function?
Thanks,
The way you want it you will have your whole application to lag for 3 seconds, unresponsive to any user input and external events. But it is possible, sure:
import flash.utils.getTimer;
private function createArrayCollection(myObject:Object):ArrayCollection
{
var myArrayCollection:ArrayCollection = new ArrayCollection;
if (globalWaitBoolean)
{
var waitUntil:int = getTimer() + 3000;
// Method getTimer() returns time in ms passed since app start.
// So you just have to wait until it is greater than appointed time.
while (getTimer() < waitUntil)
{
// Do nothing.
}
}
return (myArrayCollection);
}
Still, if you want to do it in a correct way of doing it:
import flash.utils.setTimeout;
private function callerMethod():void
{
// Blah blah blah.
// ...
// Finally.
createArrayCollection(sourceData, asyncResult);
}
private function createArrayCollection(myObject:Object, handler:Function):void
{
var result:ArrayCollection = new ArrayCollection;
if (globalWaitBoolean) setTimeout(handler, 3000, result);
else handler(result);
}
private function asyncResult(source:ArrayCollection):void
{
// The rest of your processing code.
}
Normal (synchronous) code flow would not return until the value is prepared, so should you desire to actually wait for 3 seconds while not allowing your app to do anything, use getTimer() approach from #Organis's answer. If you'll go for an asynchronus result, you'll need to face and overcome some more problems.
First, when do you expect your returned ArrayCollection to actually arrive. Speaking of code design, asynchronous code requires a whole lot of assumptions, thread safety etc etc, and even while AS3/Flash does not have true multithreading unless you count Workers, the code flow with events is not as obvious. So, whoever called your createArrayCollection() MUST NOT expect value returned from it right away. So, speaking about your direct question, NO, you can't avoid timers of some sort if you desire a responsive application. But you can use them with an approach that would involve an indirectly returned result.
Second, whether there might be concurring requests for more array collections from objects if your app would require these - you have to prepare for any kind of interference that might be caused by this. Say your function is triggered by a button click - what if that button would get clicked more than once in 3 seconds?
Third, actual route to processing code is not direct with asynchronous return. You need either a callback, an event handler (which is essentially a semi-native callback), a code that periodically checks for value presence (enter frame handler, etc) or a similar trick to gather the value that's returned asynchronously, and then transfer it to any relevant code that would process it further. Therefore, you would need to design an interface capable of receiving complex data (source object forward, array collection backward) and then carefully test it against all the possible cases and flaws.
An example of implementing all that is very long, I'll try to outline it somehow. Ler's assume you have a sort of "server" class that accepts requests for data and processes it synchronously (no wait) or asynchronously (wait). It accepts a source object of type "T" and provides a newly created object of type ArrayCollection, supplied as a parameter to whatever callback function sent to it. Also it accepts a delay (a simple way to show sync/async return would be a boolean, but why not getting an int?) as a parameter, and guarantees (to the extent of event model limitations) that after this delay the callback will be called ASAP. The architecture will then look like this:
class Processor {
Dictionary requests; // here all the requests that are delayed will be stored
public function dpr(source:T,callback:Function,delay:int=0):void{...}
// creates requests and stores them
private function syncProcess(source:T):ArrayCollection {...}
// whatever routine you want to get variably delayed
private function processTimeout(e:Event=null):void {...}
// processes events from "setTimeout()" and calls callbacks
}
Note that asynchronous approach forced to create three more entities than a synchronous one. First is the request holding structure (the dictionary here), second is timeout event handler, third is whatever callback you'll desire to get called when the data is ready. The code flow would go like this:
Synchronous call would result in the callback directly called from within the class: request->processTimeout->syncProcess()->callback. Asynchronous call will have the callback called from within Timer::timerComplete event handler via setTimeout called within request, with data that originally came from request stored in requests.
You could use an embedded/inline function:
private function createArrayCollection(myObject:Object):ArrayCollection {
var myArrayCollection:ArrayCollection = new ArrayCollection();
if (globalWaitBoolean) {
var milliseconds:int = 3000;
//delay here for 3 seconds
setTimeout(function()
{
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
},
milliseconds);
}
else
{
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
}
}
The inner function will have access to all local vars of the outer function.
I have a slightly unusual situation with SharedObject:
The situation: where we have a SWF (browser) based application running on a single local machine. The SWF accesses local content and is reloaded every XX number of seconds/minutes/hours.
The state of application has to be stored within a single SharedObject (this is using the '/' parameter to force it to global) in a JSON style.
The SWF loads the SO before it makes any state updates and correctly calls flush() immediately after to save state.
The Problem: Everything runs fine BUT occasionally there is a situation where by 2 instances of the same SWF are existing at the same in different instances and both are accessing the same SharedObject.
The 2nd has to take control of the SO from the 1st, by each SWF instance setting it's instance state to an incremented number (SWF Idx) stored in the SO.
Both are loading the file before any update is made, version number checked, and will disable themselves if the saved SWF Idx is above it's own. Unfortunately the 1st SWF instance somehow isn't loading the latest version of the SharedObject because it traces out the original number (e.g. 22) instead of the now updated one (e.g. 23). While the 2nd SWF is tracing out 23 and the SO contains 23.
Is there any way that the browser could possibly be caching the SO?
Testing
I'm currently testing the situation by running one browser instance locally then launching a 2nd. Making copies of the SO before and after each state.
I've also run the 1st and 2nd via IntelliJ and can see that SharedObject.getLocal is being called and checked each time.
I've included the basics of the code I'm using below where:
__so = is the public SharedObject variable
_thisSWFIdx = is the private variable inside AS3 storing the current instances SWF Index
__so.data.activeSWFIdx = the latest SWF Index
The SO 'get' I'm using is:
SharedObject.getLocal(_SO_ID, "/");
The check i'm doing is:
if (_thisSWFIdx < __so.data.activeSWFIdx) {
__amActiveSWF = false;
}
The saving of the variable to SO:
__so.data.activeSWFIdx = _thisSWFIdx;
flush();
Additonal info:
This is running on both mac & windows
FlashPlayer 10.3
Pure AS3
compiled in IntelliJ
same issue in FF, Chrome and IE
primary machine Macbook
I can't find anything within the documentation or existing threads so any help will be much appreciated.
After a little test, I remarked the behavior that you've mentioned : after updating the SharedObject by a second SWF, the first one can't get the new value only after a reload.
To avoid that, I think, as a workaround, you can use a second SWF which will just work on the SharedObject by reading / writing data.
For that, take this example :
so.swf : the SWF which will read and write data.
var shared_object_name:String = 'so';
var shared_object:SharedObject = SharedObject.getLocal(shared_object_name, '/');
// get the id
function get_id(): int {
return shared_object.data.id ? shared_object.data.id : -1;
}
// set the id
function set_id(new_id:int): void {
shared_object.data.id = new_id;
shared_object.flush();
}
app.swf : your global app which will use the "so.swf" to do SharedObject's operations :
var init:Boolean = true;
var swf_id:int = 1;
var swf_disabled:Boolean = false;
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, on_SWFLoad);
loader.load(new URLRequest('so.swf'));
function on_SWFLoad(e:Event): void
{
var loaded_swf:MovieClip = MovieClip(loader.content);
var current_id:int = loaded_swf.get_id();
// if it's initial run
if(init){
init = false;
if(current_id > 0){
swf_id = current_id + 1;
}
loaded_swf.set_id(swf_id);
} else { // we are verifying if there is a new active SWF
if (swf_id <= current_id) {
swf_disabled = true;
}
}
}
function stage_onPress(e:MouseEvent){
// if this SWF is disabled, stop getting the "id"
if(swf_disabled){
stage.removeEventListener(MouseEvent.CLICK, stage_onPress);
return;
}
// otherwise, get the "id" to verify if there is another active SWF
loader.load(new URLRequest('so.swf'));
}
stage.addEventListener(MouseEvent.CLICK, stage_onPress);
I used MouseEvent.CLICK only for testing purposes, of course you have to use a Timer or something else to verify every n seconds (for example) that you have another active SWF to disable the current one...
Hope that can help.
I have this custom event handler that shows a popup and accepts input from the user:
private var mySkinnablePopupContainer:MySkinnablePopupContainer;
private function handleShowGridPopupEvent(event:ShowGridPopupEvent):void {
var mouseDownOutSideHandler:Function = function(mdEvent:FlexMouseEvent):void {
// At this point, event.targetControl contains the wrong object (usually the previous targetControl)
if (mdEvent.relatedObject != event.targetControl) {
mySkinnablePopupContainer.close();
}
}
var gridPopupSelectionHandler:Function = function(popEvent:PopUpEvent):void {
if (!popEvent.commit) return;
// At this point, event.targetData contains the wrong object (usually the previous targetData)
myModel.doSomethingWithData(popEvent.data.selectedItem, event.targetData);
}
if (!mySkinnablePopupContainer) {
mySkinnablePopupContainer = new MySkinnablePopupContainer();
mySkinnablePopupContainer.addEventListener(PopUpEvent.CLOSE, gridPopupSelectionHandler);
mySkinnablePopupContainer.addEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, mouseDownOutSideHandler);
}
// At this point, event.targetData contains the correct object
mySkinnablePopupContainer.dataProvider = getMyDPArrayCollection(event.targetData);
mySkinnablePopupContainer.open(this);
var point:Point = event.targetControl.localToGlobal(new Point());
mySkinnablePopupContainer.x = point.x + event.targetControl.width - mySkinnablePopupContainer.width;
mySkinnablePopupContainer.y = point.y + event.targetControl.height;
}
Every time the function handler gets called, it will have the correct ShowGridPopupEvent object but by the time it calls the
gridPopupSelectionHandler, it will contain the old object from a previous call. It works the first time, subsequent calls fails.
Somehow the reference to the event object changed somewhere in between before opening the popup and after.
Any idea what am I doing wrong here? Is this a bug with flex?
found the prob. since im attaching listener only once, it will reference the old listener, with the reference to the old data. i guess i was expecting its reference to be updated whenever i create the closure. not in this case. possible fix is to remove the listener and re-add it again but I abandoned the idea of using closures, and aside from what RIAStar mentioned, it is also impractical as it only gives more overhead by creating a new function for every invocation of the handler.
Is there anyway to save a var value forever?
Let´s say I have this:
private var b:int;
private var xb:int;
public function UPD()
{
start();
}
private function start():void
{
//add a button to do something
//add a button to do whatever
}
//define the listener for the buttons (MouseEvent.CLICK, something & MouseEvent.CLICK, whatever)
private function something(e:MouseEvent):void
{
if (!b){b = (1 + xb);}
//another function to do something else after adding b + xb
}
private function whatever(e:MouseEvent):void
{
xb++
}
This way xb (and b) will have 1 more each time I click on the whatever button...now let's say I close everything...how can I have the values I had before, back, when I open it again, so xb doesn´t start from 0 again, but it keeps the clicks from previous time?
You need to incorporate some means of external storage for the values such that you can retrieve them when you come back to the program after having closed and reopened it. This could take the form of storing the data in a database (MySQL), or a data file (text/xml), or as a SharedObject (Flash version of a cookie).
You can store the value in a shared object (which is basically like a cookie but for flash): http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/SharedObject.html.
On startup, you can retrieve the value, and every time it changes, you can save it back into the SharedObject again. This doesn't propagate through multiple clients, however. (So each client will have a different count)