Browse Source

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
pull/20211/head
Max Katz 2 months ago
committed by GitHub
parent
commit
5f0cb2ea45
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 365
      src/Avalonia.Controls/Design.cs
  2. 57
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  3. 149
      tests/Avalonia.Controls.UnitTests/DesignTests.cs
  4. 38
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  5. 193
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DesignModeTests.cs
  6. 54
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

365
src/Avalonia.Controls/Design.cs

@ -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;
}
}
}

57
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -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;

149
tests/Avalonia.Controls.UnitTests/DesignTests.cs

@ -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);
}
}

38
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -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()
{

193
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DesignModeTests.cs

@ -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);
}
}

54
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -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()
{

Loading…
Cancel
Save