Browse Source

Remove ReactiveUI (#20101)

release/11.3.10
Julien Lebosquain 3 months ago
parent
commit
1f6f1d7b49
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 3
      Avalonia.Desktop.slnf
  2. 21
      Avalonia.sln
  3. 5
      build/ReactiveUI.props
  4. 1
      nukebuild/Build.cs
  5. 4
      samples/Generators.Sandbox/App.xaml.cs
  6. 37
      samples/Generators.Sandbox/Controls/SignUpView.xaml
  7. 39
      samples/Generators.Sandbox/Controls/SignUpView.xaml.cs
  8. 14
      samples/Generators.Sandbox/Generators.Sandbox.csproj
  9. 2
      samples/Generators.Sandbox/Program.cs
  10. 105
      samples/Generators.Sandbox/ViewModels/SignUpViewModel.cs
  11. 15
      samples/Generators.Sandbox/Views/SignUpView.xaml.cs
  12. 8
      samples/ReactiveUIDemo/App.axaml
  13. 37
      samples/ReactiveUIDemo/App.axaml.cs
  14. 19
      samples/ReactiveUIDemo/MainWindow.axaml
  15. 22
      samples/ReactiveUIDemo/MainWindow.axaml.cs
  16. 28
      samples/ReactiveUIDemo/ReactiveUIDemo.csproj
  17. 11
      samples/ReactiveUIDemo/ViewModels/BarViewModel.cs
  18. 11
      samples/ReactiveUIDemo/ViewModels/FooViewModel.cs
  19. 9
      samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs
  20. 21
      samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs
  21. 16
      samples/ReactiveUIDemo/Views/BarView.axaml
  22. 28
      samples/ReactiveUIDemo/Views/BarView.axaml.cs
  23. 16
      samples/ReactiveUIDemo/Views/FooView.axaml
  24. 28
      samples/ReactiveUIDemo/Views/FooView.axaml.cs
  25. 1
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
  26. 29
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  27. 4
      src/Avalonia.ReactiveUI/Attributes.cs
  28. 59
      src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs
  29. 89
      src/Avalonia.ReactiveUI/AutoSuspendHelper.cs
  30. 14
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  31. 75
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  32. 110
      src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs
  33. 92
      src/Avalonia.ReactiveUI/AvaloniaScheduler.cs
  34. 66
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  35. 66
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  36. 184
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  37. 129
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  38. 1
      tests/Avalonia.Generators.Tests/Avalonia.Generators.Tests.csproj
  39. 8
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/CustomControls.txt
  40. 4
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/CustomControls.txt
  41. 4
      tests/Avalonia.Generators.Tests/Views/AttachedProps.xml
  42. 4
      tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml
  43. 10
      tests/Avalonia.Generators.Tests/Views/CustomControls.xml
  44. 9
      tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs
  45. 8
      tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs
  46. 146
      tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs
  47. 117
      tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
  48. 16
      tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
  49. 207
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  50. 45
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs
  51. 196
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
  52. 116
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs
  53. 223
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
  54. 52
      tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs
  55. 144
      tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

3
Avalonia.Desktop.slnf

@ -9,7 +9,6 @@
"samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"samples\\RenderDemo\\RenderDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
@ -31,7 +30,6 @@
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
"src\\Avalonia.Vulkan\\Avalonia.Vulkan.csproj",
"src\\Avalonia.ReactiveUI\\Avalonia.ReactiveUI.csproj",
"src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj",
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
@ -64,7 +62,6 @@
"tests\\Avalonia.LeakTests\\Avalonia.LeakTests.csproj",
"tests\\Avalonia.Markup.UnitTests\\Avalonia.Markup.UnitTests.csproj",
"tests\\Avalonia.Markup.Xaml.UnitTests\\Avalonia.Markup.Xaml.UnitTests.csproj",
"tests\\Avalonia.ReactiveUI.UnitTests\\Avalonia.ReactiveUI.UnitTests.csproj",
"tests\\Avalonia.RenderTests.WpfCompare\\Avalonia.RenderTests.WpfCompare.csproj",
"tests\\Avalonia.Skia.RenderTests\\Avalonia.Skia.RenderTests.csproj",
"tests\\Avalonia.Skia.UnitTests\\Avalonia.Skia.UnitTests.csproj",

21
Avalonia.sln

@ -45,8 +45,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE
src\Shared\StreamCompatibilityExtensions.cs = src\Shared\StreamCompatibilityExtensions.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Markup\Avalonia.Markup\Avalonia.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
@ -107,7 +105,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\NetCore.props = build\NetCore.props
build\NetFX.props = build\NetFX.props
build\NullableEnable.props = build\NullableEnable.props
build\ReactiveUI.props = build\ReactiveUI.props
build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
build\Rx.props = build\Rx.props
build\SampleApp.props = build\SampleApp.props
@ -169,8 +166,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.X11", "src\Avaloni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
@ -219,8 +214,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\Avalonia.Analyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
@ -359,10 +352,6 @@ Global
{3E53A01A-B331-47F3-B828-4A5717E77A24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E53A01A-B331-47F3-B828-4A5717E77A24}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E53A01A-B331-47F3-B828-4A5717E77A24}.Release|Any CPU.Build.0 = Release|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|Any CPU.Build.0 = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -495,10 +484,6 @@ Global
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|Any CPU.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.Build.0 = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -591,10 +576,6 @@ Global
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.Build.0 = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -754,7 +735,6 @@ Global
{3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
@ -774,7 +754,6 @@ Global
{47F8530C-F19B-4B1A-B4D6-EB231522AE5D} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}

5
build/ReactiveUI.props

@ -1,5 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="ReactiveUI" Version="20.1.1" />
</ItemGroup>
</Project>

1
nukebuild/Build.cs

@ -252,7 +252,6 @@ partial class Build : NukeBuild
RunCoreTest("Avalonia.Markup.UnitTests");
RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests");
RunCoreTest("Avalonia.Headless.NUnit.PerAssembly.UnitTests");
RunCoreTest("Avalonia.Headless.NUnit.PerTest.UnitTests");
RunCoreTest("Avalonia.Headless.XUnit.PerAssembly.UnitTests");

4
samples/Generators.Sandbox/App.xaml.cs

@ -12,9 +12,9 @@ public class App : Application
{
var view = new Views.SignUpView
{
ViewModel = new SignUpViewModel()
DataContext = new SignUpViewModel()
};
view.Show();
base.OnFrameworkInitializationCompleted();
}
}
}

37
samples/Generators.Sandbox/Controls/SignUpView.xaml

@ -1,45 +1,58 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Generators.Sandbox.Controls"
x:Class="Generators.Sandbox.Controls.SignUpView">
<StackPanel>
xmlns:vm="clr-namespace:Generators.Sandbox.ViewModels"
x:Class="Generators.Sandbox.Controls.SignUpView"
x:DataType="vm:SignUpViewModel">
<UserControl.Styles>
<Style Selector="DataValidationErrors">
<Setter Property="ErrorTemplate">
<Setter.Value>
<DataTemplate />
</Setter.Value>
</Setter>
</Style>
</UserControl.Styles>
<StackPanel>
<controls:CustomTextBox Margin="0 10 0 0"
x:Name="UserNameTextBox"
Text="{Binding UserName}"
Watermark="Please, enter user name..."
UseFloatingWatermark="True" />
<TextBlock x:Name="UserNameValidation"
<TextBlock x:Name="UserNameValidation"
Text="{Binding UserNameValidation}"
Foreground="Red"
FontSize="12" />
<TextBox Margin="0 10 0 0"
x:Name="PasswordTextBox"
Text="{Binding Password}"
Watermark="Please, enter your password..."
UseFloatingWatermark="True"
PasswordChar="*" />
<TextBlock x:Name="PasswordValidation"
<TextBlock x:Name="PasswordValidation"
Text="{Binding PasswordValidation}"
Foreground="Red"
FontSize="12" />
<TextBox Margin="0 10 0 0"
x:Name="ConfirmPasswordTextBox"
Text="{Binding ConfirmPassword}"
Watermark="Please, confirm the password..."
UseFloatingWatermark="True"
PasswordChar="*" />
<TextBlock x:Name="ConfirmPasswordValidation"
Text="{Binding ConfirmPasswordValidation}"
TextWrapping="Wrap"
Foreground="Red"
FontSize="12" />
<TextBlock>
<TextBlock.Inlines>
<InlineCollection>
<Run x:Name="SignUpButtonDescription" />
</InlineCollection>
</TextBlock.Inlines>
</TextBlock>
<TextBlock Text="Press the button below to sign up." />
<Button Margin="0 10 0 5"
Content="Sign up"
Command="{Binding SignUp}"
x:Name="SignUpButton" />
<TextBlock x:Name="CompoundValidation"
TextWrapping="Wrap"
Foreground="Red"
FontSize="12" />
FontSize="12"
Text="{Binding CompoundValidation}" />
</StackPanel>
</UserControl>

39
samples/Generators.Sandbox/Controls/SignUpView.xaml.cs

@ -1,10 +1,4 @@
using System;
using System.Reactive.Disposables;
using Avalonia.ReactiveUI;
using Generators.Sandbox.ViewModels;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
using Avalonia.Controls;
namespace Generators.Sandbox.Controls;
@ -14,41 +8,12 @@ namespace Generators.Sandbox.Controls;
/// references are living in a separate partial class file. See also:
/// https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/
/// </summary>
public partial class SignUpView : ReactiveUserControl<SignUpViewModel>
public partial class SignUpView : UserControl
{
public SignUpView()
{
// The InitializeComponent method is also generated automatically
// and lives in the autogenerated part of the partial class.
InitializeComponent();
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, x => x.UserName, x => x.UserNameTextBox.Text)
.DisposeWith(disposables);
this.Bind(ViewModel, x => x.Password, x => x.PasswordTextBox.Text)
.DisposeWith(disposables);
this.Bind(ViewModel, x => x.ConfirmPassword, x => x.ConfirmPasswordTextBox.Text)
.DisposeWith(disposables);
this.BindCommand(ViewModel, x => x.SignUp, x => x.SignUpButton)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.UserName, x => x.UserNameValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.ConfirmPassword, x => x.ConfirmPasswordValidation.Text)
.DisposeWith(disposables);
var newLineFormatter = new SingleLineFormatter(Environment.NewLine);
this.BindValidation(ViewModel, x => x.CompoundValidation.Text, newLineFormatter)
.DisposeWith(disposables);
// The references to text boxes below are also auto generated.
// Use Ctrl+Click in order to view the generated sources.
UserNameTextBox.Text = "Joseph!";
PasswordTextBox.Text = "1234";
ConfirmPasswordTextBox.Text = "1234";
SignUpButtonDescription.Text = "Press the button below to sign up.";
});
}
}

14
samples/Generators.Sandbox/Generators.Sandbox.csproj

@ -1,4 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework>
@ -6,23 +7,24 @@
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="**\*.xaml"/>
<AvaloniaXaml Include="**\*.xaml"/>
<!-- Note this AdditionalFiles directive. -->
<AdditionalFiles Include="**\*.xaml" SourceItemGroup="AvaloniaXaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ReactiveUI.Validation" Version="3.0.22"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj"/>
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj"/>
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj"/>
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj"/>
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets"/>
<Import Project="..\..\build\SourceGenerators.props"/>
<Import Project="..\..\build\NullableEnable.props" />
</Project>

2
samples/Generators.Sandbox/Program.cs

@ -1,5 +1,4 @@
using Avalonia;
using Avalonia.ReactiveUI;
namespace Generators.Sandbox;
@ -9,7 +8,6 @@ internal static class Program
private static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UseReactiveUI()
.UsePlatformDetect()
.LogToTrace();
}

105
samples/Generators.Sandbox/ViewModels/SignUpViewModel.cs

@ -1,70 +1,81 @@
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Generators.Sandbox.ViewModels;
public class SignUpViewModel : ReactiveValidationObject
public class SignUpViewModel : ObservableValidator
{
private string _userName = string.Empty;
private string _password = string.Empty;
private string _confirmPassword = string.Empty;
private string? _userName;
private string? _password;
private string? _confirmPassword;
public SignUpViewModel()
{
this.ValidationRule(
vm => vm.UserName,
name => !string.IsNullOrWhiteSpace(name),
"UserName is required.");
UserName = "Joseph!";
Password = "1234";
ConfirmPassword = "1234";
SignUp = new RelayCommand(() => { }, () => !HasErrors);
this.ValidationRule(
vm => vm.Password,
password => !string.IsNullOrWhiteSpace(password),
"Password is required.");
this.ValidationRule(
vm => vm.Password,
password => password?.Length > 2,
password => $"Password should be longer, current length: {password.Length}");
ErrorsChanged += OnErrorsChanged;
}
this.ValidationRule(
vm => vm.ConfirmPassword,
confirmation => !string.IsNullOrWhiteSpace(confirmation),
"Confirm password field is required.");
public RelayCommand SignUp { get; }
var passwordsObservable =
this.WhenAnyValue(
x => x.Password,
x => x.ConfirmPassword,
(password, confirmation) =>
password == confirmation);
[Required]
public string? UserName
{
get => _userName;
set => SetProperty(ref _userName, value, validate: true);
}
this.ValidationRule(
vm => vm.ConfirmPassword,
passwordsObservable,
"Passwords must match.");
public string? UserNameValidation
=> GetValidationMessage(nameof(UserName));
SignUp = ReactiveCommand.Create(() => {}, this.IsValid());
[Required]
[MinLength(2)]
public string? Password
{
get => _password;
set
{
if (SetProperty(ref _password, value, validate: true))
ValidateProperty(ConfirmPassword, nameof(ConfirmPassword));
}
}
public ReactiveCommand<Unit, Unit> SignUp { get; }
public string? PasswordValidation
=> GetValidationMessage(nameof(Password));
public string UserName
[Required]
[Compare(nameof(Password))]
public string? ConfirmPassword
{
get => _userName;
set => this.RaiseAndSetIfChanged(ref _userName, value);
get => _confirmPassword;
set => SetProperty(ref _confirmPassword, value, validate: true);
}
public string Password
public string? ConfirmPasswordValidation
=> GetValidationMessage(nameof(ConfirmPassword));
public string? CompoundValidation
=> GetValidationMessage(null);
private void OnErrorsChanged(object? sender, DataErrorsChangedEventArgs e)
{
get => _password;
set => this.RaiseAndSetIfChanged(ref _password, value);
if (e.PropertyName is not null)
OnPropertyChanged(e.PropertyName + "Validation");
OnPropertyChanged(CompoundValidation);
SignUp.NotifyCanExecuteChanged();
}
public string ConfirmPassword
private string? GetValidationMessage(string? propertyName)
{
get => _confirmPassword;
set => this.RaiseAndSetIfChanged(ref _confirmPassword, value);
var message = string.Join(Environment.NewLine, GetErrors(propertyName).Select(v => v.ErrorMessage));
return string.IsNullOrEmpty(message) ? null : message;
}
}
}

15
samples/Generators.Sandbox/Views/SignUpView.xaml.cs

@ -1,7 +1,4 @@
using System.Reactive.Disposables;
using Avalonia.ReactiveUI;
using Generators.Sandbox.ViewModels;
using ReactiveUI;
using Avalonia.Controls;
namespace Generators.Sandbox.Views;
@ -11,18 +8,12 @@ namespace Generators.Sandbox.Views;
/// references are living in a separate partial class file. See also:
/// https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/
/// </summary>
public partial class SignUpView : ReactiveWindow<SignUpViewModel>
public partial class SignUpView : Window
{
public SignUpView()
{
// The InitializeComponent method is also generated automatically
// and lives in the autogenerated part of the partial class.
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(view => view.ViewModel)
.BindTo(this, view => view.SignUpControl.ViewModel)
.DisposeWith(disposables);
});
}
}
}

8
samples/ReactiveUIDemo/App.axaml

@ -1,8 +0,0 @@
<Application
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ReactiveUIDemo.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

37
samples/ReactiveUIDemo/App.axaml.cs

@ -1,37 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
using ReactiveUIDemo.ViewModels;
using ReactiveUIDemo.Views;
using Splat;
namespace ReactiveUIDemo
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
Locator.CurrentMutable.Register(() => new FooView(), typeof(IViewFor<FooViewModel>));
Locator.CurrentMutable.Register(() => new BarView(), typeof(IViewFor<BarViewModel>));
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow();
base.OnFrameworkInitializationCompleted();
}
public static int Main(string[] args)
=> BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.LogToTrace();
}
}

19
samples/ReactiveUIDemo/MainWindow.axaml

@ -1,19 +0,0 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="ReactiveUIDemo.MainWindow"
xmlns:vm="using:ReactiveUIDemo.ViewModels"
xmlns:rxui="using:Avalonia.ReactiveUI"
Title="AvaloniaUI ReactiveUI Demo"
x:DataType="vm:MainWindowViewModel">
<TabControl TabStripPlacement="Left">
<TabItem Header="RoutedViewHost">
<DockPanel DataContext="{Binding RoutedViewHost}">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8">
<Button Command="{Binding ShowFoo}">Foo</Button>
<Button Command="{Binding ShowBar}">Bar</Button>
</StackPanel>
<rxui:RoutedViewHost Router="{Binding Router}"/>
</DockPanel>
</TabItem>
</TabControl>
</Window>

22
samples/ReactiveUIDemo/MainWindow.axaml.cs

@ -1,22 +0,0 @@
using ReactiveUIDemo.ViewModels;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReactiveUIDemo
{
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.DataContext = new MainWindowViewModel();
this.AttachDevTools();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

28
samples/ReactiveUIDemo/ReactiveUIDemo.csproj

@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\BarView.axaml.cs">
<DependentUpon>BarView.axaml</DependentUpon>
</Compile>
<Compile Update="Views\FooView.axaml.cs">
<DependentUpon>FooView.axaml</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

11
samples/ReactiveUIDemo/ViewModels/BarViewModel.cs

@ -1,11 +0,0 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class BarViewModel : ReactiveObject, IRoutableViewModel
{
public BarViewModel(IScreen screen) => HostScreen = screen;
public string UrlPathSegment => "Bar";
public IScreen HostScreen { get; }
}
}

11
samples/ReactiveUIDemo/ViewModels/FooViewModel.cs

@ -1,11 +0,0 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class FooViewModel : ReactiveObject, IRoutableViewModel
{
public FooViewModel(IScreen screen) => HostScreen = screen;
public string UrlPathSegment => "Foo";
public IScreen HostScreen { get; }
}
}

9
samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs

@ -1,9 +0,0 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class MainWindowViewModel : ReactiveObject
{
public RoutedViewHostPageViewModel RoutedViewHost { get; } = new();
}
}

21
samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs

@ -1,21 +0,0 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class RoutedViewHostPageViewModel : ReactiveObject, IScreen
{
public RoutedViewHostPageViewModel()
{
Foo = new(this);
Bar = new(this);
Router.Navigate.Execute(Foo);
}
public RoutingState Router { get; } = new();
public FooViewModel Foo { get; }
public BarViewModel Bar { get; }
public void ShowFoo() => Router.Navigate.Execute(Foo);
public void ShowBar() => Router.Navigate.Execute(Bar);
}
}

16
samples/ReactiveUIDemo/Views/BarView.axaml

@ -1,16 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReactiveUIDemo.Views.BarView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border Background="Blue">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48">
Bar!
</TextBlock>
</Border>
</UserControl>

28
samples/ReactiveUIDemo/Views/BarView.axaml.cs

@ -1,28 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using ReactiveUIDemo.ViewModels;
namespace ReactiveUIDemo.Views
{
internal partial class BarView : UserControl, IViewFor<BarViewModel>
{
public BarView()
{
InitializeComponent();
}
public BarViewModel? ViewModel { get; set; }
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (BarViewModel?)value;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

16
samples/ReactiveUIDemo/Views/FooView.axaml

@ -1,16 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReactiveUIDemo.Views.FooView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border Background="Red">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48">
Foo!
</TextBlock>
</Border>
</UserControl>

28
samples/ReactiveUIDemo/Views/FooView.axaml.cs

@ -1,28 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using ReactiveUIDemo.ViewModels;
namespace ReactiveUIDemo.Views
{
internal partial class FooView : UserControl, IViewFor<FooViewModel>
{
public FooView()
{
InitializeComponent();
}
public FooViewModel? ViewModel { get; set; }
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (FooViewModel?)value;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

1
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj

@ -35,7 +35,6 @@
<ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj"/>
<ProjectReference Condition="'$(Configuration)' == 'Debug'" Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj"/>
<ProjectReference Include="..\..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj"/>
<ProjectReference Include="..\..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj"/>
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj"/>
<ProjectReference Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj"/>
</ItemGroup>

29
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -1,29 +0,0 @@
using Avalonia.Controls;
using Avalonia.Threading;
using ReactiveUI;
using Splat;
namespace Avalonia.ReactiveUI
{
public static class AppBuilderExtensions
{
/// <summary>
/// Initializes ReactiveUI framework to use with Avalonia. Registers Avalonia
/// scheduler, an activation for view fetcher, a template binding hook. Remember
/// to call this method if you are using ReactiveUI in your application.
/// </summary>
public static AppBuilder UseReactiveUI(this AppBuilder builder) =>
builder.AfterPlatformServicesSetup(_ => Locator.RegisterResolverCallbackChanged(() =>
{
if (Locator.CurrentMutable is null)
{
return;
}
PlatformRegistrationManager.SetRegistrationNamespaces(RegistrationNamespace.Avalonia);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
}));
}
}

4
src/Avalonia.ReactiveUI/Attributes.cs

@ -1,4 +0,0 @@
using System.Reflection;
using Avalonia.Metadata;
[assembly: XmlnsDefinition("http://reactiveui.net", "Avalonia.ReactiveUI")]

59
src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs

@ -1,59 +0,0 @@
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Templates;
using ReactiveUI;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// AutoDataTemplateBindingHook is a binding hook that checks ItemsControls
/// that don't have DataTemplates, and assigns a default DataTemplate that
/// loads the View associated with each ViewModel.
/// </summary>
public class AutoDataTemplateBindingHook : IPropertyBindingHook
{
private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>((x, _) =>
{
var control = new ViewModelViewHost();
var context = control.GetObservable(Control.DataContextProperty);
control.Bind(ViewModelViewHost.ViewModelProperty, context);
control.HorizontalContentAlignment = HorizontalAlignment.Stretch;
control.VerticalContentAlignment = VerticalAlignment.Stretch;
return control;
},
true);
/// <inheritdoc/>
public bool ExecuteHook(
object? source, object target,
Func<IObservedChange<object, object>[]> getCurrentViewModelProperties,
Func<IObservedChange<object, object>[]> getCurrentViewProperties,
BindingDirection direction)
{
var viewProperties = getCurrentViewProperties();
var lastViewProperty = viewProperties.LastOrDefault();
var itemsControl = lastViewProperty?.Sender as ItemsControl;
if (itemsControl == null)
return true;
var propertyName = viewProperties.Last().GetPropertyName();
if (propertyName != "Items" &&
propertyName != "ItemsSource")
return true;
if (itemsControl.ItemTemplate != null)
return true;
if (itemsControl.DataTemplates != null &&
itemsControl.DataTemplates.Count > 0)
return true;
itemsControl.ItemTemplate = DefaultItemTemplate;
return true;
}
}
}

89
src/Avalonia.ReactiveUI/AutoSuspendHelper.cs

@ -1,89 +0,0 @@
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
using System.Threading;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using ReactiveUI;
using System;
using Avalonia.Controls.ApplicationLifetimes;
using Splat;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI AutoSuspendHelper which initializes suspension hooks for
/// Avalonia applications. Call its constructor in your app's composition root,
/// before calling the RxApp.SuspensionHost.SetupDefaultSuspendResume method.
/// </summary>
public sealed class AutoSuspendHelper : IEnableLogger, IDisposable
{
private readonly Subject<IDisposable> _shouldPersistState = new Subject<IDisposable>();
private readonly Subject<Unit> _isLaunchingNew = new Subject<Unit>();
/// <summary>
/// Initializes a new instance of the <see cref="AutoSuspendHelper"/> class.
/// </summary>
/// <param name="lifetime">Pass in the Application.ApplicationLifetime property.</param>
public AutoSuspendHelper(IApplicationLifetime lifetime)
{
RxApp.SuspensionHost.IsResuming = Observable.Never<Unit>();
RxApp.SuspensionHost.IsLaunchingNew = _isLaunchingNew;
if (Avalonia.Controls.Design.IsDesignMode)
{
this.Log().Debug("Design mode detected. AutoSuspendHelper won't persist app state.");
RxApp.SuspensionHost.ShouldPersistState = Observable.Never<IDisposable>();
}
else if (lifetime is IControlledApplicationLifetime controlled)
{
this.Log().Debug("Using IControlledApplicationLifetime events to handle app exit.");
controlled.Exit += (sender, args) => OnControlledApplicationLifetimeExit();
RxApp.SuspensionHost.ShouldPersistState = _shouldPersistState;
}
else if (lifetime != null)
{
var type = lifetime.GetType().FullName;
var message = $"Don't know how to detect app exit event for {type}.";
throw new NotSupportedException(message);
}
else
{
var message = "ApplicationLifetime is null. "
+ "Ensure you are initializing AutoSuspendHelper "
+ "after Avalonia application initialization is completed.";
throw new ArgumentNullException(message);
}
var errored = new Subject<Unit>();
AppDomain.CurrentDomain.UnhandledException += (o, e) => errored.OnNext(Unit.Default);
RxApp.SuspensionHost.ShouldInvalidateState = errored;
}
/// <summary>
/// Call this method in your App.OnFrameworkInitializationCompleted method.
/// </summary>
public void OnFrameworkInitializationCompleted() => _isLaunchingNew.OnNext(Unit.Default);
/// <summary>
/// Disposes internally stored observers.
/// </summary>
public void Dispose()
{
_shouldPersistState.Dispose();
_isLaunchingNew.Dispose();
}
private void OnControlledApplicationLifetimeExit()
{
this.Log().Debug("Received IControlledApplicationLifetime exit event.");
var manual = new ManualResetEvent(false);
_shouldPersistState.OnNext(Disposable.Create(() => manual.Set()));
manual.WaitOne();
this.Log().Debug("Completed actions on IControlledApplicationLifetime exit event.");
}
}
}

14
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks);netstandard2.0</TargetFrameworks>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project>

75
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -1,75 +0,0 @@
using System;
using System.Reactive.Linq;
using Avalonia.VisualTree;
using Avalonia.Controls;
using Avalonia.Interactivity;
using ReactiveUI;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// Determines when Avalonia IVisuals get activated.
/// </summary>
public class AvaloniaActivationForViewFetcher : IActivationForViewFetcher
{
/// <summary>
/// Returns affinity for view.
/// </summary>
public int GetAffinityForView(Type view)
{
return typeof(Visual).IsAssignableFrom(view) ? 10 : 0;
}
/// <summary>
/// Returns activation observable for activatable Avalonia view.
/// </summary>
public IObservable<bool> GetActivationForView(IActivatableView view)
{
if (!(view is Visual visual)) return Observable.Return(false);
if (view is Control control) return GetActivationForControl(control);
return GetActivationForVisual(visual);
}
/// <summary>
/// Listens to Loaded and Unloaded
/// events for Avalonia Control.
/// </summary>
private IObservable<bool> GetActivationForControl(Control control)
{
var controlLoaded = Observable
.FromEventPattern<RoutedEventArgs>(
x => control.Loaded += x,
x => control.Loaded -= x)
.Select(args => true);
var controlUnloaded = Observable
.FromEventPattern<RoutedEventArgs>(
x => control.Unloaded += x,
x => control.Unloaded -= x)
.Select(args => false);
return controlLoaded
.Merge(controlUnloaded)
.DistinctUntilChanged();
}
/// <summary>
/// Listens to AttachedToVisualTree and DetachedFromVisualTree
/// events for Avalonia IVisuals.
/// </summary>
private IObservable<bool> GetActivationForVisual(Visual visual)
{
var visualLoaded = Observable
.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => visual.AttachedToVisualTree += x,
x => visual.AttachedToVisualTree -= x)
.Select(args => true);
var visualUnloaded = Observable
.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => visual.DetachedFromVisualTree += x,
x => visual.DetachedFromVisualTree -= x)
.Select(args => false);
return visualLoaded
.Merge(visualUnloaded)
.DistinctUntilChanged();
}
}
}

110
src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs

@ -1,110 +0,0 @@
using System.Reactive;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.ReactiveUI;
public static class AvaloniaObjectReactiveExtensions
{
/// <summary>
/// Gets a subject for an <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <param name="priority">
/// The priority with which binding values are written to the object.
/// </param>
/// <returns>
/// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the
/// property.
/// </returns>
public static ISubject<object?> GetSubject(
this AvaloniaObject o,
AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue)
{
return Subject.Create<object?>(
Observer.Create<object?>(x => o.SetValue(property, x, priority)),
o.GetObservable(property));
}
/// <summary>
/// Gets a subject for an <see cref="AvaloniaProperty"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <param name="priority">
/// The priority with which binding values are written to the object.
/// </param>
/// <returns>
/// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the
/// property.
/// </returns>
public static ISubject<T> GetSubject<T>(
this AvaloniaObject o,
AvaloniaProperty<T> property,
BindingPriority priority = BindingPriority.LocalValue)
{
return Subject.Create<T>(
Observer.Create<T>(x => o.SetValue(property, x, priority)),
o.GetObservable(property));
}
/// <summary>
/// Gets a subject for a <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <param name="priority">
/// The priority with which binding values are written to the object.
/// </param>
/// <returns>
/// An <see cref="ISubject{Object}"/> which can be used for two-way binding to/from the
/// property.
/// </returns>
public static ISubject<BindingValue<object?>> GetBindingSubject(
this AvaloniaObject o,
AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue)
{
return Subject.Create<BindingValue<object?>>(
Observer.Create<BindingValue<object?>>(x =>
{
if (x.HasValue)
{
o.SetValue(property, x.Value, priority);
}
}),
o.GetBindingObservable(property));
}
/// <summary>
/// Gets a subject for a <see cref="AvaloniaProperty"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <param name="priority">
/// The priority with which binding values are written to the object.
/// </param>
/// <returns>
/// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the
/// property.
/// </returns>
public static ISubject<BindingValue<T>> GetBindingSubject<T>(
this AvaloniaObject o,
AvaloniaProperty<T> property,
BindingPriority priority = BindingPriority.LocalValue)
{
return Subject.Create<BindingValue<T>>(
Observer.Create<BindingValue<T>>(x =>
{
if (x.HasValue)
{
o.SetValue(property, x.Value, priority);
}
}),
o.GetBindingObservable(property));
}
}

92
src/Avalonia.ReactiveUI/AvaloniaScheduler.cs

@ -1,92 +0,0 @@
using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using Avalonia.Threading;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A reactive scheduler that uses Avalonia's <see cref="Dispatcher"/>.
/// </summary>
public class AvaloniaScheduler : LocalScheduler
{
/// <summary>
/// Users can schedule actions on the dispatcher thread while being on the correct thread already.
/// We are optimizing this case by invoking user callback immediately which can lead to stack overflows in certain cases.
/// To prevent this we are limiting amount of reentrant calls to <see cref="Schedule{TState}"/> before we will
/// schedule on a dispatcher anyway.
/// </summary>
private const int MaxReentrantSchedules = 32;
private int _reentrancyGuard;
/// <summary>
/// The instance of the <see cref="AvaloniaScheduler"/>.
/// </summary>
public static readonly AvaloniaScheduler Instance = new AvaloniaScheduler();
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaScheduler"/> class.
/// </summary>
private AvaloniaScheduler()
{
}
/// <inheritdoc/>
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
IDisposable PostOnDispatcher()
{
var composite = new CompositeDisposable(2);
var cancellation = new CancellationDisposable();
Dispatcher.UIThread.Post(() =>
{
if (!cancellation.Token.IsCancellationRequested)
{
composite.Add(action(this, state));
}
}, DispatcherPriority.Background);
composite.Add(cancellation);
return composite;
}
if (dueTime == TimeSpan.Zero)
{
if (!Dispatcher.UIThread.CheckAccess())
{
return PostOnDispatcher();
}
else
{
if (_reentrancyGuard >= MaxReentrantSchedules)
{
return PostOnDispatcher();
}
try
{
_reentrancyGuard++;
return action(this, state);
}
finally
{
_reentrancyGuard--;
}
}
}
else
{
var composite = new CompositeDisposable(2);
composite.Add(DispatcherTimer.RunOnce(() => composite.Add(action(this, state)), dueTime));
return composite;
}
}
}
}

66
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -1,66 +0,0 @@
using System;
using Avalonia.Controls;
using ReactiveUI;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI <see cref="UserControl"/> that implements the <see cref="IViewFor{TViewModel}"/> interface and
/// will activate your ViewModel automatically if the view model implements <see cref="IActivatableViewModel"/>.
/// When the DataContext property changes, this class will update the ViewModel property with the new DataContext
/// value, and vice versa.
/// </summary>
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
public class ReactiveUserControl<TViewModel> : UserControl, IViewFor<TViewModel> where TViewModel : class
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002", Justification = "Generic avalonia property is expected here.")]
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty
.Register<ReactiveUserControl<TViewModel>, TViewModel?>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveUserControl{TViewModel}"/> class.
/// </summary>
public ReactiveUserControl()
{
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
}
/// <summary>
/// The ViewModel.
/// </summary>
public TViewModel? ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (TViewModel?)value;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DataContextProperty)
{
if (ReferenceEquals(change.OldValue, ViewModel)
&& change.NewValue is null or TViewModel)
{
SetCurrentValue(ViewModelProperty, change.NewValue);
}
}
else if (change.Property == ViewModelProperty)
{
if (ReferenceEquals(change.OldValue, DataContext))
{
SetCurrentValue(DataContextProperty, change.NewValue);
}
}
}
}
}

66
src/Avalonia.ReactiveUI/ReactiveWindow.cs

@ -1,66 +0,0 @@
using System;
using Avalonia.Controls;
using ReactiveUI;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI <see cref="Window"/> that implements the <see cref="IViewFor{TViewModel}"/> interface and will
/// activate your ViewModel automatically if the view model implements <see cref="IActivatableViewModel"/>. When
/// the DataContext property changes, this class will update the ViewModel property with the new DataContext value,
/// and vice versa.
/// </summary>
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
public class ReactiveWindow<TViewModel> : Window, IViewFor<TViewModel> where TViewModel : class
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002", Justification = "Generic avalonia property is expected here.")]
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty
.Register<ReactiveWindow<TViewModel>, TViewModel?>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveWindow{TViewModel}"/> class.
/// </summary>
public ReactiveWindow()
{
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
}
/// <summary>
/// The ViewModel.
/// </summary>
public TViewModel? ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (TViewModel?)value;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DataContextProperty)
{
if (ReferenceEquals(change.OldValue, ViewModel)
&& change.NewValue is null or TViewModel)
{
SetCurrentValue(ViewModelProperty, change.NewValue);
}
}
else if (change.Property == ViewModelProperty)
{
if (ReferenceEquals(change.OldValue, DataContext))
{
SetCurrentValue(DataContextProperty, change.NewValue);
}
}
}
}
}

184
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -1,184 +0,0 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia;
using ReactiveUI;
using Splat;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// This control hosts the View associated with ReactiveUI RoutingState,
/// and will display the View and wire up the ViewModel whenever a new
/// ViewModel is navigated to. Nested routing is also supported.
/// </summary>
/// <remarks>
/// <para>
/// ReactiveUI routing consists of an IScreen that contains current
/// RoutingState, several IRoutableViewModels, and a platform-specific
/// XAML control called RoutedViewHost.
/// </para>
/// <para>
/// RoutingState manages the ViewModel navigation stack and allows
/// ViewModels to navigate to other ViewModels. IScreen is the root of
/// a navigation stack; despite the name, its views don't have to occupy
/// the whole screen. RoutedViewHost monitors an instance of RoutingState,
/// responding to any changes in the navigation stack by creating and
/// embedding the appropriate view.
/// </para>
/// <para>
/// Place this control to a view containing your ViewModel that implements
/// IScreen, and bind IScreen.Router property to RoutedViewHost.Router property.
/// <code>
/// <![CDATA[
/// <rxui:RoutedViewHost
/// HorizontalAlignment="Stretch"
/// VerticalAlignment="Stretch"
/// Router="{Binding Router}">
/// <rxui:RoutedViewHost.DefaultContent>
/// <TextBlock Text="Default Content"/>
/// </rxui:RoutedViewHost.DefaultContent>
/// </rxui:RoutedViewHost>
/// ]]>
/// </code>
/// </para>
/// <para>
/// See <see href="https://reactiveui.net/docs/handbook/routing/">
/// ReactiveUI routing documentation website</see> for more info.
/// </para>
/// </remarks>
public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.
/// </summary>
public static readonly StyledProperty<RoutingState?> RouterProperty =
AvaloniaProperty.Register<RoutedViewHost, RoutingState?>(nameof(Router));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewContract"/> property.
/// </summary>
public static readonly StyledProperty<string?> ViewContractProperty =
AvaloniaProperty.Register<RoutedViewHost, string?>(nameof(ViewContract));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
/// </summary>
public static readonly StyledProperty<object?> DefaultContentProperty =
ViewModelViewHost.DefaultContentProperty.AddOwner<RoutedViewHost>();
/// <summary>
/// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
/// </summary>
public RoutedViewHost()
{
this.WhenActivated(disposables =>
{
var routerRemoved = this
.WhenAnyValue(x => x.Router)
.Where(router => router == null)!
.Cast<object?>();
var viewContract = this.WhenAnyValue(x => x.ViewContract);
this.WhenAnyValue(x => x.Router)
.Where(router => router != null)
.SelectMany(router => router!.CurrentViewModel)
.Merge(routerRemoved)
.CombineLatest(viewContract)
.Subscribe(tuple => NavigateToViewModel(tuple.First, tuple.Second))
.DisposeWith(disposables);
});
}
/// <summary>
/// Gets or sets the <see cref="RoutingState"/> of the view model stack.
/// </summary>
public RoutingState? Router
{
get => GetValue(RouterProperty);
set => SetValue(RouterProperty, value);
}
/// <summary>
/// Gets or sets the view contract.
/// </summary>
public string? ViewContract
{
get => GetValue(ViewContractProperty);
set => SetValue(ViewContractProperty, value);
}
/// <summary>
/// Gets or sets the content displayed whenever there is no page currently routed.
/// </summary>
public object? DefaultContent
{
get => GetValue(DefaultContentProperty);
set => SetValue(DefaultContentProperty, value);
}
/// <summary>
/// Gets or sets the ReactiveUI view locator used by this router.
/// </summary>
public IViewLocator? ViewLocator { get; set; }
protected override Type StyleKeyOverride => typeof(TransitioningContentControl);
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
/// <param name="contract">The contract for view resolution.</param>
private void NavigateToViewModel(object? viewModel, string? contract)
{
if (Router == null)
{
this.Log().Warn("Router property is null. Falling back to default content.");
Content = DefaultContent;
return;
}
if (viewModel == null)
{
this.Log().Info("ViewModel is null. Falling back to default content.");
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance == null)
{
if (contract == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
}
else
{
this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content.");
}
Content = DefaultContent;
return;
}
if (contract == null)
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
}
else
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'.");
}
viewInstance.ViewModel = viewModel;
if (viewInstance is IDataContextProvider provider)
provider.DataContext = viewModel;
Content = viewInstance;
}
}
}

129
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@ -1,129 +0,0 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Styling;
using ReactiveUI;
using Splat;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// This content control will automatically load the View associated with
/// the ViewModel property and display it. This control is very useful
/// inside a DataTemplate to display the View associated with a ViewModel.
/// </summary>
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewModel"/> property.
/// </summary>
public static readonly AvaloniaProperty<object?> ViewModelProperty =
AvaloniaProperty.Register<ViewModelViewHost, object?>(nameof(ViewModel));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewContract"/> property.
/// </summary>
public static readonly StyledProperty<string?> ViewContractProperty =
AvaloniaProperty.Register<ViewModelViewHost, string?>(nameof(ViewContract));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
/// </summary>
public static readonly StyledProperty<object?> DefaultContentProperty =
AvaloniaProperty.Register<ViewModelViewHost, object?>(nameof(DefaultContent));
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
/// </summary>
public ViewModelViewHost()
{
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel, x => x.ViewContract)
.Subscribe(tuple => NavigateToViewModel(tuple.Item1, tuple.Item2))
.DisposeWith(disposables);
});
}
/// <summary>
/// Gets or sets the ViewModel to display.
/// </summary>
public object? ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
/// <summary>
/// Gets or sets the view contract.
/// </summary>
public string? ViewContract
{
get => GetValue(ViewContractProperty);
set => SetValue(ViewContractProperty, value);
}
/// <summary>
/// Gets or sets the content displayed whenever there is no page currently routed.
/// </summary>
public object? DefaultContent
{
get => GetValue(DefaultContentProperty);
set => SetValue(DefaultContentProperty, value);
}
/// <summary>
/// Gets or sets the view locator.
/// </summary>
public IViewLocator? ViewLocator { get; set; }
protected override Type StyleKeyOverride => typeof(TransitioningContentControl);
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
/// <param name="contract">The contract for view resolution.</param>
private void NavigateToViewModel(object? viewModel, string? contract)
{
if (viewModel == null)
{
this.Log().Info("ViewModel is null. Falling back to default content.");
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance == null)
{
if (contract == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
}
else
{
this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content.");
}
Content = DefaultContent;
return;
}
if (contract == null)
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
}
else
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'.");
}
viewInstance.ViewModel = viewModel;
if (viewInstance is StyledElement styled)
styled.DataContext = viewModel;
Content = viewInstance;
}
}
}

1
tests/Avalonia.Generators.Tests/Avalonia.Generators.Tests.csproj

@ -8,7 +8,6 @@
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\tools\Avalonia.Generators\Avalonia.Generators.csproj" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />

8
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/CustomControls.txt

@ -9,9 +9,9 @@ namespace Sample.App
partial class SampleView
{
[global::System.CodeDom.Compiler.GeneratedCode("Avalonia.Generators.NameGenerator.InitializeComponentCodeGenerator", "$GeneratorVersion")]
internal global::Avalonia.ReactiveUI.RoutedViewHost ClrNamespaceRoutedViewHost;
internal global::Avalonia.Controls.ColorPicker ClrNamespaceColorPicker;
[global::System.CodeDom.Compiler.GeneratedCode("Avalonia.Generators.NameGenerator.InitializeComponentCodeGenerator", "$GeneratorVersion")]
internal global::Avalonia.ReactiveUI.RoutedViewHost UriRoutedViewHost;
internal global::Avalonia.Controls.ColorPicker UriColorPicker;
[global::System.CodeDom.Compiler.GeneratedCode("Avalonia.Generators.NameGenerator.InitializeComponentCodeGenerator", "$GeneratorVersion")]
internal global::Controls.CustomTextBox UserNameTextBox;
@ -30,8 +30,8 @@ namespace Sample.App
}
var __thisNameScope__ = this.FindNameScope();
ClrNamespaceRoutedViewHost = __thisNameScope__?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("ClrNamespaceRoutedViewHost");
UriRoutedViewHost = __thisNameScope__?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("UriRoutedViewHost");
ClrNamespaceColorPicker = __thisNameScope__?.Find<global::Avalonia.Controls.ColorPicker>("ClrNamespaceColorPicker");
UriColorPicker = __thisNameScope__?.Find<global::Avalonia.Controls.ColorPicker>("UriColorPicker");
UserNameTextBox = __thisNameScope__?.Find<global::Controls.CustomTextBox>("UserNameTextBox");
}
}

4
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/CustomControls.txt

@ -7,9 +7,9 @@ namespace Sample.App
partial class SampleView
{
[global::System.CodeDom.Compiler.GeneratedCode("Avalonia.Generators.NameGenerator.OnlyPropertiesCodeGenerator", "$GeneratorVersion")]
internal global::Avalonia.ReactiveUI.RoutedViewHost ClrNamespaceRoutedViewHost => this.FindNameScope()?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("ClrNamespaceRoutedViewHost");
internal global::Avalonia.Controls.ColorPicker ClrNamespaceColorPicker => this.FindNameScope()?.Find<global::Avalonia.Controls.ColorPicker>("ClrNamespaceColorPicker");
[global::System.CodeDom.Compiler.GeneratedCode("Avalonia.Generators.NameGenerator.OnlyPropertiesCodeGenerator", "$GeneratorVersion")]
internal global::Avalonia.ReactiveUI.RoutedViewHost UriRoutedViewHost => this.FindNameScope()?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("UriRoutedViewHost");
internal global::Avalonia.Controls.ColorPicker UriColorPicker => this.FindNameScope()?.Find<global::Avalonia.Controls.ColorPicker>("UriColorPicker");
[global::System.CodeDom.Compiler.GeneratedCode("Avalonia.Generators.NameGenerator.OnlyPropertiesCodeGenerator", "$GeneratorVersion")]
internal global::Controls.CustomTextBox UserNameTextBox => this.FindNameScope()?.Find<global::Controls.CustomTextBox>("UserNameTextBox");
}

4
tests/Avalonia.Generators.Tests/Views/AttachedProps.xml

@ -1,10 +1,8 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:Avalonia.ReactiveUI;assembly=Avalonia.ReactiveUI"
xmlns:rxui="http://reactiveui.net"
x:Class="Sample.App.AttachedProps"
Design.Width="300">
<TextBox Name="UserNameTextBox"
Watermark="Username input"
UseFloatingWatermark="True" />
</Window>
</Window>

4
tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml

@ -1,10 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:Avalonia.ReactiveUI;assembly=Avalonia.ReactiveUI"
xmlns:rxui="http://reactiveui.net"
x:Class="Sample.App.ControlWithoutWindow"
Design.Width="300">
<TextBox Name="UserNameTextBox"
Watermark="Username input"
UseFloatingWatermark="True" />
</UserControl>
</UserControl>

10
tests/Avalonia.Generators.Tests/Views/CustomControls.xml

@ -1,11 +1,11 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:Avalonia.ReactiveUI;assembly=Avalonia.ReactiveUI"
xmlns:custom="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.ColorPicker"
xmlns:controls="clr-namespace:Controls"
x:Class="Sample.App.CustomControls"
xmlns:rxui="http://reactiveui.net">
<custom:RoutedViewHost Name="ClrNamespaceRoutedViewHost" />
<rxui:RoutedViewHost Name="UriRoutedViewHost" />
xmlns:av="https://github.com/avaloniaui">
<custom:ColorPicker Name="ClrNamespaceColorPicker" />
<av:ColorPicker Name="UriColorPicker" />
<controls:CustomTextBox Name="UserNameTextBox" />
<controls:EvilControl Name="EvilName" />
</Window>
</Window>

9
tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs

@ -4,7 +4,6 @@ using Avalonia.Controls;
using Avalonia.Generators.Common;
using Avalonia.Generators.Common.Domain;
using Avalonia.Generators.Compiler;
using Avalonia.ReactiveUI;
using Avalonia.Generators.Tests.Views;
using Xunit;
@ -53,11 +52,11 @@ public class XamlXNameResolverTests
Assert.NotEmpty(controls);
Assert.Equal(3, controls.Count);
Assert.Equal("ClrNamespaceRoutedViewHost", controls[0].Name);
Assert.Equal("UriRoutedViewHost", controls[1].Name);
Assert.Equal("ClrNamespaceColorPicker", controls[0].Name);
Assert.Equal("UriColorPicker", controls[1].Name);
Assert.Equal("UserNameTextBox", controls[2].Name);
Assert.Contains(typeof(RoutedViewHost).FullName!, controls[0].TypeName);
Assert.Contains(typeof(RoutedViewHost).FullName!, controls[1].TypeName);
Assert.Contains(typeof(ColorPicker).FullName!, controls[0].TypeName);
Assert.Contains(typeof(ColorPicker).FullName!, controls[1].TypeName);
Assert.Contains("Controls.CustomTextBox", controls[2].TypeName);
}

8
tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs

@ -1,8 +0,0 @@
using Avalonia.UnitTests;
using Xunit;
// Required to avoid InvalidOperationException sometimes thrown
// from Splat.MemoizingMRUCache.cs which is not thread-safe.
// Thrown when trying to access WhenActivated concurrently.
[assembly: CollectionBehavior(DisableTestParallelization = true)]
[assembly: VerifyEmptyDispatcherAfterTest]

146
tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs

@ -1,146 +0,0 @@
using Xunit;
using ReactiveUI;
using Avalonia.ReactiveUI;
using Avalonia.UnitTests;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.VisualTree;
using Avalonia.Controls.Presenters;
using Splat;
using System.Threading.Tasks;
using System;
namespace Avalonia.ReactiveUI.UnitTests
{
public class AutoDataTemplateBindingHookTest
{
public class NestedViewModel : ReactiveObject { }
public class NestedView : ReactiveUserControl<NestedViewModel> { }
public class ExampleViewModel : ReactiveObject
{
public ObservableCollection<NestedViewModel> Items { get; } = new ObservableCollection<NestedViewModel>();
}
public class ExampleView : ReactiveUserControl<ExampleViewModel>
{
public ItemsControl List { get; } = new ItemsControl
{
Template = GetTemplate()
};
public ExampleView(Action<ItemsControl> adjustItemsControl = null)
{
adjustItemsControl?.Invoke(List);
List.ApplyTemplate();
List.Presenter.ApplyTemplate();
Content = List;
ViewModel = new ExampleViewModel();
this.OneWayBind(ViewModel, x => x.Items, x => x.List.ItemsSource);
}
}
public AutoDataTemplateBindingHookTest()
{
Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new NestedView(), typeof(IViewFor<NestedViewModel>));
}
[Fact]
public void Should_Apply_Data_Template_Binding_When_No_Template_Is_Set()
{
var view = new ExampleView();
Assert.NotNull(view.List.ItemTemplate);
Assert.IsType<FuncDataTemplate<object>>(view.List.ItemTemplate);
}
[Fact]
public void Should_Use_ViewModelViewHost_As_Data_Template_By_Default()
{
var view = new ExampleView();
view.ViewModel.Items.Add(new NestedViewModel());
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
Assert.IsType<ViewModelViewHost>(container.Child);
}
[Fact]
public void ViewModelViewHost_Should_Resolve_And_Embedd_Appropriate_View_Model()
{
var view = new ExampleView();
view.ViewModel.Items.Add(new NestedViewModel());
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
var host = (ViewModelViewHost) container.Child;
Assert.IsType<NestedViewModel>(host.ViewModel);
Assert.IsType<NestedViewModel>(host.DataContext);
host.DataContext = "changed context";
Assert.IsType<string>(host.ViewModel);
Assert.IsType<string>(host.DataContext);
}
[Fact]
public void Should_Not_Override_Data_Template_Binding_When_Item_Template_Is_Set()
{
var view = new ExampleView(control => control.ItemTemplate = GetItemTemplate());
Assert.NotNull(view.List.ItemTemplate);
Assert.IsType<FuncDataTemplate<TextBlock>>(view.List.ItemTemplate);
}
[Fact]
public void Should_Not_Use_View_Model_View_Host_When_Item_Template_Is_Set()
{
var view = new ExampleView(control => control.ItemTemplate = GetItemTemplate());
view.ViewModel.Items.Add(new NestedViewModel());
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
Assert.IsType<TextBlock>(container.Child);
}
[Fact]
public void Should_Not_Use_View_Model_View_Host_When_Data_Templates_Are_Not_Empty()
{
var view = new ExampleView(control => control.DataTemplates.Add(GetItemTemplate()));
view.ViewModel.Items.Add(new NestedViewModel());
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
Assert.IsType<TextBlock>(container.Child);
}
private static FuncDataTemplate GetItemTemplate()
{
return new FuncDataTemplate<TextBlock>((parent, scope) => new TextBlock());
}
private static FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ItemsControl>((parent, scope) => new Border
{
Background = new Media.SolidColorBrush(0xffffffff),
Child = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
}.RegisterInNameScope(scope)
});
}
}
}

117
tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs

@ -1,117 +0,0 @@
using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Reactive;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia;
using Avalonia.Threading;
using ReactiveUI;
using DynamicData;
using Xunit;
using Splat;
namespace Avalonia.ReactiveUI.UnitTests
{
public class AutoSuspendHelperTest
{
[DataContract]
public class AppState
{
[DataMember]
public string Example { get; set; }
}
public class ExoticApplicationLifetimeWithoutLifecycleEvents : IDisposable, IApplicationLifetime
{
public void Dispose() { }
}
[Fact]
public void AutoSuspendHelper_Should_Immediately_Fire_IsLaunchingNew()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
var isLaunchingReceived = false;
var application = AvaloniaLocator.Current.GetRequiredService<Application>();
application.ApplicationLifetime = lifetime;
// Initialize ReactiveUI Suspension as in real-world scenario.
var suspension = new AutoSuspendHelper(application.ApplicationLifetime);
RxApp.SuspensionHost.IsLaunchingNew.Subscribe(_ => isLaunchingReceived = true);
suspension.OnFrameworkInitializationCompleted();
Assert.True(isLaunchingReceived);
}
}
[Fact]
public void AutoSuspendHelper_Should_Throw_When_Not_Supported_Lifetime_Is_Used()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (var lifetime = new ExoticApplicationLifetimeWithoutLifecycleEvents())
{
var application = AvaloniaLocator.Current.GetRequiredService<Application>();
application.ApplicationLifetime = lifetime;
Assert.Throws<NotSupportedException>(() => new AutoSuspendHelper(application.ApplicationLifetime));
}
}
[Fact]
public void AutoSuspendHelper_Should_Throw_When_Lifetime_Is_Null()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var application = AvaloniaLocator.Current.GetService<Application>();
Assert.Throws<ArgumentNullException>(() => new AutoSuspendHelper(application.ApplicationLifetime));
}
}
[Fact]
public void ShouldPersistState_Should_Fire_On_App_Exit_When_SuspensionDriver_Is_Initialized()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
using (var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
var shouldPersistReceived = false;
var application = AvaloniaLocator.Current.GetRequiredService<Application>();
application.ApplicationLifetime = lifetime;
// Initialize ReactiveUI Suspension as in real-world scenario.
var suspension = new AutoSuspendHelper(application.ApplicationLifetime);
RxApp.SuspensionHost.CreateNewAppState = () => new AppState { Example = "Foo" };
RxApp.SuspensionHost.ShouldPersistState.Subscribe(_ => shouldPersistReceived = true);
RxApp.SuspensionHost.SetupDefaultSuspendResume(new FakeSuspensionDriver());
suspension.OnFrameworkInitializationCompleted();
lifetime.Shutdown();
Assert.True(shouldPersistReceived);
Assert.Equal("Foo", RxApp.SuspensionHost.GetAppState<AppState>().Example);
}
}
private class FakeSuspensionDriver : ISuspensionDriver
{
public IObservable<object> LoadState() => Observable.Empty<object>();
public IObservable<Unit> SaveState(object state) => Observable.Empty<Unit>();
public IObservable<Unit> InvalidateState() => Observable.Empty<Unit>();
}
}
}

16
tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" />
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
</ItemGroup>
</Project>

207
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -1,207 +0,0 @@
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;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Avalonia.ReactiveUI.UnitTests
{
public class AvaloniaActivationForViewFetcherTest
{
public class TestUserControl : UserControl, IActivatableView { }
public class TestUserControlWithWhenActivated : UserControl, IActivatableView
{
public bool Active { get; private set; }
public TestUserControlWithWhenActivated()
{
this.WhenActivated(disposables =>
{
Active = true;
Disposable
.Create(() => Active = false)
.DisposeWith(disposables);
});
}
}
public class TestWindowWithWhenActivated : Window, IActivatableView
{
public bool Active { get; private set; }
public TestWindowWithWhenActivated()
{
this.WhenActivated(disposables =>
{
Active = true;
Disposable
.Create(() => Active = false)
.DisposeWith(disposables);
});
}
}
public class ActivatableViewModel : IActivatableViewModel
{
public ViewModelActivator Activator { get; }
public bool IsActivated { get; private set; }
public ActivatableViewModel()
{
Activator = new ViewModelActivator();
this.WhenActivated(disposables =>
{
IsActivated = true;
Disposable
.Create(() => IsActivated = false)
.DisposeWith(disposables);
});
}
}
public class ActivatableWindow : ReactiveWindow<ActivatableViewModel>
{
public ActivatableWindow()
{
Content = new Border();
this.WhenActivated(disposables => { });
}
}
public class ActivatableUserControl : ReactiveUserControl<ActivatableViewModel>
{
public ActivatableUserControl()
{
Content = new Border();
this.WhenActivated(disposables => { });
}
}
public AvaloniaActivationForViewFetcherTest() =>
Locator
.CurrentMutable
.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
[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;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(activated[0]);
Assert.Equal(1, activated.Count);
fakeRenderedDecorator.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
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()
{
var userControl = new TestUserControlWithWhenActivated();
Assert.False(userControl.Active);
var fakeRenderedDecorator = new TestRoot();
fakeRenderedDecorator.Child = userControl;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(userControl.Active);
fakeRenderedDecorator.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(userControl.Active);
}
[Fact]
public void Activation_For_View_Fetcher_Should_Support_Windows()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new TestWindowWithWhenActivated();
Assert.False(window.Active);
window.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(window.Active);
window.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(window.Active);
}
}
[Fact]
public void Activatable_Window_View_Model_Is_Activated_And_Deactivated()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var viewModel = new ActivatableViewModel();
var window = new ActivatableWindow { ViewModel = viewModel };
Assert.False(viewModel.IsActivated);
window.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(viewModel.IsActivated);
window.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(viewModel.IsActivated);
}
}
[Fact]
public void Activatable_User_Control_View_Model_Is_Activated_And_Deactivated()
{
var root = new TestRoot();
var viewModel = new ActivatableViewModel();
var control = new ActivatableUserControl { ViewModel = viewModel };
Assert.False(viewModel.IsActivated);
root.Child = control;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(viewModel.IsActivated);
root.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(viewModel.IsActivated);
}
}
}

45
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs

@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class AvaloniaObjectTests_GetSubject
{
[Fact]
public void GetSubject_Returns_Values()
{
var source = new Class1 { Foo = "foo" };
var target = source.GetSubject(Class1.FooProperty);
var result = new List<string>();
target.Subscribe(x => result.Add(x));
source.Foo = "bar";
source.Foo = "baz";
Assert.Equal(new[] { "foo", "bar", "baz" }, result);
}
[Fact]
public void GetSubject_Sets_Values()
{
var source = new Class1 { Foo = "foo" };
var target = source.GetSubject(Class1.FooProperty);
target.OnNext("bar");
Assert.Equal("bar", source.Foo);
}
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =
AvaloniaProperty.Register<Class1, string>(nameof(Foo), "foodefault");
public string Foo
{
get => GetValue(FooProperty);
set => SetValue(FooProperty, value);
}
}
}
}

196
tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs

@ -1,196 +0,0 @@
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Threading;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveUserControlTest : ScopedTestBase
{
public class ExampleViewModel : ReactiveObject, IActivatableViewModel
{
public bool IsActive { get; private set; }
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public ExampleViewModel() => this.WhenActivated(disposables =>
{
IsActive = true;
Disposable
.Create(() => IsActive = false)
.DisposeWith(disposables);
});
}
public class ExampleView : ReactiveUserControl<ExampleViewModel> { }
public ReactiveUserControlTest() =>
Locator
.CurrentMutable
.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_User_Control_View_Model()
{
var root = new TestRoot();
var view = new ExampleView();
root.Child = view;
var viewModel = new ExampleViewModel();
Assert.Null(view.ViewModel);
view.DataContext = viewModel;
Assert.Equal(view.ViewModel, viewModel);
Assert.Equal(view.DataContext, viewModel);
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
view.ViewModel = viewModel;
Assert.Equal(viewModel, view.ViewModel);
Assert.Equal(viewModel, view.DataContext);
view.ViewModel = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
}
[Fact]
public void Should_Start_With_NotNull_Activated_ViewModel()
{
var root = new TestRoot();
var view = new ExampleView {ViewModel = new ExampleViewModel()};
Assert.False(view.ViewModel.IsActive);
root.Child = view;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
root.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
[Fact]
public void Should_Start_With_NotNull_Activated_DataContext()
{
var root = new TestRoot();
var view = new ExampleView {DataContext = new ExampleViewModel()};
Assert.False(view.ViewModel.IsActive);
root.Child = view;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
root.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
[Fact]
public void Should_Inherit_DataContext()
{
var vm1 = new ExampleViewModel();
var vm2 = new ExampleViewModel();
var view = new ExampleView();
var root = new TestRoot(view);
Assert.Null(view.DataContext);
Assert.Null(view.ViewModel);
root.DataContext = vm1;
Assert.Same(vm1, view.DataContext);
Assert.Same(vm1, view.ViewModel);
root.DataContext = null;
Assert.Null(view.DataContext);
Assert.Null(view.ViewModel);
root.DataContext = vm2;
Assert.Same(vm2, view.DataContext);
Assert.Same(vm2, view.ViewModel);
}
// https://github.com/AvaloniaUI/Avalonia/issues/15060
[Fact]
public void Should_Not_Inherit_DataContext_Of_Wrong_Type()
{
var view = new ExampleView();
var root = new TestRoot(view);
Assert.Null(view.DataContext);
Assert.Null(view.ViewModel);
root.DataContext = this;
Assert.Same(this, view.DataContext);
Assert.Null(view.ViewModel);
}
[Fact]
public void Should_Not_Overlap_Change_Notifications()
{
var vm1 = new ExampleViewModel();
var vm2 = new ExampleViewModel();
var view1 = new ExampleView();
var view2 = new ExampleView();
Assert.Null(view1.DataContext);
Assert.Null(view2.DataContext);
Assert.Null(view1.ViewModel);
Assert.Null(view2.ViewModel);
view1.DataContext = vm1;
Assert.Same(vm1, view1.DataContext);
Assert.Same(vm1, view1.ViewModel);
Assert.Null(view2.DataContext);
Assert.Null(view2.ViewModel);
view2.DataContext = vm2;
Assert.Same(vm1, view1.DataContext);
Assert.Same(vm1, view1.ViewModel);
Assert.Same(vm2, view2.DataContext);
Assert.Same(vm2, view2.ViewModel);
view1.ViewModel = null;
Assert.Null(view1.DataContext);
Assert.Null(view1.ViewModel);
Assert.Same(vm2, view2.DataContext);
Assert.Same(vm2, view2.ViewModel);
view2.ViewModel = null;
Assert.Null(view1.DataContext);
Assert.Null(view2.DataContext);
Assert.Null(view1.ViewModel);
Assert.Null(view2.ViewModel);
}
}
}

116
tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs

@ -1,116 +0,0 @@
using System.Reactive.Disposables;
using Avalonia.Threading;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveWindowTest
{
public class ExampleViewModel : ReactiveObject, IActivatableViewModel
{
public bool IsActive { get; private set; }
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public ExampleViewModel() => this.WhenActivated(disposables =>
{
IsActive = true;
Disposable
.Create(() => IsActive = false)
.DisposeWith(disposables);
});
}
public class ExampleWindow : ReactiveWindow<ExampleViewModel> { }
public ReactiveWindowTest() =>
Locator
.CurrentMutable
.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_Window_View_Model()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var view = new ExampleWindow();
var viewModel = new ExampleViewModel();
view.Show();
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
view.DataContext = viewModel;
Assert.Equal(viewModel, view.ViewModel);
Assert.Equal(viewModel, view.DataContext);
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
view.ViewModel = viewModel;
Assert.Equal(viewModel, view.ViewModel);
Assert.Equal(viewModel, view.DataContext);
view.ViewModel = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
}
}
[Fact]
public void Should_Start_With_NotNull_Activated_ViewModel()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var view = new ExampleWindow { ViewModel = new ExampleViewModel() };
Assert.False(view.ViewModel.IsActive);
view.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
view.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
}
[Fact]
public void Should_Start_With_NotNull_Activated_DataContext()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var view = new ExampleWindow { DataContext = new ExampleViewModel() };
Assert.False(view.ViewModel.IsActive);
view.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
view.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
}
}
}

223
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@ -1,223 +0,0 @@
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;
using Avalonia.Markup.Xaml;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Reactive;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Avalonia.ReactiveUI.UnitTests
{
public class RoutedViewHostTest
{
public class FirstRoutableViewModel : ReactiveObject, IRoutableViewModel
{
public string UrlPathSegment => "first";
public IScreen HostScreen { get; set; }
}
public class FirstRoutableView : ReactiveUserControl<FirstRoutableViewModel> { }
public class AlternativeFirstRoutableView : ReactiveUserControl<FirstRoutableViewModel> { }
public class SecondRoutableViewModel : ReactiveObject, IRoutableViewModel
{
public string UrlPathSegment => "second";
public IScreen HostScreen { get; set; }
}
public class SecondRoutableView : ReactiveUserControl<SecondRoutableViewModel> { }
public class AlternativeSecondRoutableView : ReactiveUserControl<SecondRoutableViewModel> { }
public class ScreenViewModel : ReactiveObject, IScreen
{
public RoutingState Router { get; } = new RoutingState();
}
public static string AlternativeViewContract => "AlternativeView";
public RoutedViewHostTest()
{
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new FirstRoutableView(), typeof(IViewFor<FirstRoutableViewModel>));
Locator.CurrentMutable.Register(() => new SecondRoutableView(), typeof(IViewFor<SecondRoutableViewModel>));
Locator.CurrentMutable.Register(() => new AlternativeFirstRoutableView(), typeof(IViewFor<FirstRoutableViewModel>), AlternativeViewContract);
Locator.CurrentMutable.Register(() => new AlternativeSecondRoutableView(), typeof(IViewFor<SecondRoutableViewModel>), AlternativeViewContract);
}
[Fact]
public void RoutedViewHost_Should_Stay_In_Sync_With_RoutingState()
{
var screen = new ScreenViewModel();
var defaultContent = new TextBlock();
var host = new RoutedViewHost
{
Router = screen.Router,
DefaultContent = defaultContent,
PageTransition = null
};
var root = new TestRoot
{
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content);
var first = new FirstRoutableViewModel();
screen.Router.Navigate.Execute(first).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<FirstRoutableView>(host.Content);
Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext);
Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel);
var second = new SecondRoutableViewModel();
screen.Router.Navigate.Execute(second).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<SecondRoutableView>(host.Content);
Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext);
Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel);
screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<FirstRoutableView>(host.Content);
Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext);
Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel);
screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content);
}
[Fact]
public void RoutedViewHost_Should_Stay_In_Sync_With_RoutingState_And_Contract()
{
var screen = new ScreenViewModel();
var defaultContent = new TextBlock();
var host = new RoutedViewHost
{
Router = screen.Router,
DefaultContent = defaultContent,
PageTransition = null
};
var root = new TestRoot
{
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content);
var first = new FirstRoutableViewModel();
screen.Router.Navigate.Execute(first).Subscribe();
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.IsType<FirstRoutableView>(host.Content);
Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext);
Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.IsType<AlternativeFirstRoutableView>(host.Content);
Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).DataContext);
Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).ViewModel);
var second = new SecondRoutableViewModel();
screen.Router.Navigate.Execute(second).Subscribe();
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.IsType<SecondRoutableView>(host.Content);
Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext);
Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.IsType<AlternativeSecondRoutableView>(host.Content);
Assert.Equal(second, ((AlternativeSecondRoutableView)host.Content).DataContext);
Assert.Equal(second, ((AlternativeSecondRoutableView)host.Content).ViewModel);
screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<AlternativeFirstRoutableView>(host.Content);
Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).DataContext);
Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).ViewModel);
screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content);
}
[Fact]
public void RoutedViewHost_Should_Show_Default_Content_When_Router_Is_Null()
{
var screen = new ScreenViewModel();
var defaultContent = new TextBlock();
var host = new RoutedViewHost
{
DefaultContent = defaultContent,
PageTransition = null,
Router = null
};
var root = new TestRoot
{
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.Equal(defaultContent, host.Content);
host.Router = screen.Router;
Assert.NotNull(host.Content);
Assert.Equal(defaultContent, host.Content);
var first = new FirstRoutableViewModel();
screen.Router.Navigate.Execute(first).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<FirstRoutableView>(host.Content);
host.Router = null;
Assert.NotNull(host.Content);
Assert.Equal(defaultContent, host.Content);
host.Router = screen.Router;
Assert.NotNull(host.Content);
Assert.IsType<FirstRoutableView>(host.Content);
}
}
}

52
tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs

@ -1,52 +0,0 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class TransitioningContentControlTest
{
[Fact]
public void Transitioning_Control_Template_Should_Be_Instantiated()
{
var target = new TransitioningContentControl
{
PageTransition = null,
Template = GetTemplate(),
Content = "Foo"
};
target.ApplyTemplate();
target.Presenter.UpdateChild();
var child = ((Visual)target).GetVisualChildren().Single();
Assert.IsType<Border>(child);
child = child.GetVisualChildren().Single();
Assert.IsType<ContentPresenter>(child);
child = child.GetVisualChildren().Single();
Assert.IsType<TextBlock>(child);
}
private static FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ContentControl>((parent, scope) =>
{
return new Border
{
Background = new Media.SolidColorBrush(0xffffffff),
Child = new ContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
}.RegisterInNameScope(scope)
};
});
}
}
}

144
tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

@ -1,144 +0,0 @@
using Avalonia.Controls;
using Avalonia.Threading;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
using Xunit;
namespace Avalonia.ReactiveUI.UnitTests
{
public class ViewModelViewHostTest
{
public class FirstViewModel { }
public class FirstView : ReactiveUserControl<FirstViewModel> { }
public class AlternativeFirstView : ReactiveUserControl<FirstViewModel> { }
public class SecondViewModel : ReactiveObject { }
public class SecondView : ReactiveUserControl<SecondViewModel> { }
public class AlternativeSecondView : ReactiveUserControl<SecondViewModel> { }
public static string AlternativeViewContract => "AlternativeView";
public ViewModelViewHostTest()
{
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor<FirstViewModel>));
Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor<SecondViewModel>));
Locator.CurrentMutable.Register(() => new AlternativeFirstView(), typeof(IViewFor<FirstViewModel>), AlternativeViewContract);
Locator.CurrentMutable.Register(() => new AlternativeSecondView(), typeof(IViewFor<SecondViewModel>), AlternativeViewContract);
}
[Fact]
public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel()
{
var defaultContent = new TextBlock();
var host = new ViewModelViewHost
{
DefaultContent = defaultContent,
PageTransition = null
};
var root = new TestRoot
{
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
var first = new FirstViewModel();
host.ViewModel = first;
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstView), host.Content.GetType());
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
var second = new SecondViewModel();
host.ViewModel = second;
Assert.NotNull(host.Content);
Assert.Equal(typeof(SecondView), host.Content.GetType());
Assert.Equal(second, ((SecondView)host.Content).DataContext);
Assert.Equal(second, ((SecondView)host.Content).ViewModel);
host.ViewModel = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
host.ViewModel = first;
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstView), host.Content.GetType());
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
}
[Fact]
public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel_And_Contract()
{
var defaultContent = new TextBlock();
var host = new ViewModelViewHost
{
DefaultContent = defaultContent,
PageTransition = null
};
var root = new TestRoot
{
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
var first = new FirstViewModel();
host.ViewModel = first;
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstView), host.Content.GetType());
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.Equal(typeof(AlternativeFirstView), host.Content.GetType());
Assert.Equal(first, ((AlternativeFirstView)host.Content).DataContext);
Assert.Equal(first, ((AlternativeFirstView)host.Content).ViewModel);
var second = new SecondViewModel();
host.ViewModel = second;
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(SecondView), host.Content.GetType());
Assert.Equal(second, ((SecondView)host.Content).DataContext);
Assert.Equal(second, ((SecondView)host.Content).ViewModel);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.Equal(typeof(AlternativeSecondView), host.Content.GetType());
Assert.Equal(second, ((AlternativeSecondView)host.Content).DataContext);
Assert.Equal(second, ((AlternativeSecondView)host.Content).ViewModel);
host.ViewModel = null;
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
}
}
}
Loading…
Cancel
Save