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:
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...
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With