WaitSpin, ProgressPanel and threads…

Introduction

With my project CBR on CodePlex, I had to work on the new Task Parallel
Library to be compared with old classical threads. This leads me on infinite progress bar control and a non-blocking or intrusive progress UI.

To share my work, you will find below:

  • How to write a WaitSpin control and create several designs in Expression Designer…
  • Create a custom ItemsControl to be a sliding panel with multi-progress cancelable items…
  • Multi-Threading : Threads, Taks and Background worker…

Screenshots

Screenshot: “Demonstration app – Threads and sliding panel”

Screenshot: “Demonstration app – WaitSpin designs”

WaitSpin

The Code

It is derivated from the Control class. It is very simple, define a few properties and methods. The requirement for the template is to have a PART_LoadingAnimation that define a storyboard to animate the control.

/// <summary>
/// Enumeration for representing state of an animation.
/// </summary>
public enum AnimationState
{
	/// <summary>
	/// The animation is playing.
	/// </summary>
	Playing,

	/// <summary>
	/// The animation is paused.
	/// </summary>
	Paused,

	/// <summary>
	/// The animation is stopped.
	/// </summary>
	Stopped
}

/// <summary>
/// A control that shows a loading animation.
/// </summary>
public class WaitSpin : Control
{
	#region --------------------CONSTRUCTORS--------------------

	static WaitSpin()
	{
		FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(WaitSpin),
			new FrameworkPropertyMetadata(typeof(WaitSpin)));
	}

	/// <summary>
	/// LoadingAnimation constructor.
	/// </summary>
	public WaitSpin()
	{
		this.DefaultStyleKey = typeof(WaitSpin);
	}

	#endregion

	#region --------------------DEPENDENCY PROPERTIES--------------------

	#region -------------------- fill--------------------
	/// <summary>
	/// fill property.
	/// </summary>
	public static readonly DependencyProperty ShapeFillProperty = 
		DependencyProperty.Register("ShapeFill", typeof(Brush), typeof(WaitSpin), null);

	/// <summary>
	/// Gets or sets the fill.
	/// </summary>
	[System.ComponentModel.Category("Loading Animation Properties"), System.ComponentModel.Description("The fill for the shapes.")]
	public Brush ShapeFill
	{
		get { return (Brush)GetValue(ShapeFillProperty); }
		set { SetValue(ShapeFillProperty, value); }
	}
	#endregion

	#region -------------------- stroke--------------------
	/// <summary>
	/// Ellipse stroke property.
	/// </summary>
	public static readonly DependencyProperty ShapeStrokeProperty = 
		DependencyProperty.Register("ShapeStroke", typeof(Brush), typeof(WaitSpin), null);

	/// <summary>
	/// Gets or sets the ellipse stroke.
	/// </summary>
	[System.ComponentModel.Category("Loading Animation Properties"), System.ComponentModel.Description("The stroke for the shapes.")]
	public Brush ShapeStroke
	{...
	#endregion

	#region --------------------Is playing--------------------
	/// <summary>
	/// Playing status
	/// </summary>
	public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register("IsPlaying", typeof(bool), typeof(WaitSpin),
		new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsPlayingChanged)));

	/// <summary>
	/// OnIsPlayingChanged callback
	/// </summary>
	/// <param name="d"></param>
	/// <param name="e"></param>
	private static void OnIsPlayingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
			return;

		WaitSpin element = d as WaitSpin;
		element.ChangePlayMode((bool)e.NewValue);
	}

	/// <summary>
	/// IsPlaying
	/// </summary>
	[System.ComponentModel.Category("Loading Animation Properties"), System.ComponentModel.Description("Incates wheter is playing or not.")]
	public bool IsPlaying
	{...
	#endregion

	#region --------------------Associated element--------------------

	/// <summary>
	/// Associated element to disable when loading
	/// </summary>
	public static readonly DependencyProperty AssociatedElementProperty = DependencyProperty.Register("AssociatedElement", typeof(UIElement), typeof(WaitSpin), null);

	/// <summary>
	/// Gets or sets the associated element to disable when loading
	/// </summary>
	[System.ComponentModel.Category("Loading Animation Properties"), System.ComponentModel.Description("Associated element that will be disabled when playing.")]
	public UIElement AssociatedElement
	{...

	#endregion

	#region --------------------AutoPlay--------------------

	/// <summary>
	/// Gets or sets a value indicating whether the animation should play on load.
	/// </summary>
	public static readonly DependencyProperty AutoPlayProperty = DependencyProperty.Register("AutoPlay", typeof(bool), typeof(WaitSpin),
		new FrameworkPropertyMetadata(new PropertyChangedCallback(OnAutoPlayChanged)));

	private static void OnAutoPlayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
			return;

		WaitSpin element = d as WaitSpin;
		element.ChangePlayMode((bool)e.NewValue);
	}

	/// <summary>
	/// Gets or sets a value indicating whether the animation should play on load.
	/// </summary>
	[System.ComponentModel.Category("Loading Animation Properties"), System.ComponentModel.Description("The animation should play on load.")]
	public bool AutoPlay
	{...

	#endregion

	#endregion

	#region --------------------PROPERTIES--------------------
	...

	/// <summary>
	/// Gets the animation state,
	/// </summary>
	public AnimationState AnimationState
	{
		get { return this._animationState; }
	}

	#endregion

	/// <summary>
	/// Gets the parts out of the template.
	/// </summary>
	public override void OnApplyTemplate()
	{
		base.OnApplyTemplate();

		//retreive the animation part
		this._loadingAnimation = (Storyboard)this.GetTemplateChild("PART_LoadingAnimation");

		if (this.AutoPlay)
			Begin();
	}

	/// <summary>
	/// Begins the loading animation.
	/// </summary>
	internal void ChangePlayMode( bool playing )
	{
		if (this._loadingAnimation == null) return;

		if (playing)
		{
			if (this._animationState != AnimationState.Playing)
				Begin();
		}
		else
		{
			if (this._animationState != AnimationState.Stopped)
				Stop();
		}
	}

	/// <summary>
	/// Begins the loading animation.
	/// </summary>
	public void Begin()
	{
		if (this._loadingAnimation != null)
		{
			this._animationState = AnimationState.Playing;
			this._loadingAnimation.Begin();

			this.Visibility = System.Windows.Visibility.Visible;
			if (AssociatedElement != null)
				AssociatedElement.IsEnabled = false;
		}
	}

	/// <summary>
	/// Pauses the animation.
	/// </summary>
	public void Pause()
	{
		if (this._loadingAnimation != null)
		{
			this._animationState = AnimationState.Paused;
			this._loadingAnimation.Pause();
		}
	}

	/// <summary>
	/// Resumes the animation.
	/// </summary>
	public void Resume()
	{
		if (this._loadingAnimation != null)
		{
			this._animationState = AnimationState.Playing;
			this._loadingAnimation.Resume();
		}
	}

	/// <summary>
	/// Stops the animation.
	/// </summary>
	public void Stop()
	{
		if (this._loadingAnimation != null)
		{
			this._animationState = AnimationState.Stopped;
			this._loadingAnimation.Stop();

			this.Visibility = System.Windows.Visibility.Hidden;
			if (AssociatedElement != null)
				AssociatedElement.IsEnabled = true;
		}
	}
}

The basic provided XAML style define the Visibility, ShapeFill and ShapeStroke properties. Joined is a simple ellipse template and a storyboard to play with shape transparency.

Note that the template is surrounded by a Viewbox binded to the control dimensions – that’s the easiest way i found to make it adjustable because Canvas are nightmare…

And ellipse properties are binded to our control properties  Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}"

<Style x:Key="{x:Type local:WaitSpin}" TargetType="{x:Type local:WaitSpin}">
	<Setter Property="Visibility" Value="Visible" />
	<Setter Property="ShapeFill" Value="White" />
	<Setter Property="ShapeStroke" Value="#00000000" />
	<Setter Property="Template">
	<Setter.Value>
		<ControlTemplate TargetType="{x:Type local:WaitSpin}">
		<Viewbox Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
			<Canvas x:Name="Document" Width="100" Height="100" Clip="F1 M 0,0L 100,0L 100,100L 0,100L 0,0">
			<Canvas.Resources>
				<Storyboard.....
			</Canvas.Resources>

			<Ellipse x:Name="ellipse8" Width="15" Height="15" Canvas.Left="12" Canvas.Top="12" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="0.66" />
			<Ellipse x:Name="ellipse7" Width="15" Height="15" Canvas.Left="0" Canvas.Top="40" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="0.33" />
			<Ellipse x:Name="ellipse6" Width="15" Height="15" Canvas.Left="12" Canvas.Top="68" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="0" />
			<Ellipse x:Name="ellipse5" Width="15" Height="15" Canvas.Left="40" Canvas.Top="80" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="0" />
			<Ellipse x:Name="ellipse4" Width="15" Height="15" Canvas.Left="68" Canvas.Top="68" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="0" />
			<Ellipse x:Name="ellipse3" Width="15" Height="15" Canvas.Left="80" Canvas.Top="40" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="0" />
			<Ellipse x:Name="ellipse2" Width="15" Height="15" Canvas.Left="68" Canvas.Top="12" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="0" />
			<Ellipse x:Name="ellipse1" Width="15" Height="15" Canvas.Left="40" Canvas.Top="0" Stretch="Fill" StrokeThickness="1" StrokeLineJoin="Round" Stroke="{TemplateBinding ShapeStroke}" Fill="{TemplateBinding ShapeFill}" Opacity="1" />

			</Canvas>
		</Viewbox> 
		</ControlTemplate>
	</Setter.Value>
</Setter>
</Style>

Create designs

The easiest way to create design is to combine Expression Designer and Blend. My Model is based on a 100×100 document. In the Designer, create the shapes you want and export it with : “selected objects” + (3rd) “XAML SL4 / CWPF canvas” + “Always name” + “Place grouped objects in container” + “Paths” + “Convert to effets”

Copy the canvas from the exported file into a WaitSpin style copy to replace the ellipses.

Take also care about the order and name of elements if you use the pre-defined opacity storyboard that exist in the basic style, then just rename the elements to “EllipseX” or change the TargetName of each KeyFrames.

<Storyboard x:Key="PART_LoadingAnimation" x:Name="PART_LoadingAnimation" RepeatBehavior="Forever">
	<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
....

Or create a new storyboard in Blend like the rotating one defined in the “ie” style and printed below – Visual Studio don’t like it in the designer, but it’s working.

<Storyboard x:Key="PART_LoadingAnimation" x:Name="PART_LoadingAnimation" RepeatBehavior="Forever">
	<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="Document">
		<EasingDoubleKeyFrame KeyTime="00:00:02" Value="360"/>
	</DoubleAnimationUsingKeyFrames>
</Storyboard>

Note : Expression suite is out of the scope…i am not mastering it at all !

Multi-progress panel

For an online programm, often calls are made to a distant web service that is not allways responding a quick manner. So you launch some asynchronous methods and wait for a response. The idea is to make a “panel” like the download one in Internet Explorer, small, non intrusive, cancelable and multi-process in this case. I wan it to be MVVM compliant too !

First, we need to choose the base control and define the viewmodel : ItemsControl seems to answer the requirement, then lets write the ViewModel for our process items :

public class ProcessItem : ViewModelBase
{
	//init data
	public bool UseTempo { get; set; }
	public DateTime StartTime { get; set; }
	public bool CanCancel { get; set; }
	public bool ShowProgress { get; set; }
	public bool ShowPercentage { get; set; }

	private string _Title;
	public string Title
	{
		get { return _Title; }
		set
		{
			if (_Title != value)
			{
				_Title = value;
				RaisePropertyChanged("Title");
			}
		}
	}

	private string _Message;
	public string Message
	{
		get { return _Message; }
		set
		{
			if (_Message != value)
			{
				_Message = value;
				RaisePropertyChanged("Message");
			}
		}
	}

	public bool WaitForCancel { get; set; }

	#region cancel command

	private ICommand cancelCommand;
	public ICommand CancelCommand
	{
		get
		{
			if (cancelCommand == null)
				cancelCommand = new DelegateCommand(CancelCommandExecute, CancelCommandCanExecute);
			return cancelCommand;
		}
	}

	virtual public bool CancelCommandCanExecute()
	{
		return CanCancel;
	}

	virtual public void CancelCommandExecute()
	{
		WaitForCancel = true;
	}

	#endregion
}

To write the control create a class that inherit from ItemsControl.

public class ProcessPanel : ItemsControl

Then we override the OnApplyTemplate to retreive the opening and closing animation that are defined in the template

public override void OnApplyTemplate()
{
	base.OnApplyTemplate();

	_CloseStoryboard = (Storyboard)this.GetTemplateChild("PART_CloseStoryboard");
	_CloseAnim = _CloseStoryboard.Children[0] as DoubleAnimation;

	_OpenStoryboard = (Storyboard)this.GetTemplateChild("PART_OpenStoryboard");
	_OpenAnim = _OpenStoryboard.Children[0] as DoubleAnimation;
}

After that we only subscribe to the items collection event to expand or reduce the panel that will be animated by our storyboard. Note that the StoryBoard object arround the animation are the only ways to modify the From and To properties. Otherwise you will get exceptions with frezzed objects because the storyboard is playing them.

protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
	base.OnItemsChanged(e);

	if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
	{
		Console.WriteLine("play open : from " + _OpenAnim.From + "to" + _OpenAnim.To);
		_OpenStoryboard.Begin();

		_OpenAnim.From = _OpenAnim.To;
		_OpenAnim.To += 35;

		_CloseAnim.From = _OpenAnim.To;
		_CloseAnim.To = _OpenAnim.From;
	}...

The style for our control is like below : I define a DockPanel that contains the storyboards, then a simple grid that has a border for shaping the control and an ItemPresenter that contains my ItemsControl collection. ItemTemplate is replaced with simple DataTemplate that is binded to the ProcessItem viewmodel previously seens

<Style x:Key="{x:Type local:ProcessPanel}" TargetType="{x:Type local:ProcessPanel}">
	<Setter Property="Visibility" Value="Visible" />
	<Setter Property="Template">
	<Setter.Value>
		<ControlTemplate TargetType="{x:Type local:ProcessPanel}">
		<DockPanel x:Name="PART_Container" VerticalAlignment="Bottom" Panel.ZIndex="1000">
		<DockPanel.Resources>
			<Storyboard x:Name="PART_CloseStoryboard" x:Key="PART_CloseStoryboard">
				<DoubleAnimation x:Name="CloseAnim"
				Storyboard.TargetName="PART_Container"
				Storyboard.TargetProperty="(Height)"
				From="41"
				To="0"
				Duration="00:00:00.4000000" />
			</Storyboard>

			<Storyboard x:Name="PART_OpenStoryboard" x:Key="PART_OpenStoryboard">
...
		</DockPanel.Resources>
		<Grid Margin="0">
			<Border x:Name="top" Grid.ColumnSpan="4" CornerRadius="7,7,0,0">
			<Border.Background>
				<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
				...
				</LinearGradientBrush>
			</Border.Background>
			</Border>
			<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
		</Grid>
		</DockPanel>
		</ControlTemplate>
	</Setter.Value>
	</Setter>
	<Setter Property="ItemsControl.ItemTemplate">
		<Setter.Value>
			<DataTemplate>
			<Grid Margin="0" Height="35">
				<Grid.ColumnDefinitions>
...
		<TextBlock Grid.Column="0" VerticalAlignment="Center" Margin="5" Background="Transparent"
		Text="{Binding Title}" TextWrapping="WrapWithOverflow" TextTrimming="WordEllipsis" />
		<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="5" Background="Transparent"
		Text="{Binding Message}" TextWrapping="WrapWithOverflow" TextTrimming="WordEllipsis" />
		<controls:WaitSpin Grid.Column="2" AutoPlay="True" Margin="6"></controls:WaitSpin>
		<Button Grid.Column="3" Margin="6" Content="x" Command="{Binding CancelCommand}" />
			</Grid>
			</DataTemplate>
		</Setter.Value>
	</Setter>
</Style>

Thread, Task and BackgroundWorker

Explanations

On CBR, I gave a try to the TPL to compare with my thread implementation…that was a catastroph…So I choose to go deeper and try to compare these methods. I choose to implement a “recursive disk/folder parsing” method to find images. This allow us to continue also with how to use the WaitSpin and ProgressPanel controls.

The algorithm is allways the same and basically like below – depending on the method : BtnClick => create a ProcessItem => add it to collection => start a “process thread” => when finished, remove ProcessItem

Thread way

Nothing special but note that I use the BeginInvoke on the application thread to add and remove the item. It is asynchron and can lead into errors.

private void btnThread_Click(object sender, RoutedEventArgs e)
{
	ProcessItem pi = new ProcessItem()
	{
	Title = "btnThread_Click",
	Message = "btnThread_Click",
	CanCancel = true,
	ShowProgress = true,
	ShowPercentage = false,
	Data = this.tbFolder.Text,
	StartTime = DateTime.Now,
	UseTempo = chkUseTempo.IsChecked.Value
	};

	Thread t = new Thread(new ParameterizedThreadStart(LaunchThread));
	t.IsBackground = true;
	t.Priority = ThreadPriority.Normal;
	t.Start(pi);
}

private void LaunchThread(object param)
{
	try
	{
	ProcessItem pi = param as ProcessItem;

	Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate
	{
	_list.Add(pi);
	listBox1.Items.Add(pi.StartTime);
	});

	pi.Message = "Processing folders";

	ProcessMethod(pi, pi.Data as string);

	Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate
	{
	_list.Remove(pi);
	listBox1.Items.Add(DateTime.Now - pi.StartTime);
	});
	}
...}

internal void ProcessMethod(object param, string folder)
{
	ProcessItem pi = param as ProcessItem;

...

	pi.Message = "Processing folder " + directory.Name;

	foreach (FileInfo file in directory.GetFiles("*.*"))
	{
		if (pi.WaitForCancel)
		{
			pi.Message = "canceled by the user";
		return;
		}

		pi.Message = "Processing file " + file.Name;

		if (file.Extension == ".jpg")
		{
			Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate
			{
				listBox1.Items.Add(file.Name);
			});
		}

...
}

TPL – First try…

The idea was : wow ! make some parallel tasks and see how quick it is….but I forgot that creating a Task is CPU expensive and that my hard disk is the bottleneck in this case ! Image that even if the framework can create 20000 task in a second my hard disk is not capable of reading so quick ! Below is the original code :

public void ParallelProcessMethod(object param, string folder)
{
	ProcessItem pi = param as ProcessItem;

	try
	{
	DirectoryInfo directory = new DirectoryInfo(folder);
	if (!directory.Exists)
		...

	Parallel.ForEach<FileInfo>(directory.GetFiles("*.*"), fi =>
	{
...

	Parallel.ForEach<DirectoryInfo>(directory.GetDirectories("*", SearchOption.TopDirectoryOnly), dir =>
	{
...	
		ParallelProcessMethod(param, dir.FullName);

...

Advise : Be sure that the task number will not grow too much and that the processing is long enough to compensate the task creation cost.

TPL – Second try…

Remove the Parallel.ForEach and everything gets better ! Here is the handler. I put the code for adding and removing the ProcessItem here to take advantage of the ContinueWith method. The process code is the same.

private void btnTask_Click(object sender, RoutedEventArgs e)
{
	ProcessItem pi = new ProcessItem()
	{
	Title = "btnTask_Click",
	Message = "btnTask_Click",
	CanCancel = true,
	ShowProgress = true,
	ShowPercentage = false,
	Data = this.tbFolder.Text,
	StartTime = DateTime.Now,
	UseTempo = chkUseTempo.IsChecked.Value
	};

	Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate
	{
	_list.Add(pi);
	listBox1.Items.Add(pi.StartTime);
	});

	Task tk = Task.Factory.StartNew(() =>
	{
	try
	{
		pi.Message = "Processing folders";

		ParallelProcessMethod(pi, pi.Data as string);
	}
	catch (Exception err)
	{
	}
	}).ContinueWith(ant =>
	{
		_list.Remove(pi);
		listBox1.Items.Add(DateTime.Now - pi.StartTime);
		//updates UI no problem as we are using correct SynchronizationContext
	}, TaskScheduler.FromCurrentSynchronizationContext());
	}
...

Background Worder to finish

I implemented  the BackgroundWorker in a quick and classic way
like below :

private void btnWorker_Click(object sender, RoutedEventArgs e)
{
	BackgroundWorker _Worker = null;

	//init the background worker process
	_Worker = new BackgroundWorker();
	_Worker.WorkerReportsProgress = true;
	_Worker.WorkerSupportsCancellation = true;
	_Worker.DoWork += new DoWorkEventHandler(bw_DoBuildWork);
	_Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
	_Worker.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);

	ProcessItem pi = new ProcessItem()
	{
	Title = "btnWorker_ClickbtnWorker_Click",
	Message = "btnWorker_Click",
	CanCancel = true,
	ShowProgress = true,
	ShowPercentage = false,
	Data = this.tbFolder.Text,
	StartTime = DateTime.Now,
	UseTempo = chkUseTempo.IsChecked.Value
	};

	// Start the asynchronous operation.
	_Worker.RunWorkerAsync(pi);
}
...

void bw_DoBuildWork(object sender, DoWorkEventArgs e)
{
	ProcessItem pi = e.Argument as ProcessItem;

	Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate
	{
		_list.Add(pi);
		listBox1.Items.Add(pi.StartTime);
	});

	BackProcessMethod(pi, pi.Data as string);

	e.Result = pi;
}

internal void BackProcessMethod(object param, string folder)
{
	ProcessItem pi = param as ProcessItem;
	...
	//the same as others
}

void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
	...empty
}

void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
	ProcessItem pi = e.Result as ProcessItem;

	// First, handle the case where an exception was thrown.
	if (e.Error != null)
	{
	}
	else if (e.Cancelled)
	{
	// Next, handle the case where the user canceled the operation.
	// Note that due to a race condition in the DoWork event handler, the Cancelled
	// flag may not have been set, even though CancelAsync was called.
	}

	Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate
	{
	_list.Remove(pi);
	listBox1.Items.Add(DateTime.Now - pi.StartTime);
	});
}

To summerize…

This little try, in my opinion, show 4 things :

  1. BackgroundWorker
    method has got us more code
  2. Execution speed is the same when they are all well implemented
  3. BackgroundWorker seems a bit obsolet with his progress handler and more rigid
  4. Thread are more classical way of writing regarding linq style of the Tasks
  5. Tasks certainly reveals avantages in a more complex scenario !

By the way, it is more a fashon than anything else (this is to make TPL fan over-react 🙂 )

Conclusion

This article is a very quick draft of what you can use for further developpement. I hope you will enjoy it as much as I do…next improvment is to reduce the panel after user interaction…but I have troubles…

History

  • v1.0
    • First version: everything there. I would like to add a auto-hide function if mouse leave the panel…
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s