如何制作 UI-MarkupExtension

How to make a UI-MarkupExtension

我有一个简单的 UIElement,我想把它变成 MarkupExtension:

[MarkupExtensionReturnType(typeof(FrameworkElement))]
public class PinkRectangle : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    { 
        return new Rectangle {Height = 100, Width = 300, Fill = Brushes.HotPink };
    }
}

它在大多数情况下都非常有效。唯一的例外是在列表中:

<local:WindowEx x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.winfx/200x/xaml"
    xmlns:local="clr-namespace:WpfApp1"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    MyProperty="{Binding local:PinkRectangle}"> <!--this one works.-->
    <local:WindowsEx.MyList>
        <!--<Grid/> If I comment this line in, it works-->
        <local:PinkRectangle/>
    </local:WindowsEx.MyList>

    <ContentPresenter Content="{Binding MyProperty}"/>
</local:WindowEx>

Collection Syntax中说:

If the type of a property is a collection, then the inferred collection type does not need to be specified in the markup as an object element. Instead, the elements that are intended to become the items in the collection are specified as one or more child elements of the property element. Each such item is evaluated to an object during loading and added to the collection by calling the Add method of the implied collection.

但是,xaml 将上面的语法解释为 MyList = PinkRectangle 而不是 MyList.Add(PinkRectangle) 但是如果我先放入一个网格,它会正确地为两者调用 MyList.Add() . 在两种情况下告诉 xaml 调用 MyList.Add() 的正确语法是什么?

下面是创建 Minimal, Reproducable Example 的其余代码:

namespace WpfApp1
{
    // I use this class to directly set a few unusual properties directly in xaml.
    public class WindowEx : Window
    {
        //If I remove the set property, the error goes away, but I need the setter.
        public ObservableCollection<object> MyList {get; set; } = new ObservableCollection();

        public object MyProperty
        {
            get { return GetValue(MyPropertyProperty); }
            set { SetValue(MyPropertyProperty, value); }
        }
        public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(nameof(MyProperty), typeof(object), typeof(MainWindow), new PropertyMetaData(0));
     }

    public partial class MainWindow : WindowEx
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

- 编辑 -

我发现如果我从 MyList 中删除 set{ },问题就会消失,因为 xaml 不再认为有 setter,但最终我需要能够设置我的列表....

可怜的 XAML 解析器真的对这一切感到困惑...:O) 通过消除歧义来帮助它:在您的 XAML 中明确实例化 MyList

XAML:

<local:UserControlEx x:Class="WpfApp14.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp14"
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="450">

    <local:UserControlEx.MyList>
        <local:ObjectCollection>
            <local:CoolBlueRectangle/>
            <local:CoolBlueRectangle/>
            <local:CoolBlueRectangle/>
            <local:CoolBlueRectangle/>
            <local:CoolBlueRectangle/>
        </local:ObjectCollection>
    </local:UserControlEx.MyList>

    <Grid>
        <ItemsControl HorizontalAlignment="Left" 
                      ItemsSource="{Binding MyList}"/>
    </Grid>

</local:UserControlEx>

其中,

public class ObjectCollection : ObservableCollection<object>
{
}

顺便说一句,命名约定是您的标记 class 定义应使用 Extension 后缀。

public class CoolBlueRectangleExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
    }
}

如果 MyProperty 仅在 XAML 中初始化并且您永远不需要或不想绑定它,您可以更简单地执行此操作,而不必将 XAML 与集合类型。要使用附加的 属性 执行此操作,您可以将实际的集合引用存储在静态扩展 class 私有的依赖项 属性 中,依赖项 属性 名称用前导下划线或其他东西。在这种情况下,您必须自然地在 GetMyProperty() 中初始化集合:只需检查目标对象的私有依赖项 属性 是否为 null,并根据需要进行初始化。

注意GetMyProperty必须是静态的。命名约定是 'Get' 前缀必须存在,方法名称的其余部分是 "property" 名称。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public static StringCollection GetMyProperty(MainWindow wnd)
    {
        return wnd._myProperty;
    }

    private StringCollection _myProperty = new StringCollection();
}

public class StringCollection : ObservableCollection<String>
{
}
<local:MainWindow.MyProperty>
    <sys:String>Foo</sys:String>
</local:MainWindow.MyProperty>