放浪軍師のアプリ開発局

Xamarin.Formsを使ってAndroid,iOS,UWP,WPFで動くアプリを開発したりしています。

Prism を使わずに Xamarin.Forms で MVVM

放浪軍師の Xamarin.Forms によるアプリ開発
あけましておめでとうございます。ちょっと間が空いてしまい申し訳ない。遊んでたわけじゃなくて遊んでましたすいません。

おしらせ

実は今年から Xamarin だけでなく Unity の学習も始めようと思っています。ゲームエンジンで有名な方の奴です。
というのも、かねてよりバーチャルリアリティの技術に非常に心惹かれるものがありまして、いつかやってみたいとは思っていたのですが、忙しい事もあり手を出していませんでした。
…ですが、去年末より VTuber に関するイベント(最強バーチャルタレントオーディション〜極〜 通称:バーチャル蠱毒)
dic.pixiv.net
にどっぷりハマり、そこで明確な目標みたいなものが出来ましたのでこれを機に勉強を進めたいと思っています。もちろん Xamarin は乱ちゃんや業務で必要ですので、引き続き勉強していこうと思っています。その為、このブログの名称は「放浪軍師のXamarin.Formsアプリ開発局」から「放浪軍師のアプリ開発」に変更いたします。ご了承ください。

では前回告知したように Xamarin.Forms.WPF に Prism を適用していきたいと思います。

Xamarin.Forms.WPF + Prism は不可能です

無理でした!色々試したけれど無理でした!というか、無理だという事をSpacekeyさんに教えていただきました。(昨年は大変お世話になりました!今年もよろしくお願いします!)



Xamarin.Forms.WPF + Prism が不可能というのは結構ググりまくってたんですが見つからない情報でしたね。GitHub の issues で検索…そういう手もあるのか…。今後は活用したいです。

…で、仕方が無いので Prism を使わずに Xamarin.Forms してみようと思います。勿論 MVVM です。でもよく考えたら Prism 無しでやった事ないんですよね~。 Prism を使わない MVVM の構成の情報もあまり落ちてなくてかなり苦戦しました。苦戦はいつものことだけどな!

環境

  • Visual Studio 2017 Community
  • Xamarin.Forms 3.4.0.1009999
  • ReactiveProperty 5.2.0

下準備

1.rksoftware さん記事を見ながら Android iOS UWP WPF のプロジェクトを作成します(ぶん投げすいません。今年もよろしくお願いします!)。
rksoftware.hatenablog.com
2.Nugetパッケージから ReactiveProperty を追加します。
3.PCLに最初から用意されている MainPage.xaml を削除します。
4.PCLをこんな感じの構成にします。
※Views に追加しているのは Xamarin.Forms の コンテンツページです。
f:id:roamschemer:20190113010031p:plain

要所

全体のコードはGitHubを参照してもらうとして、ここでは要所だけ紹介しようと思います。

App.xaml.cs
public App()
{
    InitializeComponent();
    MainPage = new NavigationPage(new Views.MainPageView()); //起動時に開くViewクラスを指定する
}

最初に開く画面の指定ですね。 Prism の場合は await NavigationService.NavigateAsync("NavigationPage/MainPageView"); のような指定をしますが、この NavigationService が今回は使えないのでこうなります。他の場所でも出てきますが、この「遷移先を文字列で指定できない」というのは微妙に厄介かもしれません。

Views/MainPageView.xaml.cs
public MainPageView()
{
    InitializeComponent();
    BindingContext = new ViewModels.MainPageViewModel(this.Navigation); //繋げるViewModelクラスを指定する
}

紐づけ先の ViewModel を指定します。 Prism の場合はクラスの名称で ViewModel と自動的につながる為、このような記述は不要ですね。
また、this.Navigationはその先に遷移する場合に必須になります。…といっても、遷移するかどうかなんてあとから変わる事も多いし、基本的に書いておいてもいいんじゃないですかね~

Extends/BindableBase.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace NoPrismMVVM.Extends
{
    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
        {
            if (Equals(field, value)) { return false; }
            field = value;
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); return true;
        }
    }
}

Prism の場合、 ViewModel や Model で BindableBase という INotifyPropertyChanged を継承したクラスを継承させます。これは Binding を行うために用いられるのですが、Prism 特有の物である為このように別枠で実装しています。そうすれば Prism の時と変わらない記述が出来るようになるわけですね。
これはかずきさんのところで見つけた書き方を一部改変したものになります。ほんといつもお世話になっております!
blog.okazuki.jp
しかしこの Extends というフォルダ名で良いのかどうか…この辺いつも悩むんだけれど、みなさんどう名付けられてるんですかね?

ViewModels/MainPageViewModel.cs
public class MainPageViewModel : BindableBase
{

    public ReactiveProperty<string> Label1 { get; private set; }
    public ReactiveCommand<string> Button1 { get; } = new ReactiveCommand<string>();
    public ReactiveCommand<string> Button2 { get; } = new ReactiveCommand<string>();
    public ReactiveCommand<string> Button3 { get; } = new ReactiveCommand<string>();
    public ReactiveCommand<string> Button4 { get; } = new ReactiveCommand<string>();
    public ReactiveCommand<string> Button5 { get; } = new ReactiveCommand<string>();

    public ReactiveCommand NextPageButton { get; } = new ReactiveCommand();
    public Model model = new Model();

    public MainPageViewModel(INavigation navigation)
    {
        //Model→ViewModel
        this.Label1 = model.ObserveProperty(x => x.Name).ToReactiveProperty();
        //Button
        Button1.Subscribe(x => model.NamePlus(x));
        Button2.Subscribe(x => model.NamePlus(x));
        Button3.Subscribe(x => model.NamePlus(x));
        Button4.Subscribe(x => model.NamePlus(x));
        Button5.Subscribe(x => model.NamePlus(x));
        //Button(ページ遷移)
        NextPageButton.Subscribe(async _ => await navigation.PushAsync(new Views.NextPageView())); 
    }
}

前述の BindableBase クラスを実装した為、Prism ありと基本同じです。ReactiveProperty での Binding も問題なさそうです。ただ、画面遷移に NavigationService を使えない影響はここでも出てきます。これが地味に厄介なところで、遷移先を文字列で指定できないので以前紹介した ReactiveCommand での画面遷移が使えません。
PrismとReactiveCommand<T>で楽々画面遷移 - 放浪軍師のアプリ開発局
地味に便利なのでなんかいい方法があれば良いですけど…仕方ないので今のところはこれで行こうと思います。

また、Model は BindableBase を継承すれば何も変わらないのでここでは省略します。

挙動

f:id:roamschemer:20190113012623g:plain:w500
左上が WPF 左下が UWP 右がAndroidです。ちゃんと挙動してますね!

まとめ

いや~勉強になりました! Prism が何をしてくれているのか今まで認識できていませんでしたが、今回のテストでよくわかりますね。
便利や Prism ... さらば Prism ... いや、業務のアプリでは使うけどね。

でもこれでやっと乱ちゃんを WPF でも配布できるようになるぞ!もうちょっとだけ待ってくれよな!

※後日 Xamarin.Forms.WPF + Prism の方法が見つかりました。
www.gunshi.info