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.