MVVM ProgressBar

There are a lot of options for doing a ProgressBar in WPF using MVVM you can Google it and see for yourself. In this article I'll show a hybrid way of doing MVVM and having a ProgressBar that does not conform to the MVVM premiss (but it is really simple to use).

 The idea is that you can do all the things you are used to do with MVVM but when you get to the ProgressBar you use events and a little code-behind.

Here is the code for the ViewModel:

public class MainWindowViewModel
{
    private BackgroundWorker _backgroundWorker;

    public event EventHandler TaskStarting = (s,e) => { };

    public event ProgressChangedEventHandler ProgressChanged
    {
        add { _backgroundWorker.ProgressChanged += value; }
        remove { _backgroundWorker.ProgressChanged -= value; }
    }

    public event RunWorkerCompletedEventHandler TaskCompleted
    {
        add { _backgroundWorker.RunWorkerCompleted += value; }
        remove { _backgroundWorker.RunWorkerCompleted -= value; }
    }

    private ICommand _executeLongTask;

    public ICommand ExecuteLongTask
    {
        get
        {
            if (_executeLongTask == null)
            {
                _executeLongTask = new RelayCommand(param => _backgroundWorker.RunWorkerAsync());
            }
            return _executeLongTask;
        }
    }

    public MainWindowViewModel()
    {
        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.WorkerReportsProgress = true;
        _backgroundWorker.DoWork += executeTask;
    }

    private void executeTask(object sender, DoWorkEventArgs e)
    {
        OnTaskStarting();
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(100);
            _backgroundWorker.ReportProgress(i + 1);
        }
    }

    private void OnTaskStarting()
    {
        TaskStarting(this, EventArgs.Empty);
    }
}

 This ViewModel class has three events: TaskStartiing, ProgressChanged and TaskCompleted. The last two are just events that I exposed from the BackgroundWorker that will execute my long runing task. The code-behind for the Window will subscribe to these events:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        MainWindowViewModel vm = new MainWindowViewModel();
        vm.TaskStarting += TaskStarted;
        vm.ProgressChanged += ProgressChanged;
        vm.TaskCompleted += TaskCompleted;

        DataContext = vm;
    }

    void TaskStarted(object sender, EventArgs e)
    {
        this.Dispatcher.Invoke(new Action(() => ProgressPopup.IsOpen = true));
    }

    void TaskCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        this.Dispatcher.Invoke(new Action(() => ProgressPopup.IsOpen = false));
    }

    void ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.Dispatcher.Invoke(new Action(() => ProgressBar.Value = e.ProgressPercentage));
    }
}

Notice that since my long running task is being executed in another thread I need to use the Dispatcher to update any user interface elements.

My Window has a Popup with a ProgressBar inside it. I use the Starting and Completed events to show and hide the Popup. The ProgressBar itself is only updated in the ProgessChanged event. I also added an animation to show a blinking progress message during the execution.

<Window x:Class="MvvmProgressBar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="400">
    <StackPanel>
        <Button Command="{Binding ExecuteLongTask}">Run Long Task</Button>
        <Popup Name="ProgressPopup" 
               Placement="Center" 
               Width="300"
               IsOpen="False">
            <Border BorderThickness="10"
                    BorderBrush="Black"
                    Background="Gray"
                    Padding="30,50">
                <StackPanel>
                    <TextBlock Foreground="White"
                           FontWeight="Bold"
                           FontSize="16"
                           Name="txt"
                           Text="Processing...">
                    <TextBlock.Triggers>
                        <EventTrigger RoutedEvent="TextBlock.Loaded">
               <BeginStoryboard>
                  <Storyboard>
                     <DoubleAnimation
                        AutoReverse="True"
                        Duration="0:0:1"
                        From="1.0"
                        RepeatBehavior="Forever"
                        Storyboard.TargetName="txt"
                        Storyboard.TargetProperty="Opacity"
                        To="0.0"/>
                  </Storyboard>
               </BeginStoryboard>
            </EventTrigger>
                    </TextBlock.Triggers>
                    </TextBlock>
                    <ProgressBar Name="ProgressBar"
                                 Height="30" 
                                 BorderThickness="2" />
                </StackPanel>
            </Border>
        </Popup>
    </StackPanel>
</Window>

This example is really simple but it goes to show you that you may, from time to time, do some non-MVVM code and it won't make your app suck.

 

MVVM Multiselect Listbox

Although MVVM is a great pattern you have to learn to work with it somethings are hard to do until you get the hang of it.

One of these things is doing a Multiselect Listbox. My first idea Google it, of course. The best solution I found was this one by Marlon Grech. Marlon has a lot of good stuff about WPF in his blog and he definitely knows what he is talking about. This solution however became a little slow when I wanted to perform a Select All and Unselect All on the list.

I decided to implement a specialized list for this kind of situation. I call it a SelectionList and it is a list of SelectionItems. The idea is to have a collection of items that have a IsSelected property and a Item property that contains the real value you want. I think the code speaks for itself.

public class SelectionItem<T> : INotifyPropertyChanged
{
    #region Fields

        private bool isSelected;

        private T item;

        #endregion

    #region Properties

        public bool IsSelected
        {
            get { return isSelected; }
            set
            {
                if (value == isSelected) return;
                isSelected = value;
                OnPropertyChanged("IsSelected");
                OnSelectionChanged();
            }
        }

        public T Item
        {
            get { return item; }
            set
            {
                if (value.Equals(item)) return;
                item = value;
                OnPropertyChanged("Item");
            }
        }

        #endregion

    #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        public event EventHandler SelectionChanged;

        #endregion

    #region ctor

        public SelectionItem(T item)
            : this(false, item)
        {
        }

        public SelectionItem(bool selected, T item)
        {
            this.isSelected = selected;
            this.item = item;
        }

        #endregion

    #region Event invokers

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler changed = PropertyChanged;
            if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
        }

        private void OnSelectionChanged()
        {
            EventHandler changed = SelectionChanged;
            if (changed != null) changed(this, EventArgs.Empty);
        }

        #endregion
}

 The SelectionItem class is what really makes things happen. It takes an ordinary class like an string and wraps it adding a IsSelected property. This is the class of the objects that will be bound to each ListItem of the ListBox.

public class SelectionList<T> : 
    ObservableCollection<SelectionItem<T>> where T : IComparable<T>
{
    #region Properties

        /// <summary>
        /// Returns the selected items in the list
        /// </summary>
        public IEnumerable<T> SelectedItems
        {
            get { return this.Where(x => x.IsSelected).Select(x => x.Item); }
        }

        /// <summary>
        /// Returns all the items in the SelectionList
        /// </summary>
        public IEnumerable<T> AllItems
        {
            get { return this.Select(x => x.Item); }
        }

        #endregion

    #region ctor

        public SelectionList(IEnumerable<T> col)
            : base(toSelectionItemEnumerable(col))
        {

        }

        #endregion

    #region Public methods

        /// <summary>
        /// Adds the item to the list
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)
        {
            int i = 0;
            foreach (T existingItem in AllItems)
            {
                if (item.CompareTo(existingItem) < 0) break;
                i++;
            }
            Insert(i, new SelectionItem<T>(item));
        }

        /// <summary>
        /// Checks if the item exists in the list
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(T item)
        {
            return AllItems.Contains(item);
        }

        /// <summary>
        /// Selects all the items in the list
        /// </summary>
        public void SelectAll()
        {
            foreach (SelectionItem<T> selectionItem in this)
            {
                selectionItem.IsSelected = true;
            }
        }

        /// <summary>
        /// Unselects all the items in the list
        /// </summary>
        public void UnselectAll()
        {
            foreach (SelectionItem<T> selectionItem in this)
            {
                selectionItem.IsSelected = false;
            }
        }

        #endregion

    #region Helper methods

        /// <summary>
        /// Creates an SelectionList from any IEnumerable
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private static IEnumerable<SelectionItem<T>> toSelectionItemEnumerable(IEnumerable<T> items)
        {
            List<SelectionItem<T>> list = new List<SelectionItem<T>>();
            foreach (T item in items)
            {
                SelectionItem<T> selectionItem = new SelectionItem<T>(item);
                list.Add(selectionItem);
            }
            return list;
        }

        #endregion
}

The SelectionList is basically an ObservableCollection of SelectionItem.

Now that you have a list of items that can be bound to each ListBoxItem I needed to figure out how to bind the IsSelected property of my items to the is IsSelected property of the ListBoxItem. I found the solution in

this post

in the MSDN Forums. You need to use this style:

<ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="IsSelected" 
                Value="{Binding IsSelected}"/>
    </Style>
</ListBox.ItemContainerStyle>

Example:

Imagine you need a window with a list of sports from which you have to select the sports you like. In this window you need to be able to add items to the list and select and unselect all items at once. Here is the code for the window and the ViewModel:

<Window x:Class="MvvmChecklistBox.MultiSelectWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MvvmChecklistBox"
        Title="MultiSelectWindow" Height="300" Width="300">
    <Window.Resources>
        <local:MainWindowViewModel x:Key="viewModel"/>
    </Window.Resources>
    <StackPanel Margin="5" DataContext="{StaticResource viewModel}">
        <TextBlock>Sport:</TextBlock>
        <TextBox Name="textBoxNewSport"
                 Text="{Binding NewSport}"/>
        <Button Content="Add new sport"
                Command="{Binding AddCommand}"/>
        <ListBox Name="checkboxList"
                 ItemsSource="{Binding Sports}"
                 DisplayMemberPath="Item"
                 Margin="0,5" 
                 SelectionMode="Multiple">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="IsSelected" 
                            Value="{Binding IsSelected}"/>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
        <Button Content="Select All"
                Margin="0,5"
                Command="{Binding SelectAllCommand}"/>
        <Button Content="Unselect All"
                Margin="0,5"
                Command="{Binding UnselectAllCommand}"/>
    </StackPanel>
</Window>
public class MainWindowViewModel : INotifyPropertyChanged
{
    #region Fields

        private ICommand _selectAllCommand;

        private ICommand _unselectAllCommand;

        private ICommand _addCommand;

        private string _newSport;

        #endregion

    #region Properties

        public SelectionList<string> Sports { get; set; }

        public string NewSport
        {
            get { return _newSport; }
            set
            {
                if (value == _newSport) return;
                _newSport = value;
                OnPropertyChanged("NewSport");
            }
        }

        #endregion

    #region Commands

        public ICommand SelectAllCommand
        {
            get
            {
                if (_selectAllCommand == null)
                {
                    _selectAllCommand = new RelayCommand(param => Sports.SelectAll());
                }
                return _selectAllCommand;
            }
        }

        public ICommand UnselectAllCommand
        {
            get
            {
                if (_unselectAllCommand == null)
                {
                    _unselectAllCommand = new RelayCommand(param => Sports.UnselectAll());
                }
                return _unselectAllCommand;
            }
        }

        public ICommand AddCommand
        {
            get
            {
                if (_addCommand == null)
                {
                    _addCommand = new RelayCommand(param =>
                    {
                        Sports.Add(NewSport);
                        NewSport = string.Empty;
                    });
                }
                return _addCommand;
            }
        }

        #endregion

    #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

    #region ctor

        public MainWindowViewModel()
        {
            string[] sports = { "Baseball", "Basketball", "Football", "Handball", "Soccer", "Volleyball" };
            Sports = new SelectionList<string>(sports);
        }

        #endregion

    #region Event invokers

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler changed = PropertyChanged;
            if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
}

Here is the result:

MultiSelectWindow

The solution was really simple after I though of the SelectionList. I wanted however to do a little bit more. Instead of the simple Listbox I had to build a list of CheckBoxes. To good thing is that as far as the ViewModel is concerned we are all set, there's no need to change anything. All we need to change is the View (which is a Window in our case) using a DataTemple in the ListBox ItemTemplate to place a Checkbox instead of regular item.

<ListBox.ItemTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding IsSelected}" 
                  Content="{Binding Item}"/>
    </DataTemplate>
</ListBox.ItemTemplate>

Did it work? Kind of... It works but something weird happens because you can select all the checkboxes you want and then select the ListItem which gets highlighted. So now you have the checkbox selection and the item highlight selection and the two of them do not match. I don't want the ListBox item to get highlighted. Once again Google helped and I found a style to do just what I wanted in Alex Filo's blog. Here is the code:

<ListBox.ItemContainerStyle>
    <Style>
        <Setter Property="ListBoxItem.Background" Value="Transparent"/>
        <Setter Property="ListBoxItem.Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                    <Border x:Name="Bd" 
                    SnapsToDevicePixels="true" 
                    Background="{TemplateBinding Background}" 
                    BorderBrush="{TemplateBinding BorderBrush}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Padding="{TemplateBinding Padding}">
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter Property="Background" TargetName="Bd" Value="Transparent" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.ItemContainerStyle>

CheckBoxListError

Now we're done! Here is the final code for the view:

<Window x:Class="MvvmChecklistBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="300">
    <StackPanel Margin="5">
        <TextBlock>Sport:</TextBlock>
        <TextBox Name="textBoxNewSport"
                 Text="{Binding NewSport}"/>
        <Button Content="Add new sport"
                Command="{Binding AddCommand}"/>
        <ListBox Name="checkboxList"
                 ItemsSource="{Binding Sports}"
                 Margin="0,5">
            <ListBox.ItemContainerStyle>
                <Style>
                    <Setter Property="ListBoxItem.Background" Value="Transparent"/>
                    <Setter Property="ListBoxItem.Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                <Border x:Name="Bd" 
                                SnapsToDevicePixels="true" 
                                Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                Padding="{TemplateBinding Padding}">
                                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsSelected" Value="true">
                                        <Setter Property="Background" TargetName="Bd" Value="Transparent" />
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding IsSelected}" 
                              Content="{Binding Item}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Content="Select All"
                Margin="0,5"
                Command="{Binding SelectAllCommand}"/>
        <Button Content="Unselect All"
                Margin="0,5"
                Command="{Binding UnselectAllCommand}"/>
    </StackPanel>
</Window>

I'm sure that someone has had this problem and the SelectionList seems like a straight forward solution. I haven't found anything like it but I'm sure I can't be the first to think of it. Well, I hope this helps you and if you find of think of a better solution please leave a comment.