如何在依赖属性上引发属性更改事件?

Posted

技术标签:

【中文标题】如何在依赖属性上引发属性更改事件?【英文标题】:How To Raise Property Changed events on a Dependency Property? 【发布时间】:2011-01-29 15:17:54 【问题描述】:

我有一个具有两个属性的控件。一个是DependencyProperty,另一个是第一个的“别名”。当第一个被更改时,如何为第二个(别名)引发 PropertyChanged 事件。

注意:我使用的是DependencyObjects,而不是INotifyPropertyChanged(试过了,没用,因为我的控件是ListVie 子类)

这样的.....

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)

    base.OnPropertyChanged(e);
    if (e.Property == MyFirstProperty)
    
        RaiseAnEvent( MySecondProperty ); /// what is the code that would go here?
        

如果我使用 INotify,我可以这样做......

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");
        
    

我可以在哪里从一个 setter 引发多个属性的 PropertyChanged 事件?我需要能够做同样的事情,只使用DependencyProperties

【问题讨论】:

如果您没有使用 INotifyPropertyChanged,则没有要引发的 PropertyChanged 事件(除非您在谈论自定义事件?)。另外,我不确定“别名”属性是 DP 还是普通 CLR 属性——你能澄清一下吗?谢谢! “别名”不是 DP,但我希望它像一个 DP。这样当第一个属性发生更改时,我的 UI 将收到另一个已更改的通知。 【参考方案1】:

我遇到了类似的问题,我有一个依赖属性,我希望类监听更改事件以从服务中获取相关数据。

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");

【讨论】:

我知道我迟到了,但这个答案解决了我的疑惑,谢谢! 晚了几个月 - 但这就像一个魅力。我一直试图破解这个有一段时间了。谢谢! 这似乎比接受的答案更合适。 这允许Label 具有IsSelected 属性(如TreeViewItem 行为)来更改背景颜色。完美。 我知道我来晚了,但我只是在谷歌搜索时发现了这一点,其他人也可能如此。我喜欢这种方法,但您可以像这样内联回调减少开销:new PropertyMetadata((o,e) => (o as Customer)?.OnCustomerChanged()));【参考方案2】:

    在你的课堂上实现INotifyPropertyChanged

    注册依赖属性时在属性元数据中指定回调。

    在回调中,引发PropertyChanged 事件。

添加回调:

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

在回调中提高PropertyChanged

private static void OnFirstPropertyChanged(
   DependencyObject sender, DependencyPropertyChangedEventArgs e)

   PropertyChangedEventHandler h = PropertyChanged;
   if (h != null)
   
      h(sender, new PropertyChangedEventArgs("Second"));
   

【讨论】:

first 已经是一个 dp,继承自我的基类 (ListView),并且我正在捕获 OnPropertyChanged 中的属性更改。我会试试这个,看看我们得到了什么。 如何从静态方法中获取对PropertyChanged 事件的引用?您首先需要投射到 INotifyPropertyChanged 并访问那里的事件。 您的答案不起作用,因为您试图通过静态方法访问非静态成员。您应该按照@BrettRyan 的状态编辑您的答案。 静态方法中如何调用propertychanged? 这似乎不起作用。似乎如果一个对象是 DependencyObject,WPF 引擎会忽略 INotifyPropertyChanged 并且不订阅它的事件。【参考方案3】:

我认为 OP 提出了错误的问题。下面的代码将表明,无需手动从依赖属性中引发PropertyChanged EVENT 即可获得所需的结果。这样做的方法是处理依赖属性上的PropertyChanged CALLBACK 并在那里设置其他依赖属性的值。以下是一个工作示例。 在下面的代码中,MyControl 有两个依赖属性 - ActiveTabIntActiveTabString。当用户点击主机(MainWindow)上的按钮时,ActiveTabString被修改。依赖属性上的PropertyChanged CALLBACK 设置ActiveTabInt 的值。 PropertyChanged EVENT 不是由 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>

【讨论】:

我对此有一个怪癖,我找不到RaisePropertyChanged(,而是不得不使用RaisePropertyChangedEventImmediately(【参考方案4】:

我同意 Sam 和 Xaser 的观点,并且实际上已经走得更远了。我认为您根本不应该在UserControl 中实现INotifyPropertyChanged 接口……控件已经是DependencyObject,因此已经带有通知。将INotifyPropertyChanged 添加到DependencyObject 是多余的,并且对我来说“闻起来”是错误的。

我所做的是将这两个属性都实现为DependencyProperties,正如 Sam 所建议的那样,但随后只是让“第一个”依赖属性中的 PropertyChangedCallback 改变了“第二个”依赖属性的值。由于两者都是依赖属性,因此两者都会自动向任何感兴趣的订阅者发出更改通知(例如数据绑定等)

在这种情况下,依赖属性 A 是字符串 InviteText,这会触发依赖属性 B 的更改,即名为 ShowInviteVisibility 属性。如果您希望通过数据绑定将某些文本完全隐藏在控件中,这将是一个常见的用例。

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));

请注意,这里没有包含UserControl 签名或构造函数,因为它们没有什么特别之处;他们根本不需要从 INotifyPropertyChanged 继承。

【讨论】:

我同意你的观点,使用 INPC 没有意义。不过,您的示例的行为与 OP 的示例不同。您创建的重复数据可能会变得不同步:ShowInvite 可以公开更改,但不会更新 InviteText。 OP 的 SecondProperty 是只读的。 ShowInvite 应该是只读的 DependencyProperty。【参考方案5】:

我质疑当第一个属性发生变化时在第二个属性上引发PropertyChanged 事件的逻辑。如果第二个属性值发生变化,则可能会在此处引发 PropertyChanged 事件。

无论如何,您的问题的答案是您应该实施INotifyPropertyChange。此接口包含PropertyChanged 事件。实现INotifyPropertyChanged 让其他代码知道该类具有PropertyChanged 事件,以便代码可以连接处理程序。实现INotifyPropertyChange 后,OnPropertyChanged 的 if 语句中的代码为:

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

【讨论】:

我是从 ListView 控件派生的。我可以添加 INotify,事实上,我试过了。没有骰子,当我试图引发事件时,什么也没发生。我想要“alais”的属性是依赖属性。

以上是关于如何在依赖属性上引发属性更改事件?的主要内容,如果未能解决你的问题,请参考以下文章

位图属性更改时如何引发事件? [复制]

1. 依赖项属性 简单理解

IOC 控制反转Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )(代

WPF中XAML的触发器的属性,事件 都有那些?以及如何寻找

WPF 控件模板不根据依赖属性绑定值更改

ICommand 依赖属性