Simple WPF Progress Window with Cancellation
Tuesday, July 22nd, 2008Original Problem
Many times I have encountered a scenario where all I need to do is perform some small operation that takes a considerable amount of time. If I execute this operation on the UI thread, the UI will block until the operation is complete. Obviously this is not ideal. To address this need, the .NET engineers created the BackgroundWorker class in .NET 2.0. Its purpose is to execute a simple operation on a worker thread, and it does this very well. However, that is only half the battle. If an operation is happening in the background and the user needs to be notified of its progress, the BackgroundWorker must be wired up to some display that displays the progress. In WinForms, developers had many problems initially because they were trying to update controls from the worker thread. .NET 2.0 and later throws an exception if this cross-thread control updating is attempted since it is not allowed in either WinForms or WPF. If you did not know already, all UI controls can only be modified from the thread that created them (the UI thread). There are many examples scattered throughout the internet describing how a developer must ‘post’ the information back to the UI thread before updating a control, such as:
m_TextBox.Invoke(new UpdateTextCallback(this.UpdateText), new object[]{”Text generated on non-UI thread.”});
WPF to the rescue
WPF has come to the rescue with its support for cross-thread property binding and notification. In other words, you can bind a progress bar’s current value to a property that gets changed on another thread. WPF will handle cross thread invoking automatically. This means it becomes very easy to design a WPF window that can not only display the progress of an operation, but also status text and other items that may be updated on the worker thread as an operation progresses, all without having to worry about invoking, callbacks, and threads.

Since most simple operations have the same pattern, a simple interface can be designed that simple operations can implement. We can then design a single WPF window that will display the progress of an operation by attaching to the interface rather than the simple operation:
Once the ProgressWindow and IProgressOperation classes are complete, many simple operations can be designed without having to worry about implementing another UI to display their progress. Here is an example of the IProgressOperation interface:
public interface IProgressOperation
{
int Total { get; }
int Current { get; }
void Start();
void CancelAsync();
event EventHandler ProgressChanged;
event EventHandler ProgressTotalChanged;
event EventHandler Complete;
}
Now we can implement a simple class that exports event logs, and implements this interface. Start() will start the BackgroundWorker, CancelAsync() will set a flag that the operation will check periodically, and the properties Total and Current provide the progress information needed for a progress display.
public void Start()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
}
public void CancelAsync()
{
this._isCancelationPending = true;
}
public int Current
{
get
{
return this._current;
}
private set
{
this._current = value;
OnProgressChanged(EventArgs.Empty);
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Total = …
…
for (…)
{
//exit if the user cancels
if (this._isCancelationPending == true)
{
return;
}
this.Current++;
}
}
Now for the WPF window that will display this IProgressOperation. Since we will make the window implement INotifyPropertyChanged, when the ProgressChanged or ProgressTotalChanged events are raised from the background operation (and coming from a worker thread), we will just raise the events for WPF to update its bindings:
void _operation_ProgressChanged(object sender, EventArgs e)
{
OnPropertyChanged(”Current”);
}
void _operation_TotalChanged(object sender, EventArgs e)
{
OnPropertyChanged(”Total”);
}
public int Current
{
get
{
return this._operation.Current;
}
}
public int Total
{
get
{
return this._operation.Total;
}
}
Finally we have the XAML for the ProgressWindow that shows how it has both progress text and the Progressbar bound to the Current and Total properties that the window exposes to itself from the IProgressOperation.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”32″/>
<RowDefinition Height=”*”/>
<RowDefinition Height=”42″/>
</Grid.RowDefinitions>
<StackPanel Grid.Row=”0″ Orientation=”Horizontal” HorizontalAlignment=”Center” VerticalAlignment=”Bottom”>
<TextBlock FontSize=”16″>Processing: </TextBlock>
<TextBlock FontSize=”16″ Margin=”5,0,0,0″ Text=”{Binding Path=Current, ElementName=Window, Mode=OneWay}”/>
<TextBlock FontSize=”16″ Margin=”5,0,5,0″>of</TextBlock>
<TextBlock FontSize=”16″ Text=”{Binding Path=Total, ElementName=Window, Mode=OneWay}”/>
</StackPanel>
<ProgressBar Grid.Row=”1″
Width=”200″
Height=”20″
Value=”{Binding Path=Current, ElementName=Window, Mode=OneWay}”
Minimum=”0″
Maximum=”{Binding Path=Total, ElementName=Window, Mode=OneWay}”></ProgressBar>
<StackPanel Grid.Row=”2″ Orientation=”Horizontal” HorizontalAlignment=”Center”>
<Button Margin=”6,6,6,6″ Width=”80″ Height=”30″ Click=”CancelClick”>Cancel</Button>
</StackPanel>
</Grid>
How do we start the operation and display the ProgressWindow? It is just a matter of creating a new ProgressWindow and passing in the IProgressOperation into its constructor:
ProgressWindow progressWindow = new ProgressWindow(new EventLogExporter());
progressWindow.ShowDialog();
The ProgressWindow will call the Start() method on the IProgressOperation once the window has completely loaded.
Keep in mind that this ProgressWindow is a very simple example demonstrating the power of WPF and binding. It can easily be extended to display many other items such as status text or other information, all using binding and not having to worry about invoking on the UI thread. You can also use the concepts provided in this article to design your own more powerful and customized ProgressWindow.
Sample: SimpleWPFProgressWindow