Browse Source

Merge branch 'master' into feature/9524-window-closing-reason

pull/9715/head
Max Katz 4 years ago
committed by GitHub
parent
commit
afb6c6cb05
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .editorconfig
  2. 2
      src/Avalonia.Base/Input/KeyEventArgs.cs
  3. 2
      src/Avalonia.Base/Input/TextInputEventArgs.cs
  4. 45
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  5. 53
      src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
  6. 53
      src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
  7. 7
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  8. 10
      src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs
  9. 9
      src/Avalonia.Controls/NativeMenuBar.cs
  10. 7
      src/Avalonia.Controls/NativeMenuItemSeparator.cs
  11. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  12. 17
      src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
  13. 21
      src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
  14. 9
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  15. 2
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  16. 3
      src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs
  17. 4
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  18. 15
      src/Windows/Avalonia.Win32/WindowImpl.cs
  19. 3
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  20. 115
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  21. 44
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  22. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs
  23. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
  24. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

2
.editorconfig

@ -140,6 +140,8 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme
# CS1591: Missing XML comment for publicly visible type or member # CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = suggestion dotnet_diagnostic.CS1591.severity = suggestion
# CS0162: Remove unreachable code
dotnet_diagnostic.CS0162.severity = error
# CA1304: Specify CultureInfo # CA1304: Specify CultureInfo
dotnet_diagnostic.CA1304.severity = warning dotnet_diagnostic.CA1304.severity = warning
# CA1802: Use literals where appropriate # CA1802: Use literals where appropriate

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

@ -5,7 +5,7 @@ namespace Avalonia.Input
{ {
public class KeyEventArgs : RoutedEventArgs public class KeyEventArgs : RoutedEventArgs
{ {
internal KeyEventArgs() public KeyEventArgs()
{ {
} }

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

@ -4,7 +4,7 @@ namespace Avalonia.Input
{ {
public class TextInputEventArgs : RoutedEventArgs public class TextInputEventArgs : RoutedEventArgs
{ {
internal TextInputEventArgs() public TextInputEventArgs()
{ {
} }

45
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
@ -116,26 +117,42 @@ namespace Avalonia.PropertyStore
private void SetValue(BindingValue<TValue> value) private void SetValue(BindingValue<TValue> value)
{ {
if (Frame.Owner is null) static void Execute(BindingEntryBase<TValue, TSource> instance, BindingValue<TValue> value)
return; {
if (instance.Frame.Owner is null)
return;
LoggingUtils.LogIfNecessary(Frame.Owner.Owner, Property, value); LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value);
if (value.HasValue) if (value.HasValue)
{ {
if (!_hasValue || !EqualityComparer<TValue>.Default.Equals(_value, value.Value)) if (!instance._hasValue || !EqualityComparer<TValue>.Default.Equals(instance._value, value.Value))
{
instance._value = value.Value;
instance._hasValue = true;
if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
}
}
else if (value.Type != BindingValueType.DoNothing)
{ {
_value = value.Value; instance.ClearValue();
_hasValue = true; if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
if (_subscription is not null && _subscription != s_creatingQuiet) instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority);
Frame.Owner?.OnBindingValueChanged(this, Frame.Priority);
} }
} }
else if (value.Type != BindingValueType.DoNothing)
if (Dispatcher.UIThread.CheckAccess())
{ {
ClearValue(); Execute(this, value);
if (_subscription is not null && _subscription != s_creatingQuiet) }
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority); else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
} }
} }

53
src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs

@ -1,5 +1,6 @@
using System; using System;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
@ -40,20 +41,56 @@ namespace Avalonia.PropertyStore
public void OnNext(T value) public void OnNext(T value)
{ {
if (Property.ValidateValue?.Invoke(value) != false) static void Execute(ValueStore owner, StyledPropertyBase<T> property, T value)
_owner.SetValue(Property, value, BindingPriority.LocalValue); {
if (property.ValidateValue?.Invoke(value) != false)
owner.SetValue(property, value, BindingPriority.LocalValue);
else
owner.ClearLocalValue(property);
}
if (Dispatcher.UIThread.CheckAccess())
{
Execute(_owner, Property, value);
}
else else
_owner.ClearLocalValue(Property); {
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = _owner;
var property = Property;
var newValue = value;
Dispatcher.UIThread.Post(() => Execute(instance, property, newValue));
}
} }
public void OnNext(BindingValue<T> value) public void OnNext(BindingValue<T> value)
{ {
LoggingUtils.LogIfNecessary(_owner.Owner, Property, value); static void Execute(LocalValueBindingObserver<T> instance, BindingValue<T> value)
{
var owner = instance._owner;
var property = instance.Property;
LoggingUtils.LogIfNecessary(owner.Owner, property, value);
if (value.HasValue) if (value.HasValue)
_owner.SetValue(Property, value.Value, BindingPriority.LocalValue); owner.SetValue(property, value.Value, BindingPriority.LocalValue);
else if (value.Type != BindingValueType.DataValidationError) else if (value.Type != BindingValueType.DataValidationError)
_owner.ClearLocalValue(Property); owner.ClearLocalValue(property);
}
if (Dispatcher.UIThread.CheckAccess())
{
Execute(this, value);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
}
} }
} }
} }

53
src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Security.Cryptography;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
@ -34,28 +36,47 @@ namespace Avalonia.PropertyStore
public void OnNext(object? value) public void OnNext(object? value)
{ {
if (value is BindingNotification n) static void Execute(LocalValueUntypedBindingObserver<T> instance, object? value)
{ {
value = n.Value; var owner = instance._owner;
LoggingUtils.LogIfNecessary(_owner.Owner, Property, n); var property = instance.Property;
}
if (value == AvaloniaProperty.UnsetValue) if (value is BindingNotification n)
{ {
_owner.ClearLocalValue(Property); value = n.Value;
} LoggingUtils.LogIfNecessary(owner.Owner, property, n);
else if (value == BindingOperations.DoNothing) }
{
// Do nothing! if (value == AvaloniaProperty.UnsetValue)
{
owner.ClearLocalValue(property);
}
else if (value == BindingOperations.DoNothing)
{
// Do nothing!
}
else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue))
{
owner.SetValue(property, typedValue, BindingPriority.LocalValue);
}
else
{
owner.ClearLocalValue(property);
LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value);
}
} }
else if (UntypedValueUtils.TryConvertAndValidate(Property, value, out var typedValue))
if (Dispatcher.UIThread.CheckAccess())
{ {
_owner.SetValue(Property, typedValue, BindingPriority.LocalValue); Execute(this, value);
} }
else else if (value != BindingOperations.DoNothing)
{ {
_owner.ClearLocalValue(Property); // To avoid allocating closure in the outer scope we need to capture variables
LoggingUtils.LogInvalidValue(_owner.Owner, Property, typeof(T), value); // locally. This allows us to skip most of the allocations when on UI thread.
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
} }
} }
} }

7
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -50,10 +50,13 @@ namespace Avalonia.Controls
InheritsWidth = true; InheritsWidth = true;
} }
internal DataGrid OwningGrid /// <summary>
/// Gets the <see cref="T:Avalonia.Controls.DataGrid"/> control that contains this column.
/// </summary>
protected internal DataGrid OwningGrid
{ {
get; get;
set; internal set;
} }
internal int Index internal int Index

10
src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs

@ -22,14 +22,18 @@ namespace Avalonia.Controls
return DefaultValueConverter.Instance.Convert(value, targetType, parameter, culture); return DefaultValueConverter.Instance.Convert(value, targetType, parameter, culture);
} }
// This suppresses a warning saying that we should use String.IsNullOrEmpty instead of a string
// comparison, but in this case we want to explicitly check for Empty and not Null.
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
if (targetType != null && targetType.IsNullableType()) if (targetType != null && targetType.IsNullableType())
{ {
var strValue = value as string; var strValue = value as string;
if (string.IsNullOrEmpty(strValue))
// This suppresses a warning saying that we should use String.IsNullOrEmpty instead of a string
// comparison, but in this case we want to explicitly check for Empty and not Null.
#pragma warning disable CA1820
if (strValue == string.Empty)
#pragma warning restore CA1820
{ {
return null; return null;
} }

9
src/Avalonia.Controls/NativeMenuBar.cs

@ -23,15 +23,6 @@ namespace Avalonia.Controls
}); });
} }
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenu))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItem))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemBase))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemSeparator))]
public NativeMenuBar()
{
}
public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable) public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable)
{ {
menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable); menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable);

7
src/Avalonia.Controls/NativeMenuItemSeparator.cs

@ -1,7 +1,10 @@
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public class NativeMenuItemSeparator : NativeMenuItemBase public class NativeMenuItemSeparator : NativeMenuItem
{ {
public NativeMenuItemSeparator()
{
Header = "-";
}
} }
} }

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

@ -109,7 +109,7 @@ namespace Avalonia.Controls.Presenters
static TextPresenter() static TextPresenter()
{ {
AffectsRender<TextPresenter>(CaretBrushProperty, SelectionBrushProperty); AffectsRender<TextPresenter>(CaretBrushProperty, SelectionBrushProperty, TextElement.ForegroundProperty);
} }
public TextPresenter() public TextPresenter()

17
src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml

@ -9,17 +9,16 @@
IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}" IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}"> Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles> <Menu.Styles>
<!-- Don't use x:DataType and compiled bindings here, as it might crash https://github.com/AvaloniaUI/Avalonia/pull/7954 --> <Style Selector="MenuItem" x:DataType="NativeMenuItem">
<Style Selector="MenuItem"> <Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Header" Value="{ReflectionBinding Header}"/> <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="IsEnabled" Value="{ReflectionBinding IsEnabled}"/> <Setter Property="InputGesture" Value="{Binding Gesture}"/>
<Setter Property="InputGesture" Value="{ReflectionBinding Gesture}"/> <Setter Property="Items" Value="{Binding Menu.Items}"/>
<Setter Property="Items" Value="{ReflectionBinding Menu.Items}"/> <Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="Command" Value="{ReflectionBinding Command}"/> <Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
<Setter Property="CommandParameter" Value="{ReflectionBinding CommandParameter}"/>
<Setter Property="(NativeMenuBar.EnableMenuItemClickForwarding)" Value="True"/> <Setter Property="(NativeMenuBar.EnableMenuItemClickForwarding)" Value="True"/>
<!--NativeMenuItem is IBitmap and MenuItem is Image--> <!--NativeMenuItem is IBitmap and MenuItem is Image-->
<Setter Property="Icon" Value="{ReflectionBinding Icon , Converter={StaticResource AvaloniaThemesFluentNativeMenuBarIBitmapToImageConverter}}"/> <Setter Property="Icon" Value="{Binding Icon , Converter={StaticResource AvaloniaThemesFluentNativeMenuBarIBitmapToImageConverter}}"/>
</Style> </Style>
</Menu.Styles> </Menu.Styles>
</Menu> </Menu>

21
src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml

@ -9,17 +9,16 @@
<Menu IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}" <Menu IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}"> Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles> <Menu.Styles>
<!-- Don't use x:DataType and compiled bindings here, as it might crash https://github.com/AvaloniaUI/Avalonia/pull/7954 --> <Style Selector="MenuItem" x:DataType="NativeMenuItem">
<Style Selector="MenuItem"> <Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Header" Value="{ReflectionBinding Header}" /> <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="IsEnabled" Value="{ReflectionBinding IsEnabled}" /> <Setter Property="InputGesture" Value="{Binding Gesture}"/>
<Setter Property="InputGesture" Value="{ReflectionBinding Gesture}" /> <Setter Property="Items" Value="{Binding Menu.Items}"/>
<Setter Property="Items" Value="{ReflectionBinding Menu.Items}" /> <Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="Command" Value="{ReflectionBinding Command}" /> <Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
<Setter Property="CommandParameter" Value="{ReflectionBinding CommandParameter}" /> <Setter Property="(NativeMenuBar.EnableMenuItemClickForwarding)" Value="True"/>
<Setter Property="(NativeMenuBar.EnableMenuItemClickForwarding)" Value="True" /> <!--NativeMenuItem is IBitmap and MenuItem is Image-->
<!-- NativeMenuItem is IBitmap and MenuItem is Image --> <Setter Property="Icon" Value="{Binding Icon , Converter={StaticResource AvaloniaThemesSimpleNativeMenuBarIBitmapToImageConverter}}"/>
<Setter Property="Icon" Value="{ReflectionBinding Icon, Converter={StaticResource AvaloniaThemesSimpleNativeMenuBarIBitmapToImageConverter}}" />
</Style> </Style>
</Menu.Styles> </Menu.Styles>
</Menu> </Menu>

9
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -1137,11 +1137,14 @@ namespace Avalonia.Skia
if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0) if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0)
{ {
var srcDashes = pen.DashStyle.Dashes; var srcDashes = pen.DashStyle.Dashes;
var dashesArray = new float[srcDashes.Count];
for (var i = 0; i < srcDashes.Count; ++i) var count = srcDashes.Count % 2 == 0 ? srcDashes.Count : srcDashes.Count * 2;
var dashesArray = new float[count];
for (var i = 0; i < count; ++i)
{ {
dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth; dashesArray[i] = (float) srcDashes[i % srcDashes.Count] * paint.StrokeWidth;
} }
var offset = (float)(pen.DashStyle.Offset * pen.Thickness); var offset = (float)(pen.DashStyle.Offset * pen.Thickness);

2
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1409,7 +1409,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool TranslateMessage(ref MSG lpMsg); public static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll")] [DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance); public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")]

3
src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.ExceptionServices;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Angle;
@ -88,8 +87,6 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
return null; return null;
} }
return new AngleWin32PlatformGraphics(egl, AngleWin32EglDisplay.CreateSharedD3D11Display(egl));
foreach (var api in (options?.AllowedPlatformApis ?? new [] foreach (var api in (options?.AllowedPlatformApis ?? new []
{ {
AngleOptions.PlatformApi.DirectX11 AngleOptions.PlatformApi.DirectX11

4
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Remote;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.Win32.Automation; using Avalonia.Win32.Automation;
using Avalonia.Win32.Input; using Avalonia.Win32.Input;
using Avalonia.Win32.Interop.Automation; using Avalonia.Win32.Interop.Automation;
@ -106,6 +107,9 @@ namespace Avalonia.Win32
_touchDevice?.Dispose(); _touchDevice?.Dispose();
//Free other resources //Free other resources
Dispose(); Dispose();
// Schedule cleanup of anything that requires window to be destroyed
Dispatcher.UIThread.Post(AfterCloseCleanup);
return IntPtr.Zero; return IntPtr.Zero;
} }

15
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -643,12 +643,6 @@ namespace Avalonia.Win32
_hwnd = IntPtr.Zero; _hwnd = IntPtr.Zero;
} }
if (_className != null)
{
UnregisterClass(_className, GetModuleHandle(null));
_className = null;
}
_framebuffer.Dispose(); _framebuffer.Dispose();
} }
@ -1144,6 +1138,15 @@ namespace Avalonia.Win32
} }
} }
private void AfterCloseCleanup()
{
if (_className != null)
{
UnregisterClass(_className, GetModuleHandle(null));
_className = null;
}
}
private void MaximizeWithoutCoveringTaskbar() private void MaximizeWithoutCoveringTaskbar()
{ {
IntPtr monitor = MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); IntPtr monitor = MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST);

3
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -17,6 +17,9 @@
<EmbeddedResource Remove="..\Avalonia.RenderTests\Assets\NotoColorEmoji.ttf" /> <EmbeddedResource Remove="..\Avalonia.RenderTests\Assets\NotoColorEmoji.ttf" />
<EmbeddedResource Include="Media\TextFormatting\BreakPairTable.txt" /> <EmbeddedResource Include="Media\TextFormatting\BreakPairTable.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Nito.AsyncEx.Context" Version="5.1.2" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" /> <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" />

115
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -12,6 +12,7 @@ using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
using Nito.AsyncEx;
using Xunit; using Xunit;
#nullable enable #nullable enable
@ -920,6 +921,120 @@ namespace Avalonia.Base.UnitTests
} }
} }
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
public void Typed_Bind_Executes_On_UIThread(BindingPriority priority)
{
AsyncContext.Run(async () =>
{
var target = new Class1();
var source = new Subject<string>();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var raised = 0;
var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
.Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
var services = new TestServices(
threadingInterface: threadingInterfaceMock.Object);
target.PropertyChanged += (s, e) =>
{
Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId);
++raised;
};
using (UnitTestApplication.Start(services))
{
target.Bind(Class1.FooProperty, source, priority);
await Task.Run(() => source.OnNext("foobar"));
Dispatcher.UIThread.RunJobs();
Assert.Equal("foobar", target.GetValue(Class1.FooProperty));
Assert.Equal(1, raised);
}
});
}
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
public void Untyped_Bind_Executes_On_UIThread(BindingPriority priority)
{
AsyncContext.Run(async () =>
{
var target = new Class1();
var source = new Subject<object>();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var raised = 0;
var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
.Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
var services = new TestServices(
threadingInterface: threadingInterfaceMock.Object);
target.PropertyChanged += (s, e) =>
{
Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId);
++raised;
};
using (UnitTestApplication.Start(services))
{
target.Bind(Class1.FooProperty, source, priority);
await Task.Run(() => source.OnNext("foobar"));
Dispatcher.UIThread.RunJobs();
Assert.Equal("foobar", target.GetValue(Class1.FooProperty));
Assert.Equal(1, raised);
}
});
}
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
public void BindingValue_Bind_Executes_On_UIThread(BindingPriority priority)
{
AsyncContext.Run(async () =>
{
var target = new Class1();
var source = new Subject<BindingValue<string>>();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var raised = 0;
var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
.Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
var services = new TestServices(
threadingInterface: threadingInterfaceMock.Object);
target.PropertyChanged += (s, e) =>
{
Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId);
++raised;
};
using (UnitTestApplication.Start(services))
{
target.Bind(Class1.FooProperty, source, priority);
await Task.Run(() => source.OnNext("foobar"));
Dispatcher.UIThread.RunJobs();
Assert.Equal("foobar", target.GetValue(Class1.FooProperty));
Assert.Equal(1, raised);
}
});
}
[Fact] [Fact]
public async Task Bind_With_Scheduler_Executes_On_Scheduler() public async Task Bind_With_Scheduler_Executes_On_Scheduler()
{ {

44
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@ -7,8 +7,10 @@ using System.Threading.Tasks;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
using Nito.AsyncEx;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
@ -519,25 +521,39 @@ namespace Avalonia.Base.UnitTests
} }
[Fact] [Fact]
public async Task Bind_Executes_On_UIThread() public void Bind_Executes_On_UIThread()
{ {
var target = new Class1(); AsyncContext.Run(async () =>
var source = new Subject<object>(); {
var currentThreadId = Thread.CurrentThread.ManagedThreadId; var target = new Class1();
var source = new Subject<object>();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var raised = 0;
var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>(); var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
.Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
var services = new TestServices( var services = new TestServices(
threadingInterface: threadingInterfaceMock.Object); threadingInterface: threadingInterfaceMock.Object);
using (UnitTestApplication.Start(services)) target.PropertyChanged += (s, e) =>
{ {
target.Bind(Class1.FooProperty, source); Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId);
++raised;
};
await Task.Run(() => source.OnNext("foobar")); using (UnitTestApplication.Start(services))
} {
target.Bind(Class1.FooProperty, source);
await Task.Run(() => source.OnNext("foobar"));
Dispatcher.UIThread.RunJobs();
Assert.Equal("foobar", target.Foo);
Assert.Equal(1, raised);
}
});
} }
[Fact] [Fact]

2
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs

@ -166,6 +166,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
[Fact] [Fact]
public void Binding_Method_To_Command_Collected() public void Binding_Method_To_Command_Collected()
{ {
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
WeakReference<ViewModel> MakeRef() WeakReference<ViewModel> MakeRef()
{ {
var weakVm = new WeakReference<ViewModel>(null); var weakVm = new WeakReference<ViewModel>(null);

7
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs

@ -1,6 +1,8 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Xml; using System.Xml;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Xunit; using Xunit;
@ -9,6 +11,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
public class MergeResourceIncludeTests public class MergeResourceIncludeTests
{ {
static MergeResourceIncludeTests()
{
RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle);
}
[Fact] [Fact]
public void MergeResourceInclude_Works_With_Single_Resource() public void MergeResourceInclude_Works_With_Single_Resource()
{ {

8
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.Styling;
using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Media; using Avalonia.Media;
@ -15,6 +17,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
public class StyleIncludeTests public class StyleIncludeTests
{ {
static StyleIncludeTests()
{
RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle);
AssetLoader.RegisterResUriParsers();
}
[Fact] [Fact]
public void StyleInclude_Is_Built() public void StyleInclude_Is_Built()
{ {

Loading…
Cancel
Save