Définition
Un behavior est une surcouche d'attached property.
L'AB est définie dans une classe non-statique pour pouvoir ensuite être attachée à un DependencyObject.
L'AB correspond a une fonctionnalité autonome qui peut s'ajouter à des DependencyObjects pour étendre leurs fonctionnalités.
Paquet NuGet à installer
Microsoft.Xaml.Behaviors.Wpf
Le plus récent: Exemples
System.Windows.Interactivity.WPF / Expression.Blend.Sdk.WPF
- Microsoft.Expression.Interactions
- System.Windows.Interactivity
InvokeCommandAction
Permet d'executer une commande suite à un évènement ou au changement de valeur d'une propriété.
Ici on execute la commande SelectionChangedCmd lorsque l’évènement SelectionChanged est lancé.
MainWindow.xaml
|
<Window xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors">
<Grid>
<ComboBox x:Name="cb">
<ComboBoxItem>Item #1</ComboBoxItem>
<ComboBoxItem IsSelected="True">Item #2</ComboBoxItem>
<Behaviors:Interaction.Triggers>
<Behaviors:EventTrigger EventName="SelectionChanged">
<Behaviors:InvokeCommandAction Command="{Binding ChangeColorCmd}"
CommandParameter="{Binding ElementName=cb}" />
</Behaviors:EventTrigger>
</Behaviors:Interaction.Triggers>
</ComboBox>
|
CallMethodAction
Permet d'executer une méthode suite à un évènement ou au changement de valeur d'une propriété.
La méthode doit être publique et ne pas avoir d'arguments.
MainWindow.xaml
|
<Window xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors">
<Behaviors:Interaction.Triggers>
<Behaviors:EventTrigger EventName="Loaded">
<Behaviors:CallMethodAction TargetObject="{Binding Mode=OneWay}"
MethodName="OnViewLoaded" />
</Behaviors:EventTrigger>
</Behaviors:Interaction.Triggers>
|
MainViewModel.cs
|
public void OnViewLoaded()
{ }
|
ChangePropertyAction
Permet de modifier un propriété suite à un évènement ou au changement de valeur d'une propriété.
MainWindow.xaml
|
<Window xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors">
<RadioButton Content="Good"
VerticalAlignment="Stretch"
IsChecked="True"
x:Name="rb1">
<Behaviors:Interaction.Triggers>
<Behaviors:DataTrigger Binding="{Binding IsChecked, ElementName=rb1}"
Value="True">
<Behaviors:ChangePropertyAction TargetObject="{Binding}"
PropertyName="CustomerStatus">
<Behaviors:ChangePropertyAction.Value>
<local:CustomerStatus>Good</local:CustomerStatus>
</Behaviors:ChangePropertyAction.Value>
</Behaviors:ChangePropertyAction>
</Behaviors:DataTrigger>
</Behaviors:Interaction.Triggers>
</RadioButton>
<RadioButton Content="Bad"
VerticalAlignment="Stretch"
x:Name="rb2">
<Behaviors:Interaction.Triggers>
<Behaviors:DataTrigger Binding="{Binding IsChecked, ElementName=rb2}"
Value="True">
<Behaviors:ChangePropertyAction TargetObject="{Binding}"
PropertyName="CustomerStatus">
<Behaviors:ChangePropertyAction.Value>
<local:CustomerStatus>Bad</local:CustomerStatus>
</Behaviors:ChangePropertyAction.Value>
</Behaviors:ChangePropertyAction>
</Behaviors:DataTrigger>
</Behaviors:Interaction.Triggers>
</RadioButton>
|
MainViewModel.cs
|
public class MainViewModel : BindableBase
{
public CustomerStatus CustomerStatus { get; set; }
}
public enum CustomerStatus
{
Good,
Bad
}
|
Custom Behavior
- écrire une fonctionnalité dans le code-behind
- déplacer le code-behind dans un behavior
NumericInputBehavior.cs
|
// un behavior pour les TextBox
public class NumericInputBehavior : Behavior<TextBox>
{
private static readonly Regex decimalRegex =
new Regex($@"^\d+{CultureInfo.CurrentCulture.NumberFormat.PercentDecimalSeparator}?\d*$", RegexOptions.Compiled);
// dependency property
public bool EnableDecimalInput
{
get => (bool) GetValue(EnableDecimalInputProperty);
set => SetValue(EnableDecimalInputProperty, value);
}
public static readonly DependencyProperty EnableDecimalInputProperty =
DependencyProperty.Register("EnableDecimalInputProperty", typeof(bool),
typeof(NumericInputBehavior), new PropertyMetadata(false));
// called when XAML is parsed and behavior is added
protected override void OnAttached()
{
// AssociatedObject correspond au control attachant le behavior
AssociatedObject.PreviewTextInput += OnPreviewTextInput;
}
// never called unless programmatic code removes behavior from parent collection or calls Detach
protected override void OnDetaching()
{
AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
}
private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (EnableDecimalInput)
{
if (!decimalRegex.IsMatch(AssociatedObject.Text + e.Text))
e.Handled = true;
}
else
{
if (!Char.IsDigit(e.Text[0]))
e.Handled = true;
}
}
}
|
MyWindow.xaml
|
<Window xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors">
<TextBox>
<Behaviors:Interaction.Behaviors>
<local:MyTextBoxBehavior EnableDecimalInput="True" />
</Behaviors:Interaction.Behaviors>
</TextBox>
|
Attacher le behavior via un style
MyWindow.xaml
|
<telerik:RadGridView ItemsSource="{Binding Items}"
ValidatesOnDataErrors="InViewMode">
<telerik:RadGridView.RowStyle>
<Style TargetType="telerik:GridViewRow"
BasedOn="{StaticResource {x:Type telerik:GridViewRow}}">
<Setter Property="local:MyBehavior.IsEnabled" Value="True" />
</Style>
</telerik:RadGridView.RowStyle>
</telerik:RadGridView>
|
MyBehavior.cs
|
class MyBehavior : Behavior<GridViewRow>
{
protected override void OnAttached()
{
// AssociatedObject correspond au control attachant le behavior
base.OnAttached();
}
protected override void OnDetaching()
{
base.OnDetaching();
}
public static bool GetIsEnabled(DependencyObject element)
{
return (bool) element.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject element, bool value)
{
element.SetValue(IsEnabledProperty, value);
}
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(MyBehavior),
new PropertyMetadata(default(bool), OnIsEnabledChanged));
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behaviors = Interaction.GetBehaviors(d);
if ((bool)e.NewValue)
{
if (!behaviors.OfType<MyBehavior>().Any())
{
behaviors.Add(new MyBehavior());
}
}
else
{
behaviors.RemoveAll(b => b is MyBehavior);
}
}
}
|
Custom behavior vs custom control
Utilisé de préférence un custom behavior, sauf si:
- il faut accéder aux membres protected
- il faut modifier le control template
Custom Trigger
Comme le custom behavior mais définit seulement les conditions de déclenchement pas le behavior. Celui-ci est définit dans le xaml à chaque déclaration du trigger.
EmployeeTrigger.cs
|
public class EmployeeTrigger : TriggerBase<TextBlock>
{
protected override void OnAttached()
{
AssociatedObject.Loaded += OnLoaded;
AssociatedObject.Unloaded += OnUnloaded;
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= OnLoaded;
AssociatedObject.Unloaded -= OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
AssociatedObject.DataContextChanged += OnDataContextChanged;
Refresh();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
AssociatedObject.DataContextChanged -= OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Refresh();
}
private void Refresh()
{
if (AssociatedObject.DataContext is Employee employee && employee.IsNew)
{
base.InvokeActions(null);
}
}
}
|
MyWindow.xaml
|
<Window xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors">
<ItemsControl ItemsSource="{Binding Employees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<Behaviors:Interaction.Triggers>
<local:EmployeeTrigger>
<Behaviors:ChangePropertyAction PropertyName="Foreground">
<Behaviors:ChangePropertyAction.Value>
<SolidColorBrush Color="Red" />
</Behaviors:ChangePropertyAction.Value>
</Behaviors:ChangePropertyAction>
</local:EmployeeTrigger>
</Behaviors:Interaction.Triggers>
|