// Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using OmniXaml; using Perspex.Platform; namespace Perspex.Markup.Xaml { using Context; using Controls; using Data; using OmniXaml.ObjectAssembler; using System.Linq; /// /// Loads XAML for a perspex application. /// public class PerspexXamlLoader : XmlLoader { private static PerspexParserFactory s_parserFactory; private static IInstanceLifeCycleListener s_lifeCycleListener = new PerspexLifeCycleListener(); private static Stack s_uriStack = new Stack(); /// /// Initializes a new instance of the class. /// public PerspexXamlLoader() : this(GetParserFactory()) { } /// /// Initializes a new instance of the class. /// /// The parser factory to use. public PerspexXamlLoader(IParserFactory xamlParserFactory) : base(xamlParserFactory) { } /// /// Gets the URI of the XAML file currently being loaded. /// /// /// TODO: Making this internal for now as I'm not sure that this is the correct /// thing to do, but its needd by to get the URL of /// the currently loading XAML file, as we can't use the OmniXAML parsing context /// there. Maybe we need a way to inject OmniXAML context into the objects its /// constructing? /// internal static Uri UriContext => s_uriStack.Count > 0 ? s_uriStack.Peek() : null; /// /// Loads the XAML into a Perspex component. /// /// The object to load the XAML into. public static void Load(object obj) { Contract.Requires(obj != null); var loader = new PerspexXamlLoader(); loader.Load(obj.GetType(), obj); } /// /// Loads the XAML for a type. /// /// The type. /// /// The optional instance into which the XAML should be loaded. /// /// The loaded object. public object Load(Type type, object rootInstance = null) { Contract.Requires(type != null); // HACK: Currently Visual Studio is forcing us to change the extension of xaml files // in certain situations, so we try to load .xaml and if that's not found we try .xaml. // Ideally we'd be able to use .xaml everywhere var assetLocator = PerspexLocator.Current.GetService(); if (assetLocator == null) { throw new InvalidOperationException( "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); } foreach (var uri in GetUrisFor(type)) { if (assetLocator.Exists(uri)) { using (var stream = assetLocator.Open(uri)) { var initialize = rootInstance as ISupportInitialize; initialize?.BeginInit(); return Load(stream, rootInstance, uri); } } } throw new FileNotFoundException("Unable to find view for " + type.FullName); } /// /// Loads XAML from a URI. /// /// The URI of the XAML file. /// /// A base URI to use if is relative. /// /// /// The optional instance into which the XAML should be loaded. /// /// The loaded object. public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) { Contract.Requires(uri != null); var assetLocator = PerspexLocator.Current.GetService(); if (assetLocator == null) { throw new InvalidOperationException( "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); } using (var stream = assetLocator.Open(uri, baseUri)) { return Load(stream, rootInstance, uri); } } /// /// Loads XAML from a string. /// /// The string containing the XAML. /// /// The optional instance into which the XAML should be loaded. /// /// The loaded object. public object Load(string xaml, object rootInstance = null) { Contract.Requires(xaml != null); using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) { return Load(stream, rootInstance); } } /// /// Loads XAML from a stream. /// /// The stream containing the XAML. /// /// The optional instance into which the XAML should be loaded. /// /// The URI of the XAML /// The loaded object. public object Load(Stream stream, object rootInstance = null, Uri uri = null) { try { if (uri != null) { s_uriStack.Push(uri); } var result = base.Load(stream, new Settings { RootInstance = rootInstance, InstanceLifeCycleListener = s_lifeCycleListener, ParsingContext = new Dictionary { { "Uri", uri } } }); var topLevel = result as TopLevel; if (topLevel != null) { DelayedBinding.ApplyBindings(topLevel); } return result; } finally { if (uri != null) { s_uriStack.Pop(); } } } private static PerspexParserFactory GetParserFactory() { if (s_parserFactory == null) { s_parserFactory = new PerspexParserFactory(); } return s_parserFactory; } /// /// Gets the URI for a type. /// /// The type. /// The URI. private static IEnumerable GetUrisFor(Type type) { var asm = type.GetTypeInfo().Assembly.GetName().Name; var typeName = type.FullName; yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); } } } namespace Perspex { public static class XamlObjectExtensions { public static TObject LoadFromXaml(this TObject obj) { Markup.Xaml.PerspexXamlLoader.Load(obj); return obj; } } }