无法将命令绑定到 MvvmCross 按钮

Cannot Bind a Command to a MvvmCross Button

我正在尝试创建一个导航到 mvvmCross 中另一个页面的按钮。应用程序正确启动,但按下按钮时没有任何反应。

我使用的是 Bable project 的修改版本,它已更新为使用 mvvmcross 8。并且被精简为仅包含我认为在两个视图模型之间移动的最小值。

这是当前代码:

核心class库:

FirstViewModel.cs

using MvvmCross.Commands;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;

 namespace Babel2.Core.ViewModels {
    public class FirstViewModel : MvxViewModel
    {
        private readonly IMvxNavigationService _navigationService;
        
        public FirstViewModel(IMvxNavigationService navigationService)
        {
            _navigationService = navigationService;
        }

        public IMvxCommand GoCommand => new MvxCommand(() => _navigationService.Navigate<SecondViewModel>());

    } }

SecondViewModel.cs

using MvvmCross.ViewModels;

namespace Babel2.Core.ViewModels
{
    public class FirstViewModel : MvxViewModel
    {
        private readonly IMvxNavigationService _navigationService;
        
        public FirstViewModel(IMvxNavigationService navigationService)
        {
            _navigationService = navigationService;
        }

        public IMvxCommand GoCommand => new MvxCommand(() => _navigationService.Navigate<SecondViewModel>());

    }
}

App.cs

using MvvmCross.IoC;
using MvvmCross.ViewModels;

namespace Babel2.Core
{
    public class App : MvxApplication
    {
        public override void Initialize()
        {
            CreatableTypes()
                .EndingWith("Service")
                .AsInterfaces()
                .RegisterAsLazySingleton();

            RegisterAppStart<ViewModels.FirstViewModel>();
        }
    }
}

Android 项目

FirstView.axml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <Button 
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:text="Second Page Go!"
            local:MvxBind="Click GoCommand" />    
</LinearLayout>

SecondView.axml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="40dp"
        android:text="Second View" />

</LinearLayout>

SplashScreen.axml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Loading...." />
</LinearLayout>

FirstView.cs

using Android.App;
using Android.OS;
using MvvmCross.Platforms.Android.Views;

namespace Babel2.Droid.Views
{
    [Activity(Label = "View for FirstViewModel")]
    public class FirstView : MvxActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.FirstView);
        }
    }
}

SecondView.cs

using Android.OS;
using Android.Views;
using MvvmCross.Platforms.Android.Views.Fragments;

namespace Babel2.Droid.Views
{
    public class SecondView : MvxFragment
    {
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);
            View view = inflater.Inflate(Resource.Layout.SecondView, container, false);
            return view;
        }
        
    }
}

LinkerPleaseInclude.cs

using System.Collections.Specialized;
using System.Windows.Input;
using Android.App;
using Android.Views;
using Android.Widget;
using MvvmCross;

namespace Babel2.Droid
{
    [Preserve(AllMembers = true)]
    public class LinkerPleaseInclude
    {
                public void Include(Button button)
        {
            button.Click += (s,e) => button.Text = button.Text + "";
        }

        public void Include(CheckBox checkBox)
        {
            checkBox.CheckedChange += (sender, args) => checkBox.Checked = !checkBox.Checked;
        }
        
        public void Include(Switch @switch)
        {
            @switch.CheckedChange += (sender, args) => @switch.Checked = !@switch.Checked;
        }

        public void Include(View view)
        {
            view.Click += (s, e) => view.ContentDescription = view.ContentDescription + "";
        }

        public void Include(TextView text)
        {
            text.TextChanged += (sender, args) => text.Text = "" + text.Text;
            text.Hint = "" + text.Hint;
        }
        
        public void Include(CheckedTextView text)
        {
            text.TextChanged += (sender, args) => text.Text = "" + text.Text;
            text.Hint = "" + text.Hint;
        }

        public void Include(CompoundButton cb)
        {
            cb.CheckedChange += (sender, args) => cb.Checked = !cb.Checked;
        }

        public void Include(SeekBar sb)
        {
            sb.ProgressChanged += (sender, args) => sb.Progress = sb.Progress + 1;
        }

        public void Include(Activity act)
        {
            act.Title = act.Title + "";
        }

        public void Include(INotifyCollectionChanged changed)
        {
            changed.CollectionChanged += (s,e) => { var test = $"{e.Action}{e.NewItems}{e.NewStartingIndex}{e.OldItems}{e.OldStartingIndex}"; };
        }

        public void Include(ICommand command)
        {
            command.CanExecuteChanged += (s, e) => { if (command.CanExecute(null)) command.Execute(null); };
        }
        
        // public void Include(MvvmCross.Platform.IoC.MvxPropertyInjector injector)
        // {
        //     injector = new MvvmCross.Platform.IoC.MvxPropertyInjector ();
        // } 

        public void Include(System.ComponentModel.INotifyPropertyChanged changed)
        {
            changed.PropertyChanged += (sender, e) =>  {
                var test = e.PropertyName;
            };
        }
    }
}

Setup.cs

using Babel2.Core;
using Microsoft.Extensions.Logging;
using MvvmCross.Platforms.Android.Core;
using Serilog.Extensions.Logging;

namespace Babel2.Droid
{
    public class Setup : MvxAndroidSetup<App>
    {
        protected override ILoggerProvider CreateLogProvider()
        {
            return new SerilogLoggerProvider();
        }

        protected override ILoggerFactory CreateLogFactory()
        {
            return new SerilogLoggerFactory();
        }
    }
}

SplashScreen.cs

using Android.App;
using Android.Content.PM;
using MvvmCross.Platforms.Android.Views;

namespace Babel2.Droid
{
    [Activity(
        Label = "Babel2.Droid"
        , MainLauncher = true
        , Icon = "@drawable/icon"
        , Theme = "@style/AppTheme"
        , NoHistory = true
        , ScreenOrientation = ScreenOrientation.Portrait)]
    public class SplashScreen : MvxSplashScreenActivity
    {
        public SplashScreen()
            : base(Resource.Layout.SplashScreen)
        {
        }
    }
}

您需要将 ViewModel 与 View 连接起来,在您的情况下,它将是:

public class FirstView : MvxActivity<FirstViewModel>

你的第二个视图也是一样的:

public class SecondView : MvxFragment<SecondViewModel>

记得勾选Playground repo to explore how to use the framework. Also, you can check the Star Wars sample repo

另一方面,不要忘记为每个视图添加展示属性,请阅读official docs about Android View Presenters