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

78

我有一个拥有两个属性的控件。其中一个是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事件(除非你是在谈论自定义事件?)。另外,我不确定“alias”属性是DP还是普通CLR属性 - 你能澄清一下吗?谢谢! - itowlson
“alias”不是DP,但我希望它像DP一样运作。这样当第一个属性被更改时,我的UI将会被通知到另一个属性已经被更改。 - Muad'Dib
6个回答

77

我遇到了一个类似的问题,我的依赖属性需要监听变化事件,以便从服务中获取相关数据。

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

2
我知道我来晚了,但这个答案解决了我的疑惑,非常感谢! - Nahuel Ianni
1
几个月晚了,但这个方法非常有效。我一直在尝试解决这个问题。谢谢! - imdandman
3
这个答案似乎比被采纳的答案更恰当。 - IAbstract
1
这个代码可以让 Label 拥有一个 IsSelected 属性(就像 TreeViewItem 的行为),以改变背景颜色。完美。 - IAbstract
1
我知道我来晚了,但是当我谷歌ist时我刚刚发现这个。其他人也可能会喜欢这种方法,但是你可以内联回调以减少开销,像这样:new PropertyMetadata((o,e) => (o as Customer)?.OnCustomerChanged())); - user3163684

50
  1. 在你的类中实现INotifyPropertyChanged 接口。

  2. 在注册依赖属性时,在属性元数据中指定一个回调函数。

  3. 在回调函数中,触发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"));
   }
}

首先,已经有一个数据绑定的属性,这是从我的基类(ListView)继承而来的,并且我在OnPropertyChanged中捕获属性更改。我会尝试这样做,看看我们得到了什么。 - Muad'Dib
35
在静态方法中如何获取对 "PropertyChanged" 事件的引用?您需要先将其转换为 "INotifyPropertyChanged" 并在那里访问该事件。 - Brett Ryan
8
你的回答无法使用,因为你试图通过静态方法访问非静态成员。你应该按照@BrettRyan的方式编辑你的回答。 - kkyr
1
你如何在静态方法中调用propertychanged? - Bhuvanesh Narayanasamy
2
这似乎不起作用。如果一个对象是“DependencyObject”,WPF引擎会忽略“INotifyPropertyChanged”并且不会订阅它的事件。 - whatsisname
我不认为这是一个解决方案,因为“PropertyChanged”事件无法在静态方法中访问。 - Gowshik

11

我认为提问者问错了问题。以下代码将证明,不需要手动从依赖属性触发PropertyChanged事件来实现所需的结果。正确的方法是在依赖属性上处理PropertyChanged回调,并在那里设置其他依赖属性的值。以下是一个工作示例。 在下面的代码中,MyControl有两个依赖属性-ActiveTabIntActiveTabString。当用户点击宿主(MainWindow)上的按钮时,会修改ActiveTabString。依赖属性上的PropertyChanged回调设置ActiveTabInt的值。并没有手动由MyControl触发PropertyChanged事件。

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()”。 - Solx

5

我同意Sam和Xaser的观点,实际上我已经进一步思考了这个问题。我认为你根本不应该在UserControl中实现INotifyPropertyChanged接口...因为控件已经是一个DependencyObject,因此已经具备通知功能。将INotifyPropertyChanged添加到DependencyObject是冗余的,对我来说感觉不对。

我的方法是像Sam建议的那样将两个属性都实现为DependencyProperties,但是只需让“第一个”依赖属性的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进行子类化。

1
我同意你的观点,使用INPC没有意义。不过你的例子和OP的例子行为不同。你的例子会创建可以变得不同步的重复数据:ShowInvite是公开可更改的,但不会更新InviteText。而OP的SecondProperty是只读的。ShowInvite应该是一个只读的DependencyProperty。 - Zach Mierzejewski

0

我对在第一个属性发生变化时触发PropertyChanged事件的逻辑提出质疑。如果第二个属性的值发生变化,那么PropertyChanged事件可以在那里被触发。

无论如何,回答你的问题是你应该实现INotifyPropertyChange。这个接口包含了PropertyChanged事件。实现INotifyPropertyChanged让其他代码知道这个类有PropertyChanged事件,以便代码可以连接处理程序。在实现INotifyPropertyChange之后,你的OnPropertyChanged中的if语句中的代码是:

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

我正在从ListView控件派生。我可以添加INotify,并且实际上我尝试过了。但是,当我尝试触发事件时,却没有任何反应。我想要“别名”的属性是一个依赖属性。 - Muad'Dib

0

在之前被接受的答案基础上,对于我来说,缺少了转换以访问非静态 PropertyChanged

  1. 在你的类中实现 INotifyPropertyChanged,例如 UserControl CustomView

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

  3. 在回调中,进行转换并引发 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)
{
   var control = (CustomView)sender;
   PropertyChangedEventHandler h = control.PropertyChanged;
   if (h != null)
   {
      h(sender, new PropertyChangedEventArgs("Second"));
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接