Я использую сериализацию 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>
Свойство Языки для каждого объекта Страна должно быть заполнено с помощью 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> , заполненный ссылочными элементами. Это работает, потому что обратные ссылки на экземпляры Language разрешаются следующей строкой кода:
var token = service.Resolve(item);
Но если XAML содержит прямые ссылки (поскольку объекты Language объявляются после объектов Country), он прерывается, потому что для этого требуются токены fixup, которые (очевидно) не могут быть переведенным в Язык.
if (token == null)
{
var names = new[] { item };
token = service.GetFixupToken(names, true);
}
В качестве эксперимента я попытался преобразовать возвращенную коллекцию в Collection <object> в надежде, что XAML каким-то образом разрешит токены позже, но в процессе десериализации она выдаст недопустимые исключения исключения.
Кто-нибудь может предложить, как лучше всего это сделать?
Большое спасибо, Тим