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.Collections.Generic; |
||||
using System.Runtime.CompilerServices; |
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Metadata; |
||||
using Avalonia.Styling; |
using Avalonia.Styling; |
||||
|
|
||||
namespace Avalonia.Controls |
namespace Avalonia.Controls |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Provides attached properties and helpers for design-time support.
|
||||
|
/// </summary>
|
||||
public static class Design |
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; } |
public static bool IsDesignMode { get; internal set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the Height attached property.
|
||||
|
/// </summary>
|
||||
public static readonly AttachedProperty<double> HeightProperty = AvaloniaProperty |
public static readonly AttachedProperty<double> HeightProperty = AvaloniaProperty |
||||
.RegisterAttached<Control, double>("Height", typeof (Design)); |
.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) |
public static void SetHeight(Control control, double value) |
||||
{ |
{ |
||||
control.SetValue(HeightProperty, 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) |
public static double GetHeight(Control control) |
||||
{ |
{ |
||||
return control.GetValue(HeightProperty); |
return control.GetValue(HeightProperty); |
||||
} |
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the Width attached property.
|
||||
|
/// </summary>
|
||||
public static readonly AttachedProperty<double> WidthProperty = AvaloniaProperty |
public static readonly AttachedProperty<double> WidthProperty = AvaloniaProperty |
||||
.RegisterAttached<Control, double>("Width", typeof(Design)); |
.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) |
public static void SetWidth(Control control, double value) |
||||
{ |
{ |
||||
control.SetValue(WidthProperty, 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) |
public static double GetWidth(Control control) |
||||
{ |
{ |
||||
return control.GetValue(WidthProperty); |
return control.GetValue(WidthProperty); |
||||
} |
} |
||||
|
|
||||
public static readonly AttachedProperty<object> DataContextProperty = AvaloniaProperty |
/// <summary>
|
||||
.RegisterAttached<Control, object>("DataContext", typeof (Design)); |
/// 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); |
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); |
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 |
public static readonly AttachedProperty<Control?> PreviewWithProperty = AvaloniaProperty |
||||
.RegisterAttached<AvaloniaObject, Control?>("PreviewWith", typeof (Design)); |
.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) |
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) |
public static void SetPreviewWith(ResourceDictionary target, Control? control) |
||||
{ |
{ |
||||
_previewWith ??= new(); |
s_previewWith[target] = control is not null ? new FuncTemplate<Control>(() => control) : null; |
||||
_previewWith[target] = control; |
} |
||||
|
|
||||
|
/// <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) |
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) |
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 |
public static readonly AttachedProperty<IStyle> DesignStyleProperty = AvaloniaProperty |
||||
.RegisterAttached<Control, IStyle>("DesignStyle", typeof(Design)); |
.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) |
public static void SetDesignStyle(Control control, IStyle value) |
||||
{ |
{ |
||||
control.SetValue(DesignStyleProperty, 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) |
public static IStyle GetDesignStyle(Control control) |
||||
{ |
{ |
||||
return control.GetValue(DesignStyleProperty); |
return control.GetValue(DesignStyleProperty); |
||||
} |
} |
||||
|
|
||||
|
[PrivateApi] |
||||
public static void ApplyDesignModeProperties(Control target, Control source) |
public static void ApplyDesignModeProperties(Control target, Control source) |
||||
{ |
{ |
||||
if (source.IsSet(WidthProperty)) |
if (source.IsSet(WidthProperty)) |
||||
target.Width = source.GetValue(WidthProperty); |
target.Bind(Layoutable.WidthProperty, target.GetBindingObservable(WidthProperty)); |
||||
if (source.IsSet(HeightProperty)) |
if (source.IsSet(HeightProperty)) |
||||
target.Height = source.GetValue(HeightProperty); |
target.Bind(Layoutable.HeightProperty, target.GetBindingObservable(HeightProperty)); |
||||
if (source.IsSet(DataContextProperty)) |
if (source.IsSet(DataContextProperty)) |
||||
target.DataContext = source.GetValue(DataContextProperty); |
target.Bind(StyledElement.DataContextProperty, target.GetBindingObservable(DataContextProperty)); |
||||
if (source.IsSet(DesignStyleProperty)) |
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