Monday, August 14, 2017

WPF Tip #18 - Extended WPF Toolkit - Using the CheckedListBox Control

In today's tip, we're going to take a quick look at another control in the Extended WPF Toolkit, the CheckedListBox control.
CheckListBox_wpf
This control provides all of the functionality expected from a ListBox with the addition of checkboxes as a visual cue to indicate the selection state of each item.

Using the control is quite simple. Here's an example of the XAML required to display a CheckedListBox on a WPF window. It is binding to the same collection of Sentence objects used in Tip #17.

<tk:CheckListBox ItemsSource="{Binding Sentences, IsAsync=True}"
                  SelectedItem="{Binding SelectedSentence}"
                  Command="{Binding ItemSelectedCommand}"/>

ItemsSource binding to a collection to populate the list items. SelectedItem provides a way to find out the currently 'selected' item, the last item checked/unchecked. The Command will fire each time an item in the list is checked or unchecked, allowing ViewModel logic to act on this change.

Go give it a try in your WPF project. Happy coding!


del.icio.us Tags: ,

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}"/>
</tk:BusyIndicator>

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;
     RaisePropertyChanged(nameof(IsGridLoading));

     //no direct interaction with the UI is allowed from this method
     lock (_sentenceLock)
     {
         Sentences.Clear();
     }

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

     //work has completed. you can now interact with the UI
     IsGridLoading = false;
     RaisePropertyChanged(nameof(IsGridLoading));
}

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(words[rand.Next(words.Length)]);
             }
             result.Append(". ");

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

            Thread.Sleep(100);

            result.Clear();
         }
     });

    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.

baconipsum-loading

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

Happy coding!


del.icio.us Tags: ,,,