Как создать расширение разметки XAML, возвращающее коллекцию

Я использую сериализацию XAML для графа объектов (вне WPF / Silverlight) и пытаюсь создать пользовательское расширение разметки, которое позволит заполнять свойство коллекции с помощью ссылок на выбранные члены коллекции, определенные в другом месте XAML.

Вот упрощенный фрагмент XAML, демонстрирующий то, чего я хочу достичь:

<myClass.Languages>
    <LanguagesCollection>
        <Language x:Name="English" />
        <Language x:Name="French" />
        <Language x:Name="Italian" />
    </LanguagesCollection>
</myClass.Languages>

<myClass.Countries>
    <CountryCollection>
        <Country x:Name="UK" Languages="{LanguageSelector 'English'}" />
        <Country x:Name="France" Languages="{LanguageSelector 'French'}" />
        <Country x:Name="Italy" Languages="{LanguageSelector 'Italian'}" />
        <Country x:Name="Switzerland" Languages="{LanguageSelector 'English, French, Italian'}" />
    </CountryCollection>
</myClass.Countries>

Свойство Languages каждого объекта Country должно быть заполнено IEnumerable, содержащим ссылки на объекты Language, указанные в LanguageSelector, который является пользовательским расширением разметки.

Вот моя попытка создать пользовательское расширение разметки, которое будет выполнять эту роль:

[ContentProperty("Items")]
[MarkupExtensionReturnType(typeof(IEnumerable<Language>))]
public class LanguageSelector : MarkupExtension
{
    public LanguageSelector(string items)
    {
        Items = items;
    }

    [ConstructorArgument("items")]
    public string Items { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var service = serviceProvider.GetService(typeof(IXamlNameResolver)) as IXamlNameResolver;
        var result = new Collection<Language>();

        foreach (var item in Items.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(item => item.Trim()))
        {
            var token = service.Resolve(item);

            if (token == null)
            {
                var names = new[] { item };
                token = service.GetFixupToken(names, true);
            }

            if (token is Language)
            {
                result.Add(token as Language);
            }
        }

        return result;
    }
}

На самом деле, этот код почти работает. Пока ссылающиеся объекты объявляются в XAML до объектов, которые на них ссылаются, метод ProvideValue корректно возвращает IEnumerable, заполненный ссылающимися элементами. Это работает, потому что обратные ссылки на экземпляры Language разрешаются следующей строкой кода:

var token = service.Resolve(item);

Но, если XAML содержит прямые ссылки (потому что объекты Language объявлены после объектов Country), это ломается, потому что это требует фиксирующих токенов, которые (очевидно) не могут быть приведены к Language.

if (token == null)
{
    var names = new[] { item };
    token = service.GetFixupToken(names, true);
}

В качестве эксперимента я попробовал преобразовать возвращаемую коллекцию в Collection в надежде, что XAML сможет как-то разрешить токены позже, но это приводит к исключениям invalid cast при десериализации.

Может ли кто-нибудь подсказать, как лучше всего заставить это работать?

Большое спасибо, Tim

13
задан Tim Coulter 28 November 2011 в 21:15
поделиться