web-dev-qa-db-fra.com

Comment lever des événements de propriété modifiée sur une propriété de dépendance?

OK, alors j'ai ce contrôle avec deux propriétés. L'un d'eux est un DependencyProperty, l'autre est un "alias" du premier. Ce qu'il me faut, c'est déclencher l'événement PropertyChanged pour le deuxième (l'alias) lorsque le premier est modifié.

NOTE : J'utilise DependencyObjects, pas INotifyPropertyChanged (j'ai essayé, cela n'a pas fonctionné car mon contrôle est une ListView sous-classée).

quelque chose comme ça.....

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
    base.OnPropertyChanged(e);
    if (e.Property == MyFirstProperty)
    {
        RaiseAnEvent( MySecondProperty ); /// what is the code that would go here?
    }    
}

Si j'utilisais un INotify, je pourrais faire comme ça ...

public string SecondProperty
{
    get
    {
        return this.m_IconPath;
    }
}

public string IconPath
{
    get
    {
        return this.m_IconPath;
    }
    set
    {
        if (this.m_IconPath != value)
        {
            this.m_IconPath = value;
        this.SendPropertyChanged("IconPath");
        this.SendPropertyChanged("SecondProperty");
        }
    }
}

où je peux soulever des événements PropertyChanged sur plusieurs propriétés à partir d'un seul setter. Je dois pouvoir faire la même chose, en utilisant uniquement DependencyProperties.

60
Muad'Dib
  1. Implémentez INotifyPropertyChanged dans votre classe.

  2. Spécifiez un rappel dans les métadonnées de la propriété lorsque vous enregistrez la propriété de dépendance.

  3. Dans le rappel, déclenchez l'événement PropertyChanged.

Ajout du rappel:

public static DependencyProperty FirstProperty = DependencyProperty.Register(
  "First", 
  typeof(string), 
  typeof(MyType),
  new FrameworkPropertyMetadata(
     false, 
     new PropertyChangedCallback(OnFirstPropertyChanged)));

Levée de PropertyChanged dans le rappel:

private static void OnFirstPropertyChanged(
   DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
   PropertyChangedEventHandler h = PropertyChanged;
   if (h != null)
   {
      h(sender, new PropertyChangedEventArgs("Second"));
   }
}
38
Robert Rossney

J'ai rencontré un problème similaire dans lequel j'ai une propriété de dépendance que je voulais que la classe écoute pour que les événements de modification récupèrent les données associées d'un service.

public static readonly DependencyProperty CustomerProperty = 
    DependencyProperty.Register("Customer", typeof(Customer),
        typeof(CustomerDetailView),
        new PropertyMetadata(OnCustomerChangedCallBack));

public Customer Customer {
    get { return (Customer)GetValue(CustomerProperty); }
    set { SetValue(CustomerProperty, value); }
}

private static void OnCustomerChangedCallBack(
        DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    CustomerDetailView c = sender as CustomerDetailView;
    if (c != null) {
        c.OnCustomerChanged();
    }
}

protected virtual void OnCustomerChanged() {
    // Grab related data.
    // Raises INotifyPropertyChanged.PropertyChanged
    OnPropertyChanged("Customer");
}
54
Brett Ryan

Je pense que le PO pose la mauvaise question. Le code ci-dessous montre qu'il n'est pas nécessaire de lever manuellement le PropertyChanged EVENT à partir d'une propriété de dépendance pour obtenir le résultat souhaité. Pour ce faire, gérez le PropertyChanged CALLBACK sur la propriété de dépendance et définissez les valeurs des autres propriétés de dépendance. Ce qui suit est un exemple de travail. Dans le code ci-dessous, MyControl a deux propriétés de dépendance, ActiveTabInt et ActiveTabString. Lorsque l'utilisateur clique sur le bouton de l'hôte (MainWindow), ActiveTabString est modifié. PropertyChanged CALLBACK sur la propriété de dépendance définit la valeur de ActiveTabInt. Le PropertyChanged EVENT n'est pas déclenché manuellement par MyControl.

MainWindow.xaml.cs

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        ActiveTabString = "zero";
    }

    private string _ActiveTabString;
    public string ActiveTabString
    {
        get { return _ActiveTabString; }
        set
        {
            if (_ActiveTabString != value)
            {
                _ActiveTabString = value;
                RaisePropertyChanged("ActiveTabString");
            }
        }
    }

    private int _ActiveTabInt;
    public int ActiveTabInt
    {
        get { return _ActiveTabInt; }
        set
        {
            if (_ActiveTabInt != value)
            {
                _ActiveTabInt = value;
                RaisePropertyChanged("ActiveTabInt");
            }
        }
    }

    #region INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        ActiveTabString = (ActiveTabString == "zero") ? "one" : "zero";
    }

}

public class MyControl : Control
{
    public static List<string> Indexmap = new List<string>(new string[] { "zero", "one" });


    public string ActiveTabString
    {
        get { return (string)GetValue(ActiveTabStringProperty); }
        set { SetValue(ActiveTabStringProperty, value); }
    }

    public static readonly DependencyProperty ActiveTabStringProperty = DependencyProperty.Register(
        "ActiveTabString",
        typeof(string),
        typeof(MyControl), new FrameworkPropertyMetadata(
            null,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            ActiveTabStringChanged));


    public int ActiveTabInt
    {
        get { return (int)GetValue(ActiveTabIntProperty); }
        set { SetValue(ActiveTabIntProperty, value); }
    }
    public static readonly DependencyProperty ActiveTabIntProperty = DependencyProperty.Register(
        "ActiveTabInt",
        typeof(Int32),
        typeof(MyControl), new FrameworkPropertyMetadata(
            new Int32(),
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));


    static MyControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));

    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
    }


    private static void ActiveTabStringChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        MyControl thiscontrol = sender as MyControl;

        if (Indexmap[thiscontrol.ActiveTabInt] != thiscontrol.ActiveTabString)
            thiscontrol.ActiveTabInt = Indexmap.IndexOf(e.NewValue.ToString());

    }
}

MainWindow.xaml

    <StackPanel Orientation="Vertical">
    <Button Content="Change Tab Index" Click="Button_Click" Width="110" Height="30"></Button>
    <local:MyControl x:Name="myControl" ActiveTabInt="{Binding ActiveTabInt, Mode=TwoWay}" ActiveTabString="{Binding ActiveTabString}"></local:MyControl>
</StackPanel>

App.xaml

<Style TargetType="local:MyControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:MyControl">
                    <TabControl SelectedIndex="{Binding ActiveTabInt, Mode=TwoWay}">
                        <TabItem Header="Tab Zero">
                            <TextBlock Text="{Binding ActiveTabInt}"></TextBlock>
                        </TabItem>
                        <TabItem Header="Tab One">
                            <TextBlock Text="{Binding ActiveTabInt}"></TextBlock>
                        </TabItem>
                    </TabControl>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
10
Sam

Je suis d’accord avec Sam et Xaser et j’ai pris la question un peu plus loin. Je ne pense pas que vous devriez implémenter l'interface INotifyPropertyChanged dans un UserControl ... le contrôle est déjà un DependencyObject et est donc déjà livré avec des notifications. Ajouter INotifyPropertyChanged à un objet DependencyObject est redondant et ne me "sent" pas bien.

Ce que j'ai fait est d'implémenter les deux propriétés en tant que DependencyProperties, comme le suggère Sam, mais ensuite, le PropertyChangedCallback de la "première" propriété de dépendance a simplement modifié la valeur de la "deuxième" propriété de dépendance. Étant donné que ces deux propriétés sont des propriétés de dépendance, elles émettent automatiquement des notifications de modification à tous les abonnés intéressés (par exemple, la liaison de données, etc.).

Dans ce cas, la propriété de dépendance A est la chaîne InviteText, qui déclenche une modification de la propriété de dépendance B, la propriété de visibilité nommée ShowInvite. Ce serait un cas d'utilisation courant si vous souhaitez pouvoir masquer complètement un texte dans un contrôle via la liaison de données.

    public string InviteText  
    {
        get { return (string)GetValue(InviteTextProperty); }
        set { SetValue(InviteTextProperty, value); }
    }

    public static readonly DependencyProperty InviteTextProperty =
        DependencyProperty.Register("InviteText", typeof(string), typeof(InvitePrompt), new UIPropertyMetadata(String.Empty, OnInviteTextChanged));

    private static void OnInviteTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        InvitePrompt Prompt = d as InvitePrompt;
        if (Prompt != null)
        {
            string text = e.NewValue as String;
            Prompt.ShowInvite = String.IsNullOrWhiteSpace(text) ? Visibility.Collapsed : Visibility.Visible;
        }
    }

    public Visibility ShowInvite
    {
        get { return (Visibility)GetValue(ShowInviteProperty); }
        set { SetValue(ShowInviteProperty, value); }
    }

    public static readonly DependencyProperty ShowInviteProperty =
        DependencyProperty.Register("ShowInvite", typeof(Visibility), typeof(InvitePrompt), new PropertyMetadata(Visibility.Collapsed));

Remarque Je n'inclus pas la signature ou le constructeur UserControl ici car ils n'ont rien de spécial; ils n'ont pas besoin de sous-classe de INotifyPropertyChanged.

3
karfus

Je m'interroge sur la logique consistant à déclencher un événement PropertyChanged sur la deuxième propriété lorsqu'il s'agit de la première propriété en cours de modification. Si la valeur de la deuxième propriété change, l'événement PropertyChanged peut être déclenché ici.

Quoi qu'il en soit, la réponse à votre question est que vous devez implémenter INotifyPropertyChange. Cette interface contient l'événement PropertyChanged. L'implémentation de INotifyPropertyChanged permet à un autre code de savoir que la classe a l'événement PropertyChanged, de sorte que le code peut connecter un gestionnaire. Après avoir implémenté INotifyPropertyChange, le code qui figure dans l’instruction if de votre OnPropertyChanged est le suivant:

if (PropertyChanged != null)
    PropertyChanged(new PropertyChangedEventArgs("MySecondProperty"));
0
Scott J