放浪軍師のXamarin.Formsアプリ開発局

Xamarin.Forms+Prism+ReactivePropertyで素人がAndroidやUWPのアプリを右往左往しながら開発している様を発信していきます。性質上間違いも多いのでご注意ください。

乱ちゃんProjectその3(一部修正とルーレット化)

放浪軍師のXamarin.Formsによるアプリ開発
今回は表示をルーレットっぽくします!
まず、初めて訪問された方は以下をお読みください。

www.gunshi.info


前回の修正

まず先に前回の修正を行います。
色々調べていたのですが、どうも以下の記事(@OXamarinさん)等によるとModelに定義したメソッドは返答値を設定しないのが正解みたいです。代わりに別のプロパティを用意する事になります。疎結合というらしいですが何故疎結合のほうが良いのかはまだよくわからない。多分返答値を返すよりも使い方に幅が出るからだと思います。生成した値を更に他で使ったりかな?
oxamarin.com

あともう一つ、kohzy99さんより掲示板でご指摘頂いた部分も修正します。
こういった指摘は非常に助かります。感謝!

Randomクラスのインスタンス生成についてですが、
引数のないコンストラクタで生成した場合は自動的にEnvironment.TickCount が seed として使われるようですので、
int seed = Environment.TickCount;
の行はいらなさそうですね。
(というか、もしあえてseedを使うなら
Random rnd = new System.Random(seed); ですね)

参考URL
http://www.atmarkit.co.jp/fdotnet/dotnettips/035random/random.html

あと、クラス内のメソッドの名前は
GetRandamData とかの(動詞)+(目的語)の方がいいような気がします。。。

seedの件は確かにおかしいとは思っていたのですがそのまま突っ切ってました。もうちょっと調べてみるべきでしたね。
そしてメソッドの名称の件。確かにGet~に統一すると、名称をド忘れした際なんかはGetまで入力すればVSの補助機能により候補に挙がってきてくれるので助かる場合もありそうです。
…というかそもそも英文って動詞が先なんでしたっけ?(俺の英語能力は0です…)
…という事で、これらを修正すると以下のようになります。

[randomdata.cs]

using System;
using System.Collections.Generic;
using System.Text;

namespace HelloXamarin.Models
{
    class RandomData
    {
        /// <summary>
        /// プロパティ ランダム値
        /// </summary>
        public string RandomValue { get; private set; }

        ~~<略>~~

        /// <summary>
        /// ランダム値を生成
        /// </summary>
        public void SetRandomData()
        {
            //シード値を取得(乱数固定化の阻止)
            int seed = Environment.TickCount;
            // Randomクラスのインスタンス生成
            Random rnd = new System.Random(seed);
            // listの数からダンダムで値を取得
            int i = rnd.Next(0, list.Count);
            // 代入
            RandomValue = list[i];
        }
    }
}

public string RandomValue { get; private set; }
を追加。ランダム値を格納するプロパティです。外部からsetする事は無いはずなので読み取り専用にしました。
また、元々はRandomDataGet()だったものは、SetRandomData()と名称を変更して返答値を無くして、結果をRandomValueへsetするようにしました。疎結合

[MainPageViewModel.cs]

using HelloXamarin.Models;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace HelloXamarin.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        public MainPageViewModel(INavigationService navigationService) 
            : base (navigationService)
        {
            //進言コマンドボタンが押された際に何をする?
            SingenRandomCommand = new DelegateCommand(GetRandomCommand);
        }

        /// <summary>
        /// 進言コマンドを取得する
        /// </summary>
        private void GetRandomCommand()
        {
            //数字を範囲指定
            RandomData r1;
            r1 = new RandomData(1,3);
            r1.SetRandomData();
            SingenLabel = r1.RandomValue;

            //文字列を指定
            //RandomData r2;
            //r2 = new RandomData(new string[] {"藤崎","紐緒","虹野"});
            //r2.SetRandomData();
            //SingenLabel = r2.RandomValue;

        }
    }
}

こっちはRandomCommandGet()をGetRandomCommand()に名称変更。SingenLabelは返答値を受け取らずにRandomValueを参照するように修正しています。

ルーレット化

さて、ここからが今回の本題です。
今の現状ではボタンを押すとランダムで値を表示することはできますが、あまりに味気ないのでボタンを押すとチラチラと値が変更したのち、いずれかの値が表示されるようにします。
こんな感じですね。
f:id:roamschemer:20180202002312g:plain
さっそくGetRandomCommand()を改造します。

        /// <summary>
        /// 進言コマンドを取得する
        /// </summary>
        private void GetRandomCommand()
        {
            //数字を範囲指定
            RandomData r1;
            r1 = new RandomData(1, 3);

            int loopNo = 10; //ループ回数(回)
            int loopTime = 5000; //ループ時間(msec)

            //基準となるウェイト時間(msec)
            float oneWaitTime = loopTime / (((loopNo - 1) * loopNo) / 2);

            for (int i = 1; i <= loopNo; i++)
            {
                r1.SetRandomData();
                SingenLabel = r1.RandomValue;
                if(i<loopNo)
                {
                    //少しずつウェイト時間を長くする
                    var task = System.Threading.Tasks.Task.Delay((int)(oneWaitTime * i));
                    task.Wait();
                }
            }

        }

こんな感じにしてみました。loopNoで指定した回数ループします。loopTime(msec)経過後に停止します。ルーレット感は徐々にループ中のウェイト時間を長くすることで再現しています。
実際に実行するとこうなりました。

f:id:roamschemer:20180320001044g:plain:w150
ん???どうやら失敗の様子。
ボタンを押した後何も反応せず、5秒後に数字が表示されています。
さて、原因はどこにあるのか…?
多分ですがSingenLabel = r1.RandomValue;でラベルの値を更新してはいますが、それがデータバインディングによってViewに反映するのはGetRandomCommand()を抜けた後とかになるんじゃなかろうか?
…となると、SingenLabel = r1.RandomValue;の直後にViewに反映させるための何かしらの仕組みが必要という事になります。

で、それっぽい情報を探しに探し回って以下の記事(@Tachibana446さん)を発見。多分これが鍵になりそうです。
qiita.com

…がダメ!
ViewModelにINotifyPropertyChangedを実装すればいけるっぽいが、そのやり方がよくわからない…。多分まだC#の理解が足りないんだと思います。コードがいまいち読めん…。

ん~…悔しいがとりあえず今日はここまで!
まさかこんなところで躓くとは…ちっくしょー!