committed by
GitHub
37 changed files with 678 additions and 302 deletions
@ -1,6 +1,6 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300" |
|||
Title="Avalonia Control Gallery" |
|||
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog" |
|||
xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog"> |
|||
xmlns:local="clr-namespace:ControlCatalog"> |
|||
<local:MainView/> |
|||
</Window> |
|||
@ -1,11 +1,237 @@ |
|||
namespace Avalonia.Markup.Xaml |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml.Data; |
|||
using Avalonia.Markup.Xaml.PortableXaml; |
|||
using Avalonia.Platform; |
|||
using Portable.Xaml; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Markup.Xaml |
|||
{ |
|||
public class AvaloniaXamlLoader : AvaloniaXamlLoaderPortableXaml |
|||
/// <summary>
|
|||
/// Loads XAML for a avalonia application.
|
|||
/// </summary>
|
|||
public class AvaloniaXamlLoader |
|||
{ |
|||
public static object Parse(string xaml) |
|||
=> new AvaloniaXamlLoader().Load(xaml); |
|||
private readonly AvaloniaXamlSchemaContext _context = GetContext(); |
|||
|
|||
private static AvaloniaXamlSchemaContext GetContext() |
|||
{ |
|||
var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>(); |
|||
|
|||
if (result == null) |
|||
{ |
|||
result = AvaloniaXamlSchemaContext.Create(); |
|||
|
|||
AvaloniaLocator.CurrentMutable |
|||
.Bind<AvaloniaXamlSchemaContext>() |
|||
.ToConstant(result); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
|
|||
/// </summary>
|
|||
public AvaloniaXamlLoader() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the XAML into a Avalonia component.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to load the XAML into.</param>
|
|||
public static void Load(object obj) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(obj != null); |
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
loader.Load(obj.GetType(), obj); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the XAML for a type.
|
|||
/// </summary>
|
|||
/// <param name="type">The type.</param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(Type type, object rootInstance = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(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 = AvaloniaLocator.Current.GetService<IAssetLoader>(); |
|||
|
|||
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(); |
|||
try |
|||
{ |
|||
return Load(stream, type.Assembly, rootInstance, uri); |
|||
} |
|||
finally |
|||
{ |
|||
initialize?.EndInit(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
throw new FileNotFoundException("Unable to find view for " + type.FullName); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads XAML from a URI.
|
|||
/// </summary>
|
|||
/// <param name="uri">The URI of the XAML file.</param>
|
|||
/// <param name="baseUri">
|
|||
/// A base URI to use if <paramref name="uri"/> is relative.
|
|||
/// </param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(uri != null); |
|||
|
|||
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>(); |
|||
|
|||
if (assetLocator == null) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); |
|||
} |
|||
|
|||
var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); |
|||
using (var stream = asset.Item1) |
|||
{ |
|||
try |
|||
{ |
|||
return Load(stream, asset.Item2, rootInstance, uri); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
var uriString = uri.ToString(); |
|||
if (!uri.IsAbsoluteUri) |
|||
{ |
|||
uriString = new Uri(baseUri, uri).AbsoluteUri; |
|||
} |
|||
throw new XamlLoadException("Error loading xaml at " + uriString, e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads XAML from a string.
|
|||
/// </summary>
|
|||
/// <param name="xaml">The string containing the XAML.</param>
|
|||
/// <param name="localAssembly">Default assembly for clr-namespace:</param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(xaml != null); |
|||
|
|||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) |
|||
{ |
|||
return Load(stream, localAssembly, rootInstance); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads XAML from a stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream containing the XAML.</param>
|
|||
/// <param name="localAssembly">Default assembly for clr-namespace</param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <param name="uri">The URI of the XAML</param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null) |
|||
{ |
|||
var readerSettings = new XamlXmlReaderSettings() |
|||
{ |
|||
BaseUri = uri, |
|||
LocalAssembly = localAssembly |
|||
}; |
|||
|
|||
var reader = new XamlXmlReader(stream, _context, readerSettings); |
|||
|
|||
object result = LoadFromReader( |
|||
reader, |
|||
AvaloniaXamlContext.For(readerSettings, rootInstance)); |
|||
|
|||
var topLevel = result as TopLevel; |
|||
|
|||
if (topLevel != null) |
|||
{ |
|||
DelayedBinding.ApplyBindings(topLevel); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null) |
|||
{ |
|||
var writer = AvaloniaXamlObjectWriter.Create( |
|||
reader.SchemaContext, |
|||
context, |
|||
parentAmbientProvider); |
|||
|
|||
XamlServices.Transform(reader, writer); |
|||
writer.ApplyAllDelayedProperties(); |
|||
return writer.Result; |
|||
} |
|||
|
|||
internal static object LoadFromReader(XamlReader reader) |
|||
{ |
|||
//return XamlServices.Load(reader);
|
|||
return LoadFromReader(reader, null); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the URI for a type.
|
|||
/// </summary>
|
|||
/// <param name="type">The type.</param>
|
|||
/// <returns>The URI.</returns>
|
|||
private static IEnumerable<Uri> 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); |
|||
} |
|||
|
|||
public static object Parse(string xaml, Assembly localAssembly = null) |
|||
=> new AvaloniaXamlLoader().Load(xaml, localAssembly); |
|||
|
|||
public static T Parse<T>(string xaml) |
|||
=> (T)Parse(xaml); |
|||
public static T Parse<T>(string xaml, Assembly localAssembly = null) |
|||
=> (T)Parse(xaml, localAssembly); |
|||
} |
|||
} |
|||
@ -1,227 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml.Data; |
|||
using Avalonia.Markup.Xaml.PortableXaml; |
|||
using Avalonia.Platform; |
|||
using Portable.Xaml; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Markup.Xaml |
|||
{ |
|||
/// <summary>
|
|||
/// Loads XAML for a avalonia application.
|
|||
/// </summary>
|
|||
public class AvaloniaXamlLoaderPortableXaml |
|||
{ |
|||
private readonly AvaloniaXamlSchemaContext _context = GetContext(); |
|||
|
|||
private static AvaloniaXamlSchemaContext GetContext() |
|||
{ |
|||
var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>(); |
|||
|
|||
if (result == null) |
|||
{ |
|||
result = AvaloniaXamlSchemaContext.Create(); |
|||
|
|||
AvaloniaLocator.CurrentMutable |
|||
.Bind<AvaloniaXamlSchemaContext>() |
|||
.ToConstant(result); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
|
|||
/// </summary>
|
|||
public AvaloniaXamlLoaderPortableXaml() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the XAML into a Avalonia component.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to load the XAML into.</param>
|
|||
public static void Load(object obj) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(obj != null); |
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
loader.Load(obj.GetType(), obj); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the XAML for a type.
|
|||
/// </summary>
|
|||
/// <param name="type">The type.</param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(Type type, object rootInstance = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(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 = AvaloniaLocator.Current.GetService<IAssetLoader>(); |
|||
|
|||
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(); |
|||
try |
|||
{ |
|||
return Load(stream, rootInstance, uri); |
|||
} |
|||
finally |
|||
{ |
|||
initialize?.EndInit(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
throw new FileNotFoundException("Unable to find view for " + type.FullName); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads XAML from a URI.
|
|||
/// </summary>
|
|||
/// <param name="uri">The URI of the XAML file.</param>
|
|||
/// <param name="baseUri">
|
|||
/// A base URI to use if <paramref name="uri"/> is relative.
|
|||
/// </param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(uri != null); |
|||
|
|||
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>(); |
|||
|
|||
if (assetLocator == null) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); |
|||
} |
|||
|
|||
using (var stream = assetLocator.Open(uri, baseUri)) |
|||
{ |
|||
try |
|||
{ |
|||
return Load(stream, rootInstance, uri); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
var uriString = uri.ToString(); |
|||
if (!uri.IsAbsoluteUri) |
|||
{ |
|||
uriString = new Uri(baseUri, uri).AbsoluteUri; |
|||
} |
|||
throw new XamlLoadException("Error loading xaml at " + uriString, e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads XAML from a string.
|
|||
/// </summary>
|
|||
/// <param name="xaml">The string containing the XAML.</param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(string xaml, object rootInstance = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(xaml != null); |
|||
|
|||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) |
|||
{ |
|||
return Load(stream, rootInstance); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads XAML from a stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream containing the XAML.</param>
|
|||
/// <param name="rootInstance">
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </param>
|
|||
/// <param name="uri">The URI of the XAML</param>
|
|||
/// <returns>The loaded object.</returns>
|
|||
public object Load(Stream stream, object rootInstance = null, Uri uri = null) |
|||
{ |
|||
var readerSettings = new XamlXmlReaderSettings() |
|||
{ |
|||
BaseUri = uri, |
|||
LocalAssembly = rootInstance?.GetType().GetTypeInfo().Assembly |
|||
}; |
|||
|
|||
var reader = new XamlXmlReader(stream, _context, readerSettings); |
|||
|
|||
object result = LoadFromReader( |
|||
reader, |
|||
AvaloniaXamlContext.For(readerSettings, rootInstance)); |
|||
|
|||
var topLevel = result as TopLevel; |
|||
|
|||
if (topLevel != null) |
|||
{ |
|||
DelayedBinding.ApplyBindings(topLevel); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null) |
|||
{ |
|||
var writer = AvaloniaXamlObjectWriter.Create( |
|||
reader.SchemaContext, |
|||
context); |
|||
|
|||
XamlServices.Transform(reader, writer); |
|||
writer.ApplyAllDelayedProperties(); |
|||
return writer.Result; |
|||
} |
|||
|
|||
internal static object LoadFromReader(XamlReader reader) |
|||
{ |
|||
//return XamlServices.Load(reader);
|
|||
return LoadFromReader(reader, null); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the URI for a type.
|
|||
/// </summary>
|
|||
/// <param name="type">The type.</param>
|
|||
/// <returns>The URI.</returns>
|
|||
private static IEnumerable<Uri> 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); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit c0664014455392ac221a765e66f9837704339b6f |
|||
Subproject commit cdf46d7892def8a6ba29f12a9339147377f7cf5c |
|||
@ -0,0 +1,71 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Styling; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions |
|||
{ |
|||
public class BindingExtensionTests |
|||
{ |
|||
|
|||
[Fact] |
|||
public void BindingExtension_Binds_To_Source() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Resources> |
|||
<x:String x:Key='text'>foobar</x:String> |
|||
</Window.Resources> |
|||
|
|||
<TextBlock Name='textBlock' Text='{Binding Source={StaticResource text}}'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var textBlock = window.FindControl<TextBlock>("textBlock"); |
|||
|
|||
window.Show(); |
|||
|
|||
Assert.Equal("foobar", textBlock.Text); |
|||
} |
|||
} |
|||
|
|||
private IDisposable StyledWindow(params (string, string)[] assets) |
|||
{ |
|||
var services = TestServices.StyledWindow.With( |
|||
assetLoader: new MockAssetLoader(assets), |
|||
theme: () => new Styles |
|||
{ |
|||
WindowStyle(), |
|||
}); |
|||
|
|||
return UnitTestApplication.Start(services); |
|||
} |
|||
|
|||
private Style WindowStyle() |
|||
{ |
|||
return new Style(x => x.OfType<Window>()) |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter( |
|||
Window.TemplateProperty, |
|||
new FuncControlTemplate<Window>(x => |
|||
new ContentPresenter |
|||
{ |
|||
Name = "PART_ContentPresenter", |
|||
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], |
|||
})) |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue