0 Comments

There are times when we want to remove a field from a table. This usually is no problem, except when the field has already been added to a client form. If we remove the field from the table but we don’t remove it first from the form then we get an error saying the client form is wrong. Removing a field from a table doesn’t automatically remove it from the client form. We should always remove the field from the form first before removing it from the table. But what if we forget to do this? (or if we miss some forms, because there is no easy way to know in which forms the field is used).

Before I explain how to remove the field from the form I will give a little background on how fields are used in a client form. Client forms represent a view for data segments. Every client form has a primary data segment and zero or many secondary data segments. The primary data segment stores all the fields for the underlying table. Secondary data segments are used for secondary tables, tables with foreign keys to the primary table. When a field is added to a form, the form designer automatically adds the field to the corresponding data segment, and also adds it to the form definition (the view). So if we remove the field from the form, the form designer only removes it from the view, but it doesn’t remove it from the data segment. There are two ways to solve this problem.

formEditorThe first way is to use the form editor. Just go to any field in the form (on the primary or secondary segment), then go to properties and edit the PivotalBindings property. Click in the DataField property and you will see all the fields associated with the data segment. From here you just use the “Delete data field” link to remove the unwanted fields. This will remove the field from the data segment. You can check that the field is actually removed by looking at the data segment.

Data segments can be found in the toolkit under the menu “Data Templates, Active & Device Forms”. Primary segments will always have the 1 ordinal. The problem is that the toolkit doesn’t allow you to edit the data template, since this could make client forms corrupted.


However, sometimes you can’t even access the form using the form designer. In this case we need to solve it using the second way. You can work around this by editing the data template associated to the form using SQL on the BM database. You can use this query to remove the field from the corresponding segment.

delete from ng_form_field f where f.ng_form_field_id in (
select ff.ng_form_field_id 
from 
  sc_form cf inner join 
  ng_form af on cf.active_form_id = af.ng_form_id inner join
  ng_form_segment fs on fs.ng_form_id = af.ng_form_id inner join
  ng_form_field ff on ff.form_segment_id = fs.ng_form_segment_id inner join
  table_fields tf on tf.table_fields_id = ff.field_id
where 
  cf.form_name = '<formName>' and
  fs.form_segment_name = '<segmentName>' and
  tf.field_name = '<fieldname>'
)

In the above SQL, you need to know the name of the form, the name of the segment, and the field name. The form and field name are easily known, but the segment name might be difficult to know. If this is the case, use the toolkit to find out the name of the segment by going to the menu “Data Tempaltes, Active & Device Forms” and look at the data segments that conform the client form and write down the name of the segment that has the field you want to remove.

All this could be avoided of course if we remember to remove the field from the form (using the first way described above) before removing the field from the table.

0 Comments

When using the out-of-the-box Pivotal PCM module, there are missing LD Strings for the error messages related with non-existing fields/tables.

Missing LD string errorFor example, if you try to use the data access API to get a non-existing field, as shown in the following example, you will get an HRESULT exception. The thing is that this error doesn’t tell us anything and it hides the real error, which is that the field we’re trying to access doesn’t exist.


using(DataTable t = this.DefaultDataAccess.GetDataTable("Contact",new string[] { "NonExistingColumn"}))
{
    // do something
}

In the example above, we are trying to get a non-existing field called NonExistingColumn from the Contact table. The error is non-intuitive and it is hard to debug, since it will not show any debug information in the event viewer and developers think that it is some error related with the platform.

As mentioned before, the problem is due to the lack of some LD Strings in the BM. I have reported this error to Pivotal and we hope to have a PCM version that solves this in the near future. Meanwhile, the solution is to import the missing LD Strings.

My good friend Pablo Oliveira, from Grupo Lanka has created a file with the missing LD Strings:

APPLICATION ERROR CODE,NoType,NoType,Lanka system string,0x0409
APPLICATION ERROR CODE,Choice,Choice,Lanka system string,0x0409
APPLICATION ERROR CODE,ClientTask,ClientTask,Lanka system string,0x0409
APPLICATION ERROR CODE,Command,Command,Lanka system string,0x0409
APPLICATION ERROR CODE,CommandHandler,CommandHandler,Lanka system string,0x0409
APPLICATION ERROR CODE,DataSection,DataSection,Lanka system string,0x0409
APPLICATION ERROR CODE,DataTemplate,DataTemplate,Lanka system string,0x0409
APPLICATION ERROR CODE,Form,Form,Lanka system string,0x0409
APPLICATION ERROR CODE,InContextTableMenuItem,InContextTableMenuItem,Lanka system string,0x0409
APPLICATION ERROR CODE,InboundCommunicationAddress,InboundCommunicationAddress,Lanka system string,0x0409
APPLICATION ERROR CODE,LDGroup,LDGroup,Lanka system string,0x0409
APPLICATION ERROR CODE,LDLanguage,LDLanguage,Lanka system string,0x0409
APPLICATION ERROR CODE,LDString,LDString,Lanka system string,0x0409
APPLICATION ERROR CODE,LetterExpress,LetterExpress,Lanka system string,0x0409
APPLICATION ERROR CODE,List,List,Lanka system string,0x0409
APPLICATION ERROR CODE,Query,Query,Lanka system string,0x0409
APPLICATION ERROR CODE,Report,Report,Lanka system string,0x0409
APPLICATION ERROR CODE,Security,Security,Lanka system string,0x0409
APPLICATION ERROR CODE,Search,Search,Lanka system string,0x0409
APPLICATION ERROR CODE,SearchResultsList,SearchResultsList,Lanka system string,0x0409
APPLICATION ERROR CODE,ServerTask,ServerTask,Lanka system string,0x0409
APPLICATION ERROR CODE,SmartPortalCategory,SmartPortalCategory,Lanka system string,0x0409
APPLICATION ERROR CODE,SmartPortalContentItem,SmartPortalContentItem,Lanka system string,0x0409
APPLICATION ERROR CODE,SmartPortalPlugIn,SmartPortalPlugIn,Lanka system string,0x0409
APPLICATION ERROR CODE,SmartPortalPlugInAttr,SmartPortalPlugInAttr,Lanka system string,0x0409
APPLICATION ERROR CODE,Subject,Subject,Lanka system string,0x0409
APPLICATION ERROR CODE,Table,Table,Lanka system string,0x0409
APPLICATION ERROR CODE,TableField,TableField,Lanka system string,0x0409
APPLICATION ERROR CODE,Task,Task,Lanka system string,0x0409
APPLICATION ERROR CODE,TaskGroup,TaskGroup,Lanka system string,0x0409
APPLICATION ERROR CODE,TaskPad,TaskPad,Lanka system string,0x0409
APPLICATION ERROR CODE,Topic,Topic,Lanka system string,0x0409

You can copy and paste the above text in a file with a .csv extension, and then use the Toolkit to import it in the BM (use the agent “Import System Strings”). Once imported you will get the right error when trying to reference non-existing objects using the data access API.

Correct error for non existing objects


0 Comments

In the previous post (How to show a progress bar for a long operation in Pivotal) I talked about how to improve the user experience when executing long running operations. I explained how to show a progress bar to provide feedback to users that an operation is in progress and they should (patiently) wait. The user experience changes a lot when we provide feedback to users.

In this post I will talk about an alternative for long running operations that basically removes the wait time from the user. In this case, the long running operation will be executing on the server while the user gets back the control of the system. An example that shows a good use of an asynchronous operation is the following: suppose that when the user submits a new service ticket there are a bunch of emails being sent (to the supervisor, to the service queue, to the user acknowledging the submission, etc.) All these emails take time to be sent. If we do this on the server task associated to the service ticket, then the user will have to wait a (possibly) long time until all emails are being sent. All the user wants is to submit the incident, he/she doesn’t care if the emails are being sent at submission time or later.

CropperCapture[5]There are many ways to do this. You can set up an email queue and then have a background process (e.g. the scheduler script) to read the queue and send emails. In this case the submission task is responsible to submit the email jobs to the queue. In this post I will talk of a simpler way: a generic asynchronous task that executes server task method in the background.


For illustration purposes, let’s use a simple task to simulate our long running operation. The same example for our previous post will do:

[TaskExecute]
public virtual void LongRunningOperation(Id recordId)
{
    Trace.WriteLine(String.Format("Starting LongRunningOperation with recordId '{0}'", recordId));
    System.Threading.Thread.Sleep(20000);
    Trace.WriteLine(String.Format("Finishing LongRunningOperation with recordId '{0}'", recordId));
}

The client task makes a call to this method:

[ClientTaskCommand]
public virtual void LongRunningTask()
{
    try
    {
        Globals.Execute(this.DataTemplate, "LongRunningOperation", new object[] { this.RecordId });
        PivotalMessageBox.Show("Done", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
    }
    catch (Exception e)
    {
        Globals.HandleException(e.InnerException ?? e, true);
    }
}

As it is right now, the user will have to wait 20 seconds for the task to execute, and during this time the client is freezing doing nothing. Let’s see how we can change this and make all this asynchronous. The client will remain the same, we don’t to change the client side code. The server side code, however, will suffer a little change:

[TaskExecute]
public virtual void LongRunningOperationSync(Id recordId)
{
    Trace.WriteLine(String.Format("Starting LongRunningOperation with recordId '{0}'", recordId));
    System.Threading.Thread.Sleep(20000);
    Trace.WriteLine(String.Format("Finishing LongRunningOperation with recordId '{0}'", recordId));
}

[TaskExecute]
public virtual void LongRunningOperation(Id recordId)
{
    TransitionPointParameter t = new TransitionPointParameter();
    t.SetUserDefinedParameter(1, "Ticket Service");
    t.SetUserDefinedParameter(2, "LongRunningOperationSync");
    t.SetUserDefinedParameter(3, recordId);

    this.SystemServer.ExecuteServerTask("Async Service", "ExecuteApp", t.ParameterList, false);
}

We have split our long running operation in two: the LongRunningOperation method now makes a call to a new service task called “Async Service”, executing the method “ExecuteApp”, and passing some transition point parameters. These parameters are: the name of the service task (in this example I’m assuming our service task is called “Ticket Service”), and the method that actually does the hard work, and the parameters for such method. Our second method, the LongRunningOperationSync, is the one that actually does the job. The body of this method is the same as we had before.

The Async Service task is a generic service task that executes another service task asynchronously. Let’s see how to implement our async service:

[TaskExecute]
public virtual void ExecuteApp(ParameterList parameterList)
{
    string serverName = TypeConvert.ToString(TransitionPointParameter.GetUserDefinedParameter(1));
    string methodName = TypeConvert.ToString(TransitionPointParameter.GetUserDefinedParameter(2));

    object[] parameters = new object[] { };
    if (this.TransitionPointParameter.UserDefinedParametersNumber > 2) // get the rest of parameters
    {
        List<object> passedParameters = new List<object>();
        for (int i = 3; i <= this.TransitionPointParameter.UserDefinedParametersNumber; i++)
        {
            object parameter = this.TransitionPointParameter.GetUserDefinedParameter(i);
            if (String.IsNullOrWhiteSpace(TypeConvert.ToString(parameter)))
                passedParameters.Add(null);
            else
                passedParameters.Add(parameter);
        }
        parameters = passedParameters.ToArray();
    }

    ExecuteApp(serverName, methodName, parameters);
}

/// <summary>
/// Executes an application server task method
/// </summary>
/// <param name="appName">The name of the application server task</param>
/// <param name="methodName">The name of the server task method</param>
/// <param name="methodParameters">An array of parameters to be passed to the server taks</param>
private bool ExecuteApp(string appName, string methodName, object[] methodParameters)
{
    try
    {
        XElement xmlCommand = BuildExecuteServiceTaskCommand(appName, methodName, methodParameters == null ? new object[] {} : methodParameters);
        SendCommandAsync(xmlCommand);

        return true;
    }
    catch (Exception e)
    {
        // log the error
        throw;
    }
}

The service exposes only one public method. This method is responsible to retrieve the required parameters: the service task and method name, and the parameters for such method. It parses the transition point parameters for such information and then it calls the second method, which is more interesting. This method calls two helper methods: BuildExecuteServiceTask (to build an XML document) and SendCommandAsync (to do the async call). Let’s see these methods are implemented.

The BuildExecuteServiceTaskCommand job is to build an XML document according to the PBS XML specification (see the Pivotal Composite API Reference). This document will be sent to the PBS XML interface.

private const string PivotalNamespace = "urn:schemas-pivotal-com/LifecycleServer60";

private XElement BuildExecuteServiceTaskCommand(string serverRuleName, string methodName, params object[] parameters)
{
    XElement commandXml = new XElement(PivotalNamespace + "executeAppServerRule",
        new XElement(PivotalNamespace + "appServerRuleName", serverRuleName),
        new XElement(PivotalNamespace + "appServerRuleMethod", methodName));

    XElement commandXmlDocument = new XElement(PivotalNamespace + "command",
        new XElement(PivotalNamespace + "systemName", SystemServer.SystemInformation.SystemName),
        new XElement(PivotalNamespace + "loginType", "Client"));

    commandXmlDocument.Add(commandXml);

    if (parameters != null)
    {
        XElement commandParametersXml = new XElement(PivotalNamespace + "commandParameters");
        commandXml.Add(commandParametersXml);

        for (int i = 0; i < 6; i++) // the first six parameters must be empty
            commandParametersXml.Add(new XElement(PivotalNamespace + "emptyParameter"));

        foreach (object parameter in parameters)
        {
            string type, value;
            GetParameterData(parameter, out type, out value);
            commandParametersXml.Add(new XElement(PivotalNamespace + type, value));
        }
    }

    return commandXmlDocument;
}

For our example, the resulting XML document will be something like this:

<command xmlns="urn:schemas-pivotal-com/LifecycleServer60">
    <systemName>CRM</systemName>
    <loginType>Client</loginType>
    <executeAppServerRule>
        <appServerRuleName>Async Service</appServerRuleName>
        <appServerRuleMethod>ExecuteApp</appServerRuleMethod>
        <commandParameters>
            <emptyParameter/>
            <emptyParameter/>
            <emptyParameter/>
            <emptyParameter/>
            <emptyParameter/>
            <emptyParameter/>            
            <stringParameter>Ticket Service</stringParameter>
            <stringParameter>LongRunningOperation</stringParameter>
            <binaryParameter>0000000000000A81</binaryParameter>
        </commandParameters>
    </executeAppServerRule>
    <collectStatistics/>
</command>

This XML will be sent using a .Net HttpRequest object in the SendCommandAsync method. There is one method that requires attention in the above code, the GetParameterData method. This method converts a parameter to the Pivotal equivalent using the following strings:

//binaryParameter
//booleanParameter
//stringParameter
//stringParameter
//floatParameter
//dateParameter
//floatParameter
//binaryParameter
//binaryParameter
//integerParameter
//binaryParameter
//stringParameter
//timeInstantParameter
//timeInstantParameter

Since this is just a matter of querying the parameter type and then decide which string of the above to use, I will leave this out in sake of space, since the implementation is more related to .Net than to Pivotal and is not difficult to do. Let’s us see how the XML document is sent asynchronously to the PBS.

In order to do an async operation we will use the BeginInvoke method defined by .Net delegates. Our delegate needs the XML document to send, and the URI to send to:

private delegate void SendAsyncDelegate(XElement xmlDocument, Uri url);
private const string PivotalUrl = "http://localhost/ePower/XMLServices.asp";

The PBS endpoint for XML documents is XMLServices.asp, located in the root of the ePower virtual directory (this endpoint is old, but still works and is used by many Pivotal services such as the EMS, the scheduled script, ePartner/eService, etc.).

And this is how we send the document:

private void SendCommandAsync(XElement xmlDocument)
{
    SendAsyncDelegate sendAsyc = new SendAsyncDelegate(SendXml2UrlAsyncDelegate);
    IAsyncResult result = sendAsyc.BeginInvoke(xmlDocument, new Uri(PivotalUrl), null, null);
}

protected void SendXml2UrlAsyncDelegate(XElement xmlDocument, Uri url)
{
    try
    {
        HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(url);
        httpRequest.Method = "POST";
        httpRequest.ContentType = "text/xml";
        httpRequest.Credentials = CredentialCache.DefaultCredentials;

        StreamWriter writer = new StreamWriter(httpRequest.GetRequestStream());
        writer.Write(xmlDocument.ToString());
        writer.Flush();
        writer.Close();

        HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
        XmlTextReader reader = new XmlTextReader(httpResponse.GetResponseStream());
        XElement xmlResponse = XElement.Load(reader);

        reader.Close();
        httpResponse.Close();
    }
    catch (WebException e)
    {
        // handle error
    }
}

There are some interesting things here: notice how we create the HTTP request using the default network credentials, which will use NTLM credentials to call the endpoint. Also notice that we don’t care about the response (it’s an async method). Line 4 contains the most important part of our implementation: the asynchronous call.

This is all we need to improve the user experience when executing a long running operation: submit an async call to the server task method and return control to the user as soon as possible. Of courser there are some things that need to be considered before we can actually put this into a production system. Here is a list of things that need to be done:

  • Error handling: in the code above you’ve noticed that I’ve not done any error handling.
  • Security: the PBS endpoint might be configured in HTTPS. Also the default credentials might not be what we want. In this case we could do user impersonation to do the actual call
  • Logging: the async service doesn’t care about the result of the operation, but the user might. In this case we would need some kind of status report on the async tasks.
  • Form task methods: the example above works for service task, but sometimes we want to call methods in a form task.

All the above considerations are not difficult to do. Personally I prefer to use a solution that gives me all this and more: I personally use the Pivotal Development Tools created by Grupo Lanka, which include an async service with all that’s required for a good implementation of async tasks.

For now on, there is no excuse to leave the user wondering what happened with their application, you now have two tools to improve the user experience: progress bars and asynchronous calls. I hope you implement them!

1 Comments

There are times when we write some process that takes a long time to execute. The process might be launched by the end user (let’s assume this is a requirement). If this is the case we will need to provide some feedback to the user regarding the status of the process. In common UI design this is achieved by a progress bar control. It might not be obvious but the user perception changes when feedback is provided. If end users start the long operation, and no feedback is provided, they will wonder whether the process hang or is still running, and they will probably think that the application is dead slow. If feedback is provided then they know that the operation is still in progress and they will be more comprehensive about the application performance (of course, there will be a point when they will change their mind and go back thinking the application is slow).

Lets assume that we created the long running process as a server task method, and we create a windows client task to let users fire this process. Unfortunately in Pivotal there is no way, at least out of the box, to provide this kind of feedback. To make things worst, once the server task is fired, the users looses control of the application (it looks like the application is hang) until the server task finishes and returns to the client. So, what can we do to provide users with some feedback?

One approach that I have taken is to execute the whole process in an asynchronous way, returning control to the user immediately while the process is queued on the server task. I will talk about this approach in an upcoming article, but for now lets suppose that all we want to do is to provide feedback using a well known progress bar.

We mentioned before that once the server task is fired the windows client application freezes until the server task finishes. This is due to the fact that, out of the box, the server task is launched in the same UI thread. We all know that this is bad for the UI experience. So in this article I will explain how to launch a server task in a background thread, leaving the UI free to do UI work, such as showing a progress bar.

Lets start with want we want to accomplish: we want to fire the server task from the task pad, show a modal form displaying a progress bar and informing the user that a long process is being executed, and once the process finishes we want to close the modal form. The following image shows what we want to accomplish:

modalProgressForm

Lets assume this is our server task method:

[TaskExecute]
public virtual void LongRunningOperation(Id recordId)
{
    Trace.WriteLine(String.Format("Starting LongRunningOperation with recordId '{0}'", recordId));
    System.Threading.Thread.Sleep(20000);
    Trace.WriteLine(String.Format("Finishing LongRunningOperation with recordId '{0}'", recordId));
}

Lets assume that we will fire the server task from a task in the task pad. We would normally write something like this:

[ClientTaskCommand]
public virtual void LongRunningTask()
{
    try
    {
        Globals.Execute(this.DataTemplate, "LongRunningOperation", new object[] { this.RecordId });
        PivotalMessageBox.Show("Done", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
    }
    catch (Exception e)
    {
        Globals.HandleException(e.InnerException ?? e, true);
    }
}

But, as explained before this will freeze the whole UI, so we need to do the server task call in another thread. Lets start by creating a client side library, and write the following method:

/// <summary>
/// Displays a modal form showing a progress bar. 
/// The form will execute the <paramref name="methodName"/> on <paramref name="serverTaskName"/> in a background thread.
/// If the <paramref name="closeOnFinish"/> is <c>true</c> then the modal form will auto close when the server task is finished executing
/// </summary>
/// <param name="title">The title of the modal form</param>
/// <param name="message">A message to display beneath the progress bar informing the user of what's going on</param>
/// <param name="closeOnFinish">If <c>true</c> the the form will auto close when the server task execution ends. If <c>false</c> then the user will be able to dismiss the modal form using a button when the server task execution ends.</param>
/// <param name="serverTaskName">The name of the server task to execute</param>
/// <param name="methodName">The method on the server task to execute</param>
/// <param name="parameters">The parameters required by the server task method</param>
public static void ExecuteTaskWithProgress(string title, string message, bool closeOnFinish, string serverTaskName, string methodName, params object[] parameters)
{
    TransitionPointParameter transitionPointParameters = new TransitionPointParameter();

    transitionPointParameters.SetExtendedParameter("title", title);
    transitionPointParameters.SetExtendedParameter("message", message);
    
    transitionPointParameters.SetExtendedParameter("serverTaskName", serverTaskName);
    transitionPointParameters.SetExtendedParameter("methodName", methodName);
    transitionPointParameters.SetExtendedParameter("closeOnFinish", closeOnFinish);

    for(int i = 0; i < parameters.Length; i++)
    {
        transitionPointParameters.SetUserDefinedParameter(i + 1,parameters[i]);
    }

    Id systemId = Id.Create(Globals.GetSystemSetting("System_Id"));
    Globals.ShowModalForm("Progress Form", systemId, transitionPointParameters.ParameterList);
}

This method is just an utility method that takes some parameters and creates a transition point parameter structure that will be passed to a modal form. The parameters are explained as part of the XML documentation. The only thing that might be weird is the way the parameters are packed into the transition point parameters structure, it is made this way because otherwise values would be lost if sending the object array is it is. We will how to unpack these parameters later. For now, we need to create the “Progress Form” form. You have probably guessed that this form is attached to the System table. This is how we design the form:

progressFormIs pretty straightforward: it has two controls, one is a PivotalCustomControlContainer which we named progressContainer, this is where, at runtime, we will put the progress bar. The other control is a PivotalLabel which we named messageText, and is where we put the message that we want to display to the user (the parameter message in the ExecuteTaskWithProgress method defined above). This form is associated to the System table and is called “Progress Form”. Make sure you give the appropriate permissions to the security groups to see the form. Now all we need is to create a form client task that takes care of the work.

We use the Pivotal CRM Templates for Visual Studio to quickly create a form client task (the templates take care of all the plumbing for us). All we need to do is override the OnFormInitialized method:

public partial class ProgressModalForm : FormClientTask
{
    public ProgressModalForm()
    {
    }

    public override void OnFormInitialized()
    {
        try
        {
            base.OnFormInitialized();
            SetupProgress();
            ExecuteTask();
        }
        catch (Exception e)
        {
            Globals.HandleException(e, true);
        }
    }
    
    private void SetupProgress()
    {
        //...
    }
    
    private void ExecuteTask()
    {
        //...
    }
}

All the work is done in those two methods: SetupProgress and ExecuteTask. First we need to define some variables at the class level, we will explain these variables later:

private Exception exception = null;
private Dispatcher uiDispatcher = null;
private ProgressBar progress = null;

The SetupProgress method takes care of the UI part: it creates the progress bar, removes the form buttons and sets some parameters such as the message and the form title. Here is the code for this method. I’m obviating the part that setups the buttons since this is found in Pivotal documentation, and I don’t want to extend the article with boilerplate code.

private void SetupProgress()
{
    // code to setup form buttons, and the logic to display the OK button based on the closeOnFinish parameter
    
    
    string title = TypeConvert.ToString(this.TransitionPointParameter.GetExtendedParameter("title"));
    this.FormView.Title = title;

    string message = TypeConvert.ToString(this.TransitionPointParameter.GetExtendedParameter("message"));
    PivotalLabel messageLabel = this.FormControl.GetControlByName("messageText") as PivotalLabel;
    if (messageLabel != null) messageLabel.Text = message;

    PivotalCustomControlContainer progressContainer = this.FormControl.GetControlByName("progressContainer") as PivotalCustomControlContainer;
    if (progressContainer != null)
    {
        Application.EnableVisualStyles();

        Panel panel = new Panel();
        panel.Size = progressContainer.Size;

        progress = new ProgressBar()
        {
            Style = ProgressBarStyle.Marquee,
            Dock = DockStyle.Fill
        };
        panel.Controls.Add(progress);

        progressContainer.CustomControl = panel;
    }
}

Notice how the progress bar is created using a Marquee stile, meaning that this will show a progress bar that repeats until the task is finished. It is also possible to create a progress bar to show actual progress, but I will leave that for some other article (or you can use the Pivotal CRM Data Visualization Module by Grupo Lanka). It is important to notice that you need to enable visual styles (Application.EnableVisualStyles()) for the progress bar to actually work. Also, notice how we keep the progress bar control globally available in the class, using one of the variables we defined earlier. This is because we need to stop the progress bar once the task execution ends.

The ExecuteTask method is where all the magic happens:

private void ExecuteTask()
{
    uiDispatcher = Dispatcher.CurrentDispatcher;

    string serverTaskName = TypeConvert.ToString(this.TransitionPointParameter.GetExtendedParameter("serverTaskName"));
    string methodName = TypeConvert.ToString(this.TransitionPointParameter.GetExtendedParameter("methodName"));

    List<object> parameters = new List<object>();
    List<Type> parameterTypes = new List<Type>();

    for (int i = 1; i <= this.TransitionPointParameter.UserDefinedParametersNumber; i++)
    {
        object parameter = this.TransitionPointParameter.GetUserDefinedParameter(i);
        Type type = parameter.GetType();
        if (type.Name == "Byte[]")
        {
            parameters.Add(Id.Create(parameter));
            parameterTypes.Add(typeof(Id));
        }
        else
        {
            parameters.Add(parameter);
            parameterTypes.Add(parameter.GetType());
        }
    }

    Action action = new Action(() =>
    {
        try
        {
            this.SystemClient.ExecuteServerTask(serverTaskName, methodName, parameterTypes.ToArray(), parameters.ToArray());
        }
        catch (Exception e)
        {
            exception = e;
        }
    });
    action.BeginInvoke(new AsyncCallback(OnExecuteFinished), action);
}

The first thing is to set the current dispatcher for the UI thread. We will need this when changing UI components from inside the thread that executes the server task (remember that you can only change UI components from the UI thread). The next step is to unpack the parameters from the transition point parameters. We then need to configure an Action object and invoke that action in a new thread. The action will call the server task using the Pivotal API, as showed in line 31. We can use the BeginInvoke method of the Action object to start execution of the server task in a non-UI thread. This is done in line 38. We need to provide an AsyncCallback method that will be executed once the action execution ends:

private void OnExecuteFinished(IAsyncResult ar)
{
    Action action = ar.AsyncState as Action;
    action.EndInvoke(ar);

    if (uiDispatcher != null)
    {
        uiDispatcher.BeginInvoke(new Action(() =>
        {
            this.FormView.Dirty = false;
    
            if (exception != null)
                Globals.HandleException(exception, true);

            if (TypeConvert.ToBoolean(this.TransitionPointParameter.GetExtendedParameter("closeOnFinish")))
            {
                try { this.FormView.CloseForm(true); } catch {}
            }
            else
            {
                if (progress != null) progress.Style = ProgressBarStyle.Blocks;
            }
        }));
    }
}

Once the server task ends execution the OnExecuteFinished method is called and the UI is updated. Notice how any modifications to the UI controls are done using the dispatcher we captured before, making sure that the UI is modified in the UI thread. We then take care of closing the form, or just stopping the progress bar (depending on the cloaseOnFinish parameter).

There are some things that need to be taken care of in order to have a complete solution:

  • Make sure you use LD Strings all around to make the code globalized
  • Right now, if the user closes the modal form the the server task will still be executing on the background but the user will not get any feedback. A good implementation will have to check whether the user can close the modal form or not.
  • Provide a good error handling behavior (in the examples above, this is done using the variable exception) to improve the user experience.

This is all there is to it. Make sure you display a nice progress bar showing some feedback to end users next time you need to execute a heavy task.

I encourage you to look at the Pivotal Development Tools provided by Grupo Lanka, since they have very nice tools to easy up Pivotal development, including this one feature to show progress bars.

0 Comments

If you’ve worked with Pivotal CRM you’re probably familiar with the RSys_Last_Id table. This table is located in the ED and stores the last assigned id (the primary key for a record) for every table in the system. When the Pivotal Business Server (PBS) needs to create a new record it will use this table to know what is the next id to assign to that record.

rsysLastIdWhen you create a new table (using the Toolkit) and then do an Apply Customization Changes (ACC) using the Pivotal Administration Console (PAC) then a new record will be inserted into RSys_Last_Id, with two values: one for the table name and the other value for the id, which is initialized to 0x0000000000000000.

When the first record for the table is created the PBS will assign its id the value 0x0000000000000001, however if we look at the RSys_Last_Id table, the Last_Id_Value for the table is not 0x0000000000000001, but instead is 0x0000000000000010.

The reason why this occurs is due to the way the PBS works with these ids. The PBS has a cache of ids per table. When the PBS starts, this cache is empty, but immediately after creating the first record for a table the PBS creates a cache of 16 ids for such table. For the case of a new table, when the first record is created the PBS requests 16 Ids from the RSys_Last_Id table, and thus the Last_Id_Value is set to 0x0000000000000010 (these values are hex numbers: 10 hex = 16 decimal). The PBS assigns ids from the cache until the cache is empty again and no more ids are available for the table. When the 16th record is created the PBS will use the last id from the cache and the cache will be empty. When the 17th record is created the PBS realizes the cache is empty, so it requests another 16 ids from the RSys_Last_Id table, so the value of the Last_Id_Value for such table is set to 0x0000000000000020 (20 hex = 32 decimal).

Notice that if the PBS is shutdown the ids that are still in cache are lost. This explains why the record ids for a given table might have missing ids. As an example: suppose that 5 records have been created for a table, the PBS has used 5 ids out of the cache. At this point the PBS is shutdown, and the ids in the cache (16 – 5 = 11 ids) will be lost. When the PBS is restarted (it starts with an empty cache) and a new record for the table is requested (the 6th record, for this example) the PBS will ask again the RSys_Last_Id table for another 16 ids, but the RSys_Last_Id table had the Last_Id_Value set to 0x0000000000000010 (before the PBS was restarted), so this time it will provide 16 ids, ranging from 0x0000000000000010 to 0x0000000000000020. The 6th record will be created with the id 0x0000000000000010. At this point the table will have 6 records, with ids: 0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0x0000000000000004, 0x0000000000000005, 0x0000000000000010. Ids from 0x0000000000000006 to 0x000000000000000A were lost.