C# 如何创建返回集合的XAML标记扩展
我正在为对象图(WPF/Silverlight之外)使用XAML序列化,并尝试创建一个自定义标记扩展,该扩展将允许使用对XAML中别处定义的集合的选定成员的引用来填充集合属性 下面是一个简化的XAML片段,演示了我的目标:C# 如何创建返回集合的XAML标记扩展,c#,xaml,collections,markup-extensions,C#,Xaml,Collections,Markup Extensions,我正在为对象图(WPF/Silverlight之外)使用XAML序列化,并尝试创建一个自定义标记扩展,该扩展将允许使用对XAML中别处定义的集合的选定成员的引用来填充集合属性 下面是一个简化的XAML片段,演示了我的目标: <myClass.Languages> <LanguagesCollection> <Language x:Name="English" /> <Language x:Name="French"
<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>
但是,如果XAML包含前向引用(因为语言对象是在国家对象之后声明的),它就会中断,因为这需要修复标记,而修复标记(显然)不能转换为语言
作为一个实验,我尝试将返回的集合转换为集合,希望XAML稍后能够以某种方式解析令牌,但它在反序列化过程中抛出无效的强制转换异常
有谁能建议如何最好地让这个工作
非常感谢,,
Tim无法使用这些方法,因为它们返回的内部类型只能由在默认XAML架构上下文下工作的现有XAML编写器处理
但您可以使用以下方法:
[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) {
string[] items = Items.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new IEnumerableWrapper(items, serviceProvider);
}
class IEnumerableWrapper : IEnumerable<Language>, IEnumerator<Language> {
string[] items;
IServiceProvider serviceProvider;
public IEnumerableWrapper(string[] items, IServiceProvider serviceProvider) {
this.items = items;
this.serviceProvider = serviceProvider;
}
public IEnumerator<Language> GetEnumerator() {
return this;
}
int position = -1;
public Language Current {
get {
string name = items[position];
// TODO use any possible methods to resolve object by name
var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider
var nameScope = NameScope.GetNameScope(rootProvider.RootObject as DependencyObject);
return nameScope.FindName(name) as Language;
}
}
public void Dispose() {
Reset();
}
public bool MoveNext() {
return ++position < items.Length;
}
public void Reset() {
position = -1;
}
object IEnumerator.Current { get { return Current; } }
IEnumerator IEnumerable.GetEnumerator() { return this; }
}
}
[ContentProperty(“项目”)]
[MarkupExtensionReturnType(typeof(IEnumerable))]
公共类语言选择器:MarkupExtension{
公共语言选择器(字符串项){
项目=项目;
}
[施工争议(“项目”)]
公共字符串项{get;set;}
公共覆盖对象ProviderValue(IServiceProvider服务提供程序){
string[]items=items.Split(新[]{',},StringSplitOptions.RemoveEmptyEntries);
返回新的IEnumerableRapper(项目、服务提供商);
}
类IEnumerableRapper:IEnumerable,IEnumerator{
字符串[]项;
IServiceProvider服务提供商;
公共IEnumerableRapper(字符串[]项,IServiceProvider服务提供程序){
这个项目=项目;
this.serviceProvider=serviceProvider;
}
公共IEnumerator GetEnumerator(){
归还这个;
}
int位置=-1;
公共语言流动{
得到{
字符串名称=项目[位置];
//TODO使用任何可能的方法按名称解析对象
var rootProvider=serviceProvider.GetService(typeof(IRootObjectProvider))作为IRootObjectProvider
var nameScope=nameScope.GetNameScope(rootProvider.RootObject作为DependencyObject);
返回nameScope.FindName(name)作为语言;
}
}
公共空间处置(){
重置();
}
公共bool MoveNext(){
return++位置
这是一个完整的、有效的项目,可以解决您的问题。起初我打算建议在Country
类上使用[XamlSetMarkupExtension]
属性,但实际上您只需要XamlSchemaContext
的前向名称解析
尽管该特性的文档非常少,但实际上您可以告诉Xaml服务延迟目标元素,下面的代码将演示如何执行。请注意,即使示例中的部分颠倒了,所有语言名称都得到了正确解析
基本上,如果您需要一个无法解析的名称,您可以通过返回一个fixup令牌来请求延迟。是的,正如Dmitry提到的,这对我们来说是不透明的,但这并不重要。调用GetFixupToken(…)
时,将指定所需名称的列表。您的标记扩展名-提供了DEVALUE
,当这些名称可用时,稍后将再次调用该扩展名。在这一点上,它基本上是一个重复
此处未显示的是,您还应该检查ixamlnamesolver
上的Boolean
属性IsFixupTokenAvailable
。如果以后确实可以找到这些名称,则应返回true
。如果该值为false
,并且您仍然有未解析的名称,那么您应该努力使操作失败,这可能是因为Xaml中给出的名称最终无法解析
有些人可能会好奇地注意到,这个项目不是WPF应用程序,也就是说,它没有引用WPF库;您必须添加到此独立控制台应用程序的唯一参考是System.Xaml
。这是正确的,即使对于System.Windows.Markup
(历史工件)存在使用语句。在.NET4.0中,XAML服务支持从WPF(和其他地方)转移到核心BCL库中
我的意思是,这一变化使得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文件中的myClass
对象。与前面一样,添加对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;
}
};
}
使用系统;
使用System.Collections.Generic;
使用System.Collections.ObjectModel;
使用System.IO;
使用System.Xaml;
名称空间测试
{
公共类语言{}
公共类国家{public IEnumerable Languages{get;set;}}
公共类myClass
{
集合_l=新集合();
公共集合语言{get{return}
集合_c=新集合();
公共收集国家{get{return}
静态void Main()
{
var xxr=新的XamlXmlReader(新的StreamReader
[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) {
string[] items = Items.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new IEnumerableWrapper(items, serviceProvider);
}
class IEnumerableWrapper : IEnumerable<Language>, IEnumerator<Language> {
string[] items;
IServiceProvider serviceProvider;
public IEnumerableWrapper(string[] items, IServiceProvider serviceProvider) {
this.items = items;
this.serviceProvider = serviceProvider;
}
public IEnumerator<Language> GetEnumerator() {
return this;
}
int position = -1;
public Language Current {
get {
string name = items[position];
// TODO use any possible methods to resolve object by name
var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider
var nameScope = NameScope.GetNameScope(rootProvider.RootObject as DependencyObject);
return nameScope.FindName(name) as Language;
}
}
public void Dispose() {
Reset();
}
public bool MoveNext() {
return ++position < items.Length;
}
public void Reset() {
position = -1;
}
object IEnumerator.Current { get { return Current; } }
IEnumerator IEnumerable.GetEnumerator() { return this; }
}
}
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
}
};
}
<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>
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;
}
};
}