【问题标题】:WPF override IsEnabled from ParentWPF 覆盖父级的 IsEnabled
【发布时间】:2014-03-07 11:32:54
【问题描述】:

我刚刚搜索了一种在父控件具有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 属性,它会覆盖父级值。

这种方法有什么缺点吗?

【问题讨论】:

  • 当父级禁用时启用子级 = 子级不应是该父级的子级,设计需要重新考虑。
  • 我认为有必要提出另一个问题,以便我们可以告诉您如何使用当前布局来避免这种情况。
  • 没有单一的布局可以应用,而是整个应用程序的一般要求。根据当前用户,某些内容不应该是可编辑的。但是,应该可以在子控件中滚动和导航。因此,应启用选定的子控件(例如 TreeView)。实现这一点的最简单方法是禁用父控件并启用选定的子控件 - 否则我们将不得不手动绑定数百个具有不同视图模型的子控件 - 不是每个都自己知道它是否已启用...
  • 根据 WPF 中的设计,所有子控件都从父控件继承 isEnable 属性,因此如果该属性与父属性不匹配,则每个子控件都不允许您更改此属性。我确信还有另一种方法可以获得您需要的相同效果(也许还有一些设计更改:),但通过尝试与子 IsEnable 属性不匹配不是正确的方法。如果您可以发布更多的 GUI 设计代码会更好。
  • 我也在 VS2013 中使用了这个。我只需要我的应用程序的一个窗口中的行为。因此,我将代码放入 Behaviour 类中。使用此类,可以仅在需要时将行为附加到 UIElement。工作完美。谢谢!

标签: c# wpf dependency-properties


【解决方案1】:

这适用于我在多次使用并稍作修改的控件上的情况。

放置在这里是为了帮助未来遇到类似情况的网络搜索者:

  • 将其放置在静态构造函数中而不是事件中,否则它会尝试多次设置它并抛出“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 派生并实现了您的方法,因此我可以用它包装任何类型的视图/控件。
【解决方案2】:

问题中提出的解决方案在某些情况下可能没问题。但是,更改应用程序中所有 UIElements 的默认框架行为可能会引入兼容性问题,并且将来可能难以理解行为更改的位置/原因。

另一种方法是保留框架的默认行为,并仅在需要时在特定位置手动覆盖该行为。做到这一点的一种方法是创建一个简单的包装器元素,它打破来自父级的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>

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 但并非所有 FrameworkElements 都支持 IsReadOnly 属性。那么如何将它应用于一个窗口的所有子元素呢?
    【解决方案4】:

    另一个选项是覆盖 FrameworkPropertyMetadataOptions 以删除 Inherits 属性。我对 FontSize 也有类似的问题,而且效果很好:

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

    【讨论】:

      猜你喜欢
      • 2011-01-30
      • 2022-01-19
      • 1970-01-01
      • 2017-11-30
      • 2022-12-18
      • 1970-01-01
      • 1970-01-01
      • 2011-05-14
      • 1970-01-01
      相关资源
      最近更新 更多