Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is attached property property changed event only firing one time?

Tags:

c#

vb.net

wpf

I have a listbox binded to a list of objects. For each list item I wanted to have a rectangle whose fill color is determined by a few properties of the binded object. So I did the following:

  1. Made sure INotifyPropertyChanged was implemented on my object.
  2. Created a class to expose the properties I am interested in as attached properties.
  3. Binded the properties of the object to the attached properties of the rectangle
  4. Created a style that uses triggers to set the rectangle fill based on attached properties.

This works, but only the first time the property of the object changes. After that, the attached properties do not seem to be receiving notification when the data object's property changes. I have double checked and my data object is raising the INotifyPropertyChanged event. What could be the problem?

<Rectangle Style="{StaticResource RecordStateRectangleStyle}" 
                Width="10" Height="10" Stroke="Black"
                local:RecordAttachment.RecordState="{Binding Path=RecordState}"
                local:RecordAttachment.IsDeleted="{Binding Path=IsDeleted}" />

The Style:

   <Style x:Key="RecordStateRectangleStyle" TargetType="Rectangle">
        <Style.Resources>
            <SolidColorBrush x:Key="AddedStateBrush" Color="LightGreen" Opacity=".8" />
            <SolidColorBrush x:Key="ModifiedStateBrush" Color="Orange"  Opacity=".8" />
            <SolidColorBrush x:Key="DeletedStateBrush" Color="Red" Opacity=".8" />
        </Style.Resources>
        <Style.Triggers>
            <Trigger Property="local:RecordAttachment.RecordState" Value="{x:Static model:RecordState.Added}">
                <Setter Property="Fill" Value="{StaticResource AddedStateBrush}" />
            </Trigger>
            <Trigger Property="local:RecordAttachment.RecordState" Value="{x:Static model:RecordState.Modified}">
                <Setter Property="Fill" Value="{StaticResource ModifiedStateBrush}" />
            </Trigger>
            <Trigger Property="local:RecordAttachment.IsDeleted" Value="true">
                <Setter Property="Fill" Value="{StaticResource DeletedStateBrush}" />
            </Trigger>
        </Style.Triggers>
    </Style>

Attached Properties Class:

Public Class RecordAttachment
Public Shared ReadOnly RecordStateProperty As DependencyProperty
Public Shared ReadOnly IsDeletedProperty As DependencyProperty

Shared Sub New()
    RecordStateProperty = DependencyProperty.RegisterAttached("RecordState", _
                                                               GetType(Model.RecordState), _
                                                               GetType(RecordAttachment), _
                                                               New PropertyMetadata(Model.RecordState.Unchanged, AddressOf RecordStatePropertyChanged))
    IsDeletedProperty = DependencyProperty.RegisterAttached("IsDeleted", _
                                                          GetType(Boolean), _
                                                          GetType(RecordAttachment), _
                                                          New PropertyMetadata(AddressOf DeletedPropertyChanged))
End Sub

Public Shared Sub SetRecordState(ByVal element As UIElement, ByVal state As Model.RecordState)
    element.SetValue(RecordStateProperty, state)
End Sub
Public Shared Function GetRecordState(ByVal element As UIElement) As Model.RecordState
    Return CType(element.GetValue(RecordStateProperty), Model.RecordState)
End Function

Public Shared Sub SetIsDeleted(ByVal element As UIElement, ByVal value As Boolean)
    element.SetValue(IsDeletedProperty, value)
End Sub

Public Shared Function GetIsDeleted(ByVal element As UIElement) As Boolean
    Return CType(element.GetValue(IsDeletedProperty), Boolean)
End Function

Public Shared Sub RecordStatePropertyChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    If sender IsNot Nothing Then
        sender.SetValue(RecordStateProperty, e.NewValue)
    End If
End Sub
Public Shared Sub DeletedPropertyChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    If sender IsNot Nothing Then
        sender.SetValue(IsDeletedProperty, e.NewValue)
    End If
End Sub
End Class

Someone suggested I post C# version, so here it is:

    public class RecordAttachment
{
    public static readonly DependencyProperty RecordStateProperty;
    public static readonly DependencyProperty IsDeletedProperty;

    static RecordAttachment()
    {
        RecordStateProperty = DependencyProperty.RegisterAttached("RecordState",
                                                                  typeof(model.RecordState),
                                                                  typeof(RecordAttachment),
                                                                  new PropertyMetadata(model.RecordState.Unchanged, RecordStatePropertyChanged));
        IsDeletedProperty = DependencyProperty.RegisterAttached("IsDeleted",
                                                                 typeof(bool),
                                                                 typeof(RecordAttachment),
                                                                 new PropertyMetadata(DeletedPropertyChanged));
    }

    public static void SetRecordState(UIElement element, model.RecordState state)
    {
        element.SetValue(RecordStateProperty, state);
    }
    public static model.RecordState GetRecordState(UIElement element)
    {
        return (model.RecordState)element.GetValue(RecordStateProperty);
    }
    public static void SetIsDeleted(UIElement element, bool value)
    {
        element.SetValue(IsDeletedProperty, value);
    }
    public static bool GetIsDeleted(UIElement element)
    {
        return (bool)element.GetValue(IsDeletedProperty);
    }

    public static void RecordStatePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender != null)
            sender.SetValue(RecordStateProperty, e.NewValue);
    }
    public static void DeletedPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender != null)
            sender.SetValue(IsDeletedProperty, e.NewValue);
    }
}

UPDATE I solved my underlying problem of needing to change the color of the rectangle fill by using datatriggers instead of using the attached properties and regular triggers. I would still like to know why the attached property 'propertychanged' event is only fired once though.

I did some more googling and I came across this link where Josh Smith says 'An attached property can only be set on an element once.'. I've looked around and I can't find any explanation...

like image 985
J Cooper Avatar asked Dec 08 '22 00:12

J Cooper


2 Answers

The problem is caused by these lines of code in the property change handlers:

sender.SetValue(RecordStateProperty, e.NewValue)

and

sender.SetValue(IsDeletedProperty, e.NewValue)

By calling SetValue, you are setting a new local value on the target. Setting a local value replaces any data binding that might previously have been in place.

In short, your property change handler removes the data binding for that property.

Since you are effectively removing the binding, your property will no longer change when the data source changes because it is no longer the data source for that property.

A property change notification is just that - it tells you that the property's value is changing. You do not need to do anything in response to that if you don't want to, and in particular, it's not your responsibility to make the property change. It's going to change anyway.

like image 157
Ian Griffiths Avatar answered Dec 11 '22 09:12

Ian Griffiths


In addition to selected answer , this can also be resolved by using

Sender.SetCurrentValue(IsDeletedProperty, e.NewValue)

This will change the value of the dependency without changing the source

like image 25
Chandan Avatar answered Dec 11 '22 09:12

Chandan