Browse Source
* Support Design.PreviewWith for any previewing content * ApplyDesignModeProperties should use bindings * Add support for IDataTemplate PreviewWith * Add new members as agreed on API review * ApplyDesignModeProperties probably should be privateapi * Add xml comments * Extract Design.CreatePreviewWithControl and move it out of DesignerSupport project * Several fixes, add tets * Update API diff * wip * Add DesignModeTests, move relevant tests to this class * Restore methods accepting control, add IStyle target overload * Restore Design.SetPreviewWith(AvaloniaObject, Control) * `SetPreviewWith(any, Control? control)` should accept nullable control * Add `SetDataContext(IDataTemplate control, object? value)` * Better Obsolete messagepull/20211/head
committed by
GitHub
6 changed files with 693 additions and 163 deletions
@ -1,102 +1,433 @@ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Provides attached properties and helpers for design-time support.
|
|||
/// </summary>
|
|||
public static class Design |
|||
{ |
|||
private static Dictionary<object, Control?>? _previewWith; |
|||
private static Dictionary<object, ITemplate<Control>?> s_previewWith = []; |
|||
private static Dictionary<object, object?> s_templateDataContext = []; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the application is running in design mode.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This property is typically used to enable or disable features that should only be available
|
|||
/// at design-time, such as sample/preview data.
|
|||
/// </remarks>
|
|||
public static bool IsDesignMode { get; internal set; } |
|||
|
|||
/// <summary>
|
|||
/// Defines the Height attached property.
|
|||
/// </summary>
|
|||
public static readonly AttachedProperty<double> HeightProperty = AvaloniaProperty |
|||
.RegisterAttached<Control, double>("Height", typeof (Design)); |
|||
|
|||
/// <summary>
|
|||
/// Sets the design-time height for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to set the height for.</param>
|
|||
/// <param name="value">The height value.</param>
|
|||
public static void SetHeight(Control control, double value) |
|||
{ |
|||
control.SetValue(HeightProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the design-time height for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to get the height from.</param>
|
|||
/// <returns>The height value.</returns>
|
|||
public static double GetHeight(Control control) |
|||
{ |
|||
return control.GetValue(HeightProperty); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the Width attached property.
|
|||
/// </summary>
|
|||
public static readonly AttachedProperty<double> WidthProperty = AvaloniaProperty |
|||
.RegisterAttached<Control, double>("Width", typeof(Design)); |
|||
|
|||
/// <summary>
|
|||
/// Sets the design-time width for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to set the width for.</param>
|
|||
/// <param name="value">The width value.</param>
|
|||
public static void SetWidth(Control control, double value) |
|||
{ |
|||
control.SetValue(WidthProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the design-time width for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to get the width from.</param>
|
|||
/// <returns>The width value.</returns>
|
|||
public static double GetWidth(Control control) |
|||
{ |
|||
return control.GetValue(WidthProperty); |
|||
} |
|||
|
|||
public static readonly AttachedProperty<object> DataContextProperty = AvaloniaProperty |
|||
.RegisterAttached<Control, object>("DataContext", typeof (Design)); |
|||
/// <summary>
|
|||
/// Defines the DataContext attached property.
|
|||
/// </summary>
|
|||
public static readonly AttachedProperty<object?> DataContextProperty = AvaloniaProperty |
|||
.RegisterAttached<Control, object?>("DataContext", typeof (Design)); |
|||
|
|||
public static void SetDataContext(Control control, object value) |
|||
/// <summary>
|
|||
/// Sets the design-time data context for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to set the data context for.</param>
|
|||
/// <param name="value">The data context value.</param>
|
|||
public static void SetDataContext(Control control, object? value) |
|||
{ |
|||
control.SetValue(DataContextProperty, value); |
|||
} |
|||
|
|||
public static object GetDataContext(Control control) |
|||
/// <summary>
|
|||
/// Gets the design-time data context for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to get the data context from.</param>
|
|||
/// <returns>The data context value.</returns>
|
|||
public static object? GetDataContext(Control control) |
|||
{ |
|||
return control.GetValue(DataContextProperty); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Sets the design-time data context for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to set the data context for.</param>
|
|||
/// <param name="value">The data context value.</param>
|
|||
public static void SetDataContext(IDataTemplate control, object? value) |
|||
{ |
|||
s_templateDataContext[control] = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the design-time data context for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to get the data context from.</param>
|
|||
/// <returns>The data context value.</returns>
|
|||
public static object? GetDataContext(IDataTemplate control) |
|||
{ |
|||
return s_templateDataContext.TryGetValue(control, out var value) ? value : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the PreviewWith attached property.
|
|||
/// </summary>
|
|||
public static readonly AttachedProperty<Control?> PreviewWithProperty = AvaloniaProperty |
|||
.RegisterAttached<AvaloniaObject, Control?>("PreviewWith", typeof (Design)); |
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="AvaloniaObject"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control to be rendered in the previewer
|
|||
/// for a given object.
|
|||
/// </remarks>
|
|||
/// <param name="target">The target object.</param>
|
|||
/// <param name="control">The preview control.</param>
|
|||
// TODO12: Remove this overload in Avalonia 12
|
|||
[Obsolete("Use SetPreviewWith(AvaloniaObject, ITemplate<Control>) overload instead. Use <Template></Template> from XAML")] |
|||
public static void SetPreviewWith(AvaloniaObject target, Control? control) |
|||
{ |
|||
target.SetValue(PreviewWithProperty, control); |
|||
s_previewWith[target] = control is not null ? new FuncTemplate<Control>(() => control) : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="AvaloniaObject"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control template to be rendered in the previewer
|
|||
/// for a given object.
|
|||
/// </remarks>
|
|||
/// <param name="target">The target object.</param>
|
|||
/// <param name="template">The preview template.</param>
|
|||
public static void SetPreviewWith(AvaloniaObject target, ITemplate<Control>? template) |
|||
{ |
|||
s_previewWith[target] = template; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="ResourceDictionary"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control template to be rendered in the previewer.
|
|||
/// ResourceDictionary is attached to that control, displaying real time changes on the control.
|
|||
/// </remarks>
|
|||
/// <param name="target">The resource dictionary.</param>
|
|||
/// <param name="template">The preview template.</param>
|
|||
public static void SetPreviewWith(ResourceDictionary target, ITemplate<Control>? template) |
|||
{ |
|||
s_previewWith[target] = template; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="ResourceDictionary"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control to be rendered in the previewer.
|
|||
/// ResourceDictionary is attached to that control, displaying real time changes on the control.
|
|||
/// </remarks>
|
|||
/// <param name="target">The resource dictionary.</param>
|
|||
/// <param name="control">The preview control.</param>
|
|||
public static void SetPreviewWith(ResourceDictionary target, Control? control) |
|||
{ |
|||
_previewWith ??= new(); |
|||
_previewWith[target] = control; |
|||
s_previewWith[target] = control is not null ? new FuncTemplate<Control>(() => control) : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="IDataTemplate"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control template to be rendered in the previewer.
|
|||
/// Template must return ContentControl, and IDataTemplate will be set assigned to ContentControl.ContentTemplate property.
|
|||
/// </remarks>
|
|||
/// <param name="target">The data template.</param>
|
|||
/// <param name="template">The preview template.</param>
|
|||
public static void SetPreviewWith(IDataTemplate target, ITemplate<Control>? template) |
|||
{ |
|||
s_previewWith[target] = template is not null ? new FuncTemplate<Control>(template.Build) : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="IDataTemplate"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control to be rendered in the previewer.
|
|||
/// Template must return ContentControl, and IDataTemplate will be set assigned to ContentControl.ContentTemplate property.
|
|||
/// </remarks>
|
|||
/// <param name="target">The data template.</param>
|
|||
/// <param name="control">The preview control.</param>
|
|||
public static void SetPreviewWith(IDataTemplate target, Control? control) |
|||
{ |
|||
s_previewWith[target] = control is not null ? new FuncTemplate<Control>(() => control) : null; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="IDataTemplate"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control template to be rendered in the previewer.
|
|||
/// Template must return ContentControl, and IDataTemplate will be set assigned to ContentControl.ContentTemplate property.
|
|||
/// </remarks>
|
|||
/// <param name="target">The data template.</param>
|
|||
/// <param name="template">The preview template.</param>
|
|||
public static void SetPreviewWith(IStyle target, ITemplate<Control>? template) |
|||
{ |
|||
s_previewWith[target] = template is not null ? new FuncTemplate<Control>(template.Build) : null; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Sets a preview template for the specified <see cref="IDataTemplate"/> at design-time.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method allows you to specify a substitute control to be rendered in the previewer.
|
|||
/// Template must return ContentControl, and IDataTemplate will be set assigned to ContentControl.ContentTemplate property.
|
|||
/// </remarks>
|
|||
/// <param name="target">The data template.</param>
|
|||
/// <param name="control">The preview control.</param>
|
|||
public static void SetPreviewWith(IStyle target, Control? control) |
|||
{ |
|||
s_previewWith[target] = control is not null ? new FuncTemplate<Control>(() => control) : null; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Gets the preview control for the specified <see cref="AvaloniaObject"/> at design-time.
|
|||
/// </summary>
|
|||
/// <param name="target">The target object.</param>
|
|||
/// <returns>The preview control, or null.</returns>
|
|||
public static Control? GetPreviewWith(AvaloniaObject target) |
|||
{ |
|||
return target.GetValue(PreviewWithProperty); |
|||
return s_previewWith.TryGetValue(target, out var template) ? template?.Build() : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the preview control for the specified <see cref="ResourceDictionary"/> at design-time.
|
|||
/// </summary>
|
|||
/// <param name="target">The resource dictionary.</param>
|
|||
/// <returns>The preview control, or null.</returns>
|
|||
public static Control? GetPreviewWith(ResourceDictionary target) |
|||
{ |
|||
return _previewWith?[target]; |
|||
return s_previewWith.TryGetValue(target, out var template) ? template?.Build() : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the preview control for the specified <see cref="IDataTemplate"/> at design-time.
|
|||
/// </summary>
|
|||
/// <param name="target">The data template.</param>
|
|||
/// <returns>The preview control, or null.</returns>
|
|||
public static Control? GetPreviewWith(IDataTemplate target) |
|||
{ |
|||
return s_previewWith.TryGetValue(target, out var template) ? template?.Build() : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the preview control for the specified <see cref="IStyle"/> at design-time.
|
|||
/// </summary>
|
|||
/// <param name="target">The style.</param>
|
|||
/// <returns>The preview control, or null.</returns>
|
|||
public static Control? GetPreviewWith(IStyle target) |
|||
{ |
|||
return s_previewWith.TryGetValue(target, out var template) ? template?.Build() : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Identifies the DesignStyle attached property for design-time use.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This property allows you to apply a style to a control only at design-time, enabling
|
|||
/// custom visualizations or highlighting in the designer without affecting the runtime appearance.
|
|||
/// </remarks>
|
|||
public static readonly AttachedProperty<IStyle> DesignStyleProperty = AvaloniaProperty |
|||
.RegisterAttached<Control, IStyle>("DesignStyle", typeof(Design)); |
|||
|
|||
/// <summary>
|
|||
/// Sets the design-time style for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to set the style for.</param>
|
|||
/// <param name="value">The style value.</param>
|
|||
public static void SetDesignStyle(Control control, IStyle value) |
|||
{ |
|||
control.SetValue(DesignStyleProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the design-time style for a control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control to get the style from.</param>
|
|||
/// <returns>The style value.</returns>
|
|||
public static IStyle GetDesignStyle(Control control) |
|||
{ |
|||
return control.GetValue(DesignStyleProperty); |
|||
} |
|||
|
|||
[PrivateApi] |
|||
public static void ApplyDesignModeProperties(Control target, Control source) |
|||
{ |
|||
if (source.IsSet(WidthProperty)) |
|||
target.Width = source.GetValue(WidthProperty); |
|||
target.Bind(Layoutable.WidthProperty, target.GetBindingObservable(WidthProperty)); |
|||
if (source.IsSet(HeightProperty)) |
|||
target.Height = source.GetValue(HeightProperty); |
|||
target.Bind(Layoutable.HeightProperty, target.GetBindingObservable(HeightProperty)); |
|||
if (source.IsSet(DataContextProperty)) |
|||
target.DataContext = source.GetValue(DataContextProperty); |
|||
target.Bind(StyledElement.DataContextProperty, target.GetBindingObservable(DataContextProperty)); |
|||
if (source.IsSet(DesignStyleProperty)) |
|||
target.Styles.Add(source.GetValue(DesignStyleProperty)); |
|||
target.Styles.Add(GetDesignStyle(source)); |
|||
} |
|||
|
|||
[PrivateApi] |
|||
public static Control CreatePreviewWithControl(object target) |
|||
{ |
|||
if (target is IStyle style) |
|||
{ |
|||
var substitute = GetPreviewWith((AvaloniaObject)style); |
|||
if (substitute != null) |
|||
{ |
|||
substitute.Styles.Add(style); |
|||
return substitute; |
|||
} |
|||
|
|||
return new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, |
|||
new TextBlock {Text = "<Design.PreviewWith>"}, |
|||
new TextBlock {Text = " <Border Padding=\"20\"><!-- YOUR CONTROL FOR PREVIEW HERE --></Border>"}, |
|||
new TextBlock {Text = "</Design.PreviewWith>"}, |
|||
new TextBlock {Text = "before setters in your first Style"} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
if (target is ResourceDictionary resources) |
|||
{ |
|||
var substitute = GetPreviewWith(resources); |
|||
if (substitute != null) |
|||
{ |
|||
substitute.Resources.MergedDictionaries.Add(resources); |
|||
return substitute; |
|||
} |
|||
|
|||
return new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new TextBlock {Text = "ResourceDictionaries can't be previewed without Design.PreviewWith. Add"}, |
|||
new TextBlock {Text = "<Design.PreviewWith>"}, |
|||
new TextBlock {Text = " <Border Padding=\"20\"><!-- YOUR CONTROL FOR PREVIEW HERE --></Border>"}, |
|||
new TextBlock {Text = "</Design.PreviewWith>"}, |
|||
new TextBlock {Text = "in your resource dictionary"} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
if (target is IDataTemplate template) |
|||
{ |
|||
if (GetPreviewWith(template) is ContentControl substitute) |
|||
{ |
|||
substitute.ContentTemplate = template; |
|||
if (!substitute.IsSet(DataContextProperty) && substitute.IsSet(StyledElement.DataContextProperty)) |
|||
{ |
|||
substitute.DataContext = substitute.GetValue(StyledElement.DataContextProperty); |
|||
} |
|||
return substitute; |
|||
} |
|||
|
|||
if (GetDataContext(template) is { } dataContext) |
|||
{ |
|||
substitute = new ContentControl |
|||
{ |
|||
ContentTemplate = template, |
|||
DataContext = dataContext, |
|||
Content = dataContext |
|||
}; |
|||
return substitute; |
|||
} |
|||
|
|||
return new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new TextBlock {Text = "IDataTemplate can't be previewed without Design.PreviewWith."}, |
|||
new TextBlock {Text = "Provide ContentControl with your design data as Content. Previewer will set ContentTemplate from this file."}, |
|||
new TextBlock {Text = "<Design.PreviewWith>"}, |
|||
new TextBlock {Text = " <ContentControl Content=\"{x:Static YOUR_DATA_OBJECT_HERE}\" />"}, |
|||
new TextBlock {Text = "</Design.PreviewWith>"} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
if (target is Application) |
|||
{ |
|||
return new TextBlock { Text = "This file cannot be previewed in design view" }; |
|||
} |
|||
|
|||
if (target is AvaloniaObject avObject and not Window |
|||
&& GetPreviewWith(avObject) is { } previewWith) |
|||
{ |
|||
return previewWith; |
|||
} |
|||
|
|||
if (target is not Control control) |
|||
{ |
|||
return new TextBlock { Text = "This file cannot be previewed in design view" }; |
|||
} |
|||
|
|||
return control; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,149 @@ |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.Markup.Xaml.MarkupExtensions; |
|||
using Avalonia.Media; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.Styling; |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Primitives; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests; |
|||
|
|||
public class DesignTests : ScopedTestBase |
|||
{ |
|||
[Fact] |
|||
public void Should_Preview_Resource_Dictionary_With_Template() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
var dictionary = new ResourceDictionary { ["TestColor"] = Colors.Green }; |
|||
Design.SetPreviewWith(dictionary, |
|||
new FuncTemplate<Control>(static () => |
|||
new Border { [!Border.BackgroundProperty] = new DynamicResourceExtension("TestColor") })); |
|||
|
|||
var preview = Design.CreatePreviewWithControl(dictionary); |
|||
|
|||
var border = Assert.IsType<Border>(preview); |
|||
Assert.Equal(Colors.Green, ((ISolidColorBrush)border.Background!).Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Preview_DataTemplate_With_ContentControl() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
const string testData = "Test Data"; |
|||
var dataTemplate = new FuncDataTemplate<string>((data, _) => |
|||
new TextBlock { Text = data }); |
|||
Design.SetPreviewWith(dataTemplate, |
|||
new FuncTemplate<Control>(static () => new ContentControl { Content = testData })); |
|||
|
|||
var preview = Design.CreatePreviewWithControl(dataTemplate); |
|||
|
|||
var previewContentControl = Assert.IsType<ContentControl>(preview); |
|||
Assert.Equal(testData, previewContentControl.Content); |
|||
Assert.Same(dataTemplate, previewContentControl.ContentTemplate); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Preview_DataTemplate_With_DataContext() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
const string testData = "Test Data"; |
|||
var dataTemplate = new FuncDataTemplate<string>((data, _) => |
|||
new TextBlock { Text = data }); |
|||
Design.SetDataContext(dataTemplate, testData); |
|||
|
|||
var preview = Design.CreatePreviewWithControl(dataTemplate); |
|||
|
|||
var previewContentControl = Assert.IsType<ContentControl>(preview); |
|||
Assert.Equal(testData, previewContentControl.Content); |
|||
Assert.Same(dataTemplate, previewContentControl.ContentTemplate); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Preview_Control_With_Another_Control() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
var control = new TextBlock(); |
|||
Design.SetPreviewWith(control, |
|||
new FuncTemplate<Control>(static () => new Border())); |
|||
|
|||
var preview = Design.CreatePreviewWithControl(control); |
|||
|
|||
Assert.IsType<Border>(preview); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Apply_Design_Mode_Properties() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
var control = new ContentControl(); |
|||
|
|||
Design.SetWidth(control, 200); |
|||
Design.SetHeight(control, 150); |
|||
Design.SetDataContext(control, "TestDataContext"); |
|||
Design.SetDesignStyle(control, |
|||
new Style(x => x.OfType<ContentControl>()) |
|||
{ |
|||
Setters = { new Setter(TemplatedControl.BackgroundProperty, Brushes.Yellow) } |
|||
}); |
|||
|
|||
Design.ApplyDesignModeProperties(control, control); |
|||
|
|||
Assert.Equal(200, control.Width); |
|||
Assert.Equal(150, control.Height); |
|||
Assert.Equal("TestDataContext", control.DataContext); |
|||
Assert.Contains(control.Styles, |
|||
s => ((Style)s).Setters.OfType<Setter>().First().Property == TemplatedControl.BackgroundProperty); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Throw_Exception_On_Generic_Style() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
var preview = Design.CreatePreviewWithControl(new Style(x => x.OfType<Button>())); |
|||
|
|||
// We are not going to test specific content of the placeholder preview control.
|
|||
// But it should not throw and should not return null at least.
|
|||
Assert.NotNull(preview); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Throw_Exception_On_Generic_Resource_Dictionary() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
var preview = Design.CreatePreviewWithControl(new ResourceDictionary()); |
|||
|
|||
Assert.NotNull(preview); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Throw_Exception_On_Generic_Data_Template() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
var preview = Design.CreatePreviewWithControl(new FuncDataTemplate<string>((data, _) => |
|||
new TextBlock { Text = data })); |
|||
|
|||
Assert.NotNull(preview); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Throw_Exception_On_Application() |
|||
{ |
|||
using var _ = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
|
|||
var app = new Application(); |
|||
var preview = Design.CreatePreviewWithControl(app); |
|||
|
|||
Assert.NotNull(preview); |
|||
} |
|||
} |
|||
@ -0,0 +1,193 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml.Templates; |
|||
using Avalonia.Media; |
|||
using Avalonia.Styling; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.Xaml; |
|||
|
|||
public class DesignModeTests : XamlTestBase |
|||
{ |
|||
public static object SomeStaticProperty { get; set; } |
|||
|
|||
[Fact] |
|||
public void Design_Mode_PreviewWith_Should_Be_Ignored_Without_Design_Mode() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) |
|||
{ |
|||
var obj = (Control)AvaloniaRuntimeXamlLoader.Load(@"
|
|||
<Button xmlns='https://github.com/avaloniaui'>
|
|||
<Design.PreviewWith> |
|||
<Template> |
|||
<Border /> |
|||
</Template> |
|||
</Design.PreviewWith> |
|||
</Button>", designMode: false);
|
|||
var preview = Design.CreatePreviewWithControl(obj); |
|||
// Should return the original control, not the preview.
|
|||
Assert.IsType<Button>(preview); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Design_Mode_PreviewWith_Works_With_Control_Template() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) |
|||
{ |
|||
var obj = (Control)AvaloniaRuntimeXamlLoader.Load(@"
|
|||
<Button xmlns='https://github.com/avaloniaui'>
|
|||
<Design.PreviewWith> |
|||
<Template> |
|||
<Border> |
|||
<Button /> |
|||
</Border> |
|||
</Template> |
|||
</Design.PreviewWith> |
|||
</Button>", designMode: true);
|
|||
var preview = Design.CreatePreviewWithControl(obj); |
|||
var previewBorder = Assert.IsType<Border>(preview); |
|||
Assert.IsType<Button>(previewBorder.Child); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Design_Mode_PreviewWith_Works_With_Style() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) |
|||
{ |
|||
var obj = (Style)AvaloniaRuntimeXamlLoader.Load(@"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
|||
Selector='Border.preview-border' > |
|||
<Design.PreviewWith> |
|||
<Border Classes='preview-border' /> |
|||
</Design.PreviewWith> |
|||
<Setter Property='Background' Value='Red'/> |
|||
</Style>", designMode: true);
|
|||
var preview = Design.CreatePreviewWithControl(obj); |
|||
var previewBorder = Assert.IsType<Border>(preview); |
|||
previewBorder.ApplyStyling(); |
|||
Assert.Equal(Colors.Red, (previewBorder.Background as ISolidColorBrush)?.Color); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Design_Mode_PreviewWith_Works_With_ResourceDictionary() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) |
|||
{ |
|||
var obj = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(@"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Design.PreviewWith> |
|||
<Border Background='{DynamicResource PreviewBackground}' /> |
|||
</Design.PreviewWith> |
|||
<SolidColorBrush x:Key='PreviewBackground' Color='Red'/> |
|||
</ResourceDictionary>", designMode: true);
|
|||
var preview = Design.CreatePreviewWithControl(obj); |
|||
var previewBorder = Assert.IsType<Border>(preview); |
|||
Assert.Equal(Colors.Red, (previewBorder.Background as ISolidColorBrush)?.Color); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Design_Mode_PreviewWith_Works_With_IDataTemplate() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) |
|||
{ |
|||
var obj = (DataTemplate)AvaloniaRuntimeXamlLoader.Load(@"
|
|||
<DataTemplate xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
|||
x:DataType='SolidColorBrush'> |
|||
<Design.PreviewWith> |
|||
<ContentControl> |
|||
<ContentControl.Content> |
|||
<SolidColorBrush Color='Red'/> |
|||
</ContentControl.Content> |
|||
</ContentControl> |
|||
</Design.PreviewWith> |
|||
<Border Background='{Binding}' /> |
|||
</DataTemplate>", designMode: true);
|
|||
var preview = Design.CreatePreviewWithControl(obj); |
|||
var previewContentControl = Assert.IsType<ContentControl>(preview); |
|||
previewContentControl.ApplyTemplate(); |
|||
previewContentControl.Presenter!.UpdateChild(); |
|||
var border = previewContentControl.FindDescendantOfType<Border>(); |
|||
Assert.NotNull(border); |
|||
Assert.Equal(Colors.Red, (border.Background as ISolidColorBrush)?.Color); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Design_Mode_Properties_Should_Be_Ignored_At_Runtime_And_Set_In_Design_Mode() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) |
|||
{ |
|||
foreach (var designMode in new[] { true, false }) |
|||
{ |
|||
var obj = (Window)AvaloniaRuntimeXamlLoader.Load(@"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
|
|||
xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
|
|||
mc:Ignorable='d' |
|||
d:DataContext='data-context' |
|||
d:DesignWidth='123' |
|||
d:DesignHeight='321'> |
|||
</Window>", designMode: designMode);
|
|||
var context = Design.GetDataContext(obj); |
|||
var width = Design.GetWidth(obj); |
|||
var height = Design.GetHeight(obj); |
|||
if (designMode) |
|||
{ |
|||
Assert.Equal("data-context", context); |
|||
Assert.Equal(123, width); |
|||
Assert.Equal(321, height); |
|||
} |
|||
else |
|||
{ |
|||
Assert.False(obj.IsSet(Design.DataContextProperty)); |
|||
Assert.False(obj.IsSet(Design.WidthProperty)); |
|||
Assert.False(obj.IsSet(Design.HeightProperty)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// https://github.com/AvaloniaUI/Avalonia/issues/2570
|
|||
[Fact] |
|||
public void Design_Mode_Throws_On_Invalid_Static_Property_Reference() |
|||
{ |
|||
SomeStaticProperty = "123"; |
|||
var ex = Assert.ThrowsAny<Exception>(() => AvaloniaRuntimeXamlLoader |
|||
.Load(@"
|
|||
<UserControl |
|||
xmlns='https://github.com/avaloniaui'
|
|||
xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
|
|||
xmlns:tests='using:Avalonia.Markup.Xaml.UnitTests.Xaml' |
|||
d:DataContext='{x:Static tests:DesignModeTests.SomeStaticPropery}' |
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'/>", typeof(XamlIlTests).Assembly,
|
|||
designMode: true)); |
|||
Assert.Contains("Unable to resolve ", ex.Message); |
|||
Assert.Contains(" as static field, property, constant or enum value", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Design_Mode_DataContext_Should_Be_Set() |
|||
{ |
|||
SomeStaticProperty = "123"; |
|||
|
|||
var loaded = (UserControl)AvaloniaRuntimeXamlLoader |
|||
.Load(@"
|
|||
<UserControl |
|||
xmlns='https://github.com/avaloniaui'
|
|||
xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
|
|||
xmlns:tests='using:Avalonia.Markup.Xaml.UnitTests.Xaml' |
|||
d:DataContext='{x:Static tests:DesignModeTests.SomeStaticProperty}' |
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'/>", typeof(XamlIlTests).Assembly,
|
|||
designMode: true); |
|||
Assert.Equal(Design.GetDataContext(loaded), SomeStaticProperty); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue