web-dev-qa-db-fra.com

Comment ajouter un comportement de fusion dans un sélecteur de style

J'ai créé un comportement de mélange pour Button. Comment puis-je définir cela sur tous mes boutons dans l'application.

<Button ...>
  <i:Interaction.Behaviors>
    <local:MyBehavior />
  </i:Interaction.Behaviors>
</Button>

Cependant, quand j'essaye:

<Style>
  <Setter Property="i:Interaction.Behaviors">
    <Setter.Value>
      <local:MyBehavior />
    </Setter.Value>
  </Setter>
</Style>

Je reçois l'erreur

La propriété "Comportements" n'a pas de setter accessible.

82
Jobi Joy

J'ai eu le même problème et j'ai trouvé une solution. J'ai trouvé cette question après l'avoir résolue et je vois que ma solution a beaucoup en commun avec Mark's. Cependant, cette approche est un peu différente.

Le problème principal est que les comportements et les déclencheurs s'associent à un objet spécifique et que vous ne pouvez donc pas utiliser la même instance d'un comportement pour plusieurs objets associés différents. Lorsque vous définissez votre comportement en ligne, XAML applique cette relation biunivoque. Cependant, lorsque vous essayez de définir un comportement dans un style, le style peut être réutilisé pour tous les objets auxquels il s’applique et cela lèvera des exceptions dans les classes de comportement de base. En fait, les auteurs ont fait des efforts considérables pour nous empêcher d'essayer de le faire, sachant que cela ne fonctionnerait pas.

Le premier problème est que nous ne pouvons même pas construire une valeur de définition de comportement car le constructeur est interne. Nous avons donc besoin de notre propre comportement et déclenchons des classes de collection.

Le problème suivant est que le comportement et le déclencheur des propriétés attachées n'ont pas de paramètres et qu'ils ne peuvent donc être ajoutés qu'à XAML en ligne. Nous résolvons ce problème avec nos propres propriétés attachées qui manipulent le comportement principal et déclenchent les propriétés.

Le troisième problème est que notre collection de comportements n'est valable que pour une seule cible de style. Nous résolvons ce problème en utilisant une fonction XAML peu utilisée x:Shared="False" qui crée une nouvelle copie de la ressource à chaque fois qu'elle est référencée.

Le dernier problème est que les comportements et les déclencheurs ne sont pas comme les autres paramètres de style; nous ne voulons pas remplacer les anciens comportements par les nouveaux comportements, car ils pourraient faire des choses très différentes. Donc, si nous acceptons qu'une fois que vous ajoutez un comportement, vous ne pouvez pas le supprimer (et c'est ainsi que les comportements fonctionnent actuellement), nous pouvons conclure que les comportements et les déclencheurs doivent être additifs et cela peut être géré par nos propriétés attachées.

Voici un exemple utilisant cette approche:

<Grid>
    <Grid.Resources>
        <sys:String x:Key="stringResource1">stringResource1</sys:String>
        <local:Triggers x:Key="debugTriggers" x:Shared="False">
            <i:EventTrigger EventName="MouseLeftButtonDown">
                <local:DebugAction Message="DataContext: {0}" MessageParameter="{Binding}"/>
                <local:DebugAction Message="ElementName: {0}" MessageParameter="{Binding Text, ElementName=textBlock2}"/>
                <local:DebugAction Message="Mentor: {0}" MessageParameter="{Binding Text, RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}}"/>
            </i:EventTrigger>
        </local:Triggers>
        <Style x:Key="debugBehavior" TargetType="FrameworkElement">
            <Setter Property="local:SupplementaryInteraction.Triggers" Value="{StaticResource debugTriggers}"/>
        </Style>
    </Grid.Resources>
    <StackPanel DataContext="{StaticResource stringResource1}">
        <TextBlock Name="textBlock1" Text="textBlock1" Style="{StaticResource debugBehavior}"/>
        <TextBlock Name="textBlock2" Text="textBlock2" Style="{StaticResource debugBehavior}"/>
        <TextBlock Name="textBlock3" Text="textBlock3" Style="{StaticResource debugBehavior}"/>
    </StackPanel>
</Grid>

L'exemple utilise des déclencheurs mais les comportements fonctionnent de la même manière. Dans l'exemple, nous montrons:

  • le style peut être appliqué à plusieurs blocs de texte
  • plusieurs types de données liant tous fonctionnent correctement
  • une action de débogage qui génère du texte dans la fenêtre de sortie

Voici un exemple de comportement, notre DebugAction. Plus correctement, c'est une action, mais à travers l'abus de langage, nous appelons les comportements, déclencheurs et actions "comportements".

public class DebugAction : TriggerAction<DependencyObject>
{
    public string Message
    {
        get { return (string)GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public static readonly DependencyProperty MessageProperty =
        DependencyProperty.Register("Message", typeof(string), typeof(DebugAction), new UIPropertyMetadata(""));

    public object MessageParameter
    {
        get { return (object)GetValue(MessageParameterProperty); }
        set { SetValue(MessageParameterProperty, value); }
    }

    public static readonly DependencyProperty MessageParameterProperty =
        DependencyProperty.Register("MessageParameter", typeof(object), typeof(DebugAction), new UIPropertyMetadata(null));

    protected override void Invoke(object parameter)
    {
        Debug.WriteLine(Message, MessageParameter, AssociatedObject, parameter);
    }
}

Enfin, nos collections et propriétés attachées pour que tout cela fonctionne. Par analogie avec Interaction.Behaviors, la propriété que vous ciblez s'appelle SupplementaryInteraction.Behaviors car en définissant cette propriété, vous ajouterez des comportements à Interaction.Behaviors et de même pour les déclencheurs.

public class Behaviors : List<Behavior>
{
}

public class Triggers : List<TriggerBase>
{
}

public static class SupplementaryInteraction
{
    public static Behaviors GetBehaviors(DependencyObject obj)
    {
        return (Behaviors)obj.GetValue(BehaviorsProperty);
    }

    public static void SetBehaviors(DependencyObject obj, Behaviors value)
    {
        obj.SetValue(BehaviorsProperty, value);
    }

    public static readonly DependencyProperty BehaviorsProperty =
        DependencyProperty.RegisterAttached("Behaviors", typeof(Behaviors), typeof(SupplementaryInteraction), new UIPropertyMetadata(null, OnPropertyBehaviorsChanged));

    private static void OnPropertyBehaviorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behaviors = Interaction.GetBehaviors(d);
        foreach (var behavior in e.NewValue as Behaviors) behaviors.Add(behavior);
    }

    public static Triggers GetTriggers(DependencyObject obj)
    {
        return (Triggers)obj.GetValue(TriggersProperty);
    }

    public static void SetTriggers(DependencyObject obj, Triggers value)
    {
        obj.SetValue(TriggersProperty, value);
    }

    public static readonly DependencyProperty TriggersProperty =
        DependencyProperty.RegisterAttached("Triggers", typeof(Triggers), typeof(SupplementaryInteraction), new UIPropertyMetadata(null, OnPropertyTriggersChanged));

    private static void OnPropertyTriggersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var triggers = Interaction.GetTriggers(d);
        foreach (var trigger in e.NewValue as Triggers) triggers.Add(trigger);
    }
}

et là vous l'avez, des comportements et des déclencheurs entièrement fonctionnels appliqués à travers des styles.

73
Rick Sladkey

Résumant les réponses et cet excellent article Comportements de mélange dans les styles , je suis arrivé à cette solution générique courte et pratique:

J'ai créé une classe générique, qui pourrait être héritée par n'importe quel comportement.

public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent>
        where TComponent : System.Windows.DependencyObject
        where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior> , new ()
    {
        public static DependencyProperty IsEnabledForStyleProperty =
            DependencyProperty.RegisterAttached("IsEnabledForStyle", typeof(bool),
            typeof(AttachableForStyleBehavior<TComponent, TBehavior>), new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged)); 

        public bool IsEnabledForStyle
        {
            get { return (bool)GetValue(IsEnabledForStyleProperty); }
            set { SetValue(IsEnabledForStyleProperty, value); }
        }

        private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement uie = d as UIElement;

            if (uie != null)
            {
                var behColl = Interaction.GetBehaviors(uie);
                var existingBehavior = behColl.FirstOrDefault(b => b.GetType() ==
                      typeof(TBehavior)) as TBehavior;

                if ((bool)e.NewValue == false && existingBehavior != null)
                {
                    behColl.Remove(existingBehavior);
                }

                else if ((bool)e.NewValue == true && existingBehavior == null)
                {
                    behColl.Add(new TBehavior());
                }    
            }
        }
    }

Vous pouvez donc simplement le réutiliser avec de nombreux composants comme celui-ci:

public class ComboBoxBehaviour : AttachableForStyleBehavior<ComboBox, ComboBoxBehaviour>
    { ... }

Et en XAML assez pour déclarer:

 <Style TargetType="ComboBox">
            <Setter Property="behaviours:ComboBoxBehaviour.IsEnabledForStyle" Value="True"/>

Donc, fondamentalement, la classe AttachableForStyleBehavior a créé des choses xaml, enregistrant l'instance de comportement pour chaque composant dans le style. Pour plus de détails, veuillez consulter le lien.

22
Roma Borodov

1.Créer une propriété jointe

public static class DataGridCellAttachedProperties
{
    //Register new attached property
    public static readonly DependencyProperty IsSingleClickEditModeProperty =
        DependencyProperty.RegisterAttached("IsSingleClickEditMode", typeof(bool), typeof(DataGridCellAttachedProperties), new UIPropertyMetadata(false, OnPropertyIsSingleClickEditModeChanged));

    private static void OnPropertyIsSingleClickEditModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dataGridCell = d as DataGridCell;
        if (dataGridCell == null)
            return;

        var isSingleEditMode = GetIsSingleClickEditMode(d);
        var behaviors =  Interaction.GetBehaviors(d);
        var singleClickEditBehavior = behaviors.SingleOrDefault(x => x is SingleClickEditDataGridCellBehavior);

        if (singleClickEditBehavior != null && !isSingleEditMode)
            behaviors.Remove(singleClickEditBehavior);
        else if (singleClickEditBehavior == null && isSingleEditMode)
        {
            singleClickEditBehavior = new SingleClickEditDataGridCellBehavior();
            behaviors.Add(singleClickEditBehavior);
        }
    }

    public static bool GetIsSingleClickEditMode(DependencyObject obj)
    {
        return (bool) obj.GetValue(IsSingleClickEditModeProperty);
    }

    public static void SetIsSingleClickEditMode(DependencyObject obj, bool value)
    {
        obj.SetValue(IsSingleClickEditModeProperty, value);
    }
}

2.Créez un comportement

public class SingleClickEditDataGridCellBehavior:Behavior<DataGridCell>
        {
            protected override void OnAttached()
            {
                base.OnAttached();
                AssociatedObject.PreviewMouseLeftButtonDown += DataGridCellPreviewMouseLeftButtonDown;
            }

            protected override void OnDetaching()
            {
                base.OnDetaching();
                AssociatedObject.PreviewMouseLeftButtonDown += DataGridCellPreviewMouseLeftButtonDown;
            }

            void DataGridCellPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
            {
                 DataGridCell cell = sender as DataGridCell;
                if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
                {
                    if (!cell.IsFocused)
                    {
                        cell.Focus();
                    }
                    DataGrid dataGrid = LogicalTreeWalker.FindParentOfType<DataGrid>(cell); //FindVisualParent<DataGrid>(cell);
                    if (dataGrid != null)
                    {
                        if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
                        {
                            if (!cell.IsSelected)
                                cell.IsSelected = true;
                        }
                        else
                        {
                            DataGridRow row =  LogicalTreeWalker.FindParentOfType<DataGridRow>(cell); //FindVisualParent<DataGridRow>(cell);
                            if (row != null && !row.IsSelected)
                            {
                                row.IsSelected = true;
                            }
                        }
                    }
                }
            }    
        }

3.Créez un style et définissez la propriété associée

        <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/>
        </Style>
18
Roman Dvoskin

J'ai une autre idée, pour éviter la création d'une propriété attachée pour chaque comportement:

  1. Interface du créateur de comportement:

    public interface IBehaviorCreator
    {
        Behavior Create();
    }
    
  2. Petite collection d'assistance:

    public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { }
    
  3. Classe d'assistance qui attache le comportement:

    public static class BehaviorInStyleAttacher
    {
        #region Attached Properties
    
        public static readonly DependencyProperty BehaviorsProperty =
            DependencyProperty.RegisterAttached(
                "Behaviors",
                typeof(BehaviorCreatorCollection),
                typeof(BehaviorInStyleAttacher),
                new UIPropertyMetadata(null, OnBehaviorsChanged));
    
        #endregion
    
        #region Getter and Setter of Attached Properties
    
        public static BehaviorCreatorCollection GetBehaviors(TreeView treeView)
        {
            return (BehaviorCreatorCollection)treeView.GetValue(BehaviorsProperty);
        }
    
        public static void SetBehaviors(
            TreeView treeView, BehaviorCreatorCollection value)
        {
            treeView.SetValue(BehaviorsProperty, value);
        }
    
        #endregion
    
        #region on property changed methods
    
        private static void OnBehaviorsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is BehaviorCreatorCollection == false)
                return;
    
            BehaviorCreatorCollection newBehaviorCollection = e.NewValue as BehaviorCreatorCollection;
    
            BehaviorCollection behaviorCollection = Interaction.GetBehaviors(depObj);
            behaviorCollection.Clear();
            foreach (IBehaviorCreator behavior in newBehaviorCollection)
            {
                behaviorCollection.Add(behavior.Create());
            }
        }
    
        #endregion
    }
    
  4. Maintenant, votre comportement, qui implémente IBehaviorCreator:

    public class SingleClickEditDataGridCellBehavior:Behavior<DataGridCell>, IBehaviorCreator
    {
        //some code ...
    
        public Behavior Create()
        {
            // here of course you can also set properties if required
            return new SingleClickEditDataGridCellBehavior();
        }
    }
    
  5. Et maintenant, utilisez-le dans xaml:

    <Style TargetType="{x:Type DataGridCell}">
      <Setter Property="helper:BehaviorInStyleAttacher.Behaviors" >
        <Setter.Value>
          <helper:BehaviorCreatorCollection>
            <behaviors:SingleClickEditDataGridCellBehavior/>
          </helper:BehaviorCreatorCollection>
        </Setter.Value>
      </Setter>
    </Style>
    
10
Andi

Je n'ai pas trouvé l'article original mais j'ai pu recréer l'effet.

#region Attached Properties Boilerplate

    public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(ScrollIntoViewBehavior), new PropertyMetadata(false, OnIsActiveChanged));

    public static bool GetIsActive(FrameworkElement control)
    {
        return (bool)control.GetValue(IsActiveProperty);
    }

    public static void SetIsActive(
      FrameworkElement control, bool value)
    {
        control.SetValue(IsActiveProperty, value);
    }

    private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behaviors = Interaction.GetBehaviors(d);
        var newValue = (bool)e.NewValue;

        if (newValue)
        {
            //add the behavior if we don't already have one
            if (!behaviors.OfType<ScrollIntoViewBehavior>().Any())
            {
                behaviors.Add(new ScrollIntoViewBehavior());
            }
        }
        else
        {
            //remove any instance of the behavior. (There should only be one, but just in case.)
            foreach (var item in behaviors.ToArray())
            {
                if (item is ScrollIntoViewBehavior)
                    behaviors.Remove(item);
            }
        }
    }


    #endregion
<Style TargetType="Button">
    <Setter Property="Blah:ScrollIntoViewBehavior.IsActive" Value="True" />
</Style>
4
Jonathan Allen

J'aime l'approche montrée par les réponses de Roman Dvoskin et Jonathan Allen dans ce fil. Quand j'ai appris cette technique pour la première fois, j'ai profité de ce billet de blog qui fournit plus d'explications sur la technique. Et pour tout voir en contexte, voici le code source complet pour la classe dont l'auteur parle dans son article de blog .

0
Jason Frank

Déclarez le comportement individuel/déclencheur en tant que ressources:

<Window.Resources>

    <i:EventTrigger x:Key="ET1" EventName="Click">
        <ei:ChangePropertyAction PropertyName="Background">
            <ei:ChangePropertyAction.Value>
                <SolidColorBrush Color="#FFDAD32D"/>
            </ei:ChangePropertyAction.Value>
        </ei:ChangePropertyAction>
    </i:EventTrigger>

</Window.Resources>

Insérez-les dans la collection:

<Button x:Name="Btn1" Content="Button">

        <i:Interaction.Triggers>
             <StaticResourceExtension ResourceKey="ET1"/>
        </i:Interaction.Triggers>

</Button>
0
AnjumSKhan

Le code de comportement attend un visuel, nous pouvons donc l'ajouter uniquement sur un visuel. Donc, la seule option que je pouvais voir est d'ajouter à l'un des éléments à l'intérieur du ControlTemplate afin d'obtenir le comportement ajouté au Style et affecter sur toute l'instance d'un contrôle particulier.

0
Jobi Joy

L'article Introduction to Attached Behaviors in WPF implémente un comportement attaché à l'aide de Style uniquement, et peut également être lié ou utile.

La technique de l'article "Introduction aux comportements attachés" évite complètement les balises d'interactivité, en utilisant le style. Je ne sais pas si c'est simplement parce que c'est une technique plus datée, ou, si cela confère encore des avantages là où on devrait la préférer dans certains scénarios.

0
Bill