|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Configuration;
namespace ThreadingComponent
{
/// <summary>
/// Host that deals with what to show when a threading operation
/// is busy or fails using the AdornerLayer, and will also show
/// the data associated with the threading operation which is expected
/// to be the actual Content for the ThreadableHostControl. So typical
/// usage maybe something like the example below.
///
/// It should be noted that this control DOES use the AdornerLayer, which
/// from time to time can be tricky to obtain. As such there is the possibility
/// that the AdornerLayer may be null, and as such this control would not really
/// work as planned. To deal with this the user can choose how this can be handled
/// by using the App.Config setting "shouldThrowExceptionOnNullAdornerLayer" which
/// directs the app to throw an Exception if the AdornerLayer can not be obtained.
///
/// If the user chooses to set the "shouldThrowExceptionOnNullAdornerLayer" App.Config
/// value to "false" the threading should all work as expected, it is just the
/// busy or fails Adorners will not be shown, and the user will have to work out another way
/// of dealing with the IsBusy and Failed states of the background threading operation
/// <example>
/// <![CDATA[
///
/// <local:ThreadableHostControl HorizontalAlignment="Stretch"
/// VerticalAlignment="Stretch"
/// ThreadableItem="{Binding ThreadVM}">
/// <ListView Background="WhiteSmoke" BorderBrush="Black" BorderThickness="5"
/// ItemsSource="{Binding ThreadVM.Data.DataObject}">
/// <ListView.View>
/// <GridView>
/// <GridViewColumn Header="Text" Width="500"
/// DisplayMemberBinding="{Binding Path=Text}"/>
/// <GridViewColumn Header="Age" Width="100"
/// DisplayMemberBinding="{Binding Path=Age}"/>
/// </GridView>
/// </ListView.View>
/// </ListView>
///
/// </local:ThreadableHostControl>
/// ]]>
/// </example>
/// </summary>
public partial class ThreadableHostControl : UserControl
{
#region Data
private PropertyObserver<ThreadableItemViewModelBase> threadableItemObserver;
private AdornerLayer adornerLayer;
private BusyAdorner busyAdorner;
private FailedAdorner failedAdorner;
private Boolean shouldThrowExceptionOnNullAdornerLayer = true;
#endregion
#region Constructor
public ThreadableHostControl()
{
InitializeComponent();
//work out whether the user chose to ignore a null AdornerLayer
//which obviously impacts how the code works, but the Background
//thread will still run, its just that the Adorners that this class
//manages will not be shown. The default behaviour for the
//shouldThrowExceptionOnNullAdornerLayer flag is true
Boolean.TryParse(
ConfigurationSettings.AppSettings.Get(
"ShouldThrowExceptionOnNullAdornerLayer"),
out shouldThrowExceptionOnNullAdornerLayer);
}
#endregion
#region Overrides
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
SafeResize(busyAdorner, sizeInfo.NewSize);
SafeResize(failedAdorner, sizeInfo.NewSize);
}
#endregion
#region Private Methods
/// <summary>
/// Watches the IsBusy/Failed INPC properties of the
/// ThreadableItemViewModelBase and when they change,
/// swaps in/out the correct Adorner to suit the current
/// state of the ThreadableItemViewModelBase
/// </summary>
/// <param name="item">The ThreadableItemViewModelBase to
/// watch INPC changes on</param>
private void SetupPropertyWatcher(ThreadableItemViewModelBase item)
{
if (threadableItemObserver != null)
{
threadableItemObserver.UnregisterHandler(n => n.IsBusy);
threadableItemObserver.UnregisterHandler(n => n.Failed);
}
threadableItemObserver = new PropertyObserver<ThreadableItemViewModelBase>(item);
threadableItemObserver.RegisterHandler(n => n.IsBusy, this.IsBusyChanged);
threadableItemObserver.RegisterHandler(n => n.Failed, this.FailedChanged);
}
/// <summary>
/// Safely removes all CustomAdornerBase from the the AdornerLayer
/// </summary>
/// <param name="adorners">Collection of CustomAdornerBase to remove</param>
private void SafeRemoveAll(List<CustomAdornerBase> adorners)
{
foreach (CustomAdornerBase adorner in adorners)
SafeRemove(adorner);
}
/// <summary>
/// Causes a repaint
/// </summary>
private void InvalidateControl()
{
this.InvalidateVisual();
this.InvalidateArrange();
this.InvalidateMeasure();
}
/// <summary>
/// Shows the BusyAdorner
/// </summary>
private void IsBusyChanged(ThreadableItemViewModelBase vm)
{
adornerLayer = AdornerLayer.GetAdornerLayer(this);
//If the users chose to throw an Exception on a null AdornerLayer
//throw an Exception. The user may change this setting in the App.Config
//which obviously impacts how the code works, but the Background
//thread will still run, its just that the Adorners that this class
//manages will not be shown. Which is not how the code was intended
//to work. It would be better to find out why the AdornerLayer is null
if (shouldThrowExceptionOnNullAdornerLayer && adornerLayer == null)
throw new NotSupportedException(
"The ThreadableHostControl will only work correctly\r\n" +
"if there is an AdornerLayer found and it is not null");
if (adornerLayer != null)
{
if (vm.IsBusy)
{
SafeRemoveAll(new List<CustomAdornerBase>() { failedAdorner,busyAdorner } );
busyAdorner = new BusyAdorner(this);
adornerLayer.Add(busyAdorner);
}
else
{
SafeRemoveAll(new List<CustomAdornerBase>() { failedAdorner, busyAdorner });
}
}
//repaint
InvalidateControl();
}
/// <summary>
/// Shows the FailedAdorner
/// </summary>
private void FailedChanged(ThreadableItemViewModelBase vm)
{
if (vm.IsBusy)
return;
//If the users chose to throw an Exception on a null AdornerLayer
//throw an Exception. The user may change this setting in the App.Config
//which obviously impacts how the code works, but the Background
//thread will still run, its just that the Adorners that this class
//manages will not be shown. Which is not how the code was intended
//to work. It would be better to find out why the AdornerLayer is null
adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (shouldThrowExceptionOnNullAdornerLayer && adornerLayer == null)
throw new NotSupportedException(
"The ThreadableHostControl will only work correctly\r\n" +
"if there is an AdornerLayer found and it is not null");
if (adornerLayer != null)
{
if (vm.Failed)
{
SafeRemoveAll(new List<CustomAdornerBase>() { failedAdorner, busyAdorner });
failedAdorner = new FailedAdorner(this, vm.ErrorMessage);
adornerLayer.Add(failedAdorner);
}
else
{
SafeRemoveAll(new List<CustomAdornerBase>() { failedAdorner, busyAdorner });
}
}
//repaint
InvalidateControl();
}
private void SafeRemove(Adorner adorner)
{
if (adorner != null)
adornerLayer.Remove(adorner);
}
private void SafeResize(IResizableAdornerControl adorner, Size newSize)
{
if (adorner != null)
adorner.ResizeToFillAvailableSpace(newSize);
}
#endregion
#region DPs
#region ThreadableItem
/// <summary>
/// ThreadableItem Dependency Property
/// </summary>
public static readonly DependencyProperty ThreadableItemProperty =
DependencyProperty.Register("ThreadableItem",
typeof(ThreadableItemViewModelBase), typeof(ThreadableHostControl),
new FrameworkPropertyMetadata((ThreadableItemViewModelBase)null,
new PropertyChangedCallback(OnThreadableItemChanged)));
/// <summary>
/// Gets or sets the ThreadableItem property.
/// </summary>
public ThreadableItemViewModelBase ThreadableItem
{
get { return (ThreadableItemViewModelBase)GetValue(ThreadableItemProperty); }
set { SetValue(ThreadableItemProperty, value); }
}
/// <summary>
/// Handles changes to the ThreadableItem property.
/// </summary>
private static void OnThreadableItemChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
((ThreadableHostControl)d).SetupPropertyWatcher(
(ThreadableItemViewModelBase)e.NewValue);
}
}
#endregion
#endregion
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
Both of these at Sussex University UK.
Award(s)
I am lucky enough to have won a few awards for Zany Crazy code articles over the years
- Microsoft C# MVP 2016
- Codeproject MVP 2016
- Microsoft C# MVP 2015
- Codeproject MVP 2015
- Microsoft C# MVP 2014
- Codeproject MVP 2014
- Microsoft C# MVP 2013
- Codeproject MVP 2013
- Microsoft C# MVP 2012
- Codeproject MVP 2012
- Microsoft C# MVP 2011
- Codeproject MVP 2011
- Microsoft C# MVP 2010
- Codeproject MVP 2010
- Microsoft C# MVP 2009
- Codeproject MVP 2009
- Microsoft C# MVP 2008
- Codeproject MVP 2008
- And numerous codeproject awards which you can see over at my blog