Wednesday, August 2, 2017

WPF Tip #17 - Extended WPF Toolkit - BusyIndicator Control

Welcome back to another Extended WPF Toolkit sample. In this sample, we'll take a look at the BusyIndicator control. This easy to use control provides a quick way to inform your users that a control or Window is currently being refreshed by a long-running process. This is a great way to both update your users of progress and keep them from interacting with the controls that are updating.
Here is a fun little example that wraps a WPF DataGrid in a BusyIndicator. This is the entirety of the XAML inside the root Grid on the application's main window.

<tk:BusyIndicator IsBusy="{Binding IsGridLoading}" BusyContent="Loading, please wait..." DisplayAfter="0">
     <DataGrid local:Commands.DataGridDoubleClickCommand="{Binding GridDoubleClickCommand}" ItemsSource="{Binding Sentences, IsAsync=True}"/>

The DataGridDoubleClickCommand is a work-around to make the DataGrid's MouseDoubleClick event work with ICommand in when data binding in MVVM. More info here.

In the MainViewModel, there is an IsGridLoading boolean property that will be used to toggle the BusyIndicator on/off. The GridDoubleClickCommand invokes OnGridDoubleClicked() when the user double-clicks somewhere on the DataGrid at runtime. In this method, we're clearing and repopulating the Sentences ObservableCollection to which our DataGrid is bound. Notice the use of async/await. This is necessary to make the BusyIndicator appear. Without this, the UI will freeze during the update and never show the BusyIndicator control.

private async void OnGridDoubleClicked()
     //set the IsBusy before you start the thread
     IsGridLoading = true;

     //no direct interaction with the UI is allowed from this method
     lock (_sentenceLock)

     await BuildSentenceList(5, 15, 25, 50);

     //work has completed. you can now interact with the UI
     IsGridLoading = false;

The _sentenceLock allows us to update our ObservableCollection on a background thread. You must also initialize the collection with this code in your constructor. This tells WPF to update the collection on the UI thread and which lock object to use. More info here.

BindingOperations.EnableCollectionSynchronization(Sentences, _sentenceLock);

Each row in the DataGrid will be populated with some random Bacon Ipsum data in the BuildSentenceList method. There is an API if you would like to remove the hard-coded array of words.

private async Task BuildSentenceList(int minWords, int maxWords,
     int minSentences, int maxSentences)

     var words = new[]{"bacon", "ipsum", "dolor", "tail", "amet", "swine",
         "chicken", "short", "loin", "jowl", "turkey", "ball", "tip",
         "beef", "shank", "rump", "t-bone", "ham", "porchetta", "filet",
         "pork", "jerky", "hock", "meatball", "biltong", "steak", "brisket"};

     var rand = new Random();
     int numSentences = rand.Next(maxSentences - minSentences)
                        + minSentences + 1;
     int numWords = rand.Next(maxWords - minWords) + minWords + 1;

     var result = new StringBuilder();

    Task t = Task.Run(() =>
         for (int s = 0; s < numSentences; s++)
             for (int w = 0; w < numWords; w++)
                 if (w > 0)
                     result.Append(" ");
             result.Append(". ");

            lock (_sentenceLock)
                 Sentences.Add(new Sentence() {SentenceContent = result.ToString()});



    await t;


When the code to populate the grid fires, thanks to the 100ms Thread.Sleep between each row generated, you can see the grid slowly populating with data behind the busy indicator.


It's a great control. Just remember to update your data on a background thread!

Happy coding! Tags: ,,,

No comments:

Post a Comment