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.

Comments

Comment by Richard Simoes

This is a nice and very useful UI feature to improve the user experience in our Pivotal CRM projects. Thanks for sharing.

Richard Simoes