Browse Source

Merge remote-tracking branch 'origin/master' into fixes/osx-nswindow-refactor-fix-issues

pull/8154/head
Dan Walmsley 4 years ago
parent
commit
319fd1db2a
  1. 2
      build/CoreLibraries.props
  2. 1
      samples/BindingDemo/BindingDemo.csproj
  3. 2
      samples/ControlCatalog/ControlCatalog.csproj
  4. 6
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  5. 3
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  6. 1
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  7. 3
      samples/Previewer/Previewer.csproj
  8. 1
      samples/RenderDemo/RenderDemo.csproj
  9. 1
      samples/Sandbox/Sandbox.csproj
  10. 1
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  11. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  12. 1
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  13. 19
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  14. 5
      src/Avalonia.Base/DirectPropertyBase.cs
  15. 2
      src/Avalonia.Base/Input/InputElement.cs
  16. 8
      src/Avalonia.Base/StyledPropertyBase.cs
  17. 5
      src/Avalonia.Base/Styling/IStyleInstance.cs
  18. 10
      src/Avalonia.Base/Styling/PropertySetterInstance.cs
  19. 31
      src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs
  20. 9
      src/Avalonia.Base/Styling/Setter.cs
  21. 35
      src/Avalonia.Base/Styling/StyleInstance.cs
  22. 2
      src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
  23. 33
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  24. 75
      src/Avalonia.Controls/Canvas.cs
  25. 4
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  26. 22
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  27. 25
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  28. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  29. 2
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  30. 19
      src/Avalonia.Controls/TextBox.cs
  31. 53
      src/Avalonia.Controls/Viewbox.cs
  32. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  33. 13
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
  34. 6
      src/Avalonia.FreeDesktop/NativeMethods.cs
  35. 1
      src/Avalonia.Themes.Default/Controls/TextBox.xaml
  36. 1
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  37. 4
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  38. 11
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  39. 2
      tests/Avalonia.Controls.UnitTests/CalendarTests.cs
  40. 104
      tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
  41. 19
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  42. 221
      tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs

2
build/CoreLibraries.props

@ -3,8 +3,6 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Base/Avalonia.Base.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Base/Avalonia.Base.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Controls/Avalonia.Controls.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Controls/Avalonia.Controls.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />

1
samples/BindingDemo/BindingDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup> </ItemGroup>

2
samples/ControlCatalog/ControlCatalog.csproj

@ -25,6 +25,8 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup> </ItemGroup>

6
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -66,6 +66,12 @@
FontFamily="Comic Sans MS" FontFamily="Comic Sans MS"
InputMethod.IsInputMethodEnabled="False" InputMethod.IsInputMethodEnabled="False"
Foreground="Red"/> Foreground="Red"/>
<TextBox AcceptsReturn="True"
TextWrapping="Wrap"
Width="200"
Height="125"
LineHeight="32"
Text="Multiline TextBox with TextWrapping and increased LineHeight.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" Spacing="8" Margin="8"> <StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label> <Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label>

3
samples/IntegrationTestApp/IntegrationTestApp.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />

1
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -7,6 +7,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup> </ItemGroup>

3
samples/Previewer/Previewer.csproj

@ -9,6 +9,9 @@
</Compile> </Compile>
<EmbeddedResource Include="**\*.xaml" /> <EmbeddedResource Include="**\*.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> <Import Project="..\..\build\ReferenceCoreLibraries.props" />

1
samples/RenderDemo/RenderDemo.csproj

@ -12,6 +12,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />

1
samples/Sandbox/Sandbox.csproj

@ -10,6 +10,7 @@
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />

1
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup> </ItemGroup>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,6 +22,7 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
<ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" />

1
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" /> <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />

19
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -17,7 +17,7 @@ namespace Avalonia.Data.Core.Plugins
new Dictionary<(Type, string), PropertyInfo?>(); new Dictionary<(Type, string), PropertyInfo?>();
/// <inheritdoc/> /// <inheritdoc/>
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null; public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null;
/// <summary> /// <summary>
/// Starts monitoring the value of a property on an object. /// Starts monitoring the value of a property on an object.
@ -36,7 +36,7 @@ namespace Avalonia.Data.Core.Plugins
if (!reference.TryGetTarget(out var instance) || instance is null) if (!reference.TryGetTarget(out var instance) || instance is null)
return null; return null;
var p = GetFirstPropertyWithName(instance.GetType(), propertyName); var p = GetFirstPropertyWithName(instance, propertyName);
if (p != null) if (p != null)
{ {
@ -50,8 +50,16 @@ namespace Avalonia.Data.Core.Plugins
} }
} }
private PropertyInfo? GetFirstPropertyWithName(Type type, string propertyName) private const BindingFlags PropertyBindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName)
{ {
if (instance is IReflectableType reflectableType)
return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags);
var type = instance.GetType();
var key = (type, propertyName); var key = (type, propertyName);
if (!_propertyLookup.TryGetValue(key, out var propertyInfo)) if (!_propertyLookup.TryGetValue(key, out var propertyInfo))
@ -66,10 +74,7 @@ namespace Avalonia.Data.Core.Plugins
{ {
PropertyInfo? found = null; PropertyInfo? found = null;
const BindingFlags bindingFlags = var properties = type.GetProperties(PropertyBindingFlags);
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
var properties = type.GetProperties(bindingFlags);
foreach (PropertyInfo propertyInfo in properties) foreach (PropertyInfo propertyInfo in properties)
{ {

5
src/Avalonia.Base/DirectPropertyBase.cs

@ -2,7 +2,6 @@
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
@ -188,10 +187,10 @@ namespace Avalonia
} }
else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
{ {
return new PropertySetterLazyInstance<TValue>( return new PropertySetterTemplateInstance<TValue>(
target, target,
this, this,
() => (TValue)template.Build()); template);
} }
else else
{ {

2
src/Avalonia.Base/Input/InputElement.cs

@ -144,7 +144,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerMovedEvent = public static readonly RoutedEvent<PointerEventArgs> PointerMovedEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>( RoutedEvent.Register<InputElement, PointerEventArgs>(
"PointerMove", "PointerMoved",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>

8
src/Avalonia.Base/StyledPropertyBase.cs

@ -1,9 +1,7 @@
using System; using System;
using System.Diagnostics;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
@ -12,7 +10,7 @@ namespace Avalonia
/// </summary> /// </summary>
public abstract class StyledPropertyBase<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor public abstract class StyledPropertyBase<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor
{ {
private bool _inherits; private readonly bool _inherits;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class. /// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
@ -243,10 +241,10 @@ namespace Avalonia
} }
else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
{ {
return new PropertySetterLazyInstance<TValue>( return new PropertySetterTemplateInstance<TValue>(
target, target,
this, this,
() => (TValue)template.Build()); template);
} }
else else
{ {

5
src/Avalonia.Base/Styling/IStyleInstance.cs

@ -14,6 +14,11 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
IStyle Source { get; } IStyle Source { get; }
/// <summary>
/// Gets a value indicating whether this style has an activator.
/// </summary>
bool HasActivator { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether this style is active. /// Gets a value indicating whether this style is active.
/// </summary> /// </summary>

10
src/Avalonia.Base/Styling/PropertySetterInstance.cs

@ -44,7 +44,7 @@ namespace Avalonia.Styling
{ {
if (hasActivator) if (hasActivator)
{ {
if (_styledProperty is object) if (_styledProperty is not null)
{ {
_subscription = _target.Bind(_styledProperty, this, BindingPriority.StyleTrigger); _subscription = _target.Bind(_styledProperty, this, BindingPriority.StyleTrigger);
} }
@ -55,13 +55,15 @@ namespace Avalonia.Styling
} }
else else
{ {
if (_styledProperty is object) var target = (AvaloniaObject) _target;
if (_styledProperty is not null)
{ {
_subscription = _target.SetValue(_styledProperty!, _value, BindingPriority.Style); _subscription = target.SetValue(_styledProperty!, _value, BindingPriority.Style);
} }
else else
{ {
_target.SetValue(_directProperty!, _value); target.SetValue(_directProperty!, _value);
} }
} }
} }

31
src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs → src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs

@ -11,42 +11,42 @@ namespace Avalonia.Styling
/// evaluated. /// evaluated.
/// </summary> /// </summary>
/// <typeparam name="T">The target property type.</typeparam> /// <typeparam name="T">The target property type.</typeparam>
internal class PropertySetterLazyInstance<T> : SingleSubscriberObservableBase<BindingValue<T>>, internal class PropertySetterTemplateInstance<T> : SingleSubscriberObservableBase<BindingValue<T>>,
ISetterInstance ISetterInstance
{ {
private readonly IStyleable _target; private readonly IStyleable _target;
private readonly StyledPropertyBase<T>? _styledProperty; private readonly StyledPropertyBase<T>? _styledProperty;
private readonly DirectPropertyBase<T>? _directProperty; private readonly DirectPropertyBase<T>? _directProperty;
private readonly Func<T> _valueFactory; private readonly ITemplate _template;
private BindingValue<T> _value; private BindingValue<T> _value;
private IDisposable? _subscription; private IDisposable? _subscription;
private bool _isActive; private bool _isActive;
public PropertySetterLazyInstance( public PropertySetterTemplateInstance(
IStyleable target, IStyleable target,
StyledPropertyBase<T> property, StyledPropertyBase<T> property,
Func<T> valueFactory) ITemplate template)
{ {
_target = target; _target = target;
_styledProperty = property; _styledProperty = property;
_valueFactory = valueFactory; _template = template;
} }
public PropertySetterLazyInstance( public PropertySetterTemplateInstance(
IStyleable target, IStyleable target,
DirectPropertyBase<T> property, DirectPropertyBase<T> property,
Func<T> valueFactory) ITemplate template)
{ {
_target = target; _target = target;
_directProperty = property; _directProperty = property;
_valueFactory = valueFactory; _template = template;
} }
public void Start(bool hasActivator) public void Start(bool hasActivator)
{ {
_isActive = !hasActivator; _isActive = !hasActivator;
if (_styledProperty is object) if (_styledProperty is not null)
{ {
var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style; var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style;
_subscription = _target.Bind(_styledProperty, this, priority); _subscription = _target.Bind(_styledProperty, this, priority);
@ -77,7 +77,7 @@ namespace Avalonia.Styling
public override void Dispose() public override void Dispose()
{ {
if (_subscription is object) if (_subscription is not null)
{ {
var sub = _subscription; var sub = _subscription;
_subscription = null; _subscription = null;
@ -85,7 +85,7 @@ namespace Avalonia.Styling
} }
else if (_isActive) else if (_isActive)
{ {
if (_styledProperty is object) if (_styledProperty is not null)
{ {
_target.ClearValue(_styledProperty); _target.ClearValue(_styledProperty);
} }
@ -101,22 +101,21 @@ namespace Avalonia.Styling
protected override void Subscribed() => PublishNext(); protected override void Subscribed() => PublishNext();
protected override void Unsubscribed() { } protected override void Unsubscribed() { }
private T GetValue() private void EnsureTemplate()
{ {
if (_value.HasValue) if (_value.HasValue)
{ {
return _value.Value; return;
} }
_value = _valueFactory(); _value = (T) _template.Build();
return _value.Value;
} }
private void PublishNext() private void PublishNext()
{ {
if (_isActive) if (_isActive)
{ {
GetValue(); EnsureTemplate();
PublishNext(_value); PublishNext(_value);
} }
else else

9
src/Avalonia.Base/Styling/Setter.cs

@ -1,9 +1,7 @@
using System; using System;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Utilities;
#nullable enable #nullable enable
@ -70,12 +68,5 @@ namespace Avalonia.Styling
return Property.CreateSetterInstance(target, Value); return Property.CreateSetterInstance(target, Value);
} }
private struct SetterVisitorData
{
public IStyleable target;
public object? value;
public ISetterInstance? result;
}
} }
} }

35
src/Avalonia.Base/Styling/StyleInstance.cs

@ -11,10 +11,10 @@ namespace Avalonia.Styling
/// <summary> /// <summary>
/// A <see cref="Style"/> which has been instanced on a control. /// A <see cref="Style"/> which has been instanced on a control.
/// </summary> /// </summary>
internal class StyleInstance : IStyleInstance, IStyleActivatorSink internal sealed class StyleInstance : IStyleInstance, IStyleActivatorSink
{ {
private readonly List<ISetterInstance>? _setters; private readonly ISetterInstance[]? _setters;
private readonly List<IDisposable>? _animations; private readonly IDisposable[]? _animations;
private readonly IStyleActivator? _activator; private readonly IStyleActivator? _activator;
private readonly Subject<bool>? _animationTrigger; private readonly Subject<bool>? _animationTrigger;
@ -30,41 +30,42 @@ namespace Avalonia.Styling
_activator = activator; _activator = activator;
IsActive = _activator is null; IsActive = _activator is null;
if (setters is object) if (setters is not null)
{ {
var setterCount = setters.Count; var setterCount = setters.Count;
_setters = new List<ISetterInstance>(setterCount); _setters = new ISetterInstance[setterCount];
for (var i = 0; i < setterCount; ++i) for (var i = 0; i < setterCount; ++i)
{ {
_setters.Add(setters[i].Instance(Target)); _setters[i] = setters[i].Instance(Target);
} }
} }
if (animations is object && target is Animatable animatable) if (animations is not null && target is Animatable animatable)
{ {
var animationsCount = animations.Count; var animationsCount = animations.Count;
_animations = new List<IDisposable>(animationsCount); _animations = new IDisposable[animationsCount];
_animationTrigger = new Subject<bool>(); _animationTrigger = new Subject<bool>();
for (var i = 0; i < animationsCount; ++i) for (var i = 0; i < animationsCount; ++i)
{ {
_animations.Add(animations[i].Apply(animatable, null, _animationTrigger)); _animations[i] = animations[i].Apply(animatable, null, _animationTrigger);
} }
} }
} }
public bool HasActivator => _activator is not null;
public bool IsActive { get; private set; } public bool IsActive { get; private set; }
public IStyle Source { get; } public IStyle Source { get; }
public IStyleable Target { get; } public IStyleable Target { get; }
public void Start() public void Start()
{ {
var hasActivator = _activator is object; var hasActivator = HasActivator;
if (_setters is object) if (_setters is not null)
{ {
foreach (var setter in _setters) foreach (var setter in _setters)
{ {
@ -76,7 +77,7 @@ namespace Avalonia.Styling
{ {
_activator!.Subscribe(this, 0); _activator!.Subscribe(this, 0);
} }
else if (_animationTrigger != null) else if (_animationTrigger is not null)
{ {
_animationTrigger.OnNext(true); _animationTrigger.OnNext(true);
} }
@ -84,7 +85,7 @@ namespace Avalonia.Styling
public void Dispose() public void Dispose()
{ {
if (_setters is object) if (_setters is not null)
{ {
foreach (var setter in _setters) foreach (var setter in _setters)
{ {
@ -92,11 +93,11 @@ namespace Avalonia.Styling
} }
} }
if (_animations is object) if (_animations is not null)
{ {
foreach (var subscripion in _animations) foreach (var subscription in _animations)
{ {
subscripion.Dispose(); subscription.Dispose();
} }
} }
@ -111,7 +112,7 @@ namespace Avalonia.Styling
_animationTrigger?.OnNext(value); _animationTrigger?.OnNext(value);
if (_setters is object) if (_setters is not null)
{ {
if (IsActive) if (IsActive)
{ {

2
src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs

@ -218,6 +218,8 @@ namespace Avalonia.Controls.Primitives
{ {
// No explicit height values were set so we can autosize // No explicit height values were set so we can autosize
autoSizeHeight = true; autoSizeHeight = true;
// We need to invalidate desired height in order to grow or shrink as needed
InvalidateDesiredHeight();
measureHeight = double.PositiveInfinity; measureHeight = double.PositiveInfinity;
} }
else else

33
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -7,6 +7,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Avalonia.Collections.Pooled;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
@ -172,13 +173,13 @@ namespace Avalonia.Controls.Primitives
if (MonthView != null) if (MonthView != null)
{ {
var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth; var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth;
var children = new List<IControl>(childCount); using var children = new PooledList<IControl>(childCount);
for (int i = 0; i < Calendar.RowsPerMonth; i++) for (int i = 0; i < Calendar.RowsPerMonth; i++)
{ {
if (_dayTitleTemplate != null) if (_dayTitleTemplate != null)
{ {
var cell = _dayTitleTemplate.Build(); var cell = (Control) _dayTitleTemplate.Build();
cell.DataContext = string.Empty; cell.DataContext = string.Empty;
cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.RowProperty, 0);
cell.SetValue(Grid.ColumnProperty, i); cell.SetValue(Grid.ColumnProperty, i);
@ -186,11 +187,16 @@ namespace Avalonia.Controls.Primitives
} }
} }
EventHandler<PointerPressedEventArgs> cellMouseLeftButtonDown = Cell_MouseLeftButtonDown;
EventHandler<PointerReleasedEventArgs> cellMouseLeftButtonUp = Cell_MouseLeftButtonUp;
EventHandler<PointerEventArgs> cellMouseEnter = Cell_MouseEnter;
EventHandler<RoutedEventArgs> cellClick = Cell_Click;
for (int i = 1; i < Calendar.RowsPerMonth; i++) for (int i = 1; i < Calendar.RowsPerMonth; i++)
{ {
for (int j = 0; j < Calendar.ColumnsPerMonth; j++) for (int j = 0; j < Calendar.ColumnsPerMonth; j++)
{ {
CalendarDayButton cell = new CalendarDayButton(); var cell = new CalendarDayButton();
if (Owner != null) if (Owner != null)
{ {
@ -198,10 +204,10 @@ namespace Avalonia.Controls.Primitives
} }
cell.SetValue(Grid.RowProperty, i); cell.SetValue(Grid.RowProperty, i);
cell.SetValue(Grid.ColumnProperty, j); cell.SetValue(Grid.ColumnProperty, j);
cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown; cell.CalendarDayButtonMouseDown += cellMouseLeftButtonDown;
cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp; cell.CalendarDayButtonMouseUp += cellMouseLeftButtonUp;
cell.PointerEnter += Cell_MouseEnter; cell.PointerEnter += cellMouseEnter;
cell.Click += Cell_Click; cell.Click += cellClick;
children.Add(cell); children.Add(cell);
} }
} }
@ -214,12 +220,15 @@ namespace Avalonia.Controls.Primitives
var childCount = Calendar.RowsPerYear * Calendar.ColumnsPerYear; var childCount = Calendar.RowsPerYear * Calendar.ColumnsPerYear;
var children = new List<IControl>(childCount); var children = new List<IControl>(childCount);
CalendarButton month; EventHandler<PointerPressedEventArgs> monthCalendarButtonMouseDown = Month_CalendarButtonMouseDown;
EventHandler<PointerReleasedEventArgs> monthCalendarButtonMouseUp = Month_CalendarButtonMouseUp;
EventHandler<PointerEventArgs> monthMouseEnter = Month_MouseEnter;
for (int i = 0; i < Calendar.RowsPerYear; i++) for (int i = 0; i < Calendar.RowsPerYear; i++)
{ {
for (int j = 0; j < Calendar.ColumnsPerYear; j++) for (int j = 0; j < Calendar.ColumnsPerYear; j++)
{ {
month = new CalendarButton(); var month = new CalendarButton();
if (Owner != null) if (Owner != null)
{ {
@ -227,9 +236,9 @@ namespace Avalonia.Controls.Primitives
} }
month.SetValue(Grid.RowProperty, i); month.SetValue(Grid.RowProperty, i);
month.SetValue(Grid.ColumnProperty, j); month.SetValue(Grid.ColumnProperty, j);
month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown; month.CalendarLeftMouseButtonDown += monthCalendarButtonMouseDown;
month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp; month.CalendarLeftMouseButtonUp += monthCalendarButtonMouseUp;
month.PointerEnter += Month_MouseEnter; month.PointerEnter += monthMouseEnter;
children.Add(month); children.Add(month);
} }
} }

75
src/Avalonia.Controls/Canvas.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Reactive.Concurrency;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
@ -159,47 +160,57 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Arranges the control's children. /// Arranges a single child.
/// </summary> /// </summary>
/// <param name="finalSize">The size allocated to the control.</param> /// <param name="child">The child to arrange.</param>
/// <returns>The space taken.</returns> /// <param name="finalSize">The size allocated to the canvas.</param>
protected override Size ArrangeOverride(Size finalSize) protected virtual void ArrangeChild(Control child, Size finalSize)
{ {
foreach (Control child in Children) double x = 0.0;
{ double y = 0.0;
double x = 0.0; double elementLeft = GetLeft(child);
double y = 0.0;
double elementLeft = GetLeft(child);
if (!double.IsNaN(elementLeft)) if (!double.IsNaN(elementLeft))
{ {
x = elementLeft; x = elementLeft;
} }
else else
{
// Arrange with right.
double elementRight = GetRight(child);
if (!double.IsNaN(elementRight))
{ {
// Arrange with right. x = finalSize.Width - child.DesiredSize.Width - elementRight;
double elementRight = GetRight(child);
if (!double.IsNaN(elementRight))
{
x = finalSize.Width - child.DesiredSize.Width - elementRight;
}
} }
}
double elementTop = GetTop(child); double elementTop = GetTop(child);
if (!double.IsNaN(elementTop) ) if (!double.IsNaN(elementTop))
{ {
y = elementTop; y = elementTop;
} }
else else
{
double elementBottom = GetBottom(child);
if (!double.IsNaN(elementBottom))
{ {
double elementBottom = GetBottom(child); y = finalSize.Height - child.DesiredSize.Height - elementBottom;
if (!double.IsNaN(elementBottom))
{
y = finalSize.Height - child.DesiredSize.Height - elementBottom;
}
} }
}
child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
}
/// <summary>
/// Arranges the control's children.
/// </summary>
/// <param name="finalSize">The size allocated to the control.</param>
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
foreach (Control child in Children)
{
ArrangeChild(child, finalSize);
} }
return finalSize; return finalSize;

4
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -38,13 +38,13 @@ namespace Avalonia.Controls
/// Defines the <see cref="Header"/> property /// Defines the <see cref="Header"/> property
/// </summary> /// </summary>
public static readonly StyledProperty<object> HeaderProperty = public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); AvaloniaProperty.Register<TimePicker, object>(nameof(Header));
/// <summary> /// <summary>
/// Defines the <see cref="HeaderTemplate"/> property /// Defines the <see cref="HeaderTemplate"/> property
/// </summary> /// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); AvaloniaProperty.Register<TimePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="ClockIdentifier"/> property /// Defines the <see cref="ClockIdentifier"/> property

22
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -52,67 +52,67 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Defines the <see cref="Foreground"/> property. /// Defines the <see cref="Foreground"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty = public static readonly StyledProperty<IBrush?> ForegroundProperty =
TextElement.ForegroundProperty.AddOwner<ContentPresenter>(); TextElement.ForegroundProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontFamily"/> property. /// Defines the <see cref="FontFamily"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty = public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner<ContentPresenter>(); TextElement.FontFamilyProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontSize"/> property. /// Defines the <see cref="FontSize"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<double> FontSizeProperty = public static readonly StyledProperty<double> FontSizeProperty =
TextElement.FontSizeProperty.AddOwner<ContentPresenter>(); TextElement.FontSizeProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontStyle"/> property. /// Defines the <see cref="FontStyle"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty = public static readonly StyledProperty<FontStyle> FontStyleProperty =
TextElement.FontStyleProperty.AddOwner<ContentPresenter>(); TextElement.FontStyleProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontWeight"/> property. /// Defines the <see cref="FontWeight"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty = public static readonly StyledProperty<FontWeight> FontWeightProperty =
TextElement.FontWeightProperty.AddOwner<ContentPresenter>(); TextElement.FontWeightProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontStretch"/> property. /// Defines the <see cref="FontStretch"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty = public static readonly StyledProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<ContentPresenter>(); TextElement.FontStretchProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="TextAlignment"/> property /// Defines the <see cref="TextAlignment"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty = public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
TextBlock.TextAlignmentProperty.AddOwner<ContentPresenter>(); TextBlock.TextAlignmentProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="TextWrapping"/> property /// Defines the <see cref="TextWrapping"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<ContentPresenter>(); TextBlock.TextWrappingProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="TextTrimming"/> property /// Defines the <see cref="TextTrimming"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<TextTrimming> TextTrimmingProperty = public static readonly StyledProperty<TextTrimming> TextTrimmingProperty =
TextBlock.TextTrimmingProperty.AddOwner<ContentPresenter>(); TextBlock.TextTrimmingProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="LineHeight"/> property /// Defines the <see cref="LineHeight"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<double> LineHeightProperty = public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<ContentPresenter>(); TextBlock.LineHeightProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="MaxLines"/> property /// Defines the <see cref="MaxLines"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<int> MaxLinesProperty = public static readonly StyledProperty<int> MaxLinesProperty =
TextBlock.MaxLinesProperty.AddOwner<ContentPresenter>(); TextBlock.MaxLinesProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>

25
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -33,7 +33,7 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<IBrush?> CaretBrushProperty = public static readonly StyledProperty<IBrush?> CaretBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(CaretBrush)); AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(CaretBrush));
public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty = public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>( TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
o => o.SelectionStart, o => o.SelectionStart,
@ -43,7 +43,7 @@ namespace Avalonia.Controls.Presenters
TextBox.SelectionEndProperty.AddOwner<TextPresenter>( TextBox.SelectionEndProperty.AddOwner<TextPresenter>(
o => o.SelectionEnd, o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v); (o, v) => o.SelectionEnd = v);
/// <summary> /// <summary>
/// Defines the <see cref="Text"/> property. /// Defines the <see cref="Text"/> property.
/// </summary> /// </summary>
@ -65,6 +65,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextPresenter>(); TextBlock.TextWrappingProperty.AddOwner<TextPresenter>();
/// <summary>
/// Defines the <see cref="LineHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<TextPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="Background"/> property. /// Defines the <see cref="Background"/> property.
/// </summary> /// </summary>
@ -179,6 +185,15 @@ namespace Avalonia.Controls.Presenters
get => GetValue(TextWrappingProperty); get => GetValue(TextWrappingProperty);
set => SetValue(TextWrappingProperty, value); set => SetValue(TextWrappingProperty, value);
} }
/// <summary>
/// Gets or sets the line height. By default, this is set to <see cref="double.NaN"/>, which determines the appropriate height automatically.
/// </summary>
public double LineHeight
{
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the text alignment. /// Gets or sets the text alignment.
@ -253,7 +268,7 @@ namespace Avalonia.Controls.Presenters
get => GetValue(CaretBrushProperty); get => GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value); set => SetValue(CaretBrushProperty, value);
} }
public int SelectionStart public int SelectionStart
{ {
get get
@ -281,7 +296,7 @@ namespace Avalonia.Controls.Presenters
SetAndRaise(SelectionEndProperty, ref _selectionEnd, value); SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
} }
} }
protected override bool BypassFlowDirectionPolicies => true; protected override bool BypassFlowDirectionPolicies => true;
/// <summary> /// <summary>
@ -301,7 +316,7 @@ namespace Avalonia.Controls.Presenters
var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment,
TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides,
flowDirection: FlowDirection); flowDirection: FlowDirection, lineHeight: LineHeight);
return textLayout; return textLayout;
} }

2
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -105,7 +105,7 @@ namespace Avalonia.Controls.Primitives
} }
else else
{ {
child.Arrange(new Rect(finalSize)); ArrangeChild((Control) child, finalSize);
} }
} }
} }

2
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -118,7 +118,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="WrapSelection"/> property. /// Defines the <see cref="WrapSelection"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> WrapSelectionProperty = public static readonly StyledProperty<bool> WrapSelectionProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(WrapSelection), defaultValue: false); AvaloniaProperty.Register<SelectingItemsControl, bool>(nameof(WrapSelection), defaultValue: false);
private static readonly IList Empty = Array.Empty<object>(); private static readonly IList Empty = Array.Empty<object>();
private string _textSearchTerm = string.Empty; private string _textSearchTerm = string.Empty;

19
src/Avalonia.Controls/TextBox.cs

@ -79,8 +79,8 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0); AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
public static readonly StyledProperty<int> MaxLinesProperty = public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0); AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
public static readonly DirectProperty<TextBox, string?> TextProperty = public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>( TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text, o => o.Text,
@ -105,6 +105,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextBox>(); TextBlock.TextWrappingProperty.AddOwner<TextBox>();
/// <summary>
/// Defines see <see cref="TextPresenter.LineHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<TextBox>();
public static readonly StyledProperty<string?> WatermarkProperty = public static readonly StyledProperty<string?> WatermarkProperty =
AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark)); AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark));
@ -358,6 +364,15 @@ namespace Avalonia.Controls
get { return GetValue(MaxLinesProperty); } get { return GetValue(MaxLinesProperty); }
set { SetValue(MaxLinesProperty, value); } set { SetValue(MaxLinesProperty, value); }
} }
/// <summary>
/// Gets or sets the line height.
/// </summary>
public double LineHeight
{
get { return GetValue(LineHeightProperty); }
set { SetValue(LineHeightProperty, value); }
}
[Content] [Content]
public string? Text public string? Text

53
src/Avalonia.Controls/Viewbox.cs

@ -1,4 +1,5 @@
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -8,7 +9,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Viewbox : Control public class Viewbox : Control
{ {
private Decorator _containerVisual; private readonly ViewboxContainer _containerVisual;
/// <summary> /// <summary>
/// Defines the <see cref="Stretch"/> property. /// Defines the <see cref="Stretch"/> property.
@ -37,9 +38,10 @@ namespace Avalonia.Controls
public Viewbox() public Viewbox()
{ {
_containerVisual = new Decorator(); // The Child control is hosted inside a ViewboxContainer control so that the transform
// can be applied independently of the Viewbox and Child transforms.
_containerVisual = new ViewboxContainer();
_containerVisual.RenderTransformOrigin = RelativePoint.TopLeft; _containerVisual.RenderTransformOrigin = RelativePoint.TopLeft;
LogicalChildren.Add(_containerVisual);
VisualChildren.Add(_containerVisual); VisualChildren.Add(_containerVisual);
} }
@ -88,7 +90,22 @@ namespace Avalonia.Controls
if (change.Property == ChildProperty) if (change.Property == ChildProperty)
{ {
_containerVisual.Child = change.GetNewValue<IControl>(); var (oldChild, newChild) = change.GetOldAndNewValue<IControl>();
if (oldChild is not null)
{
((ISetLogicalParent)oldChild).SetParent(null);
LogicalChildren.Remove(oldChild);
}
_containerVisual.Child = newChild;
if (newChild is not null)
{
((ISetLogicalParent)newChild).SetParent(this);
LogicalChildren.Add(newChild);
}
InvalidateMeasure(); InvalidateMeasure();
} }
} }
@ -120,7 +137,7 @@ namespace Avalonia.Controls
var childSize = child.DesiredSize; var childSize = child.DesiredSize;
var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection); var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
InternalTransform = new ScaleTransform(scale.X, scale.Y); InternalTransform = new ImmutableTransform(Matrix.CreateScale(scale.X, scale.Y));
child.Arrange(new Rect(childSize)); child.Arrange(new Rect(childSize));
@ -129,5 +146,31 @@ namespace Avalonia.Controls
return finalSize; return finalSize;
} }
/// <summary>
/// A simple container control which hosts its child as a visual but not logical child.
/// </summary>
private class ViewboxContainer : Control
{
private IControl? _child;
public IControl? Child
{
get => _child;
set
{
if (_child != value)
{
if (_child is not null)
VisualChildren.Remove(_child);
_child = value;
if (_child is not null)
VisualChildren.Add(_child);
}
}
}
}
} }
} }

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -60,7 +60,8 @@ namespace Avalonia.Diagnostics.ViewModels
var styleDiagnostics = styledElement.GetStyleDiagnostics(); var styleDiagnostics = styledElement.GetStyleDiagnostics();
foreach (var appliedStyle in styleDiagnostics.AppliedStyles) // We need to place styles without activator first, such styles will be overwritten by ones with activators.
foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator))
{ {
var styleSource = appliedStyle.Source; var styleSource = appliedStyle.Source;

13
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs

@ -36,6 +36,13 @@ namespace Avalonia.FreeDesktop
private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x)));
private string UnescapeString(string input, string regexText, int escapeBase) =>
new Regex(regexText).Replace(input, m => Convert.ToChar(Convert.ToByte(m.Groups[1].Value, escapeBase)).ToString());
private string UnescapePathFromProcMounts(string input) => UnescapeString(input, @"\\(\d{3})", 8);
private string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16);
private void Poll(long _) private void Poll(long _)
{ {
var fProcPartitions = File.ReadAllLines(ProcPartitionsDir) var fProcPartitions = File.ReadAllLines(ProcPartitionsDir)
@ -47,14 +54,14 @@ namespace Avalonia.FreeDesktop
var fProcMounts = File.ReadAllLines(ProcMountsDir) var fProcMounts = File.ReadAllLines(ProcMountsDir)
.Select(x => x.Split(' ')) .Select(x => x.Split(' '))
.Select(x => (x[0], x[1])) .Select(x => (x[0], UnescapePathFromProcMounts(x[1])))
.Where(x => !x.Item2.StartsWith("/snap/", StringComparison.InvariantCultureIgnoreCase)); .Where(x => !x.Item2.StartsWith("/snap/", StringComparison.InvariantCultureIgnoreCase));
var labelDirEnum = Directory.Exists(DevByLabelDir) ? var labelDirEnum = Directory.Exists(DevByLabelDir) ?
new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty<FileInfo>(); new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty<FileInfo>();
var labelDevPathPairs = labelDirEnum var labelDevPathPairs = labelDirEnum
.Select(x => (GetSymlinkTarget(x.FullName), x.Name)); .Select(x => (GetSymlinkTarget(x.FullName), UnescapeDeviceLabel(x.Name)));
var q1 = from mount in fProcMounts var q1 = from mount in fProcMounts
join device in fProcPartitions on mount.Item1 equals device.Item2 join device in fProcPartitions on mount.Item1 equals device.Item2
@ -64,7 +71,7 @@ namespace Avalonia.FreeDesktop
{ {
VolumePath = mount.Item2, VolumePath = mount.Item2,
VolumeSizeBytes = device.Item1, VolumeSizeBytes = device.Item1,
VolumeLabel = x.Name VolumeLabel = x.Item2
}; };
var mountVolInfos = q1.ToArray(); var mountVolInfos = q1.ToArray();

6
src/Avalonia.FreeDesktop/NativeMethods.cs

@ -14,15 +14,15 @@ namespace Avalonia.FreeDesktop
public static string ReadLink(string path) public static string ReadLink(string path)
{ {
var symlinkMaxSize = Encoding.ASCII.GetMaxByteCount(path.Length); var symlinkSize = Encoding.UTF8.GetByteCount(path);
var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated
var symlink = ArrayPool<byte>.Shared.Rent(symlinkMaxSize + 1); var symlink = ArrayPool<byte>.Shared.Rent(symlinkSize + 1);
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize); var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try try
{ {
var symlinkSize = Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0); Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0);
symlink[symlinkSize] = 0; symlink[symlinkSize] = 0;
var size = readlink(symlink, buffer, bufferSize); var size = readlink(symlink, buffer, bufferSize);

1
src/Avalonia.Themes.Default/Controls/TextBox.xaml

@ -76,6 +76,7 @@
SelectionEnd="{TemplateBinding SelectionEnd}" SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
LineHeight="{TemplateBinding LineHeight}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}" RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"

1
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -89,6 +89,7 @@
SelectionEnd="{TemplateBinding SelectionEnd}" SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
LineHeight="{TemplateBinding LineHeight}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}" RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"

4
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@ -247,10 +247,10 @@ namespace Avalonia.Base.UnitTests.Input
new[] new[]
{ {
((object?)decorator, "PointerEnter"), ((object?)decorator, "PointerEnter"),
(decorator, "PointerMove"), (decorator, "PointerMoved"),
(decorator, "PointerLeave"), (decorator, "PointerLeave"),
(canvas, "PointerEnter"), (canvas, "PointerEnter"),
(canvas, "PointerMove") (canvas, "PointerMoved")
}, },
result); result);
} }

11
tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs

@ -91,16 +91,13 @@ namespace Avalonia.Base.UnitTests.Styling
[Fact] [Fact]
public void Setter_Should_Apply_Value_Without_Activator_With_Style_Priority() public void Setter_Should_Apply_Value_Without_Activator_With_Style_Priority()
{ {
var control = new Mock<IStyleable>(); var control = new Control();
var style = Mock.Of<Style>();
var setter = new Setter(TextBlock.TagProperty, "foo"); var setter = new Setter(TextBlock.TagProperty, "foo");
setter.Instance(control.Object).Start(false); setter.Instance(control).Start(false);
control.Verify(x => x.SetValue( Assert.Equal("foo", control.Tag);
TextBlock.TagProperty, Assert.Equal(BindingPriority.Style, control.GetDiagnostic(TextBlock.TagProperty).Priority);
"foo",
BindingPriority.Style));
} }
[Fact] [Fact]

2
tests/Avalonia.Controls.UnitTests/CalendarTests.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls.UnitTests
first.Day == second.Day; first.Day == second.Day;
} }
[Fact(Skip ="FIX ME ASAP")] [Fact]
public void SelectedDatesChanged_Should_Fire_When_SelectedDate_Set() public void SelectedDatesChanged_Should_Fire_When_SelectedDate_Set()
{ {
bool handled = false; bool handled = false;

104
tests/Avalonia.Controls.UnitTests/ViewboxTests.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -18,11 +19,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 100), target.DesiredSize); Assert.Equal(new Size(200, 100), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.True(TryGetScale(target, out Vector scale));
Assert.NotNull(scaleTransform); Assert.Equal(2.0, scale.X);
Assert.Equal(2.0, scaleTransform.ScaleX); Assert.Equal(2.0, scale.Y);
Assert.Equal(2.0, scaleTransform.ScaleY);
} }
[Fact] [Fact]
@ -36,11 +36,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(100, 50), target.DesiredSize); Assert.Equal(new Size(100, 50), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.True(TryGetScale(target, out Vector scale));
Assert.NotNull(scaleTransform); Assert.Equal(1.0, scale.X);
Assert.Equal(1.0, scaleTransform.ScaleX); Assert.Equal(1.0, scale.Y);
Assert.Equal(1.0, scaleTransform.ScaleY);
} }
[Fact] [Fact]
@ -54,11 +53,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 200), target.DesiredSize); Assert.Equal(new Size(200, 200), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.True(TryGetScale(target, out Vector scale));
Assert.NotNull(scaleTransform); Assert.Equal(2.0, scale.X);
Assert.Equal(2.0, scaleTransform.ScaleX); Assert.Equal(4.0, scale.Y);
Assert.Equal(4.0, scaleTransform.ScaleY);
} }
[Fact] [Fact]
@ -72,11 +70,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 200), target.DesiredSize); Assert.Equal(new Size(200, 200), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.True(TryGetScale(target, out Vector scale));
Assert.NotNull(scaleTransform); Assert.Equal(4.0, scale.X);
Assert.Equal(4.0, scaleTransform.ScaleX); Assert.Equal(4.0, scale.Y);
Assert.Equal(4.0, scaleTransform.ScaleY);
} }
[Fact] [Fact]
@ -90,11 +87,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(400, 200), target.DesiredSize); Assert.Equal(new Size(400, 200), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.True(TryGetScale(target, out Vector scale));
Assert.NotNull(scaleTransform); Assert.Equal(4.0, scale.X);
Assert.Equal(4.0, scaleTransform.ScaleX); Assert.Equal(4.0, scale.Y);
Assert.Equal(4.0, scaleTransform.ScaleY);
} }
[Fact] [Fact]
@ -108,11 +104,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 100), target.DesiredSize); Assert.Equal(new Size(200, 100), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.True(TryGetScale(target, out Vector scale));
Assert.NotNull(scaleTransform); Assert.Equal(2.0, scale.X);
Assert.Equal(2.0, scaleTransform.ScaleX); Assert.Equal(2.0, scale.Y);
Assert.Equal(2.0, scaleTransform.ScaleY);
} }
[Theory] [Theory]
@ -136,11 +131,9 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize); Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform; Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(expectedScale, scale.X);
Assert.NotNull(scaleTransform); Assert.Equal(expectedScale, scale.Y);
Assert.Equal(expectedScale, scaleTransform.ScaleX);
Assert.Equal(expectedScale, scaleTransform.ScaleY);
} }
[Theory] [Theory]
@ -164,11 +157,44 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize); Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform; Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(expectedScale, scale.X);
Assert.Equal(expectedScale, scale.Y);
}
[Fact]
public void Child_Should_Be_Logical_Child_Of_Viewbox()
{
var target = new Viewbox();
Assert.Empty(target.GetLogicalChildren());
var child = new Canvas();
target.Child = child;
Assert.Single(target.GetLogicalChildren(), child);
Assert.Same(child.GetLogicalParent(), target);
target.Child = null;
Assert.Empty(target.GetLogicalChildren());
Assert.Null(child.GetLogicalParent());
}
private bool TryGetScale(Viewbox viewbox, out Vector scale)
{
if (viewbox.InternalTransform is null)
{
scale = default;
return false;
}
var matrix = viewbox.InternalTransform.Value;
Matrix.TryDecomposeTransform(matrix, out var decomposed);
Assert.NotNull(scaleTransform); scale = decomposed.Scale;
Assert.Equal(expectedScale, scaleTransform.ScaleX); return true;
Assert.Equal(expectedScale, scaleTransform.ScaleY);
} }
} }
} }

19
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -617,6 +617,25 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(0, source.SubscriberCount); Assert.Equal(0, source.SubscriberCount);
} }
[Fact]
public void Binding_Can_Resolve_Property_From_IReflectableType_Type()
{
var source = new DynamicReflectableType { ["Foo"] = "foo" };
var target = new TwoWayBindingTest { DataContext = source };
var binding = new Binding
{
Path = "Foo",
};
target.Bind(TwoWayBindingTest.TwoWayProperty, binding);
Assert.Equal("foo", target.TwoWay);
source["Foo"] = "bar";
Assert.Equal("bar", target.TwoWay);
target.TwoWay = "baz";
Assert.Equal("baz", source["Foo"]);
}
private class StyledPropertyClass : AvaloniaObject private class StyledPropertyClass : AvaloniaObject
{ {

221
tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs

@ -0,0 +1,221 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using Moq;
namespace Avalonia.Markup.UnitTests.Data;
class DynamicReflectableType : IReflectableType, INotifyPropertyChanged, IEnumerable<KeyValuePair<string, object>>
{
private Dictionary<string, object> _dic = new();
public TypeInfo GetTypeInfo()
{
return new FakeTypeInfo();
}
public void Add(string key, object value)
{
_dic.Add(key, value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
}
public object this[string key]
{
get => _dic[key];
set
{
_dic[key] = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _dic.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_dic).GetEnumerator();
}
class FakeTypeInfo : TypeInfo
{
protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types,
ParameterModifier[] modifiers)
{
var propInfo = new Mock<PropertyInfo>();
propInfo.SetupGet(x => x.Name).Returns(name);
propInfo.SetupGet(x => x.PropertyType).Returns(typeof(object));
propInfo.SetupGet(x => x.CanWrite).Returns(true);
propInfo.Setup(x => x.GetValue(It.IsAny<object>(), It.IsAny<object[]>()))
.Returns((object target, object [] _) => ((DynamicReflectableType)target)._dic.GetValueOrDefault(name));
propInfo.Setup(x => x.SetValue(It.IsAny<object>(), It.IsAny<object>(), It.IsAny<object[]>()))
.Callback((object target, object value, object [] _) =>
{
((DynamicReflectableType)target)._dic[name] = value;
});
return propInfo.Object;
}
#region NotSupported
public override object[] GetCustomAttributes(bool inherit)
{
throw new NotSupportedException();
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
throw new NotSupportedException();
}
public override bool IsDefined(Type attributeType, bool inherit)
{
throw new NotSupportedException();
}
public override Module Module { get; }
public override string Namespace { get; }
public override string Name { get; }
protected override TypeAttributes GetAttributeFlagsImpl()
{
throw new NotSupportedException();
}
protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention,
Type[] types, ParameterModifier[] modifiers)
{
throw new NotSupportedException();
}
public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override Type GetElementType()
{
throw new NotSupportedException();
}
public override EventInfo GetEvent(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override EventInfo[] GetEvents(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override FieldInfo GetField(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override FieldInfo[] GetFields(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention,
Type[] types, ParameterModifier[] modifiers)
{
throw new NotSupportedException();
}
public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args,
ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
{
throw new NotSupportedException();
}
public override Type UnderlyingSystemType { get; }
protected override bool IsArrayImpl()
{
throw new NotSupportedException();
}
protected override bool IsByRefImpl()
{
throw new NotSupportedException();
}
protected override bool IsCOMObjectImpl()
{
throw new NotSupportedException();
}
protected override bool IsPointerImpl()
{
throw new NotSupportedException();
}
protected override bool IsPrimitiveImpl()
{
throw new NotSupportedException();
}
public override Assembly Assembly { get; }
public override string AssemblyQualifiedName { get; }
public override Type BaseType { get; }
public override string FullName { get; }
public override Guid GUID { get; }
protected override bool HasElementTypeImpl()
{
throw new NotSupportedException();
}
public override Type GetNestedType(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override Type[] GetNestedTypes(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override Type GetInterface(string name, bool ignoreCase)
{
throw new NotSupportedException();
}
public override Type[] GetInterfaces()
{
throw new NotSupportedException();
}
#endregion
}
}
Loading…
Cancel
Save