Вот полный и рабочий проект, который решает вашу проблему. Сначала я собирался предложить использовать атрибут [XamlSetMarkupExtension]
в вашем классе Country
, но на самом деле все, что вам нужно, - это прямое разрешение имен XamlSchemaContext
.
Хотя документация для этой функции очень тонкая на местах, вы можете фактически сказать Xaml Services отложить ваш целевой элемент, и следующий код показывает, как. Обратите внимание, что все названия ваших языков правильно разрешены, даже если разделы из вашего примера поменялись местами.
Обычно, если вам нужно имя, которое не может быть разрешено, вы запрашиваете отсрочку, возвращая токен исправления. Да, как отмечает Дмитрий, это непрозрачно для нас, но это не имеет значения. Когда вы звоните GetFixupToken(...)
, вы указываете список имен, которые вам нужны. Ваше расширение разметки - ProvideValue
, то есть - будет вызвано снова позже, когда эти имена станут доступными. В этот момент это в основном перебор.
Здесь не показано, что вы также должны проверить Boolean
свойство IsFixupTokenAvailable
на IXamlNameResolver
. Если имена действительно будут найдены позже, это должно вернуть true
. Если значение равно false
и у вас все еще есть неразрешенные имена, то вам следует выполнить полный сбой операции, предположительно потому, что имена, указанные в Xaml, в конечном итоге не могут быть разрешены.
Некоторым может быть любопытно отметить, что этот проект не является приложением WPF, т. Е. Он не ссылается на библиотеки WPF; единственная ссылка, которую вы должны добавить к этому автономному ConsoleApplication - это System.Xaml
. Это верно, хотя существует оператор using
для System.Windows.Markup
(исторический артефакт). Именно в .NET 4.0 поддержка служб XAML была перенесена из WPF (и в других местах) в основные библиотеки BCL.
ИМХО, это изменение сделало XAML Services величайшей функцией BCL, о которой никто не слышал. Нет лучшего основания для разработки больших приложений системного уровня, у которых радикальная возможность реконфигурации является основным требованием. Примером такого «приложения» является WPF.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Windows.Markup;
using System.Xaml;
namespace test
{
public class Language { }
public class Country { public IEnumerable<Language> Languages { get; set; } }
public class LanguageSelector : MarkupExtension
{
public LanguageSelector(String items) { this.items = items; }
String items;
public override Object ProvideValue(IServiceProvider ctx)
{
var xnr = ctx.GetService(typeof(IXamlNameResolver)) as IXamlNameResolver;
var tmp = items.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s_lang => new
{
s_lang,
lang = xnr.Resolve(s_lang) as Language
});
var err = tmp.Where(a => a.lang == null).Select(a => a.s_lang);
return err.Any() ?
xnr.GetFixupToken(err) :
tmp.Select(a => a.lang).ToList();
}
};
public class myClass
{
Collection<Language> _l = new Collection<Language>();
public Collection<Language> Languages { get { return _l; } }
Collection<Country> _c = new Collection<Country>();
public Collection<Country> Countries { get { return _c; } }
// you must set the name of your assembly here ---v
const string s_xaml = @"
<myClass xmlns=""clr-namespace:test;assembly=ConsoleApplication2""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
<myClass.Countries>
<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'}"" />
</myClass.Countries>
<myClass.Languages>
<Language x:Name=""English"" />
<Language x:Name=""French"" />
<Language x:Name=""Italian"" />
</myClass.Languages>
</myClass>
";
static void Main(string[] args)
{
var xxr = new XamlXmlReader(new StringReader(s_xaml));
var xow = new XamlObjectWriter(new XamlSchemaContext());
XamlServices.Transform(xxr, xow);
myClass mc = (myClass)xow.Result; /// works with forward references in Xaml
}
};
}
[редактировать ...]
Поскольку я только изучаю Службы XAML , я, возможно, обдумываю это. Ниже приведено простое решение, которое позволяет вам устанавливать любые ссылки, которые вы пожелаете - полностью в XAML - используя только встроенные расширения разметки x:Array
и x:Reference
.
Каким-то образом я не осознавал, что не только x:Reference
может заполнять атрибут (как это обычно видели: {x:Reference some_name}
), но он также может выступать в качестве тега XAML сам по себе (<Reference Name="some_name" />
). В любом случае он действует как прокси-ссылка на объект в другом месте документа. Это позволяет вам заполнить x:Array
ссылками на другие объекты XAML, а затем просто установить массив в качестве значения для вашего свойства. Парсер (ы) XAML автоматически разрешает прямые ссылки по мере необходимости.
<myClass xmlns="clr-namespace:test;assembly=ConsoleApplication2"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<myClass.Countries>
<Country x:Name="UK">
<Country.Languages>
<x:Array Type="Language">
<x:Reference Name="English" />
</x:Array>
</Country.Languages>
</Country>
<Country x:Name="France">
<Country.Languages>
<x:Array Type="Language">
<x:Reference Name="French" />
</x:Array>
</Country.Languages>
</Country>
<Country x:Name="Italy">
<Country.Languages>
<x:Array Type="Language">
<x:Reference Name="Italian" />
</x:Array>
</Country.Languages>
</Country>
<Country x:Name="Switzerland">
<Country.Languages>
<x:Array Type="Language">
<x:Reference Name="English" />
<x:Reference Name="French" />
<x:Reference Name="Italian" />
</x:Array>
</Country.Languages>
</Country>
</myClass.Countries>
<myClass.Languages>
<Language x:Name="English" />
<Language x:Name="French" />
<Language x:Name="Italian" />
</myClass.Languages>
</myClass>
Чтобы попробовать это, вот полное консольное приложение, которое создает экземпляр объекта myClass
из предыдущего файла XAML. Как и раньше, добавьте ссылку на System.Xaml.dll
и измените первую строку XAML выше, чтобы она соответствовала имени вашей сборки.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Xaml;
namespace test
{
public class Language { }
public class Country { public IEnumerable<Language> Languages { get; set; } }
public class myClass
{
Collection<Language> _l = new Collection<Language>();
public Collection<Language> Languages { get { return _l; } }
Collection<Country> _c = new Collection<Country>();
public Collection<Country> Countries { get { return _c; } }
static void Main()
{
var xxr = new XamlXmlReader(new StreamReader("XMLFile1.xml"));
var xow = new XamlObjectWriter(new XamlSchemaContext());
XamlServices.Transform(xxr, xow);
myClass mc = (myClass)xow.Result;
}
};
}