У меня есть простой 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>
В Синтаксис коллекции сказано:
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). Но если я сначала вставлю Grid, он правильно вызовет MyList.Add() для обоих. Каков правильный синтаксис, чтобы сообщить xaml о вызове MyList.Add() для обеих ситуаций?
Вот остальная часть кода для создания Минимальный воспроизводимый пример:
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();
}
}
}
- Редактировать -
Я обнаружил, что если я удалю set{ } из MyList, проблема исчезнет, потому что xaml больше не считает, что существует сеттер, но в конечном итоге мне нужно иметь возможность установить MyList....
Стоит ли вам взглянуть с помощью ILSpy, как подобное поведение реализовано, скажем, в сетке? Кажется, есть какой-то интерфейс IAddChild, который, возможно, обрабатывает это.
@dymanoid - это просто упрощенный пример. В моем проекте у меня есть Custom UserControl, который по существу состоит из списков — UserControl определяет, как они выглядят и работают вместе. Я хочу иметь возможность устанавливать то, что находится в этих списках, вне фактического контроля.





Плохой синтаксический анализатор XAML просто действительно запутался во всем этом ...: O) Помогите ему, устранив двусмысленность: создайте экземпляр MyList явно в своем XAML.
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)
{
}
}
Если MyProperty будет инициализирован только в XAML и вам никогда не понадобится или не захочется привязывать его, вы можете сделать это проще и не загромождать XAML типом коллекции. Чтобы сделать это с присоединенным свойством, вы должны сохранить фактическую ссылку на коллекцию в свойстве зависимости, закрытом для статического класса расширения, с именем свойства зависимости, украшенным начальным символом подчеркивания или чем-то еще. В этом случае вам, естественно, придется инициализировать коллекцию в GetMyProperty(): просто проверьте, является ли свойство частной зависимости нулевым для целевого объекта, и инициализируйте его по мере необходимости.
Обратите внимание, что GetMyProperty должен быть статическим. Соглашение об именах заключается в том, что должен присутствовать префикс «Get», а остальная часть имени метода является именем «свойства».
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>
Это немного странно. Можете ли вы объяснить, почему вам нужен этот
List<object>внутриWindow(особенно с общедоступным сеттером) и почему вы хотите заполнить его элементами пользовательского интерфейса? Все это кажется плохим дизайном с моей точки зрения.