Infor Technology

« | Main | »

Working with Events in LSO Scripting: Part 2

June 25, 2012

By Vince Mcgowan

In the previous blog post we looked some of the basics of working with events in S3 LSO Scripting.  And if it is not clear by now, scripting is available only on the standard presentation of S3 Form and not the List Driven version.  In this article, we will examine some more advanced topics related to scripting.

As stated in part 1, events are at the core of Windows programming.  Events are in effect a notification that some action has occurred.  And improper handling of events can cause a user some frustration for example in the form of an unresponsive screen while event processing is occurring.

One other brief review from part 1: handling events is a 3 step process in your script:

  • Attach a handler function to the form or control.
  • Implement the handler function.
  • Cleanup as the form is unloading by releasing the memory associated with the handler.

In this article the focus is on step 2, implementing the handler and special considerations.

Off-loading event processing to another function

We know that an event handler should not perform a time consuming process.  But if such a process
is required, it should be “off-loaded” to another function. There are 2 ways this can be done: tell the UI to execute the function when it is done with all the currently queued events, or run the function on another thread in the background.  One limitation to the latter method of event handling is that background processing cannot reference UI elements.  So, if data from the UI is required, that should be captured first and passed as parameters to yourbackground worker.

Let’s look at some examples.  First, to defer processing on the UI thread involves the use of a Dispatcher object which each control has.  In addition the LSO DashboardService has a dispatcher object which is often the preferred object to use.  The Dispatcher is responsible for queuing up event handlers and functions to be executed.

Besides avoiding time consuming processing, what is another case for off-loading the hander?  One
example can be shown with the ComboBox control. 
If I choose to handle the SelectionChanged event of that control, the new value is not displayed until the event handler execution is complete.  Look at this code:

public function OnComboBoxSelectionChanged(sender: Object, e: SelectionChangedEventArgs)

{

    e.Handled = true;

    var msg = "The selection is: " + form.GetCustomElementValue("MyComboBox");

    MessageBox.Show(msg);

}

If I change the selection in the combo box from “Selection 1” to “Selection 2”, look at the result in the image below.  The current value is “Selection 2”, but the control has not yet updated its displayed value.

   Eventsa
 
    
  Now, if instead of the previous code I have this:

public function OnComboBoxSelectionChanged(sender: Object, e: SelectionChangedEventArgs)

{

    e.Handled = true;

    var del: VoidDelegate = HandleSelectionChanged;

    DashboardService.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
del);

}

public function HandleSelectionChanged() : void

{

    var msg = "The selection is: " + form.GetCustomElementValue("MyComboBox");

    MessageBox.Show(msg);

}

Look at the result:

  Eventsb

You may notice something else not yet mentioned – VoidDelegate – and be asking what is that.  A delegate is simply a function delegated to execute a task.  VoidDelegate is an LSO specific delegate, one that requires no parameters and one that returns void.  In the example code, I have declared a
variable of type VoidDelegate and assigned to it the function HandleSelectionChanged which is declared to return void.

Since VoidDelegate and DashboardService are in the LSO framework and DispatcherPriority is a .NET
enumeration and not in the S3 namespace, some additional import statements are required to support this code:

import System.Windows.Threading;    // DispatcherPriority

import Mango.Core.Util;             // VoidDelegate

import Mango.UI.Services;           // DashboardService 

Executing a function on a background thread

The use of a background thread to execute a time consuming process is preferred to locking up the UI thread, which to the user may look like the application has been hung up.  Of course application requirements determine whether use of a background thread is possible.  If an immediate return of a
value is necessary, then a background process will not do since the exact timing of the result cannot be known.

But let’s look at another example and show how a background thread can be used.  In this example, we
have added a button to the form and use the Click handler to determine if the value in a particular field is unique. This involves a call to the S3 application server and a function for making asynchronous calls to the server is provided:  ServerRequestAsync.  Here’s the code involved:

public function OnButtonClick(sender: Object, e: RoutedEventArgs) 

{

    try {

        //
build up a DME call

        var dmeAPI
= ScriptConstants.DataMinePath +

               
"?PROD=" + ScriptUtil.GetUserAttribute("productline") +

                 "<some more stuff>" + "&OUT=XML";

        //
make the async server request

        var handler:
RunWorkerCompletedEventHandler = OnRequestComplete;

        ScriptUtil.ServerRequestAsync(dmeAPI, null, handler);

        form.ShowMessage("Verifying…");

    

    } catch (ex)
{

        console.WriteLine(ScriptUtil.FormatException("OnButtonClick",
ex));

    }

}

public function OnRequestComplete(sender: Object, e: RunWorkerCompletedEventArgs)

{

    // get the response

    try {

        var transXml
= e.Result;

        // examine
the xml response for unique value

        form.PositionInCurrentElement();  // will set focus
and clear the message

        

    } catch (ex)

        ConfirmDialog.ShowErrorDialog("Error", 

            ScriptUtil.FormatException("OnRequestComplete",
ex), null);

    }

}

We have again introduced something new – the RunWorkerCompletedEventHandler.  When a process is invoked on a background thread, we must identify a function on the main thread to call back to when the process is completed.  If our call to the server does not result in some sort of exception, the response Xml is contained in the event args Result property.

Again, some additional import statements are required for this code:

import System.ComponentModel;       // RunWorkerCompletedEventHandler

import Mango.UI;                    // ConfirmDialog

What if your time consuming process does not involve a call to the S3 server? Can we do a generic background thread process?  Of course, and let’s look at how.  Look at this code:

public function OnButtonClick(sender: Object, e: RoutedEventArgs) 

{

    try {

        //
create a background worker and run it

        var completedHandler:
RunWorkerCompletedEventHandler = OnWorkerComplete;

        var doWorkHandler:
DoWorkEventHandler = WorkerDoWork;

        var worker
= new BackgroundWorker();

        worker.add_DoWork(doWorkHandler);

        worker.add_RunWorkerCompleted(completedHandler);

        worker.RunWorkerAsync("some
value"); // optionally pass
parameter to DoWork function

    

    } catch (ex)
{

        console.WriteLine(ScriptUtil.FormatException("OnButtonClick",
ex));

    }

}

public function WorkerDoWork(sender: Object, e: DoWorkEventArgs) : void

{

    // executing on background
thread

    var parameter
= e.Argument;    // if a
parameter was passed

    try

    {

        e.Result = "OK" // or
e.Result = SomeTimeConsumingProcess(parameter);

    }

    catch (ex)

    {

        e.Result = ex;

    }

}

public function OnWorkerComplete(sender: Object, e: RunWorkerCompletedEventArgs) : void

{

    // get the response on the
main thread

    try {

        if (e.Result
== "OK")

        {

            // use the result if any and continue on the UI thread

            return;

        }

            

        //
handle error

    } catch (ex)

        ConfirmDialog.ShowErrorDialog("Error", ScriptUtil.FormatException("OnWorkerComplete", ex), null);

    }

}

This is actually quite similar to our previous example and requires no additional import statements.  The big difference is that the function executing the background process is local to our script file.  To accomplish this we instantiated a BackgroundWorker object and added to it a couple of event handlers. In both examples the background process was initiated from the Click handler of a button, but of course could be initiated from any function.

A couple of things to consider if implementing your own background process:

1) Passing arguments is optional, but if necessary is a parameter to the RunWorkerAsync method.  If a parameter is passed, it is retrieved in the DoWork function as the Argument property of the event args.

2) You should always catch any exceptions in the DoWork function so the callback function knows if there were errors during the processing by checking the Result property of the event args.

Summary

Handling events provide the real power behind what you can do with scripts for S3 forms in LSO. But improper handling of time consuming processes or taking action before an event handler has completed can result in an undesirable user experience. Knowing how to use asynchronous and background processing to prevent the UI from being locked up or producing misleading results during event handling can often be useful in achieving a better experience.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *



About this Blog

The latest on Infor Technology