« Attached behavior » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
 
 
(21 versions intermédiaires par le même utilisateur non affichées)
Ligne 18 : Ligne 18 :


= InvokeCommandAction =
= InvokeCommandAction =
Permet d'executer une commande suite à un évènement ou au changement de valeur d'une propriété.<br/>
Lie un événement UI à une commande du VM.<br/>
Ici on execute la commande {{boxx|SelectionChangedCmd}} lorsque l’évènement {{boxx|SelectionChanged}} est lancé.
Ici on execute la commande {{boxx|SelectionChangedCmd}} lorsque l’évènement {{boxx|SelectionChanged}} est lancé.


Ligne 38 : Ligne 38 :


= CallMethodAction =
= CallMethodAction =
Permet d'executer une méthode suite à un évènement ou au changement de valeur d'une propriété.<br/>
Lie un événement UI à une méthode du VM.<br/>
La méthode doit être publique et ne pas avoir d'arguments.
La méthode du VM doit être publique et ne pas avoir d'arguments.


<filebox fn='MainWindow.xaml'>
<filebox fn='MainWindow.xaml'>
<Window xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors">
<Window xmlns:b="http://schemas.microsoft.com/xaml/behaviors">


     <Behaviors:Interaction.Triggers>
     <b:Interaction.Triggers>
         <Behaviors:EventTrigger EventName="Loaded">
         <b:EventTrigger EventName="Loaded">
             <Behaviors:CallMethodAction TargetObject="{Binding Mode=OneWay}"
             <b:CallMethodAction TargetObject="{Binding Mode=OneTime}"
                                        MethodName="OnViewLoaded" />
                                MethodName="OnViewLoaded" />
         </Behaviors:EventTrigger>
         </b:EventTrigger>
     </Behaviors:Interaction.Triggers>
     </b:Interaction.Triggers>
</filebox>
</filebox>


Ligne 55 : Ligne 55 :
public void OnViewLoaded()
public void OnViewLoaded()
{ }
{ }
</filebox>
= TriggerAction =
Lie un événement UI à du code, équivalent du code behind.
<filebox fn='MainWindow.xaml'>
<Window xmlns:b="http://schemas.microsoft.com/xaml/behaviors">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="Click">
            <triggers:MyTriggerAction />
        </b:EventTrigger>
    </b:Interaction.Triggers>
</filebox>
<filebox fn='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);
    }
}
</filebox>
</filebox>


Ligne 110 : Ligne 137 :


= Custom Behavior =
= Custom Behavior =
# écrire une fonctionnalité dans le code-behind
Ajoute un comportement à un composant UI, comme lier un événement UI à l’exécution de code.
# déplacer le code-behind dans un behavior


<filebox fn='NumericInputBehavior.cs'>
<filebox fn='NumericInputBehavior.cs'>
Ligne 126 : Ligne 152 :
         set => SetValue(EnableDecimalInputProperty, value);
         set => SetValue(EnableDecimalInputProperty, value);
     }
     }
     public static readonly DependencyProperty EnableDecimalInputProperty =  
     public static readonly DependencyProperty EnableDecimalInputProperty =
         DependencyProperty.Register("EnableDecimalInputProperty", typeof(bool),
         DependencyProperty.Register(
             typeof(NumericInputBehavior), new PropertyMetadata(false));
            nameof(EnableDecimalInput),
            typeof(bool),
             typeof(NumericInputBehavior),
            new PropertyMetadata(false));


     // called when XAML is parsed and behavior is added
     // called when XAML is parsed and behavior is added
Ligne 134 : Ligne 163 :
     {
     {
         // AssociatedObject correspond au control attachant le behavior
         // AssociatedObject correspond au control attachant le behavior
         AssociatedObject.PreviewTextInput += OnPreviewTextInput;
         this.AssociatedObject.PreviewTextInput += OnPreviewTextInput;
     }
     }


Ligne 140 : Ligne 169 :
     protected override void OnDetaching()
     protected override void OnDetaching()
     {
     {
         AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
         this.AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
     }
     }


Ligne 160 : Ligne 189 :


<filebox fn='MyWindow.xaml'>
<filebox fn='MyWindow.xaml'>
<Window xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors">
<Window xmlns:b="http://schemas.microsoft.com/xaml/behaviors">
     <TextBox>
     <TextBox>
         <Behaviors:Interaction.Behaviors>
         <b:Interaction.Behaviors>
             <local:MyTextBoxBehavior EnableDecimalInput="True" />
             <local:MyTextBoxBehavior EnableDecimalInput="True" />
         </Behaviors:Interaction.Behaviors>
         </b:Interaction.Behaviors>
     </TextBox>
     </TextBox>
</filebox>
</filebox>
* [[ItemsControl,_ListBox,_ComboBox,_TreeView#ScrollToSelectedItemBehavior|ScrollToSelectedItemBehavior]]
* [[Prism_navigation_with_tabcontrol#RequestNavigateOnLoadedBehavior|RequestNavigateOnLoadedBehavior]]


== Attacher le behavior via un style ==
== Attacher le behavior via un style ==

Dernière version du 20 octobre 2021 à 19:59

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">
            <triggers: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

Ajoute un comportement à un composant UI, comme lier un événement UI à l’exécution de code.

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(
            nameof(EnableDecimalInput),
            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:b="http://schemas.microsoft.com/xaml/behaviors">
    <TextBox>
        <b:Interaction.Behaviors>
            <local:MyTextBoxBehavior EnableDecimalInput="True" />
        </b: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>