如何制作UI-MarkupExtension

时间:2019-07-19 20:51:53

标签: c# syntax observablecollection markup-extensions

我有一个简单的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中,它表示:

  

如果属性的类型是集合,则不需要在标记中将推断的集合类型指定为对象元素。而是将要成为集合中项目的元素指定为property元素的一个或多个子元素。每个此类项目在加载期间都会被评估为一个对象,然后通过调用隐式集合的Add方法将其添加到集合中。

但是,xaml将上面的语法解释为MyList = PinkRectangle而不是MyList.Add(PinkRectangle),但是如果我先放入Grid,它会正确地调用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,但最终我需要能够设置MyList。...

2 个答案:

答案 0 :(得分:2)

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

enter image description here

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

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

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

答案 1 :(得分:0)

如果MyProperty仅在XAML中初始化,并且您永远不需要或不想绑定它,则可以更简单地执行此操作,而不必使XAML的集合类型混乱。为此,需要使用附加属性,将实际集合引用存储在静态扩展类私有的依赖项属性中,并用前导下划线或其他内容修饰依赖项名称。在这种情况下,您自然必须在GetMyProperty()中初始化集合:只需检查目标对象的private依赖项属性是否为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>