'Reacting to external events inside an attached property

Say I have an attached property that looks something like this:

public class ThresholdAttachedProp
{
    public static readonly DependencyProperty ThresholdProperty = DependencyProperty.RegisterAttached(
        ...
        new PropertyMetadata(default(double), ThresholdPropertyChanged));

    // 'Threshold' property getter & setter omitted for brevity

    private static readonly IWarningService _warningService;

    private static void ThresholdPropertyChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
    {
        var element = dp as UIElement;
        if (_warningService == null)
        {
            _warningService = ServiceLocator.Current.GetInstance<IWarningService>();
        }
        SetControlVisibility(element);
    }

    private static void SetControlVisibility(UIElement element)
    {
        var threshold = (double)element.GetValue(ThresholdProperty);
        if (threshold > _warningService.DefconLevel)
        {
            element.Visibility = Visibility.Visible;
        }
        else
        {
            element.Visibility = Visibility.Hidden;
        }
    }
}

Which might be used like this:

<TextBlock ThresholdAttachedProp.Threshold="20" ... />

The attached prop sets the TextBlock visibility depending on whether or not the property value (20) is greater than a value exposed by another class (IWarningService.DefconLevel).

This works as expected when the view is first displayed, but I also need it to update the TextBox visibility whenever that service's value changes. I can implement a DefconLevelChanged event on the service, but I'm not sure how I could utilise it here due to the static nature of the attached property (and remembering that it could be applied to many controls across the application). Is something like this even possible in an attached prop?

A last resort would be to add a DefconLevel property which I could bind to the service, something like this:

<TextBlock ThresholdAttachedProp.Threshold="20"
           ThresholdAttachedProp.DefconLevel="{Binding WarningService.DefconLevel}" />

But I'd prefer to avoid this if possible as it would be cleaner to keep the logic hidden inside the attached prop, and not have to expose the service as a property on my VMs.

Edit I realise that setting a control's visibility is easily achieved by binding to a VM property. The "real" version of this attached property provides more functionality, using a second enum-based property allowing me to manipulate the control's Visibility or IsEnabled property based on different logic, hiding rather than collapsing, and so on. E.g.

<TextBlock ThresholdAttachedProp.Threshold="20"
           ThresholdAttachedProp.Rule="{x:Type ThresholdRule.HideIfLessThan}" ... />

This use case is almost a cross-cutting concern, used in many of the views, so implementing it as an attached property made sense as it avoids having to duplicate this logic and properties in many different VMs.



Solution 1:[1]

you can bind Visibility using a converter:

public class VisibilityThresholdConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is double defconLevel)
        {
            var threshold = Convert.ToDouble(parameter);
            if (threshold > defconLevel)
            {
                return Visibility.Visible;
            }               
        }
        return Visibility.Hidden;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

put converter instance in Resources:

<local:VisibilityThresholdConverter x:Key="defcon"/>

and bind:

<TextBlock Visibility="{Binding WarningService.DefconLevel, Converter={StaticResource defcon}, ConverterParameter=20}"/>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ASh