« Attached behavior » : différence entre les versions
Ligne 195 : | Ligne 195 : | ||
</filebox> | </filebox> | ||
* [[ItemsControl,_ListBox,_ComboBox,_TreeView#ScrollToSelectedItemBehavior ScrollToSelectedItemBehavior]] | * [[ItemsControl,_ListBox,_ComboBox,_TreeView#ScrollToSelectedItemBehavior|ScrollToSelectedItemBehavior]] | ||
== Attacher le behavior via un style == | == Attacher le behavior via un style == |
Version du 20 octobre 2021 à 19:27
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
- Microsoft.Xaml.Behaviors
- Behavior
System.Windows.Interactivity.WPF / Expression.Blend.Sdk.WPF
- Microsoft.Expression.Interactions
- MouseDragElementBehavior
- System.Windows.Interactivity
- Behavior
InvokeCommandAction
Lie un événement UI à une commande du VM.
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
Lie un événement UI à une méthode du VM.
La méthode du VM doit être publique et ne pas avoir d'arguments.
MainWindow.xaml |
<Window xmlns:b="http://schemas.microsoft.com/xaml/behaviors"> <b:Interaction.Triggers> <b:EventTrigger EventName="Loaded"> <b:CallMethodAction TargetObject="{Binding Mode=OneTime}" MethodName="OnViewLoaded" /> </b:EventTrigger> </b:Interaction.Triggers> |
MainViewModel.cs |
public void OnViewLoaded() { } |
TriggerAction
Lie un événement UI à du code, équivalent du code behind.
MainWindow.xaml |
<Window xmlns:b="http://schemas.microsoft.com/xaml/behaviors"> <b:Interaction.Triggers> <b:EventTrigger EventName="Click"> <local:MyTriggerAction /> </b:EventTrigger> </b:Interaction.Triggers> |
MyTriggerAction.cs |
public sealed class MyTriggerAction : TriggerAction<Button> { protected override void Invoke(object parameter) { var args = parameter as RoutedEventArgs; if (args == null) return; var tabControl = VisualTreeHelperPlus.GetParent<TabControl>(args.OriginalSource as DependencyObject); } } |
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 this.AssociatedObject.PreviewTextInput += OnPreviewTextInput; } // never called unless programmatic code removes behavior from parent collection or calls Detach protected override void OnDetaching() { this.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> |