diff --git a/build.cake b/build.cake
index 24f529ee4a..56653109ae 100644
--- a/build.cake
+++ b/build.cake
@@ -170,6 +170,7 @@ Task("Run-Unit-Tests-Impl")
RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
+ RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data.Parameters, false);
if (data.Parameters.IsRunningOnWindows)
{
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, false);
diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props
index acdfdd215a..1208be34b8 100644
--- a/build/ReactiveUI.props
+++ b/build/ReactiveUI.props
@@ -1,5 +1,5 @@
-
+
diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
index eb08ef9656..eba17f92e4 100644
--- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
+++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
@@ -7,6 +7,7 @@ using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
+using ReactiveUI.Legacy;
using ReactiveUI;
namespace VirtualizationDemo.ViewModels
diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
index 882d2f4ddd..f1a1f94a01 100644
--- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators
///
public class ItemContainerGenerator : IItemContainerGenerator
{
- private Dictionary _containers = new Dictionary();
+ private SortedDictionary _containers = new SortedDictionary();
///
/// Initializes a new instance of the class.
@@ -246,4 +246,4 @@ namespace Avalonia.Controls.Generators
Recycled?.Invoke(this, e);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index d7db04f369..f8440aac47 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -289,12 +289,12 @@ namespace Avalonia.Controls.Primitives
///
public override void EndInit()
{
- base.EndInit();
-
if (--_updateCount == 0)
{
UpdateFinished();
}
+
+ base.EndInit();
}
///
diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
index 3eab54115a..d763febdf3 100644
--- a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
+++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
@@ -4,6 +4,7 @@
using Avalonia.Controls;
using Avalonia.Threading;
using ReactiveUI;
+using Splat;
namespace Avalonia
{
@@ -15,6 +16,9 @@ namespace Avalonia
return builder.AfterSetup(_ =>
{
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
+ Locator.CurrentMutable.Register(
+ () => new AvaloniaActivationForViewFetcher(),
+ typeof(IActivationForViewFetcher));
});
}
}
diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
new file mode 100644
index 0000000000..828d8024e6
--- /dev/null
+++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
@@ -0,0 +1,38 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reflection;
+using System.Reactive.Linq;
+using Avalonia;
+using Avalonia.VisualTree;
+using ReactiveUI;
+
+namespace Avalonia
+{
+ public class AvaloniaActivationForViewFetcher : IActivationForViewFetcher
+ {
+ public int GetAffinityForView(Type view)
+ {
+ return typeof(IVisual).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;
+ }
+
+ public IObservable GetActivationForView(IActivatable view)
+ {
+ if (!(view is IVisual visual)) return Observable.Return(false);
+ var viewLoaded = Observable
+ .FromEventPattern(
+ x => visual.AttachedToVisualTree += x,
+ x => visual.DetachedFromVisualTree -= x)
+ .Select(args => true);
+ var viewUnloaded = Observable
+ .FromEventPattern(
+ x => visual.DetachedFromVisualTree += x,
+ x => visual.DetachedFromVisualTree -= x)
+ .Select(args => false);
+ return viewLoaded
+ .Merge(viewUnloaded)
+ .DistinctUntilChanged();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs
index d0d5b2250d..df32d9eec1 100644
--- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs
+++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs
@@ -19,7 +19,8 @@ namespace Avalonia.Rendering
private readonly IDispatcher _dispatcher;
private List _items = new List();
private IRenderTimer _timer;
- private int inTick;
+ private int _inTick;
+ private int _inUpdate;
///
/// Initializes a new instance of the class.
@@ -84,21 +85,36 @@ namespace Avalonia.Rendering
}
}
- private async void TimerTick(TimeSpan time)
+ private void TimerTick(TimeSpan time)
{
- if (Interlocked.CompareExchange(ref inTick, 1, 0) == 0)
+ if (Interlocked.CompareExchange(ref _inTick, 1, 0) == 0)
{
try
{
- if (_items.Any(item => item.NeedsUpdate))
+ if (_items.Any(item => item.NeedsUpdate) &&
+ Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0)
{
- await _dispatcher.InvokeAsync(() =>
+ _dispatcher.Post(() =>
{
- foreach (var i in _items)
+ for (var i = 0; i < _items.Count; ++i)
{
- i.Update(time);
+ var item = _items[i];
+
+ if (item.NeedsUpdate)
+ {
+ try
+ {
+ item.Update(time);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(LogArea.Visual, this, "Exception in render update: {Error}", ex);
+ }
+ }
}
- }, DispatcherPriority.Render).ConfigureAwait(false);
+
+ Interlocked.Exchange(ref _inUpdate, 0);
+ }, DispatcherPriority.Render);
}
foreach (var i in _items)
@@ -112,7 +128,7 @@ namespace Avalonia.Rendering
}
finally
{
- Interlocked.Exchange(ref inTick, 0);
+ Interlocked.Exchange(ref _inTick, 0);
}
}
}
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index d6b95bc7b0..c3c867d3fb 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -976,6 +976,9 @@ namespace Avalonia.Win32.Interop
[DllImport("shcore.dll")]
public static extern void GetScaleFactorForMonitor(IntPtr hMon, out uint pScale);
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern bool SetProcessDPIAware();
+
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags);
@@ -987,7 +990,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32", EntryPoint = "GetMonitorInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool GetMonitorInfo([In] IntPtr hMonitor, [Out] MONITORINFO lpmi);
+ public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")]
@@ -1055,12 +1058,17 @@ namespace Avalonia.Win32.Interop
}
[StructLayout(LayoutKind.Sequential)]
- internal class MONITORINFO
+ internal struct MONITORINFO
{
- public int cbSize = Marshal.SizeOf();
- public RECT rcMonitor = new RECT();
- public RECT rcWork = new RECT();
- public int dwFlags = 0;
+ public int cbSize;
+ public RECT rcMonitor;
+ public RECT rcWork;
+ public int dwFlags;
+
+ public static MONITORINFO Create()
+ {
+ return new MONITORINFO() { cbSize = Marshal.SizeOf() };
+ }
public enum MonitorOptions : uint
{
diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs
index e1df24151d..5cfceb68b7 100644
--- a/src/Windows/Avalonia.Win32/ScreenImpl.cs
+++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs
@@ -26,8 +26,8 @@ namespace Avalonia.Win32
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
(IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) =>
{
- MONITORINFO monitorInfo = new MONITORINFO();
- if (GetMonitorInfo(monitor, monitorInfo))
+ MONITORINFO monitorInfo = MONITORINFO.Create();
+ if (GetMonitorInfo(monitor,ref monitorInfo))
{
RECT bounds = monitorInfo.rcMonitor;
RECT workingArea = monitorInfo.rcWork;
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 89943b5b0a..5041942c63 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -77,6 +77,8 @@ namespace Avalonia.Win32
public static void Initialize(bool deferredRendering = true)
{
+ UnmanagedMethods.SetProcessDPIAware();
+
AvaloniaLocator.CurrentMutable
.Bind().ToSingleton()
.Bind().ToConstant(CursorFactory.Instance)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 65580ff9c8..45120fa21b 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -837,9 +837,9 @@ namespace Avalonia.Win32
if (monitor != IntPtr.Zero)
{
- MONITORINFO monitorInfo = new MONITORINFO();
+ MONITORINFO monitorInfo = MONITORINFO.Create();
- if (GetMonitorInfo(monitor, monitorInfo))
+ if (GetMonitorInfo(monitor,ref monitorInfo))
{
RECT rcMonitorArea = monitorInfo.rcMonitor;
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index a5f5f8d328..1debccd3c5 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -1,16 +1,18 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+using System.Collections.ObjectModel;
using System.Linq;
+using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
-using Avalonia.Input;
+using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
-using Avalonia.Collections;
namespace Avalonia.Controls.UnitTests
{
@@ -170,9 +172,33 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(100, 10), target.Scroll.Viewport);
}
+ [Fact]
+ public void Containers_Correct_After_Clear_Add_Remove()
+ {
+ // Issue #1936
+ var items = new AvaloniaList(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ Items = items,
+ ItemTemplate = new FuncDataTemplate(x => new TextBlock { Width = 20, Height = 10 }),
+ SelectedIndex = 0,
+ };
+
+ Prepare(target);
+
+ items.Clear();
+ items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
+ items.Remove("Item 2");
+
+ Assert.Equal(
+ items,
+ target.Presenter.Panel.Children.Cast().Select(x => (string)x.Content));
+ }
+
private FuncControlTemplate ListBoxTemplate()
{
- return new FuncControlTemplate(parent =>
+ return new FuncControlTemplate(parent =>
new ScrollViewer
{
Name = "PART_ScrollViewer",
@@ -189,7 +215,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate ListBoxItemTemplate()
{
- return new FuncControlTemplate(parent =>
+ return new FuncControlTemplate(parent =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 14e1b15ebc..bbe1d85acb 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -707,6 +707,26 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.True(target.SelectedIndex == 1);
}
+ [Fact]
+ public void Binding_With_DelayedBinding_And_Initialization_Where_DataContext_Is_Root_Works()
+ {
+ // Test for #1932.
+ var root = new RootWithItems();
+
+ root.BeginInit();
+ root.DataContext = root;
+
+ var target = new ListBox();
+ target.BeginInit();
+ root.Child = target;
+
+ DelayedBinding.Add(target, ItemsControl.ItemsProperty, new Binding(nameof(RootWithItems.Items)));
+ DelayedBinding.Add(target, ListBox.SelectedItemProperty, new Binding(nameof(RootWithItems.Selected)));
+ target.EndInit();
+ root.EndInit();
+
+ Assert.Equal("b", target.SelectedItem);
+ }
private FuncControlTemplate Template()
{
@@ -745,5 +765,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
public IList- Items { get; set; }
public Item SelectedItem { get; set; }
}
+
+ private class RootWithItems : TestRoot
+ {
+ public List Items { get; set; } = new List() { "a", "b", "c", "d", "e" };
+ public string Selected { get; set; } = "b";
+ }
}
}
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
new file mode 100644
index 0000000000..7c0ff79183
--- /dev/null
+++ b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
@@ -0,0 +1,14 @@
+
+
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
new file mode 100644
index 0000000000..97701f8437
--- /dev/null
+++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Reactive.Concurrency;
+using System.Reactive.Disposables;
+using Avalonia.Controls;
+using Avalonia.Rendering;
+using Avalonia.Platform;
+using Avalonia.UnitTests;
+using Avalonia;
+using ReactiveUI;
+using DynamicData;
+using Xunit;
+using Splat;
+
+namespace Avalonia
+{
+ public class AvaloniaActivationForViewFetcherTest
+ {
+ public class TestUserControl : UserControl, IActivatable { }
+
+ public class TestUserControlWithWhenActivated : UserControl, IActivatable
+ {
+ public bool Active { get; private set; }
+
+ public TestUserControlWithWhenActivated()
+ {
+ this.WhenActivated(disposables => {
+ Active = true;
+ Disposable
+ .Create(() => Active = false)
+ .DisposeWith(disposables);
+ });
+ }
+ }
+
+ [Fact]
+ public void Visual_Element_Is_Activated_And_Deactivated()
+ {
+ var userControl = new TestUserControl();
+ var activationForViewFetcher = new AvaloniaActivationForViewFetcher();
+
+ activationForViewFetcher
+ .GetActivationForView(userControl)
+ .ToObservableChangeSet(scheduler: ImmediateScheduler.Instance)
+ .Bind(out var activated)
+ .Subscribe();
+
+ var fakeRenderedDecorator = new TestRoot();
+ fakeRenderedDecorator.Child = userControl;
+ Assert.True(activated[0]);
+ Assert.Equal(1, activated.Count);
+
+ fakeRenderedDecorator.Child = null;
+ Assert.True(activated[0]);
+ Assert.False(activated[1]);
+ Assert.Equal(2, activated.Count);
+ }
+
+ [Fact]
+ public void Get_Affinity_For_View_Should_Return_Non_Zero_For_Visual_Elements()
+ {
+ var userControl = new TestUserControl();
+ var activationForViewFetcher = new AvaloniaActivationForViewFetcher();
+
+ var forUserControl = activationForViewFetcher.GetAffinityForView(userControl.GetType());
+ var forNonUserControl = activationForViewFetcher.GetAffinityForView(typeof(object));
+
+ Assert.NotEqual(0, forUserControl);
+ Assert.Equal(0, forNonUserControl);
+ }
+
+ [Fact]
+ public void Activation_For_View_Fetcher_Should_Support_When_Activated()
+ {
+ Locator.CurrentMutable.RegisterConstant(
+ new AvaloniaActivationForViewFetcher(),
+ typeof(IActivationForViewFetcher));
+
+ var userControl = new TestUserControlWithWhenActivated();
+ Assert.False(userControl.Active);
+
+ var fakeRenderedDecorator = new TestRoot();
+ fakeRenderedDecorator.Child = userControl;
+ Assert.True(userControl.Active);
+
+ fakeRenderedDecorator.Child = null;
+ Assert.False(userControl.Active);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs
index 16c2d3ee18..e9aefc46e6 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs
@@ -19,14 +19,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering
bool inDispatcher = false;
dispatcher.Setup(
- d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render))
+ d => d.Post(It.IsAny(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) =>
{
inDispatcher = true;
a();
inDispatcher = false;
- })
- .Returns(Task.CompletedTask);
+ });
var timer = new Mock();
@@ -71,14 +70,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var dispatcher = new Mock();
bool inDispatcher = false;
dispatcher.Setup(
- d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render))
+ d => d.Post(It.IsAny(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) =>
{
inDispatcher = true;
a();
inDispatcher = false;
- })
- .Returns(Task.CompletedTask);
+ });
var timer = new Mock();
var loop = new RenderLoop(timer.Object, dispatcher.Object);
@@ -100,9 +98,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
var dispatcher = new Mock();
dispatcher.Setup(
- d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render))
- .Callback((Action a, DispatcherPriority _) => a())
- .Returns(Task.CompletedTask);
+ d => d.Post(It.IsAny(), DispatcherPriority.Render))
+ .Callback((Action a, DispatcherPriority _) => a());
var timer = new Mock();
var loop = new RenderLoop(timer.Object, dispatcher.Object);