2011-11-28 29 views
13

Tôi đang sử dụng XAML serialization cho biểu đồ đối tượng (ngoài WPF/Silverlight) và tôi đang cố gắng tạo tiện ích đánh dấu tùy chỉnh. tham chiếu đến các thành viên được chọn của một bộ sưu tập được xác định ở nơi khác trong XAML.Cách tạo tiện ích mở rộng đánh dấu XAML trả về bộ sưu tập

Dưới đây là một đoạn mã XAML đơn giản thể hiện những gì tôi nhằm mục đích để đạt được:

<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> 

Các Ngôn ngữ tài sản của mỗi Nước đối tượng là trở nên thông dụng với một IEnumerable < Ngôn ngữ > tài liệu tham khảo có chứa tới Ngôn ngữ đối tượng được chỉ định trong LanguageSelector, là tiện ích đánh dấu tùy chỉnh.

Đây là nỗ lực của tôi lúc tạo ra phần mở rộng đánh dấu tùy chỉnh mà sẽ phục vụ trong vai trò này:

[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; 
    } 
} 

Trong thực tế, mã này hầu như hoạt động. Miễn là các đối tượng được tham chiếu được khai báo trong XAML trước khi các đối tượng tham chiếu đến chúng, phương thức Cung cấp phương thức trả về một số IEnumerable < Ngôn ngữ > được điền với các mục được tham chiếu. Này hoạt động vì các tham chiếu ngược với Ngôn ngữ trường hợp được giải quyết bằng các dòng mã sau:

var token = service.Resolve(item); 

Nhưng, nếu XAML chứa tham chiếu về phía trước (vì Ngôn ngữ đối tượng được khai báo sau khi Nước đối tượng), nó phá vỡ vì điều này yêu cầu mã thông báo sửa lỗi (rõ ràng) không thể được truyền tới Ngôn ngữ.

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

Theo một thử nghiệm tôi đã cố gắng chuyển đổi bộ sưu tập trở lại Collection < đối tượng > với hy vọng rằng XAML sẽ bằng cách nào đó giải quyết các thẻ sau, nhưng nó ném ngoại lệ cast không hợp lệ trong deserialization.

Có ai có thể đề xuất cách tốt nhất để làm việc này không?

Rất cám ơn, Tim

+0

+1 Cảm ơn bạn đã đăng bài này. Tôi thấy nó là một bài tập tuyệt vời cho đường cong học tập của tôi trên XAML Servces.Tôi hy vọng các đề xuất tôi đã đăng bên dưới có thể vẫn được sử dụng cho bạn một năm sau đó. –

+0

@Glenn Slayden: cảm ơn bạn đã theo dõi về điều này. Bạn đã đề xuất hai giải pháp rất sáng tạo. Mặc dù mã của tôi hiện đã được triển khai và đang hoạt động bằng ý tưởng do DmitryG đề xuất, sẽ rất thú vị khi xem xét và điều chỉnh nó để sử dụng cách tiếp cận ngắn gọn hơn của bạn. –

Trả lời

6

Bạn không thể sử dụng phương pháp GetFixupToken vì họ trở về một loại nội bộ mà chỉ có thể được xử lý bởi những nhà văn XAML hiện có mà làm việc dưới bối cảnh schema XAML mặc định.

Nhưng bạn có thể sử dụng các phương pháp sau đây thay vì:

[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; } 
    } 
} 
+0

Cảm ơn bạn rất nhiều! Đây là một giải pháp thực sự thông minh. –

+2

Dmitry, xem câu trả lời của tôi và giải pháp làm việc trên trang này; không có vấn đề gì khi sử dụng 'GetFixupToken' (và không yêu cầu mã hóa không được hỗ trợ) nhưng kỹ thuật chắc chắn không được ghi chép đầy đủ. Bí quyết là mã thông báo - trong khi mờ đục với bạn - được xây dựng để bạn có thể chứa các tên mà bạn cần. Những gì không được đề cập ở bất cứ đâu là bạn sau đó * trả lại mã thông báo * từ phương thức 'ProvideValue' của bạn. Thao tác này sẽ yêu cầu Dịch vụ XAML thử lại sau. –

+0

@GlennSlayden: Xin chào Glen, cảm ơn vì giải pháp thay thế. Thông tin bạn cung cấp rất quan tâm đến tôi ... (+1 !!!) – DmitryG

12

Dưới đây là một dự án đầy đủ và làm việc có thể giải quyết vấn đề của bạn. Lúc đầu, tôi sẽ đề nghị sử dụng thuộc tính [XamlSetMarkupExtension] trên lớp Country của bạn, nhưng thực sự tất cả những gì bạn cần là độ phân giải tên chuyển tiếp của XamlSchemaContext.

Mặc dù tài liệu cho tính năng đó là rất mỏng trên mặt đất, bạn có thể trên thực tế nói XAML Dịch vụ hoãn yếu tố mục tiêu của bạn, và các mã sau đây cho thấy như thế nào. Lưu ý rằng tất cả các tên ngôn ngữ của bạn được giải quyết đúng cách mặc dù các phần từ ví dụ của bạn bị đảo ngược.

Về cơ bản, nếu bạn cần một tên không thể giải quyết được, bạn yêu cầu trì hoãn bằng cách trả lại mã thông báo sửa lỗi. Đúng vậy, như Dmitry nói nó mờ đục với chúng tôi, nhưng điều đó không quan trọng. Khi bạn gọi GetFixupToken(...), bạn sẽ chỉ định danh sách tên mà bạn cần. Tiện ích mở rộng đánh dấu của bạn— ProvideValue, tức là — sẽ được gọi lại sau khi các tên đó đã có sẵn. Tại thời điểm đó, về cơ bản nó là một việc làm.

Không hiển thị ở đây là bạn cũng nên kiểm tra thuộc tính BooleanIsFixupTokenAvailable trên số IXamlNameResolver. Nếu tên thật sự được tìm thấy sau, thì điều này sẽ trả về true. Nếu giá trị là false và bạn vẫn có tên chưa được giải quyết, thì bạn nên cố gắng không hoạt động, có lẽ vì các tên được đưa ra trong Xaml cuối cùng không thể được giải quyết.

Một số người có thể tò mò muốn lưu ý rằng dự án này là không ứng dụng WPF, tức là nó không tham chiếu đến thư viện WPF; tham chiếu duy nhất bạn phải thêm vào độc lập này ConsoleApplicationSystem.Xaml. Điều này đúng ngay cả khi có tuyên bố using cho System.Windows.Markup (một tạo tác lịch sử). Chính trong .NET 4.0, việc hỗ trợ XAML Services đã được chuyển từ WPF (và các nơi khác) và vào các thư viện BCL cốt lõi.

IMHO, thay đổi này được thực hiện Dịch vụ XAML tính năng BCL lớn nhất mà không ai được nghe đến. Không có nền tảng tốt hơn để phát triển một ứng dụng cấp hệ thống lớn có khả năng cấu hình lại triệt để như là một yêu cầu chính. Một ví dụ về một 'ứng dụng' như vậy là 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 
     } 
    }; 
} 

[sửa ...]

Như Tôi chỉ học Dịch vụ XAML, tôi có thể có được overthinking nó. Dưới đây là giải pháp đơn giản cho phép bạn thiết lập bất kỳ tham chiếu nào bạn mong muốn - hoàn toàn bằng XAML - chỉ sử dụng các tiện ích đánh dấu được tích hợp sẵn x:Arrayx:Reference. Bằng cách nào đó tôi đã không nhận ra rằng không chỉ có thể x:Reference cư một thuộc tính (như nó thường thấy: {x:Reference some_name}), nhưng nó cũng có thể đứng như một thẻ XAML của riêng mình (<Reference Name="some_name" />). Trong cả hai trường hợp, nó hoạt động như một tham chiếu proxy tới một đối tượng ở đâu đó trong tài liệu. Điều này cho phép bạn điền một số x:Array với các tham chiếu đến các đối tượng XAML khác và sau đó chỉ cần đặt mảng làm giá trị cho thuộc tính của bạn. Trình phân tích cú pháp XAML tự động giải quyết các tham chiếu chuyển tiếp theo yêu cầu.

<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> 

Để dùng thử, đây là ứng dụng giao diện điều khiển hoàn chỉnh ngay lập tức đối tượng myClass từ tệp XAML trước đó. Như trước đây, hãy thêm tham chiếu đến System.Xaml.dll và thay đổi dòng đầu tiên của XAML ở trên để khớp với tên lắp ráp của bạn.

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; 
     } 
    }; 
} 
+1

Đây là một câu trả lời hay - tôi có thể hỏi tài nguyên học tập của bạn dành cho Dịch vụ XAML không? Đó là một cái gì đó tôi đang cố gắng để có được vào bản thân mình nhưng không thể tìm thấy nhiều trong cách hướng dẫn, chỉ có tài liệu MSDN có thể khá dày đặc – AlexFoxGill

+3

câu hỏi hay; suy nghĩ lại bây giờ, phần lớn những gì tôi đã học về XAML là từ những giờ vô tận dành cho .NET Reflector và kiểm tra các dấu vết ngăn xếp thời gian chạy. Và một điều chắc chắn đã giúp ngay từ đầu là tạo các lớp/lớp proxy mỏng phân lớp mọi chức năng của XamlType, XamlMember, v.v. May mắn thay, các dịch vụ XAML rất hào phóng với các callbacks này. Cược của tôi được in trên bảng điều khiển gỡ lỗi mỗi khi XAML gọi cho tôi - với thụt lề - và hiển thị các địa điểm/thời điểm tốt nhất để chèn móc thực tế của bạn. –

Các vấn đề liên quan