By now most .NET developers who are developing with Microsoft’s “Windows Presentation Foundation” or WPF are familiar with Xceed Software’s popular and freely available suite of additional WPF controls. One of the more confusing ones to work with is the “Busy Indicator” control, which many developers would like some form of within certain areas of their applications.
A little research on the Internet for either Xceed’s offering or any other will demonstrate the implementation of such a control is a bit more complex than would have originally been thought. As many who have tried and found out, the implementation is quite a bit more than just setting the control’s “busy” property to “True”.
There are quite a number of articles across the Internet that show how such a control can be implemented. Some of them appear so complex that one would not be blamed for simply not going through with the endeavor.
The complexity that comes with the implementation of this type of control stems from the fact that you must have some knowledge of handling multi-threaded processes in a WPF application. There is no getting around this since you must separate the process the “BusyIndicator” control is to cover for from the main UI thread. This is because, once a time-consuming process on the main UI thread has been initiated, there is no possibility of updating the display to inform a user of what is occurring even if such updates are made within the process itself. That can only been done through calls from the secondary process to the UI process. All of this will be shown in the steps outlined below.
Hopefully, the following will make such an implementation easier.
The following will present code in VB.NET, which should be easily convertible to C# using any of the freely available online conversion services. So let’s get started…
Download a copy of Xceed’s WPF Extended Toolkit from the following link… http://wpftoolkit.codeplex.com/ (version 2.5)
The latest commercial version of this toolkit is 2.8 (as noted on the Xceed site). However, the freely available version is version 2.5 and was updated in July 2015. As a result, it should be compatible at least with all the 4.5.x versions of the .NET Framework. The commercial version is noted as now compatible with the 4.6 version of the framework. So far Xceed has done a decent job of keeping the Community Edition current.
Once you have downloaded the file, which is in Zip-File format, extract the contents to a directory of your own choosing.
After you have extracted the contents of the Zip-File, add the following DLL to your “Toolbox” menu…” Xceed.Wpf.Toolkit.dll”
Though, there are other DLLs in the package, the one just noted will provide you with the “BusyIndicator” control as you will see once the newly added list of controls to your “Toolbox” are displayed.
To apply a basic “BusyIndicator” control to an XAML window or page first place the following header directive to the top of your window or page… (This will also be automatically added if you apply the control directly from to the “Toolbox” to your XAML markup.)
Implement the “BusyIndicator” control with the following XAML markup…
<xctk:BusyIndicator Name="xctkbiBusyIndicator" IsBusy="False" BusyContent="Please Wait..." > </xctk:BusyIndicator>
This all the actual XAML markup you will need unless you want to customize the control to have some advanced features such a button. The accompanying documentation with the download provides enough such documentation to get you on your way but if you need information on any customizations other than what is provided you will have to do a bit of further research.
The control should inherit the styling customizations you may have placed within the container in which the control is placed.
It should be noted that the control should be placed after all other elements within the container in which the control is to reside (ie: the master grid for the window or page). If it is not placed in such a manner, the control will not cover the entirety of the container area with its modal functionality.
It should also be noted that the text-string for the “BusyContent” property can be any string desired but it should be kept to a simple but informative note unless you want the control to span the length of its container.
As long as the “IsBusy” property remains “False”, the control’s interface will not be displayed.
Finally, remember to provide a name for the control so it is easily accessible in your code.
Step #5 (Master Method Code)
Now that we have the control implemented in the XAML module we can turn our attention to implementing the necessary code to handle the “BusyIndicator” control prior to its initiation and afterwards. We start this process by singling out the process that is to be covered while the control’s interface is displayed.
To some, this aspect of the implementation can be rather annoying if the process you want covered has already been implemented, tested successfully being considered completed and it returns a function value, making the addition of the “BusyIndicator” control a feature that you would now like to have added.
In this case, we have one additional process we have to implement; otherwise, if the process code has yet to be written you can implement the code with the “BusyIndicator” requirements in mind.
In the case of existing code, which has a return value, all we need to do is a add a wrapper method to the process that will be initiated on a secondary thread, which will in turn then call the actual process to be processed within the same, secondary thread.
The reason why this may be necessary is because a thread initiation process cannot receive a return value. If this is the case with your code, a “wrapper” process will be required letting that process handle any necessary processing based upon the return value from the process it subsequently calls.
Before we go any further let us clear up some possible confusion that some may have as a result of other articles that may have been read on this subject. A number of articles propose the use of the “BackgroundWorker” process. Do not worry; this is just a thread by another name. The code we will be using here is the same thing but merely using the threading namespace directly.
In the method that will call either the “wrapper” method or the actual method to be covered by the “BusyIndicator” control, place code similar to the following below…
Dim loThread As Thread ‘Call method to prepare interface Process_ThreadedBusyIndicatorAsOpened() ‘Instantiate a new thread and link call to “wrapper” threaded process or the actual process loThread = New Thread(Sub() Process_ThreadedAllRequiredEncryptionModifications(loApplicationInternalEncryptionStructure, loApplicationNewInternalEncryptionStructure)) ‘Initiate the thread and the linked process to run in it loThread.Start()
Step #6 (Wrapper Method)
Next, we can set up the necessary “wrapper” method since in our scenario the actual process to be covered by the “BusyIndicator” does in fact return an integer value indicating the success or failure of the process.
Private Sub Process_ThreadedAllRequiredEncryptionModifications(ByVal poApplicationInternalEncryptionStructure As ApplicationInternalEncryptionStructure, _ ByVal poApplicationNewInternalEncryptionStructure As ApplicationInternalEncryptionStructure) ‘This is the “wrapper” process which will in turn call an originally written ‘long running process. Both processes will run within the secondary thread. If (Process_AllRequiredEncryptionModifications(poApplicationInternalEncryptionStructure, poApplicationNewInternalEncryptionStructure) > -1) Then Get_ApplicationStateApplicationSettings() Process_ThreadedDisplayMessage("Update successful...") Else Process_ThreadedDisplayMessage("Update failed ! ") End If End Sub
Step #7 (Long Running Method Method Covered by “BusyIndicator” Control)
From the “wrapper” method we call the actual process that necessitates the use of the “BusyIndicator” control since it will be a long running process.
Private Function Process_AllRequiredEncryptionModifications(ByVal poApplicationInternalEncryptionStructure As ApplicationInternalEncryptionStructure, _ ByVal poApplicationNewInternalEncryptionStructure As ApplicationInternalEncryptionStructure) As Integer <strong> </strong>‘All necessary method code <strong> ‘Clean up after process has completed </strong> Process_ThreadedBusyIndicatorAsClosed() End Sub
Step #8 (Support Methods)
Below are two support methods that are used in our code. The first disables the master menu and initiates the “BusyIndicator” control’s interface. This method as noted above is the first method to be call in the code labeled as the “Master Method”.
The second method below is called at the end of the “long running” process to re-enable the master menu and close out the “BusyIndicator” control’s interface.
Private Sub Process_ThreadedBusyIndicatorAsOpened() ‘This call processes the initiation of any work to be done ‘prior to the initiation of the secondary threaded process. ‘ ‘This will also prepare the interface as well as initiate the ‘“BusyIndicator” control. Disable_MasterMenu() xctkbiBusyIndicator.IsBusy = True xctkbiBusyIndicator.Visibility = Windows.Visibility.Visible End Sub Private Sub Process_ThreadedBusyIndicatorAsClosed() ‘This call processes the initiation of any work to be done ‘prior to the initiation of the secondary threaded process. ‘ ‘This will also prepare the interface as well as initiate the ‘“BusyIndicator” control. Enable_MasterMenu() ‘Note that this line is one continuous line of code xctkbiBusyIndicator.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, New Action(Sub() xctkbiBusyIndicator.IsBusy = False xctkbiBusyIndicator.Visibility = Windows.Visibility.Collapsed End Sub)) End Sub
Step #9 (Calling Across Threads)
The above code outline is basically all that you need to run a “long running” process on a secondary thread covered by the “BusyIndicator” control. However, one thing that is not shown in the code is how the primary interface is updated during or at the completion of the “long running” process within the secondary thread.
Any time you need to update the primary interface from within a threaded call you must make a call to the primary thread by using a control’s “Dispatcher” class and that class’ “Invoke” method.
In the code above, we need to do this in the “wrapper” method at the conclusion of the “long running” process in order to provide a message to the user letting him or her know that the process has completed successfully or not. This also holds true for any primary interface updates that may be required such as changing the status of a control’s “enabled” status. For each primary interface control affected the same process is required.
The code below shows how to update a message label on the primary interface…
Private Sub Process_ThreadedDisplayMessage(ByVal psMessage As String) lblMessage.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, New Action(Sub() Select Case (psMessageType.ToUpper()) Case "I": lblMessage.Foreground = Brushes.DarkBlue Case "E": lblMessage.Foreground = Brushes.Red End Select lblMessage.Content = psMessage End Sub)) End Sub
Hopefully, this article has cleared up some confusion on the subject of implementing a “BusyIndicator” control.
Whether, you use the control from Xceed Software or one from another vendor or developer, the principals are the same in that a secondary process thread will always be required for a long running process to run within.