Les behaviors introduits par blend 3 servent à étendre les fonctionnalités d’un contrôle à l’aide d’une nouvel élément que l’on peut poser graphiquement dans l’interface de blend. L’ajout de comportement n’est pas nouveau, c’est utilisé dans de nombreux projets (par exemple, la navigation deepzoom de slextensions est géré de cette manière), mais ici, l’intégration à blend est plutôt pratique et permet à un intégrateur de gérer du comportement sans écrire de code.
La classe MapState me permet de mapper l’état d’une propriété d’un contrôleur à un VisualState posé sur mon contrôle.
Ma classe contrôleur de test n’a rien de particulier. Voici sa définition.
Le morceau de code qui change l’état du contrôleur.
Ma classe MapState hérite d’une classe ancêtre Behavior que vous trouverez dans l’assembly Microsoft.Expression.Interactivity.dll qui se trouve dans le dossier Libraries/Silverlight de Blend 3.
J’ai déclaré une propriété Source qui me sert à récupérer l’objet métier qui va me donner mon état et une propriété Property me donnant le nom de la propriété d’état. Ma source doit bien sur implémenter INotifyPropertyChanged pour que MapState soit notifiée du changement de propriété.
J’ai également une collection de MapStateMapping qui me fournisse un couple Valeur / VisualState.
Au changement de valeur sur ma source, je n’ai plus qu’à comparer par rapport aux valeurs de mapping est à déclancher le visualstate sur mon élément associé.
namespace SLExtensions.Interactivity
{
[ContentProperty("Mappings")]
public class MapState : Behavior<FrameworkElement>
{
public MapState()
{
Mappings = new List<MapStateMapping>();
}
#region Source
private PropertyInfo propertyInfo = null;
public object Source
{
get
{
return (object)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
/// <summary>
/// Source depedency property.
/// </summary>
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(
"Source",
typeof(object),
typeof(MapState),
new PropertyMetadata((d, e) => ((MapState)d).OnSourceChanged((object)e.OldValue, (object)e.NewValue)));
/// <summary>
/// handles the SourceProperty changes.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
private void OnSourceChanged(object oldValue, object newValue)
{
propertyInfo = null;
INotifyPropertyChanged notifyingObject = oldValue as INotifyPropertyChanged;
if (notifyingObject != null)
{
notifyingObject.PropertyChanged -= new PropertyChangedEventHandler(notifyingObject_PropertyChanged);
}
notifyingObject = newValue as INotifyPropertyChanged;
if (notifyingObject != null)
{
notifyingObject.PropertyChanged += new PropertyChangedEventHandler(notifyingObject_PropertyChanged);
}
if (newValue != null && !string.IsNullOrEmpty(Property))
{
propertyInfo = newValue.GetType().GetProperty(Property);
}
RefreshState();
}
#endregion Source
#region Property
public string Property
{
get
{
return (string)GetValue(PropertyProperty);
}
set
{
SetValue(PropertyProperty, value);
}
}
/// <summary>
/// Property depedency property.
/// </summary>
public static readonly DependencyProperty PropertyProperty =
DependencyProperty.Register(
"Property",
typeof(string),
typeof(MapState),
new PropertyMetadata((d, e) => ((MapState)d).OnPropertyChanged((string)e.OldValue, (string)e.NewValue)));
/// <summary>
/// handles the PropertyProperty changes.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
private void OnPropertyChanged(string oldValue, string newValue)
{
if (Source == null)
propertyInfo = null;
else
propertyInfo = Source.GetType().GetProperty(newValue);
RefreshState();
}
#endregion Property
public List<MapStateMapping> Mappings { get; private set; }
void notifyingObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == Property)
RefreshState();
}
private void RefreshState()
{
// Do not refresh at desing time
if (!HtmlPage.IsEnabled)
return;
if (Source != null && propertyInfo != null )
{
object propValue = propertyInfo.GetValue(Source, null);
if (propValue == null)
{
var nullMappings = from m in Mappings
where m.Value == null
|| ((m.Value is string) && string.Empty.Equals(m.Value))
select m.StateName;
try
{
SLExtensions.Controls.Animation.VisualState.GoToState(AssociatedObject, UseTransitions, nullMappings.ToArray());
}
catch
{
}
}
else
{
MapStateMapping mapping = null;
foreach (var item in Mappings)
{
var value = item.Value;
if (value != null)
{
if (object.Equals(value, propValue))
{
mapping = item;
break;
}
if (propValue is IConvertible && value is IConvertible)
{
try
{
var val2 = Convert.ChangeType(propValue, value.GetType(), CultureInfo.InvariantCulture);
if (object.Equals(val2, value))
{
mapping = item;
break;
}
}
catch
{
}
try
{
var val2 = Convert.ChangeType(value, propValue.GetType(), CultureInfo.InvariantCulture);
if (object.Equals(val2, propValue))
{
mapping = item;
break;
}
}
catch
{
}
}
}
}
if (mapping != null)
{
try
{
SLExtensions.Controls.Animation.VisualState.GoToState(AssociatedObject, UseTransitions, mapping.StateName);
}
catch
{
}
}
}
}
}
/// <summary>
///
/// </summary>
public static readonly DependencyProperty UseTransitionsProperty = DependencyProperty.Register("UseTransitions", typeof(bool), typeof(MapState), new PropertyMetadata(true));
/// <summary>
/// True if transitions should be used for the state change.
/// </summary>
public bool UseTransitions
{
get { return (bool)this.GetValue(MapState.UseTransitionsProperty); }
set { this.SetValue(MapState.UseTransitionsProperty, value); }
}
/// <summary>
/// Hooks up necessary handlers for the state changes.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
FrameworkElement element = this.AssociatedObject;
if (element != null)
{
element.Loaded += new RoutedEventHandler(element_Loaded);
RefreshState();
}
else
{
Dispatcher.BeginInvoke(delegate
{
this.OnAttached();
});
}
}
void element_Loaded(object sender, RoutedEventArgs e)
{
RefreshState();
}
}
}