Как сделать 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>

В Синтаксис коллекции сказано:

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....

Это немного странно. Можете ли вы объяснить, почему вам нужен этот List<object> внутри Window (особенно с общедоступным сеттером) и почему вы хотите заполнить его элементами пользовательского интерфейса? Все это кажется плохим дизайном с моей точки зрения.

dymanoid 28.07.2019 00:33

Стоит ли вам взглянуть с помощью ILSpy, как подобное поведение реализовано, скажем, в сетке? Кажется, есть какой-то интерфейс IAddChild, который, возможно, обрабатывает это.

Janne Matikainen 29.07.2019 14:00

@dymanoid - это просто упрощенный пример. В моем проекте у меня есть Custom UserControl, который по существу состоит из списков — UserControl определяет, как они выглядят и работают вместе. Я хочу иметь возможность устанавливать то, что находится в этих списках, вне фактического контроля.

bwall 29.07.2019 17:12
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
3
407
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Плохой синтаксический анализатор 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>

Другие вопросы по теме