これは、問題を解決する完全で機能するプロジェクトです。[XamlSetMarkupExtension]
最初は、クラスで属性を使用することを提案するつもりでしたCountry
が、実際に必要なのはXamlSchemaContext
の前方名前解決だけです。
この機能のドキュメントは非常に薄いものですが、実際にはXaml Servicesにターゲット要素を延期するように指示することができます。次のコードはその方法を示しています。例のセクションが逆になっていても、すべての言語名が適切に解決されることに注意してください。
基本的に、解決できなかった名前が必要な場合は、フィックスアップ トークンを返すことで保留を要求します。はい、ドミトリーが言及しているように、それは私たちにとって不透明ですが、それは問題ではありません. を呼び出すときにGetFixupToken(...)
、必要な名前のリストを指定します。マークアップ拡張機能 (<code>ProvideValue) は、これらの名前が使用可能になったときに再度呼び出されます。その時点で、それは基本的にやり直しです。
ここでは示されていませんが、 のプロパティも確認する必要がありBoolean
ます。名前が本当に後で見つかる場合、これは を返す必要があります。値がで、まだ未解決の名前がある場合は、おそらく Xaml で指定された名前を最終的に解決できなかったため、操作をハード フェイルする必要があります。IsFixupTokenAvailable
IXamlNameResolver
true
false
このプロジェクトはWPF アプリではないことに注意してください。つまり、WPF ライブラリを参照していません。このスタンドアロンのConsoleApplicationに追加する必要がある唯一の参照は ですSystem.Xaml
。これは、 (歴史的遺物)using
に関する記述があっても当てはまります。System.Windows.Markup
XAML サービスのサポートが WPF (およびその他の場所) からコア BCL ライブラリに移動されたのは、.NET 4.0 でした。
私見ですが、この変更により、XAML サービスは、誰も聞いたことのない最大の 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 Servicesを学んでいるので、考えすぎていたのかもしれません。以下は、組み込みのマークアップ拡張機能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;
}
};
}