diff --git a/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs b/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs index feda467c44..0aca1350ce 100644 --- a/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs +++ b/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs @@ -32,6 +32,12 @@ public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableL public static readonly DependencyProperty ViewContractObservableProperty = DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Default)); + /// + /// The ContractFallbackByPass dependency property. + /// + public static readonly DependencyProperty ContractFallbackByPassProperty = + DependencyProperty.Register("ContractFallbackByPass", typeof(bool), typeof(ViewModelViewHost), new PropertyMetadata(false)); + private string? _viewContract; /// @@ -119,12 +125,26 @@ public string? ViewContract set => ViewContractObservable = Observable.Return(value); } + /// + /// Gets or sets a value indicating whether should bypass the default contract fallback behavior. + /// + public bool ContractFallbackByPass + { + get => (bool)GetValue(ContractFallbackByPassProperty); + set => SetValue(ContractFallbackByPassProperty, value); + } + /// /// Gets or sets the view locator. /// public IViewLocator? ViewLocator { get; set; } - private void ResolveViewForViewModel(object? viewModel, string? contract) + /// + /// resolve view for view model with respect to contract. + /// + /// ViewModel. + /// contract used by ViewLocator. + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { if (viewModel is null) { @@ -133,7 +153,11 @@ private void ResolveViewForViewModel(object? viewModel, string? contract) } var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var viewInstance = viewLocator.ResolveView(viewModel, contract) ?? viewLocator.ResolveView(viewModel); + var viewInstance = viewLocator.ResolveView(viewModel, contract); + if (viewInstance is null && !ContractFallbackByPass) + { + viewInstance = viewLocator.ResolveView(viewModel); + } if (viewInstance is null) { diff --git a/src/ReactiveUI.Maui/ViewModelViewHost.cs b/src/ReactiveUI.Maui/ViewModelViewHost.cs index d03333cdee..87f202db13 100644 --- a/src/ReactiveUI.Maui/ViewModelViewHost.cs +++ b/src/ReactiveUI.Maui/ViewModelViewHost.cs @@ -40,6 +40,15 @@ public class ViewModelViewHost : ContentView, IViewFor typeof(ViewModelViewHost), Observable.Never); + /// + /// The ContractFallbackByPass dependency property. + /// + public static readonly BindableProperty ContractFallbackByPassProperty = BindableProperty.Create( + nameof(ContractFallbackByPass), + typeof(bool), + typeof(ViewModelViewHost), + false); + private string? _viewContract; /// @@ -66,21 +75,7 @@ public ViewModelViewHost() { _viewContract = x.Contract; - if (x.ViewModel is null) - { - Content = DefaultContent; - return; - } - - var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var view = (viewLocator.ResolveView(x.ViewModel, x.Contract) ?? viewLocator.ResolveView(x.ViewModel)) ?? throw new Exception($"Couldn't find view for '{x.ViewModel}'."); - if (view is not View castView) - { - throw new Exception($"View '{view.GetType().FullName}' is not a subclass of '{typeof(View).FullName}'."); - } - - view.ViewModel = x.ViewModel; - Content = castView; + ResolveViewForViewModel(x.ViewModel, x.Contract); }) }); } @@ -124,8 +119,53 @@ public string? ViewContract set => ViewContractObservable = Observable.Return(value); } + /// + /// Gets or sets a value indicating whether should bypass the default contract fallback behavior. + /// + public bool ContractFallbackByPass + { + get => (bool)GetValue(ContractFallbackByPassProperty); + set => SetValue(ContractFallbackByPassProperty, value); + } + /// /// Gets or sets the override for the view locator to use when resolving the view. If unspecified, will be used. /// public IViewLocator? ViewLocator { get; set; } + + /// + /// resolve view for view model with respect to contract. + /// + /// ViewModel. + /// contract used by ViewLocator. + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) + { + if (viewModel is null) + { + Content = DefaultContent; + return; + } + + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + + var viewInstance = viewLocator.ResolveView(viewModel, contract); + if (viewInstance is null && !ContractFallbackByPass) + { + viewInstance = viewLocator.ResolveView(viewModel); + } + + if (viewInstance is null) + { + throw new Exception($"Couldn't find view for '{viewModel}'."); + } + + if (viewInstance is not View castView) + { + throw new Exception($"View '{viewInstance.GetType().FullName}' is not a subclass of '{typeof(View).FullName}'."); + } + + viewInstance.ViewModel = viewModel; + + Content = castView; + } } diff --git a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet6_0.verified.txt b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet6_0.verified.txt index cc3c98af59..bd8354359c 100644 --- a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet6_0.verified.txt +++ b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet6_0.verified.txt @@ -118,15 +118,18 @@ namespace ReactiveUI } public class ViewModelViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, Splat.IEnableLogger { + public static readonly System.Windows.DependencyProperty ContractFallbackByPassProperty; public static readonly System.Windows.DependencyProperty DefaultContentProperty; public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; public static readonly System.Windows.DependencyProperty ViewModelProperty; public ViewModelViewHost() { } + public bool ContractFallbackByPass { get; set; } public object DefaultContent { get; set; } public string? ViewContract { get; set; } public System.IObservable ViewContractObservable { get; set; } public ReactiveUI.IViewLocator? ViewLocator { get; set; } public object? ViewModel { get; set; } + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { } } } namespace ReactiveUI.Wpf @@ -136,4 +139,4 @@ namespace ReactiveUI.Wpf public Registrations() { } public void Register(System.Action, System.Type> registerFunction) { } } -} \ No newline at end of file +} diff --git a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet7_0.verified.txt b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet7_0.verified.txt index 1b219049f1..68ca1d4599 100644 --- a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet7_0.verified.txt +++ b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet7_0.verified.txt @@ -118,15 +118,18 @@ namespace ReactiveUI } public class ViewModelViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, Splat.IEnableLogger { + public static readonly System.Windows.DependencyProperty ContractFallbackByPassProperty; public static readonly System.Windows.DependencyProperty DefaultContentProperty; public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; public static readonly System.Windows.DependencyProperty ViewModelProperty; public ViewModelViewHost() { } + public bool ContractFallbackByPass { get; set; } public object DefaultContent { get; set; } public string? ViewContract { get; set; } public System.IObservable ViewContractObservable { get; set; } public ReactiveUI.IViewLocator? ViewLocator { get; set; } public object? ViewModel { get; set; } + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { } } } namespace ReactiveUI.Wpf diff --git a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet8_0.verified.txt b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet8_0.verified.txt index 88aac0b88a..2889a484e9 100644 --- a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet8_0.verified.txt +++ b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet8_0.verified.txt @@ -118,15 +118,18 @@ namespace ReactiveUI } public class ViewModelViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, Splat.IEnableLogger { + public static readonly System.Windows.DependencyProperty ContractFallbackByPassProperty; public static readonly System.Windows.DependencyProperty DefaultContentProperty; public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; public static readonly System.Windows.DependencyProperty ViewModelProperty; public ViewModelViewHost() { } + public bool ContractFallbackByPass { get; set; } public object DefaultContent { get; set; } public string? ViewContract { get; set; } public System.IObservable ViewContractObservable { get; set; } public ReactiveUI.IViewLocator? ViewLocator { get; set; } public object? ViewModel { get; set; } + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { } } } namespace ReactiveUI.Wpf @@ -136,4 +139,4 @@ namespace ReactiveUI.Wpf public Registrations() { } public void Register(System.Action, System.Type> registerFunction) { } } -} \ No newline at end of file +} diff --git a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.Net4_7.verified.txt b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.Net4_7.verified.txt index 079d7649ed..5a06cd1f3b 100644 --- a/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.Net4_7.verified.txt +++ b/src/ReactiveUI.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.Net4_7.verified.txt @@ -116,15 +116,18 @@ namespace ReactiveUI } public class ViewModelViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, Splat.IEnableLogger { + public static readonly System.Windows.DependencyProperty ContractFallbackByPassProperty; public static readonly System.Windows.DependencyProperty DefaultContentProperty; public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; public static readonly System.Windows.DependencyProperty ViewModelProperty; public ViewModelViewHost() { } + public bool ContractFallbackByPass { get; set; } public object DefaultContent { get; set; } public string? ViewContract { get; set; } public System.IObservable ViewContractObservable { get; set; } public ReactiveUI.IViewLocator? ViewLocator { get; set; } public object? ViewModel { get; set; } + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { } } } namespace ReactiveUI.Wpf @@ -134,4 +137,4 @@ namespace ReactiveUI.Wpf public Registrations() { } public void Register(System.Action, System.Type> registerFunction) { } } -} \ No newline at end of file +} diff --git a/src/ReactiveUI.Tests/Platforms/wpf/Mocks/ViewModelViewHosts/FakeViewWithContract.cs b/src/ReactiveUI.Tests/Platforms/wpf/Mocks/ViewModelViewHosts/FakeViewWithContract.cs new file mode 100644 index 0000000000..f66f08fee6 --- /dev/null +++ b/src/ReactiveUI.Tests/Platforms/wpf/Mocks/ViewModelViewHosts/FakeViewWithContract.cs @@ -0,0 +1,85 @@ +// Copyright (c) 2023 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Windows; +using System.Windows.Controls; + +namespace ReactiveUI.Tests.Wpf; + +public static class FakeViewWithContract +{ + internal const string ContractA = "ContractA"; + internal const string ContractB = "ContractB"; + + public class MyViewModel : ReactiveObject + { + } + + /// + /// Used as the default view with no contracted. + /// + public class View0 : UserControl, IViewFor + { + + // Using a DependencyProperty as the backing store for ViewModel. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register("ViewModel", typeof(MyViewModel), typeof(View0), new PropertyMetadata(null)); + + /// + /// Gets or sets the ViewModel. + /// + public MyViewModel? ViewModel + { + get { return (MyViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = (MyViewModel?)value; } + } + + /// + /// the view with ContractA. + /// + public class ViewA : UserControl, IViewFor + { + + // Using a DependencyProperty as the backing store for ViewModel. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register("ViewModel", typeof(MyViewModel), typeof(ViewA), new PropertyMetadata(null)); + + /// + /// Gets or sets the ViewModel. + /// + public MyViewModel? ViewModel + { + get { return (MyViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = (MyViewModel?)value; } + } + + /// + /// the view as ContractB. + /// + public class ViewB : UserControl, IViewFor + { + + // Using a DependencyProperty as the backing store for ViewModel. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register("ViewModel", typeof(MyViewModel), typeof(ViewB), new PropertyMetadata(null)); + + /// + /// Gets or sets the ViewModel. + /// + public MyViewModel? ViewModel + { + get { return (MyViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = (MyViewModel?)value; } + } +} diff --git a/src/ReactiveUI.Tests/Platforms/wpf/WpfActiveContentTests.cs b/src/ReactiveUI.Tests/Platforms/wpf/WpfActiveContentTests.cs index 9005d39b7f..8b3d86b723 100644 --- a/src/ReactiveUI.Tests/Platforms/wpf/WpfActiveContentTests.cs +++ b/src/ReactiveUI.Tests/Platforms/wpf/WpfActiveContentTests.cs @@ -4,7 +4,7 @@ // See the LICENSE file in the project root for full license information. using System.Windows; - +using System.Windows.Controls; using DynamicData; using ReactiveUI.Testing; @@ -66,6 +66,108 @@ public void BindListFunctionalTest() window.Close(); } + [StaFact] + public void ViewModelHostViewTestFallback() + { + var oldLocator = Locator.GetLocator(); + + var resolver = new ModernDependencyResolver(); + resolver.InitializeSplat(); + resolver.InitializeReactiveUI(); + + // test the resolving behavior + using (resolver.WithResolver()) + { + ResolveViewBIfViewBIsRegistered(resolver); + ResolveView0WithFallbck(resolver); + ResolveNoneWithFallbckByPass(resolver); + } + + void ResolveViewBIfViewBIsRegistered(ModernDependencyResolver resolver) + { + resolver.Register(() => new FakeViewWithContract.View0(), typeof(IViewFor)); + resolver.Register(() => new FakeViewWithContract.ViewA(), typeof(IViewFor), FakeViewWithContract.ContractA); + resolver.Register(() => new FakeViewWithContract.ViewB(), typeof(IViewFor), FakeViewWithContract.ContractB); + + var window = Fixture?.App?.WpfTestWindowFactory(); + + var viewmodel = new FakeViewWithContract.MyViewModel(); + var vmvhost = new ViewModelViewHost() + { + ViewModel = viewmodel, + ViewContract = FakeViewWithContract.ContractB, + }; + window!.RootGrid.Children.Clear(); + window!.RootGrid.Children.Add(vmvhost); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + window.RaiseEvent(loaded); + vmvhost.RaiseEvent(loaded); + + Assert.NotNull(vmvhost.Content); + Assert.IsType(typeof(FakeViewWithContract.ViewB), vmvhost.Content); + window.Close(); + } + + void ResolveView0WithFallbck(ModernDependencyResolver resolver) + { + resolver.UnregisterCurrent(typeof(IViewFor), FakeViewWithContract.ContractB); + + var window = Fixture?.App?.WpfTestWindowFactory(); + + var viewmodel = new FakeViewWithContract.MyViewModel(); + var vmvhost = new ViewModelViewHost() + { + ViewModel = viewmodel, + ViewContract = FakeViewWithContract.ContractB, + ContractFallbackByPass = false, + }; + window!.RootGrid.Children.Clear(); + window!.RootGrid.Children.Add(vmvhost); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + window.RaiseEvent(loaded); + vmvhost.RaiseEvent(loaded); + + Assert.NotNull(vmvhost.Content); + Assert.IsType(typeof(FakeViewWithContract.View0), vmvhost.Content); + window.Close(); + } + + void ResolveNoneWithFallbckByPass(ModernDependencyResolver resolver) + { + resolver.UnregisterCurrent(typeof(IViewFor), FakeViewWithContract.ContractB); + + var window = Fixture?.App?.WpfTestWindowFactory(); + + var viewmodel = new FakeViewWithContract.MyViewModel(); + var vmvhost = new ViewModelViewHost() + { + ViewModel = viewmodel, + ViewContract = FakeViewWithContract.ContractB, + ContractFallbackByPass = true, + }; + window!.RootGrid.Children.Clear(); + window!.RootGrid.Children.Add(vmvhost); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + window.RaiseEvent(loaded); + vmvhost.RaiseEvent(loaded); + + Assert.Null(vmvhost.Content); + window.Close(); + } + } + [StaFact] public void TransitioningContentControlTest() { diff --git a/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj b/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj index 00edd7896b..017db2d0d3 100644 --- a/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj +++ b/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj @@ -58,6 +58,10 @@ + + + + diff --git a/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs b/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs index 613bcf0f6f..9b4cdc9934 100644 --- a/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs +++ b/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs @@ -57,6 +57,12 @@ class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger public static readonly DependencyProperty ViewContractObservableProperty = DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Default)); + /// + /// The ContractFallbackByPass dependency property. + /// + public static readonly DependencyProperty ContractFallbackByPassProperty = + DependencyProperty.Register("ContractFallbackByPass", typeof(bool), typeof(ViewModelViewHost), new PropertyMetadata(false)); + private string? _viewContract; /// @@ -151,7 +157,21 @@ public string? ViewContract /// public IViewLocator? ViewLocator { get; set; } - private void ResolveViewForViewModel(object? viewModel, string? contract) + /// + /// Gets or sets a value indicating whether should bypass the default contract fallback behavior. + /// + public bool ContractFallbackByPass + { + get => (bool)GetValue(ContractFallbackByPassProperty); + set => SetValue(ContractFallbackByPassProperty, value); + } + + /// + /// resolve view for view model with respect to contract. + /// + /// ViewModel. + /// contract used by ViewLocator. + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { if (viewModel is null) { @@ -160,7 +180,12 @@ private void ResolveViewForViewModel(object? viewModel, string? contract) } var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var viewInstance = viewLocator.ResolveView(viewModel, contract) ?? viewLocator.ResolveView(viewModel); + + var viewInstance = viewLocator.ResolveView(viewModel, contract); + if (viewInstance is null && !ContractFallbackByPass) + { + viewInstance = viewLocator.ResolveView(viewModel); + } if (viewInstance is null) { diff --git a/src/ReactiveUI.XamForms/ViewModelViewHost.cs b/src/ReactiveUI.XamForms/ViewModelViewHost.cs index 70ab7a9714..815d86d77c 100644 --- a/src/ReactiveUI.XamForms/ViewModelViewHost.cs +++ b/src/ReactiveUI.XamForms/ViewModelViewHost.cs @@ -40,6 +40,14 @@ public class ViewModelViewHost : ContentView, IViewFor typeof(ViewModelViewHost), Observable.Never); + /// + /// Identifies the property. + /// + public static readonly BindableProperty ContractFallbackByPassProperty = BindableProperty.Create( + nameof(ContractFallbackByPass), + typeof(object), + typeof(ViewModelViewHost)); + private string? _viewContract; /// @@ -67,28 +75,7 @@ public ViewModelViewHost() vmAndContract.Subscribe(x => { _viewContract = x.Contract; - - if (x.ViewModel is null) - { - Content = DefaultContent; - return; - } - - var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var view = viewLocator.ResolveView(x.ViewModel, x.Contract) ?? viewLocator.ResolveView(x.ViewModel); - - if (view is null) - { - throw new Exception($"Couldn't find view for '{x.ViewModel}'."); - } - - if (view is not View castView) - { - throw new Exception($"View '{view.GetType().FullName}' is not a subclass of '{typeof(View).FullName}'."); - } - - view.ViewModel = x.ViewModel; - Content = castView; + ResolveViewForViewModel(x.ViewModel, x.Contract); }) }; }); @@ -133,8 +120,53 @@ public string? ViewContract set => ViewContractObservable = Observable.Return(value); } + /// + /// Gets or sets a value indicating whether should bypass the default contract fallback behavior. + /// + public bool ContractFallbackByPass + { + get => (bool)GetValue(ContractFallbackByPassProperty); + set => SetValue(ContractFallbackByPassProperty, value); + } + /// /// Gets or sets the override for the view locator to use when resolving the view. If unspecified, will be used. /// public IViewLocator? ViewLocator { get; set; } -} \ No newline at end of file + + /// + /// resolve view for view model with respect to contract. + /// + /// ViewModel. + /// contract used by ViewLocator. + protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) + { + if (viewModel is null) + { + Content = DefaultContent; + return; + } + + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + + var viewInstance = viewLocator.ResolveView(viewModel, contract); + if (viewInstance is null && !ContractFallbackByPass) + { + viewInstance = viewLocator.ResolveView(viewModel); + } + + if (viewInstance is null) + { + throw new Exception($"Couldn't find view for '{viewModel}'."); + } + + if (viewInstance is not View castView) + { + throw new Exception($"View '{viewInstance.GetType().FullName}' is not a subclass of '{typeof(View).FullName}'."); + } + + viewInstance.ViewModel = viewModel; + + Content = castView; + } +}