WPF覆盖来自父级的IsEnabled属性

21

我刚刚搜索了一种在父控件的IsEnabled=false的情况下启用子控件的方法。到目前为止,我找到的所有答案都说这是不可能的 - 必须启用父控件并禁用子控件,除非仍应启用某些子控件。

然而,通过覆盖App.xaml.cs文件中的IsEnabledProperty元数据,我能够改变这种默认行为:

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;
}

现在您可以手动设置子项上的 IsEnabled 属性,它将覆盖父级值。

这种方法有什么缺点吗?


13
当父级禁用时启用子级 = 子级不应该是该父级的子级,需要重新思考设计。 - Alex
我认为有必要提出另一个问题,以便我们告诉您如何避免当前布局中的这种情况。 - Gusdor
2
没有一个单一的布局需要应用这个,但整个应用程序都有一个普遍的要求。根据当前用户,某些内容不应该是可编辑的。然而,在子控件内部滚动和导航应该是可能的。因此,选择的子控件(例如TreeView)应该是启用的。最简单的方法是禁用父控件并启用选定的子控件 - 否则我们将不得不手动绑定数百个子控件,使用不同的视图模型 - 不是每个子控件都知道自己是否启用... - LionAM
在WPF中,按设计,所有子控件都从父控件继承isEnable属性,因此如果这个属性与父属性不匹配,每个子控件都不允许您更改此属性。我相信还有另一种方法可以获得所需的相同效果(可能还需要一些设计更改:), 但是通过尝试不匹配子IsEnable属性不是正确的方法。如果您可以发布更多GUI设计代码,那将更好。 - Diego
1
我也在VS2013中使用它。我只需要在我的应用程序的一个窗口中使用这个行为。因此,我将代码放入了一个Behavior<UIElement>类中。使用这个类,只有在需要时才能将行为附加到UIElement上。完美地工作了。谢谢! - Marcus
显示剩余2条评论
4个回答

5
在某些情况下,提出的解决方案可能是可行的。但是,更改应用程序中所有UI元素的默认框架行为可能会引入兼容性问题,并且将来可能很难理解在哪里/为什么更改了行为。
一种替代方法是保持框架的默认行为,在特定位置手动覆盖该行为时再进行更改。一种方法是创建一个简单的包装器元素,从父元素中断IsEnabled继承链。
框架的默认强制回调检查父IsEnabled值并继承它。此控件设置了一个新的强制回调,仅直接返回该值而不检查继承。
public class ResetIsEnabled : ContentControl
{
    static ResetIsEnabled()
    {
        IsEnabledProperty.OverrideMetadata(
            typeof(ResetIsEnabled),
            new UIPropertyMetadata(
                defaultValue: true,
                propertyChangedCallback: (_, __) => { },
                coerceValueCallback: (_, x) => x));
    }
}

它可以这样使用

<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>

4

这种方法适用于我在多次使用且稍加修改的控件上的情况。

我在此发布,以帮助未来遇到类似情况的网页搜索者:

  • 将其放置在静态构造函数中而不是事件中,否则它会尝试多次设置并抛出“PropertyMetadata已为类型'{type}'注册。”异常。
  • 更改类型以匹配该控件

代码:

请确保查找并替换[CustomControl] 为您控件的类型名称。

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;
}

正是我所需要的。我从ContentPresenter派生并实现了你的方法,这样我就可以将任何类型的视图/控件包装起来。 - Lumo

0

另一种选择是覆盖FrameworkPropertyMetadataOptions以删除Inherits属性。我曾经遇到过类似的FontSize问题,这个方法很有效:

FontSizeProperty.OverrideMetadata(
   typeof(YourControl), 
   new FrameworkPropertyMetadata(8.0, 
      FrameworkPropertyMetadataOptions.None, changed));

0

缺点至少在于,您打破了基本概念,并且IsEnabled未用于预期范围。这种解决方法也使维护稍微复杂一些(开发人员必须首先理解为什么它的工作方式不同)。

正如评论中建议的那样,我认为重新设计此窗口会有所帮助。特别是,如果我只想禁止表单中的编辑(数据修改),我将使用其他属性,例如IsReadOnly。


但并非所有的FrameworkElement都支持IsReadOnly属性。那么我该如何将其应用于窗口的所有子元素呢? - LionAM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接