【问题标题】:WPF Attached Property has no effect at design time (xaml editor)WPF 附加属性在设计时无效(xaml 编辑器)
【发布时间】:2017-09-20 04:35:27
【问题描述】:

我有一个在运行时运行良好的附加属性,我希望它在设计时也能正常运行。我已经阅读了一些文档并遵循了一些教程,但我认为他们比我更熟悉。

这个DP(来自source blog post (microsoft.co.il))的目的是:

  • 附加到任何面板类型
  • 为每个子项设置边距
  • (未来)最终子级的自定义边距

如何让该属性在设计器中具有与运行时相同的效果?它会影响布局,所以我认为看看那里发生了什么也很重要。

简化的 XAML:

<StackPanel Orientation="Horizontal" local:PanelItems.Margin="0,0,10,0">
    <TextBox Width="120" Text="{Binding Email}"/>
    <Button Content="Search" Command="{Binding SearchCommand}"/>
</StackPanel>

当前类(在运行时工作正常)。

我已经删除了从DependencyObject 的继承,因为这对这种情况没有帮助(我正在关注 msdn 示例)。我想这些示例不处理附加属性。

public static class PanelItems : DependencyObject
{
    public static readonly DependencyProperty MarginProperty = 
        DependencyProperty.RegisterAttached(
            "Margin", typeof(Thickness), typeof(PanelItems),
            new UIPropertyMetadata(new Thickness(), MarginChangedCallback)
        );

    public static Thickness GetMargin(DependencyObject obj)
    {
        return (Thickness)obj.GetValue(MarginProperty);
    }

    public static void SetMargin(DependencyObject obj, Thickness value)
    {
        obj.SetValue(MarginProperty, value);
    }

    public static void MarginChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
    {
        // requires a panel control (e.g. a StackPanel)
        var panel = sender as Panel;
        if (panel == null) return;
        panel.Loaded += new RoutedEventHandler(OnPanelLoaded);
    }

    static void OnPanelLoaded(object sender, RoutedEventArgs e)
    {
        var panel = sender as Panel;
        foreach (var child in panel.Children.OfType<FrameworkElement>())
            child.Margin = PanelItems.GetMargin(panel);
    }        
}

解决方案说明

我添加了一个扩展方法来检查是否已为给定对象设置了值,该方法只允许针对直接子对象。我看不到其他方法,因为没有返回导致级联的原始更改的上下文。

public static class DependencyExtensions
{
    /// <summary>
    /// Checks if a DependencObject has a value set for the Dependency Property.
    /// Element is an object for conveninence purpose, and the return value is 
    /// always false if it is not a DependencyObject (e.g. a UI element)
    /// </summary>
    /// <param name="property">The DependencyProperty to check</param>
    /// <param name="element">The element (a DependencyObject) to check against</param>
    /// <returns></returns>
    public static bool IsSetFor(this DependencyProperty property, object element)
    {
        if (element is DependencyObject d)
        {
            return d.ReadLocalValue(property) != DependencyProperty.UnsetValue;
        }
        return false;
    }
}

之后,我稍微修改了事件以检查两者。嵌套面板也可以正常工作;内面板根据外面板规则,内子面板得到正确的边距。

if (sender is FrameworkElement el && MarginProperty.IsSetFor(el.Parent))
{
    el.Margin = (Thickness)args.NewValue;
}

注意:也可以调用GetMargin(el as DependencyObject),尽管值与儿童的NewValue 相同。

最后一点:我关闭了设计器上的project code,所以无论我做什么,我都看不到那里的变化。这是具有巨大效果的无标签小按钮:

【问题讨论】:

  • 从 DependencyObject 派生并声明 Margin 属性是没有意义的,因为 PanelItems 类永远不会被实例化。它应该是一个静态类。除此之外,Loaded 处理程序肯定不会被多次调用,即使您更改了 Margin 属性,或者稍后将子元素添加到面板。
  • 我的意思是当你设置属性时你不会在设计器中看到任何效果,因为面板已经加载了。方法被打破了。
  • @Clemens 我明白了,编辑更清晰。我现在又回到了一个严格的附属财产。从这个出发点有没有办法像在运行时一样影响设计者?我可以很容易地考虑到边距的应用,但是让设计师以某种方式触发事件是......

标签: c# wpf xaml


【解决方案1】:

这是一个带有值继承的 Margin 属性的快速草稿,因此它会自动应用于所有子元素。

除了所有子元素之外,Margin 也应用于 Panel 本身,您可以尝试避免这种情况(也许只需检查目标元素是否为 Panel)。

public static class PanelItems
{
    public static readonly DependencyProperty MarginProperty =
        DependencyProperty.RegisterAttached(
            "Margin", typeof(Thickness), typeof(PanelItems),
            new FrameworkPropertyMetadata(new Thickness(),
                FrameworkPropertyMetadataOptions.Inherits, MarginChangedCallback));

    public static Thickness GetMargin(DependencyObject obj)
    {
        return (Thickness)obj.GetValue(MarginProperty);
    }

    public static void SetMargin(DependencyObject obj, Thickness value)
    {
        obj.SetValue(MarginProperty, value);
    }

    private static void MarginChangedCallback(
        object sender, DependencyPropertyChangedEventArgs e)
    {
        var element = sender as FrameworkElement;

        if (element != null && !(element is Panel))
        {
            element.Margin = (Thickness)e.NewValue;
        }
    }
}

【讨论】:

  • 玩这个版本,我无法让它工作。它确实适用于层次结构,但不适用于设计模式。我还将element is Panel 更改为element.Parent is StackPanel 以消除按钮/文本框的混乱,因为它们包含显然是“面板”的网格:D 也不是一个好方法,但可以进行测试。
  • 在我的 VS 2015 和 2017 设计师中完美运行。
  • 除此之外,您也许可以简单地检查元素的父级是否具有属性集,但祖父级没有。因此,您只能将该属性应用于 Panel 的直接子级。但是请注意,您可能必须使用可空类型(即Thickness?)才能获得默认值null
  • 让它只与直接子级一起使用,这也解决了嵌套问题。非常感谢您的指点。那个博客让我走错了路。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-22
  • 2010-12-03
  • 1970-01-01
相关资源
最近更新 更多