21

I just searched for a way to enable a child control while the parent control has IsEnabled = false. All answers that I have found up to now say that it is not possible - one has to enable the parent and disable the child controls except the ones that should still be enabled.

However, by overriding the Metadata for the IsEnabledProperty in the App.xaml.cs file, I was able to change this default behavior:

protected override void OnStartup(StartupEventArgs e)
{
    UIElement.IsEnabledProperty.OverrideMetadata(typeof(FrameworkElement),
             new UIPropertyMetadata(true,IsEnabledChanged, CoerceIsEnabled));
}

private void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var childrenCount = VisualTreeHelper.GetChildrenCount(d);
    for (int i = 0; i < childrenCount; ++i)
    {
        var child = VisualTreeHelper.GetChild(d, i);
        child.CoerceValue(UIElement.IsEnabledProperty);
    }
}
private object CoerceIsEnabled(DependencyObject d, object basevalue)
{
    var parent = VisualTreeHelper.GetParent(d) as FrameworkElement;
    if (parent != null && parent.IsEnabled == false)
    {
        if (d.ReadLocalValue(UIElement.IsEnabledProperty) == DependencyProperty.UnsetValue)
        {
            return false;
        }
    }
    return basevalue;
}

Now you can manually set the IsEnabled property on a child, which overrides the parent value.

Are there any drawbacks of this approach?

SuicideSheep
  • 5,260
  • 19
  • 64
  • 117
LionAM
  • 1,271
  • 10
  • 28
  • 13
    Child enabled when parent disabled = the child shouldn't be a child of that parent, design needs some rethought. – Alex Mar 07 '14 at 11:36
  • I think another question is warranted so that we can tell you how to avoid this situation with you current layout. – Gusdor Mar 07 '14 at 11:43
  • 2
    There is no single layout where this is to be applied but a general requirement for the whole application. Depending on the current user, some content should not be editable. However, scrolling and navigating within the child controls should be possible. Therefore, selected child controls (e.g. a TreeView) should be enabled. The easiest way to achieve this is by disabling the parent control and enabling selected childs - otherwise we would have to bind hundreds of child controls manually, with varying viewmodels - not each knows by itself if it is enabled... – LionAM Mar 07 '14 at 12:23
  • By design in WPF all the child controls inherit the isEnable property from the parent so every child will not allow you to change this property if this one mismatch the parent property. I'm sure there is another way to get the same effect that you need (perhaps with some design change also :), but is not the correct way by trying to mismatch the child IsEnable propery. If you can post a bit more code of your GUI design will be better. – Diego Mar 07 '14 at 23:10
  • Use this in VS2013 + WPF + .NET 4.51, it worked perfectly. Thanks!! – Contango Aug 16 '15 at 10:22
  • 1
    I also use this in VS2013. I need the behaviour only in one Window of my application. Therfor i put the code into a Behaviour Class. With this class it is possible to attache the behaviour to an UIElement only if need. Worked perfectly. Thanks ! – Marcus Oct 12 '15 at 12:19
  • Alex, thanks for the suggestion. I thought about it, then realised this was still the best solution. – Tom Deloford Jan 21 '16 at 15:01

4 Answers4

5

The solution that is proposed in the question may be fine in some scenarios. However, changing the default framework behavior for all UIElements in the application, could introduce compatibility issues and it might be difficult in the future to understand where/why the behavior was changed.

An alternative approach would be to keep the default behavior of the framework and only override that behavior manually in specific places, when needed. One way to do this is be creating a simple wrapper element that breaks the IsEnabled inheritance chain from the parent.

The framework's default coerce callback checks the parent IsEnabled value and inherits it. This control sets a new coerce callback that just returns the value directly without checking inheritance.

public class ResetIsEnabled : ContentControl
{
    static ResetIsEnabled()
    {
        IsEnabledProperty.OverrideMetadata(
            typeof(ResetIsEnabled),
            new UIPropertyMetadata(
                defaultValue: true,
                propertyChangedCallback: (_, __) => { },
                coerceValueCallback: (_, x) => x));
    }
}

It can be used like this

<ParentControl IsEnabled="False">
  <!-- Any elements here will have IsEnabled set to false, inherited from the parent -->
  <ResetIsEnabled>
    <!-- Any child elements here will have IsEnabled set to true (the default value) -->
  </ResetIsEnabled>
</ParentControl>
mark.monteiro
  • 2,609
  • 2
  • 33
  • 38
4

This worked for my situation on a control used several times with some slight modifications.

Placing here to help any future web searchers in a similar situation:

  • placed it in a static constructor instead of an event, otherwise it tried to set it multiple times and threw a "PropertyMetadata is already registered for type '{type}'." exception.
  • Changed the type to match the control

Code:

Make sure to find and replace [CustomControl] with the type name of your control.

static [CustomControl]()
{
    UIElement.IsEnabledProperty.OverrideMetadata(typeof([CustomControl]), new UIPropertyMetadata(true, [CustomControl]_IsEnabledChanged, CoerceIsEnabled));
}

private static void [CustomControl]_IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var childrenCount = VisualTreeHelper.GetChildrenCount(d);
    for (int i = 0; i < childrenCount; ++i)
    {
        var child = VisualTreeHelper.GetChild(d, i);
        child.CoerceValue(UIElement.IsEnabledProperty);
    }
}

private static object CoerceIsEnabled(DependencyObject d, object basevalue)
{
    var parent = VisualTreeHelper.GetParent(d) as FrameworkElement;
    if (parent != null && parent.IsEnabled == false)
    {
        if (d.ReadLocalValue(UIElement.IsEnabledProperty) == DependencyProperty.UnsetValue)
        {
            return false;
        }
    }
    return basevalue;
}
Kelly Elton
  • 4,373
  • 10
  • 53
  • 97
user1568891
  • 386
  • 6
  • 12
  • Exactly what I was looking for. I derived from the ContentPresenter and implemented your methods so I can wrap any kind of view/control with it. – Lumo Jul 05 '18 at 08:03
0

The drawback is at least, that you break the basic concept, and the IsEnabled is not used for the intended scope. This workaround also makes maintenance a bit more complex (the developer has to understand first, why it works differently).

As it is suggested in comments, I would say, that a redesign of this window would help. Especially, if I would like to forbid only the editing (data modification) in the form, I would use other properties like IsReadOnly.

eldor
  • 151
  • 5
  • But not all FrameworkElements support the IsReadOnly property. So how can I apply it to all child elements of a window that do? – LionAM Oct 20 '15 at 11:42
0

Another option is to override the FrameworkPropertyMetadataOptions to remove the Inherits property. I had a similar problem with the FontSize and this worked well:

FontSizeProperty.OverrideMetadata(
   typeof(YourControl), 
   new FrameworkPropertyMetadata(8.0, 
      FrameworkPropertyMetadataOptions.None, changed));
Eric
  • 570
  • 6
  • 19