diff --git a/.gitignore b/.gitignore
index b5a46e16f4..7d672c7755 100644
--- a/.gitignore
+++ b/.gitignore
@@ -117,6 +117,7 @@ ClientBin/
*.[Pp]ublish.xml
*.pfx
*.publishsettings
+Events_Avalonia.cs
# RIA/Silverlight projects
Generated_Code/
diff --git a/.ncrunch/Sandbox.v3.ncrunchproject b/.ncrunch/Sandbox.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/Sandbox.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/Avalonia.sln b/Avalonia.sln
index ddcd61408d..34ad19b41d 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -224,7 +224,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -2040,30 +2040,30 @@ Global
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhone.Build.0 = Release|Any CPU
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.Build.0 = Release|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.ActiveCfg = Release|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.Build.0 = Release|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.AppStore|Any CPU.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.AppStore|iPhone.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2122,7 +2122,7 @@ Global
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
- {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 24398accbb..7e2bbc13bc 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -241,7 +241,6 @@ partial class Build : NukeBuild
RunCoreTest("Avalonia.Visuals.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests");
- RunCoreTest("Avalonia.ReactiveUI.Events.UnitTests");
});
Target RunRenderTests => _ => _
diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml
new file mode 100644
index 0000000000..699781eb94
--- /dev/null
+++ b/samples/Sandbox/App.axaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/samples/Sandbox/App.axaml.cs b/samples/Sandbox/App.axaml.cs
new file mode 100644
index 0000000000..7eb8345784
--- /dev/null
+++ b/samples/Sandbox/App.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace Sandbox
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+ {
+ desktopLifetime.MainWindow = new MainWindow();
+ }
+ }
+ }
+}
diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml
new file mode 100644
index 0000000000..6929f192c7
--- /dev/null
+++ b/samples/Sandbox/MainWindow.axaml
@@ -0,0 +1,4 @@
+
+
diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs
new file mode 100644
index 0000000000..b7222e043d
--- /dev/null
+++ b/samples/Sandbox/MainWindow.axaml.cs
@@ -0,0 +1,20 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sandbox
+{
+ public class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ this.InitializeComponent();
+ this.AttachDevTools();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs
new file mode 100644
index 0000000000..4d7eda8d9f
--- /dev/null
+++ b/samples/Sandbox/Program.cs
@@ -0,0 +1,17 @@
+using Avalonia;
+using Avalonia.ReactiveUI;
+
+namespace Sandbox
+{
+ public class Program
+ {
+ static void Main(string[] args)
+ {
+ AppBuilder.Configure()
+ .UsePlatformDetect()
+ .UseReactiveUI()
+ .LogToDebug()
+ .StartWithClassicDesktopLifetime(args);
+ }
+ }
+}
diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj
new file mode 100644
index 0000000000..1a0a8a7ce5
--- /dev/null
+++ b/samples/Sandbox/Sandbox.csproj
@@ -0,0 +1,18 @@
+
+
+
+ WinExe
+ netcoreapp3.1
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
index 8e82bf1a38..1e72a07760 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
@@ -49,8 +49,12 @@ namespace Avalonia.Controls
{
if(_binding is Avalonia.Data.Binding binding)
{
- // Force the TwoWay binding mode if there is a Path present. TwoWay binding requires a Path.
- if (!String.IsNullOrEmpty(binding.Path))
+ if (binding.Mode == BindingMode.OneWayToSource)
+ {
+ throw new InvalidOperationException("DataGridColumn doesn't support BindingMode.OneWayToSource. Use BindingMode.TwoWay instead.");
+ }
+
+ if (!String.IsNullOrEmpty(binding.Path) && binding.Mode == BindingMode.Default)
{
binding.Mode = BindingMode.TwoWay;
}
diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs
index fd27cb340a..3b5d57a7b8 100644
--- a/src/Avalonia.Controls/Selection/SelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/SelectionModel.cs
@@ -242,12 +242,7 @@ namespace Avalonia.Controls.Selection
{
using var update = BatchUpdate();
var o = update.Operation;
- var range = CoerceRange(start, end);
-
- if (range.Begin == -1)
- {
- return;
- }
+ var range = new IndexRange(Math.Max(0, start), end);
if (RangesEnabled)
{
diff --git a/src/Avalonia.ReactiveUI.Events/Avalonia.ReactiveUI.Events.csproj b/src/Avalonia.ReactiveUI.Events/Avalonia.ReactiveUI.Events.csproj
index b95c8946e2..75eeb92f42 100644
--- a/src/Avalonia.ReactiveUI.Events/Avalonia.ReactiveUI.Events.csproj
+++ b/src/Avalonia.ReactiveUI.Events/Avalonia.ReactiveUI.Events.csproj
@@ -1,7 +1,7 @@
netstandard2.0
- Avalonia.ReactiveUI
+ Avalonia.ReactiveUI.Events
diff --git a/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs
index 79e029ccd2..9cc94ffac3 100644
--- a/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs
+++ b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs
@@ -45,14 +45,13 @@ namespace Avalonia.Rendering
void LoopProc()
{
- var now = _st.Elapsed;
- var lastTick = now;
-
+ var lastTick = _st.Elapsed;
while (true)
{
+ var now = _st.Elapsed;
var timeTillNextTick = lastTick + _timeBetweenTicks - now;
if (timeTillNextTick.TotalMilliseconds > 1) Thread.Sleep(timeTillNextTick);
-
+ lastTick = now;
lock (_lock)
{
if (_count == 0)
@@ -63,7 +62,7 @@ namespace Avalonia.Rendering
}
_tick?.Invoke(now);
- now = _st.Elapsed;
+
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 192d6e0286..514d3b5475 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -1813,6 +1813,88 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(1, raised);
}
+ [Fact]
+ public void Handles_Removing_Last_Item_In_Two_Controls_With_Bound_SelectedIndex()
+ {
+ var items = new ObservableCollection { "foo" };
+
+ // Simulates problem with TabStrip and Carousel with bound SelectedIndex.
+ var tabStrip = new TestSelector
+ {
+ Items = items,
+ SelectionMode = SelectionMode.AlwaysSelected,
+ };
+
+ var carousel = new TestSelector
+ {
+ Items = items,
+ [!Carousel.SelectedIndexProperty] = tabStrip[!TabStrip.SelectedIndexProperty],
+ };
+
+ var tabStripRaised = 0;
+ var carouselRaised = 0;
+
+ tabStrip.SelectionChanged += (s, e) =>
+ {
+ Assert.Equal(new[] { "foo" }, e.RemovedItems);
+ Assert.Empty(e.AddedItems);
+ ++tabStripRaised;
+ };
+
+ carousel.SelectionChanged += (s, e) =>
+ {
+ Assert.Equal(new[] { "foo" }, e.RemovedItems);
+ Assert.Empty(e.AddedItems);
+ ++carouselRaised;
+ };
+
+ items.RemoveAt(0);
+
+ Assert.Equal(1, tabStripRaised);
+ Assert.Equal(1, carouselRaised);
+ }
+
+ [Fact]
+ public void Handles_Removing_Last_Item_In_Controls_With_Bound_SelectedItem()
+ {
+ var items = new ObservableCollection { "foo" };
+
+ // Simulates problem with TabStrip and Carousel with bound SelectedItem.
+ var tabStrip = new TestSelector
+ {
+ Items = items,
+ SelectionMode = SelectionMode.AlwaysSelected,
+ };
+
+ var carousel = new TestSelector
+ {
+ Items = items,
+ [!Carousel.SelectedItemProperty] = tabStrip[!TabStrip.SelectedItemProperty],
+ };
+
+ var tabStripRaised = 0;
+ var carouselRaised = 0;
+
+ tabStrip.SelectionChanged += (s, e) =>
+ {
+ Assert.Equal(new[] { "foo" }, e.RemovedItems);
+ Assert.Empty(e.AddedItems);
+ ++tabStripRaised;
+ };
+
+ carousel.SelectionChanged += (s, e) =>
+ {
+ Assert.Equal(new[] { "foo" }, e.RemovedItems);
+ Assert.Empty(e.AddedItems);
+ ++carouselRaised;
+ };
+
+ items.RemoveAt(0);
+
+ Assert.Equal(1, tabStripRaised);
+ Assert.Equal(1, carouselRaised);
+ }
+
private static void Prepare(SelectingItemsControl target)
{
var root = new TestRoot
diff --git a/tests/Avalonia.ReactiveUI.Events.UnitTests/Avalonia.ReactiveUI.Events.UnitTests.csproj b/tests/Avalonia.ReactiveUI.Events.UnitTests/Avalonia.ReactiveUI.Events.UnitTests.csproj
deleted file mode 100644
index 19a6fd138e..0000000000
--- a/tests/Avalonia.ReactiveUI.Events.UnitTests/Avalonia.ReactiveUI.Events.UnitTests.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- netcoreapp3.1
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/Avalonia.ReactiveUI.Events.UnitTests/BasicControlEventsTest.cs b/tests/Avalonia.ReactiveUI.Events.UnitTests/BasicControlEventsTest.cs
deleted file mode 100644
index 1092c98246..0000000000
--- a/tests/Avalonia.ReactiveUI.Events.UnitTests/BasicControlEventsTest.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Reactive.Linq;
-using Avalonia.Controls;
-using Avalonia.UnitTests;
-using Xunit;
-
-namespace Avalonia.ReactiveUI.Events.UnitTests
-{
- public class BasicControlEventsTest
- {
- public class EventsControl : UserControl
- {
- public bool IsAttached { get; private set; }
-
- public EventsControl()
- {
- var attached = this
- .Events()
- .AttachedToVisualTree
- .Select(args => true);
-
- this.Events()
- .DetachedFromVisualTree
- .Select(args => false)
- .Merge(attached)
- .Subscribe(marker => IsAttached = marker);
- }
- }
-
- [Fact]
- public void Should_Generate_Events_Wrappers()
- {
- var root = new TestRoot();
- var control = new EventsControl();
- Assert.False(control.IsAttached);
-
- root.Child = control;
- Assert.True(control.IsAttached);
-
- root.Child = null;
- Assert.False(control.IsAttached);
- }
- }
-}