XrmToolBox – WorkAsync and the Message Panel

My initial plan was a quick post with snippet on how to manually display the Message Panel in your XrmToolBox tool. As I started writing, I thought it would help to provide some background on why we might break from the standard methods offered by the XrmToolBox.

When creating XrmToolBox Tools, it’s important to ensure tools are responsive, meaning our user interface should not lock up or freeze. With WinForm applications, this will happen if you start a long running process from within your form or control. An example would be a call to CDS to retrieve data for a grid, so this is fairly common risk for XrmToolBox tool developers.

Working asynchronously

Luckily, the XrmToolBox offers an SDK for developers to address these situations. The SDK includes a base class for user controls with a method named WorkAsync. This method and its related WorkAsyncInfo class will keep your interface responsive by running code asynchronously. From the documentation:

This method is an abstraction of BackgroundWorker that can take an argument, do some work on another thread, report progress and get back to main thread to finalize the process.

PluginControlBase base class – Write your plugin logic asynchronously

So this is a built in method to create a background thread for long running work that will not block the primary thread execution. This is a great help because multithreading can lead to completely unexpected results.

Here is a quick example from the documentation:

WorkAsync(new WorkAsyncInfo () {
    Message = "Retrieving your user id...",
    MessageWidth = 340,
    MessageHeight = 150,
    Work = (w, e) => {
       // This code is executed in the background thread
       var request = new WhoAmIRequest();
       var response = (WhoAmIResponse)Service.Execute(request);
       w.ReportProgress(-1, "I have found the user id");
       e.Result = response.UserId;
    },
    ProgressChanged = e => {
       SetWorkingMessage(e.UserState.ToString());
    },
    PostWorkCallBack = e => {
       // This code is executed in the main thread
       MessageBox.Show($"You are {(Guid) e.Result}");
    },
    AsyncArgument = null
 });

Though our code runs on a background thread, WorkAsync allows us to update both progress and yellow message panel during execution. This keeps the main user interface responsive while keeping the user informed. The message panel details can be simple notes or a running series of updates for multiple transactions.

Here is an example of the message panel from the FetchXml Builder:

FetchXml Builder Yellow Panel

So WorkAsync allows us to focus on the Tool logic while relying on familiar, built in methods to keep our user interface responsive.

That’s great, but…

So, what if I have a situation where I can’t use WorkAsync but I want to display the message banner to keep things consistent with other Tools? This question came up recently during a discussion with Jonas Rapp about a shared repository of XrmToolBox shared controls. You can find the link and short description here.

We built some controls that encapsulate common functionality seen across tools – display a list of CDS Entity or Attribute Metadata details, or display a list of CDS records in a data grid. One approach in our design is to avoid a direct dependency on XrmToolBox to avoid clashes with versions. We protect ourselves from version issues but we lose cool features like WorkAsync.

I tried to use the WorkAsync when using the controls when building new XrmToolBox tools but I ran into some issues. For example, I have a control extending the ListView that loads a list of CDS records. You provide a CDS connection, a query or fetch xml string, and then call LoadData, and the control internally handles retrieving data from CDS and creating the ListViewItems.

I tried wrapping the LoadData call in WorkAsync, but adding the ListView items throws an exception. Because the work is being done on a background thread, the code cannot then access user interface elements on the primary thread.

So to keep things responsive, I used the BackgroundWorker directly and it works well – the user interface is responsive and my control loads in the background. Unfortunately, I am not showing the XrmToolBox message panel with which most users are familiar!

Wait, we CAN do that!

Ultimately, the message panel is simply a WinForm Panel control that the XrmToolBox instantiates, adds to the Controls collection, sets to the foreground, and removes when done. We could do the same in our control if we liked. Fortunately, we actually have access to the use the same exact method used by the XrmToolBox!

We have access to class named InformationPanel that is part of the XrmToolBox.Extensibility namespace. This class includes a static method named GetInformationPanel. This method creates a new instance of message panel we see with the WorkAsync method. We can also update the message during our processing using the static method named ChangeInformationPanelMessage. Below are some snippets on how to initialize the panel, update the message, then dispose of it when you’re done with it.

First, we need to create the new Panel control.

var infoPanel = InformationPanel.GetInformationPanel(this, "Loading", 340, 150);
infoPanel.BringToFront();
Refresh();

Here we can create a new instance of the message panel where the return type for infoPanel is type System.Windows.Forms.Panel. The reference to this as the first parameter indicates that a new Panel control should be added the this.Controls() collection. This methods should also bring the panel to the front but I added BringToFront() and Refresh() to ensure the panel is visible.

Next, we need to update the panel message.

InformationPanel.ChangeInformationPanelMessage(infoPanel, "More updates happened");

This one is pretty simple, passing a new message and the infoPanel instance. This can be called if you making multiple calls to the CDS or are processing on your results.

Finally, some clean up.

if (this.Controls.Contains(infoPanel)) {
    Controls.Remove(infoPanel);
    infoPanel.Dispose();
    infoPanel == null;
}

This final snippet is relatively simple too. The infoPanel is WinForm control should be contained in the this.Controls() collection, but we first makes sure it’s present to avoid errors. You might have a reusable method for creating and removing the message panel. Once we know the instance is removed from the controls collection, we force Dispose and set to null.

I have added this to my Power Apps Portal Dependencies Tool. In my example, I grab a new instance of the message panel when I need it and dispose when the work is complete. Alternately, you could create the instance on start up of your Tool user control and dispose of it when the control closes. It seems like either method should work and it simply depends on the complexity of your tool.

As with all tips or tricks like this, test things out in your own and drop some feedback if you find something interesting.

And as always, all comments, questions, and suggestions are welcome!


Leave a Reply

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