如何在 Xamarin Forms 中为 ReactiveUI 视图模型继承基 class 并指定派生 class?

How to inherit from base class and specify derived class for ReactiveUI view model in Xamarin Forms?

我的应用程序库中有针对 Xamarin 内容页面和视图模型的 classes,以便可以继承公共代码。我正在将 ReactiveUI 合并到这个框架中,并希望能够在 WhenActivated 子句中引用特定于适当视图模型的属性,但是在尝试这样做时遇到了问题,因为 Reactive ViewModel 属性 基于什么类型在 ReactiveContentPage<> 中给出,但我将该类型作为基本类型。

我在下面创建了一个使用 ReactiveUI.XamForms nuget 的最小可重现示例。

视图模型和关联的基础视图模型:

public class BaseViewModel : ReactiveObject
{
}

public class MainPageViewModel : BaseViewModel
{
    private string _result;
    public string Result
    {
        get => _result;
        set => this.RaiseAndSetIfChanged(ref _result, value);
    }
    private string _username;
    public string UserName
    {
        get => _username;
        set => this.RaiseAndSetIfChanged(ref _username, value);
    }
    
    public ReactiveCommand<Unit, Unit> RegisterCommand { get; }

    public MainPageViewModel()
    {
        RegisterCommand = ReactiveCommand
            .CreateFromObservable(ExecuteRegisterCommand);
    }

    private IObservable<Unit> ExecuteRegisterCommand()
    {
        Result = "Hello, " + UserName;
        return Observable.Return(Unit.Default);
    }
}

查看后面的代码:

public partial class MainPage : BasePage
{
    public MainPage()
    {
        InitializeComponent();

        BindingContext = new MainPageViewModel(); 

        this.WhenActivated(disposable =>
        {
            this.Bind(ViewModel, x => x.UserName, x => x.Username.Text)
                .DisposeWith(disposable);

            this.BindCommand(ViewModel, x => x.RegisterCommand, x => x.Register)
                .DisposeWith(disposable);

            this.Bind(ViewModel, x => x.Result, x => x.Result.Text)
               .DisposeWith(disposable);
        });
    }        
}

查看XAML:

<local:BasePage
x:Class="ReactiveUIXamarin.MainPage"         
xmlns:local="clr-namespace:ReactiveUIXamarin;assembly=ReactiveUIXamarin"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" 
xmlns="http://xamarin.com/schemas/2014/forms"
  ios:Page.UseSafeArea="true">
  <StackLayout>
      <Entry x:Name="Username" Placeholder="Username"/>
      <Button x:Name="Register" Text="Go" TextColor="White" BackgroundColor="Gray" />
      <Label x:Name="Result" />
  </StackLayout>
</local:BasePage>

查看的关联基页:

//if you replace below with ReactiveContentPage<MainPageViewModel> then it compiles, but want to keep this
//with BaseViewModel so that all pages can inherit from this
public class BasePage : ReactiveContentPage<BaseViewModel>
{
}

如上面代码注释中所述,将 ReactiveContentPage 替换为 ReactiveContentPage 允许它编译,以便您可以看到程序如何在编译状态下运行,但我想继续使用 BaseViewModel 作为键入以便所有页面都可以从 class.

继承

所以问题出在后面的视图代码中,因为即使在运行时 ReactiveUI ViewModel 属性 实际上是一个 MainPageViewModel 对象,它在编译时也不知道。所以我尝试将 ViewModel 转换为派生类型:

this.WhenActivated(disposable =>
        {
            this.Bind((MainPageViewModel)ViewModel, x => x.UserName, x => x.Username.Text)
                .DisposeWith(disposable);

            this.BindCommand((MainPageViewModel)ViewModel, x => x.RegisterCommand, x => x.Register)
                .DisposeWith(disposable);

            this.Bind((MainPageViewModel)ViewModel, x => x.Result, x => x.Result.Text)
               .DisposeWith(disposable);
        });

编译器可以使用 Bind 方法,但我无法理解 BindCommand 方法。 它给出了编译错误:

The type 'ReactiveUIXamarin.MainPage' cannot be used as type parameter 'TView' in the generic type or method 'CommandBinder.BindCommand<TView, TViewModel, TProp, TControl>(TView, TViewModel?, Expression<Func<TViewModel, TProp>>, Expression<Func<TView, TControl>>, string?)'. There is no implicit reference conversion from 'ReactiveUIXamarin.MainPage' to 'ReactiveUI.IViewFor<ReactiveUIXamarin.ViewModel.MainPageViewModel>'.

它正在尝试从 MainPage 转换为 MainPageViewModel,而我只想将 BaseViewModel 转换为 MainPageViewModel。

如何更改 BindCommand 语句使其正常工作?

把你的BasePage变成

public class BasePage<T> : ReactiveContentPage<T> where T : BaseViewModel
{
}

好的,我终于想通了。我一直在检查 ReactiveUI 的 CommandBinder,我得到的错误实际上是在说它想要的。该错误表明没有从 RectiveUIXamarin.MainPage 到 ReactiveUI.IViewFor.

的隐式转换

在这种情况下,ReactiveUIXamarin.MainPage 是 TView 参数,在查看 CommandBinder 时,预期 TView 将实现接口 IViewFor

所以我以 IViewFor 的形式将该接口添加到 MainPage Class。然后在最后添加接口的实现

进行这些更改后,一切都可以正常编译和工作。

public partial class MainPage : BasePage, IViewFor<MainPageViewModel>
{
    public MainPage()
    {
        InitializeComponent();

        BindingContext = new MainPageViewModel(); 

        this.WhenActivated(disposable =>
        {
            this.Bind((MainPageViewModel)ViewModel, x => x.UserName, x => x.Username.Text)
                .DisposeWith(disposable);

            this.BindCommand((MainPageViewModel)ViewModel, x => x.RegisterCommand, x => x.Register)
                .DisposeWith(disposable);

            this.Bind((MainPageViewModel)ViewModel, x => x.Result, x => x.Result.Text)
               .DisposeWith(disposable);
        });
    }

    MainPageViewModel IViewFor<MainPageViewModel>.ViewModel 
    { 
        get => (MainPageViewModel)ViewModel; 
        set => ViewModel = value; 
    }
}