【问题标题】:How to inherit from base class and specify derived class for ReactiveUI view model in Xamarin Forms?如何从基类继承并为 Xamarin Forms 中的 ReactiveUI 视图模型指定派生类?
【发布时间】:2021-05-26 01:38:32
【问题描述】:

我的应用程序基类中有 Xamarin 内容页面和视图模型,以便可以继承通用代码。我正在将 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 作为类型所以所有页面都可以从该类继承。

所以问题出在后面的视图代码上,因为即使在运行时 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 方法。 它给出了以下编译错误:

类型“ReactiveUIXamarin.MainPage”不能用作类型参数 泛型类型或方法中的“TView” 'CommandBinder.BindCommand(TView, TViewModel?, 表达式>, 表达式>, 字符串?)'。没有隐含的 从 'ReactiveUIXamarin.MainPage' 到的引用转换 'ReactiveUI.IViewFor'。

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

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

【问题讨论】:

  • BindCommand 不能引用 RegisterCommand,因为没有强制转换 ViewModel 引用的是 BaseViewModel。在这种情况下返回的消息是:“BaseViewModel”不包含“RegisterCommand”的定义,并且没有可访问的扩展方法“RegisterCommand”接受“BaseViewModel”类型的第一个参数
  • 啊,我误会了。我有一段时间没有绑定了;我认为这是内置类型中的一种方法。

标签: xamarin.forms reactiveui


【解决方案1】:

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

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

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

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

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; 
    }
}

【讨论】:

    【解决方案2】:

    把你的BasePage改成

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

    【讨论】:

    • 我实际上已经走这条路了,但在我的实际应用程序中,很多地方都引用了 BasePage,而且在使 BasePage 通用化时,我遇到了其他不相关的问题。如果这确实是唯一可能的方法,我将不得不重新审视它并解决这些问题,但我真的很想知道是否有答案直接更改 BindCommand 语法而无需进行BasePage 通用。
    猜你喜欢
    • 2019-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-20
    • 1970-01-01
    • 2012-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多