Remove ReactiveUI (#20101)
This commit is contained in:
committed by
GitHub
parent
fb0f3848f9
commit
70ebc1e226
@@ -10,7 +10,6 @@
|
||||
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
|
||||
"samples\\TextTestApp\\TextTestApp.csproj",
|
||||
"samples\\MiniMvvm\\MiniMvvm.csproj",
|
||||
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
|
||||
"samples\\RenderDemo\\RenderDemo.csproj",
|
||||
"samples\\SampleControls\\ControlSamples.csproj",
|
||||
"samples\\Sandbox\\Sandbox.csproj",
|
||||
@@ -32,7 +31,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",
|
||||
@@ -65,7 +63,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",
|
||||
|
||||
@@ -46,8 +46,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}"
|
||||
@@ -108,7 +106,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
|
||||
@@ -171,8 +168,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}"
|
||||
@@ -223,8 +218,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}"
|
||||
@@ -360,10 +353,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
|
||||
@@ -496,10 +485,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
|
||||
@@ -596,10 +581,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
|
||||
@@ -757,7 +738,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}
|
||||
@@ -778,7 +758,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}
|
||||
|
||||
@@ -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>
|
||||
@@ -296,7 +296,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");
|
||||
|
||||
@@ -12,9 +12,9 @@ public class App : Application
|
||||
{
|
||||
var view = new Views.SignUpView
|
||||
{
|
||||
ViewModel = new SignUpViewModel()
|
||||
DataContext = new SignUpViewModel()
|
||||
};
|
||||
view.Show();
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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="Green"
|
||||
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>
|
||||
|
||||
@@ -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.";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,70 +1,76 @@
|
||||
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;
|
||||
|
||||
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}");
|
||||
|
||||
this.ValidationRule(
|
||||
vm => vm.ConfirmPassword,
|
||||
confirmation => !string.IsNullOrWhiteSpace(confirmation),
|
||||
"Confirm password field is required.");
|
||||
|
||||
var passwordsObservable =
|
||||
this.WhenAnyValue(
|
||||
x => x.Password,
|
||||
x => x.ConfirmPassword,
|
||||
(password, confirmation) =>
|
||||
password == confirmation);
|
||||
|
||||
this.ValidationRule(
|
||||
vm => vm.ConfirmPassword,
|
||||
passwordsObservable,
|
||||
"Passwords must match.");
|
||||
|
||||
SignUp = ReactiveCommand.Create(() => {}, this.IsValid());
|
||||
ErrorsChanged += OnErrorsChanged;
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SignUp { get; }
|
||||
public RelayCommand SignUp { get; }
|
||||
|
||||
public string UserName
|
||||
[Required]
|
||||
public string? UserName {
|
||||
get;
|
||||
set => SetProperty(ref field, value, validate: true);
|
||||
}
|
||||
|
||||
public string? UserNameValidation
|
||||
=> GetValidationMessage(nameof(UserName));
|
||||
|
||||
[Required]
|
||||
[MinLength(2)]
|
||||
public string? Password
|
||||
{
|
||||
get => _userName;
|
||||
set => this.RaiseAndSetIfChanged(ref _userName, value);
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref field, value, validate: true))
|
||||
ValidateProperty(ConfirmPassword, nameof(ConfirmPassword));
|
||||
}
|
||||
}
|
||||
|
||||
public string Password
|
||||
public string? PasswordValidation
|
||||
=> GetValidationMessage(nameof(Password));
|
||||
|
||||
[Required]
|
||||
[Compare(nameof(Password))]
|
||||
public string? ConfirmPassword
|
||||
{
|
||||
get => _password;
|
||||
set => this.RaiseAndSetIfChanged(ref _password, value);
|
||||
get;
|
||||
set => SetProperty(ref field, value, validate: true);
|
||||
}
|
||||
|
||||
public string ConfirmPassword
|
||||
public string? ConfirmPasswordValidation
|
||||
=> GetValidationMessage(nameof(ConfirmPassword));
|
||||
|
||||
public string? CompoundValidation
|
||||
=> GetValidationMessage(null);
|
||||
|
||||
private void OnErrorsChanged(object? sender, DataErrorsChangedEventArgs e)
|
||||
{
|
||||
get => _confirmPassword;
|
||||
set => this.RaiseAndSetIfChanged(ref _confirmPassword, value);
|
||||
if (e.PropertyName is not null)
|
||||
OnPropertyChanged(e.PropertyName + "Validation");
|
||||
|
||||
OnPropertyChanged(CompoundValidation);
|
||||
SignUp.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetValidationMessage(string? propertyName)
|
||||
{
|
||||
var message = string.Join(Environment.NewLine, GetErrors(propertyName).Select(v => v.ErrorMessage));
|
||||
return string.IsNullOrEmpty(message) ? null : message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using ReactiveUI;
|
||||
|
||||
namespace ReactiveUIDemo.ViewModels
|
||||
{
|
||||
internal class MainWindowViewModel : ReactiveObject
|
||||
{
|
||||
public RoutedViewHostPageViewModel RoutedViewHost { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
using System.Reflection;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
[assembly: XmlnsDefinition("http://reactiveui.net", "Avalonia.ReactiveUI")]
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.Analyzers" Version="3.3.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
|
||||
+4
-4
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,9 +6,7 @@ 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 Microsoft.CodeAnalysis;
|
||||
using Xunit;
|
||||
|
||||
namespace Avalonia.Generators.Tests;
|
||||
@@ -56,11 +54,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user