Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
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.
public class ApplicationController : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private TestStates state;
public TestStates State
get { return state; }
set
if (state != value)
state = value;
OnPropertyChanged(StatePropertyName);
public const string StatePropertyName = "State";
J’instancie ce contrôleur en l’injectant dans les ressources de l’application
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="TestBehavior.App"
xmlns:controllers="clr-namespace:TestBehavior.Controllers"
>
<Application.Resources>
<controllers:ApplicationController x:Key="appController" />
</Application.Resources>
</Application>
Je rajoute les références qui vont bien à mon projet
Je peux maintenant rajoute mon behavior à l’aide de blend
Je remplie le mapping et la source de mon behavior avec blend
Le morceau de code qui change l’état du contrôleur.
private void Button_Click(object sender, RoutedEventArgs e)
ApplicationController appController = Application.Current.Resources["appController"] as ApplicationController;
appController.State = (TestStates) ((1 + (int)appController.State) % 3);
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é.
Le code complet de la classe :
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);
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)));
/// handles the SourceProperty changes.
/// <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;
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
return (string)GetValue(PropertyProperty);
SetValue(PropertyProperty, value);
/// Property depedency property.
public static readonly DependencyProperty PropertyProperty =
"Property",
typeof(string),
new PropertyMetadata((d, e) => ((MapState)d).OnPropertyChanged((string)e.OldValue, (string)e.NewValue)));
/// handles the PropertyProperty changes.
private void OnPropertyChanged(string oldValue, string newValue)
if (Source == null)
else
propertyInfo = Source.GetType().GetProperty(newValue);
#endregion Property
public List<MapStateMapping> Mappings { get; private set; }
void notifyingObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
if(e.PropertyName == Property)
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
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)
var val2 = Convert.ChangeType(propValue, value.GetType(), CultureInfo.InvariantCulture);
if (object.Equals(val2, value))
var val2 = Convert.ChangeType(value, propValue.GetType(), CultureInfo.InvariantCulture);
if (object.Equals(val2, propValue))
if (mapping != null)
SLExtensions.Controls.Animation.VisualState.GoToState(AssociatedObject, UseTransitions, mapping.StateName);
///
public static readonly DependencyProperty UseTransitionsProperty = DependencyProperty.Register("UseTransitions", typeof(bool), typeof(MapState), new PropertyMetadata(true));
/// True if transitions should be used for the state change.
public bool UseTransitions
get { return (bool)this.GetValue(MapState.UseTransitionsProperty); }
set { this.SetValue(MapState.UseTransitionsProperty, value); }
/// Hooks up necessary handlers for the state changes.
protected override void OnAttached()
base.OnAttached();
FrameworkElement element = this.AssociatedObject;
if (element != null)
element.Loaded += new RoutedEventHandler(element_Loaded);
Dispatcher.BeginInvoke(delegate
this.OnAttached();
});
void element_Loaded(object sender, RoutedEventArgs e)
En attendant de publier tout ça sur slextensions, vous pouvez télécharger le projet ici: Vous pouvez retrouver d’autres behavior sur le site de la gallery expression : http://gallery.expression.microsoft.com/