Design improvements (#18300)
* 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 message
This commit is contained in:
+348
-17
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Embedding.Offscreen;
|
||||
using Avalonia.Controls.Platform;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
|
||||
@@ -19,7 +20,6 @@ namespace Avalonia.DesignerSupport
|
||||
public static Window LoadDesignerWindow(string xaml, string assemblyPath, string xamlFileProjectPath, double renderScaling)
|
||||
{
|
||||
Window window;
|
||||
Control control;
|
||||
using (PlatformManager.DesignerMode())
|
||||
{
|
||||
var loader = AvaloniaLocator.Current.GetRequiredService<AvaloniaXamlLoader.IRuntimeXamlLoader>();
|
||||
@@ -45,60 +45,9 @@ namespace Avalonia.DesignerSupport
|
||||
DesignMode = true,
|
||||
UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue) && parsedValue
|
||||
});
|
||||
var style = loaded as IStyle;
|
||||
var resources = loaded as ResourceDictionary;
|
||||
if (style != null)
|
||||
{
|
||||
var substitute = Design.GetPreviewWith((AvaloniaObject)style);
|
||||
if (substitute != null)
|
||||
{
|
||||
substitute.Styles.Add(style);
|
||||
control = substitute;
|
||||
}
|
||||
else
|
||||
control = 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"}
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (resources != null)
|
||||
{
|
||||
var substitute = Design.GetPreviewWith(resources);
|
||||
if (substitute != null)
|
||||
{
|
||||
substitute.Resources.MergedDictionaries.Add(resources);
|
||||
control = substitute;
|
||||
}
|
||||
else
|
||||
control = 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"}
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (loaded is Application)
|
||||
control = new TextBlock { Text = "This file cannot be previewed in design view" };
|
||||
else
|
||||
control = (Control)loaded;
|
||||
|
||||
window = control as Window;
|
||||
if (window == null)
|
||||
{
|
||||
window = new Window() { Content = (Control)control };
|
||||
}
|
||||
var control = Design.CreatePreviewWithControl(loaded);
|
||||
window = control as Window ?? new Window { Content = control };
|
||||
|
||||
if (window.PlatformImpl is OffscreenTopLevelImplBase offscreenImpl)
|
||||
offscreenImpl.RenderScaling = renderScaling;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -825,44 +825,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Design_Mode_Properties_Should_Be_Ignored_At_Runtime_And_Set_In_Design_Mode()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
|
||||
{
|
||||
var xaml = @"
|
||||
<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>";
|
||||
foreach (var designMode in new[] {true, false})
|
||||
{
|
||||
var obj = (Window)AvaloniaRuntimeXamlLoader.Load(xaml, 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Slider_Properties_Can_Be_Set_In_Any_Order()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,8 @@ using System.Runtime.CompilerServices;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Data.Core;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml.UnitTests.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.UnitTests;
|
||||
using Avalonia.VisualTree;
|
||||
@@ -151,57 +148,6 @@ namespace Avalonia.Markup.Xaml.UnitTests
|
||||
|
||||
}
|
||||
|
||||
static void AssertThrows(Action callback, Func<Exception, bool> check)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback();
|
||||
}
|
||||
catch (Exception e) when (check(e))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception("Expected exception was not thrown");
|
||||
}
|
||||
|
||||
public static object SomeStaticProperty { get; set; }
|
||||
|
||||
[Fact]
|
||||
public void Bug2570()
|
||||
{
|
||||
SomeStaticProperty = "123";
|
||||
AssertThrows(() => AvaloniaRuntimeXamlLoader
|
||||
.Load(@"
|
||||
<UserControl
|
||||
xmlns='https://github.com/avaloniaui'
|
||||
xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
|
||||
xmlns:tests='clr-namespace:Avalonia.Markup.Xaml.UnitTests'
|
||||
d:DataContext='{x:Static tests:XamlIlTests.SomeStaticPropery}'
|
||||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'/>", typeof(XamlIlTests).Assembly,
|
||||
designMode: true),
|
||||
e => e.Message.Contains("Unable to resolve ")
|
||||
&& e.Message.Contains(" as static field, property, constant or enum value"));
|
||||
|
||||
}
|
||||
|
||||
[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='clr-namespace:Avalonia.Markup.Xaml.UnitTests'
|
||||
d:DataContext='{x:Static tests:XamlIlTests.SomeStaticProperty}'
|
||||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'/>", typeof(XamlIlTests).Assembly,
|
||||
designMode: true);
|
||||
Assert.Equal(Design.GetDataContext(loaded), SomeStaticProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Attached_Properties_From_Static_Types_Should_Work_In_Style_Setters_Bug_2561()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user