I have a UserControl that displays a rating as number of stars. It does this by binding a TextBlock’s Text property to a regular code-behind property which in turn uses an integer DependencyProperty Value.
In order to update the TextBlock when Value changes, I need to manually trigger the INotifyPropertyChanged.PropertyChanged event from the DependencyProperty’s PropertyChangedCallback.
This feels exceedingly excessive. Is there an easier to way to accomplish this?
<TextBlock Text="{Binding RatingDisplay, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Rating}}}" />
public partial class Rating : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(Rating), new PropertyMetadata(0, (sender, e) =>
{
((Rating)sender).RaisePropertyChanged(nameof(RatingDisplay));
}));
public Rating()
{
InitializeComponent();
}
public event PropertyChangedEventHandler? PropertyChanged;
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public string RatingDisplay => new string('*', Value);
protected void RaisePropertyChanged(string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
CodePudding user response:
You do not need to implement INotifyPropertyChanged in a class that exposes dependency properties. Dependency properties provide their own change notification mechanism.
In order to update a read-only dependency property, something like this should work:
public partial class Rating : UserControl
{
private static readonly DependencyPropertyKey RatingDisplayPropertyKey =
DependencyProperty.RegisterReadOnly(
nameof(RatingDisplay), typeof(string), typeof(Rating), null);
public static readonly DependencyProperty RatingDisplayProperty =
RatingDisplayPropertyKey.DependencyProperty;
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
nameof(Value), typeof(int), typeof(Rating),
new PropertyMetadata(0, (o, e) => o.SetValue(
RatingDisplayPropertyKey, new string('*', (int)e.NewValue))));
public Rating()
{
InitializeComponent();
}
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public string RatingDisplay => (string)GetValue(RatingDisplayProperty);
}
Alternatively, bind the Value property with a Binding Converter that creates a string from the Rating Value.
Or, probably most simple, directly access the UI element that should updated:
<TextBlock x:Name="ratingDisplay"/>
with this code behind:
public partial class Rating : UserControl
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
nameof(Value), typeof(int), typeof(Rating),
new PropertyMetadata(0, (o, e) =>
((Rating)o).ratingDisplay.Text = new string('*', (int)e.NewValue)));
public Rating()
{
InitializeComponent();
}
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
}