shuhelohelo’s blog

Xamarin.Forms多めです.

MAUI:ボタンの有効無効を条件で切り替える

devblogs.microsoft.com

CommunityToolkitを使っています。

CommunityToolkitを使うとこれまで(Xamarin.Forms)ではたくさんの定型的なコードを書かなければならなかったところが、クラスやプロパティ、メソッドへの属性の指定で済むため、実装の労力を大幅にへらすことができます。

CommunityToolkitはMAUI専用というわけではなくWPFでも使えます。

さて、今回は入力欄とボタンがあって、入力欄に何かしら入力されていない場合はボタンを無効にする、ということをやります。

こういうことです。

この記事が詳しいです。

egvijayanand.in

まずはAddメソッドのRelayCommand属性にCanExecuteに判定用のメソッドを指定します。 ここで指定した判定用のメソッドCanAddExecuteはTextプロパティが空じゃないかをチェックし、空の場合はfalse(ボタン無効)、空じゃない場合はtrue(ボタン有効)を返すメソッドです。

アプリを実行すると、以下のようにボタンが無効の状態です。 いいですね!

しかし、これだけだと仮に入力欄に入力してもボタンは有効になりません。

なぜなら、チェックするのは最初だけでTextプロパティが変更されてもチェックしなおさないからです。

なので、Textプロパティが変更されるたびにチェックされるようにする必要があります。それが NotifyCanExecuteChangedFor属性。

これをTextプロパティにつけてあげます。 対象のコマンドはAddボタンにバインディングされているAddCommandです。

これでTextプロパティが変更されるたびにAddCommandの有効・無効判定メソッドCanAddExecuteが実行されることになります。

さて、改めて実行します。

入力すると、ボタンが有効になりました! よし!

空にすると、無効になります

MAUI:画面遷移時のデータの受け渡し

画面遷移のとき、遷移先にデータを渡したいことがあります。

画面Aで選択された内容を画面B側で表示したい、とか、画面B側での処理に使いたい、といったようにです。

MAUIのShellアプリケーションではURIベースのナビゲーションを使います。 WebサイトのURLのようなページの指定方法です。

参考:

www.youtube.com

データの受け渡しのない遷移

まず、遷移先のページを作ります。

AppShell.xaml.csに遷移先のルーティング情報を追加します。 これで、"SecondPage"と指定するとSecondPageへ遷移します。

遷移は以下のようにします。

Shell.Current.GoToAsync(nameof(SecondPage));

戻るときは以下のように遷移先のURIに".."を指定します。パスで相対的に1つ前を表すのと同じです。

データの受け渡しのある遷移(単純な型の場合)

stringやintなどの単純な型の場合、渡す側はとてもシンプルです。 Webと同様に?の後に「クエリパラメータ名 = パラメータ」の形でGoToAsyncにわたすだけです。

例えばクエリパラメータ名を「MessageFrom」とすると、以下のようになります。

受け取り側は以下のようにします。

  • QueryProperty属性をViewModelにつける。
  • QueryProperty(受け取り側のクラスのプロパティ名(SecondPageViewModelクラスの), クエリパラメータ名(送る側のURIで指定したID))

このように単純な型の場合は、URIにクエリパラメータとして埋め込んで渡すことができます。

では、カスタムクラスやListなど複雑な型の場合はどうでしょうか。

データの受け渡しのある遷移(複雑な型の場合)

複雑な型の場合はURIに埋め込むことはできません。

そのかわり、GoToAsyncメソッドはそういったデータを受け渡すためのDictionary<string,object>型の引数を受け取るようにできています。

以下のように渡したいデータを「キーと値」のペアで遷移先に渡すことができます。

注意:ObservableCollectionはそのままでは送れないのでListに変換しています。

受け取り側は単純な型の場合と変わりません。 QueryProperty属性を使って、送り側で指定したキー名と受け取り側のプロパティを結びつけるだけです。

おしまい

これで好きな型のデータを遷移先に送ることができます。

MAUIでCommunityToolkit.Mvvmを使ったPubSubメッセージング

参考

www.youtube.com

メッセージングとは

送信側と受信側がお互いを意識しないで(依存しない)データのやり取りを行う仕組み、なのかな。

送信側は受信側のことを考えることなく送信したいときにメッセージを送信する。

受信側は送信側がどのタイミングでメッセージを送るかは気にせず待ち受けていて、送られてきたときに対応する。

こんな仕組み、と思う。

メリットは?

送信側、受信側がお互いに依存しないでデータのやり取りができること。

データの送受信そのものはもちろん大切だけれど、データの送受信を介して相手側の処理を開始するトリガにできること。

使い所は?

参考の動画のとおり、一覧ページから詳細ページに遷移した後、詳細ページで「削除」操作が行われたときに一覧ページで対象のアイテムが削除されるようにする、というようなページをまたいだ、任意のタイミングでの処理の実行に使う。

遷移のタイミングで、ということであれば遷移時にデータを受け渡すことができるけれど、メッセージングの場合は任意のタイミングでできるのがよい。

使い方

CommunityToolkit.Mvvm(8.0)をインストールしておく。 WeakReferenceMessengerクラスを使う。

メッセージの入れ物を作る

ここでいうメッセージとなるものはValueChangedMessage<T>型を継承したオブジェクトのこと。

このオブジェクトを受信側は待ち受け、送信側は送る。この型自体が送受信の識別子の役割も果たす。

例えばMyMessage:ValueChangedMessage<MyClass>というメッセージを用意したとする。

受信側は、「このMyMessage型のメッセージを待ち受けるよ!」というように書く。

送信側は、「MyMessage型のメッセージを送るよ!」というように書く。

なので、送受信したいメッセージの種類の数だけこういったValueChangedMessageを継承したクラスを用意する。

public class MyMessage : ValueChangedMessage<MyClass>
{
    public MyMessage(MyClass value) : base(value)
    {
    }
}

この場合、送りたいデータの型は"MyClass"型のデータ。 MyMessageの中にMyClass型のデータが含まれて送受信される。

受信側

受信側はWeakReferenceMessenger.Default.Register<T>()メソッドを使って、待ち受けるメッセージの種類と、それを受け取ったときの処理を書く。

以下のようにコンストラクタに書くことになると思う。

ラムダ式の中の第2引数(m)の中に送信側から送られてきたメッセージが入る。

この例は詳細ページ側で削除操作が行われたアイテムを一覧ページ側で一覧から削除するときの例。

    public MainPageViewModel()
    {
        //メッセージ受信側の登録
        //DeleteItemMessage型のメッセージが送られてきたときの処理を書く
        WeakReferenceMessenger.Default.Register<DeleteItemMessage>(this, (r, m) =>
        {
            //UIの更新を伴う場合はメインスレッドで実行されるようにする
            MainThread.BeginInvokeOnMainThread(() =>
            {
                Delete(m.Value);
            });
        });
    }

送信側

送信側は WeakReferenceMessenger.Default.Send()メソッドを使う。

例えば以下のようにMyClass型のデータをMyMessageに入れてメッセージを送る。

 var myClass = new MyClass();
 WeakReferenceMessenger.Default.Send(new MyMessage(myClass));

これは詳細ページで削除操作が行われたときに削除されたアイテムを載せてメッセージを送信する例。

    [RelayCommand]
    async Task Delete()
    {
        WeakReferenceMessenger.Default.Send(new DeleteItemMessage(TaskItem));
        await GoBack();
    }

おしまい

とても簡単にPubSubなメッセージングができた。

送受信それぞれのコードで相手を指定する記述は一切出て来ないことがわかる。

メッセージの型だけ知っていればよい。

MAUI:XAML側でViewModelのインテリセンスを効かせる

XAML側でデータバインディング時にViewModelのパブリックメンバーがインテリセンスで表示されたり、タイポで存在しないメンバーを指定したときに指摘してくれると嬉しいです。

Xamain.Formsのときは以下のようにデザイン時支援機能を使って以下のように書くことでインテリセンスを有効にしました。

shuhelohelo.hatenablog.com

MAUIではデザイン時支援機能ではなくx:DataTypeを使います。 以下のようにContentPageにViewModelへの参照を追加しておくことで、ContentPage以下で指定したクラスのメンバーがインテリセンスに表示されます。

<ContentPage
    x:Class="MyMauiAppPractice.MainPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:viewmodel="clr-namespace:MyMauiAppPractice.ViewModel"
    x:DataType="viewmodel:SamplesViewModel">

例えば以下のようなSampleViewModelというクラスがあったとします。

public partial class SamplesViewModel : BaseViewModel
{
    public string SampleText { get; set; }

    public ObservableCollection<Sample> Samples { get; set; }
}

このViewModelを使うView側で上記のようにx:DataTypeを使ってSampleViewModelクラスを指定しておくと、XAML側でデータバインディングの記述時にインテリセンスが効きます。

x:DataTypeでクラスを指定していないときはこのように候補が表示されません。

タイポのときは、XAML上でそれを指摘してくれます。

私にとってはMAUIを使うときにはまずはやっておきたいことの一つです。

Arduino IDEを使ったM5Stack開発環境を整える

M5Stackの開発をしたいと思い立ち、開発環境を整えてみる

色々なサイトを参考にしながらすすめる。

基本はこちらから。

Getting Started: M5Stack の開発環境を準備する | プロトタイプ向けマイコンモジュール M5Stack と 3G 拡張ボードをセットアップする | ソラコムユーザーサイト - SORACOM Users

Arduino IDEのインストール

こちらからダウンロードする

Software | Arduino

Windowsストアからもインストールできるけれど、一番上のInstaller版を選択する。 ストア版などだと後々VSCodeとの連携で失敗するかもしれないとのことだった。

そのままの設定でインストール。

インストール中に以下のようなダイアログが表示された。 まあ全部インストールする。

インストールが終わったら起動してみる。 こんな感じ。

USBドライバーのインストール

次はUSBドライバーをインストールする。

Download | m5stack-store

CP2104 DriverのWindows用をダウンロードする

ダウンロードしたzipファイルを解凍して、以下のファイルを実行する。

インストーラが起動するので、インストールする。 インストール完了。

PCとM5StackをUSBケーブルで接続する。 デバイスマネージャを開いて以下のように表示されることを確認する。 よし!

M5Stackのボード定義のインストール

Arduino IDEで使えるようにするには、Arduino IDEでボード定義というものをインストールする必要がある。 そのためにライブラリマネージャに追加のライブラリ検索範囲を設定する必要がある。 ファイル > 環境設定

環境設定が開いたら、 追加のボードマネージャのURLの欄に以下のURLを追加する。 https://dl.espressif.com/dl/package_esp32_index.json

OKを押して環境設定を閉じる。

ツール > ボード > ボードマネージャを選択する。

検索欄にESP32と入力して検索すると、esp32というパッケージが見つかるので、これをインストールする。

さて、ボードをインストールするのだけれど、自分の持っているM5Stackはどれかというとこれ。

M5Stack Gray(9軸IMU搭載)--在庫限り - SWITCH-SCIENCE

ツール > ボード > ESP32 Arduinoを選択すると、ずらっとデバイスのリストが表示されるのでその中から対象のM5Stackデバイスを選択する。 M5Stack Grayの場合はどれだ?

こちらの記事によると、Grayの場合はM5Stack-Core-ESP32とのこと。

【M5Stack】ビルド時のボード・オプションの選び方(ArduinoIDE,VSCode+PIO) | M5Stack沼人の日記

Arduino IDEで先程のリストからM5Stack-Core-ESP32を選択する。

シリアルポートを選択

Arduino IDEからM5Stackにプログラムを書き込んだり、通信するためのシリアルポートを選択します。 ツールを開いてみると、シリアルポートにCOM3(環境によって異なる)があるので、これを選択します。デバイスマネージャで確認したポートです。

M5Stackライブラリのインストール

M5Stackの様々な機能にアクセスするためのライブラリをインストールします。

スケッチ > ライブラリをインクルード > ライブラリを管理を選択する。

ライブラリマネージャが表示されるので、M5Stackと入力して検索する。 絞り込んでも山ほどあるので、頑張ってM5Stackというライブラリを探す。真ん中ぐらいかな。

依存するライブラリもインストールするか訊かれるので、全部インストールする。

あれ、途中で失敗した。スクショ取り忘れた。

でも、ファイル > スケッチ例にM5Stackが追加されている。 まあいっか。

Hello Worldしてみる

それじゃHello Worldいってみますか スケッチ例にHello Worldがあるのでそれを選択する。

setup関数でディスプレイに「Hello World」を表示するだけの簡単なサンプルが表示される。

このまま左上の⇒ボタンをクリックすればコンパイルとデプロイが始まる

結構時間かかる…初回だからとのこと。

書き込みが完了した。 以下のように表示されたらOK。

M5Stackに「Hello World」と表示されている やった!

ASP.NET MVCプロジェクトにIdentityユーザー認証機能を追加する

とりあえずチュートリアルに従ってアプリを作る。

そうだ、ユーザー認証機能を追加しよう。

というときのためのメモです。

また、Identityユーザー認証機能を後付するとプロジェクト内にIdentityで使われるクラスやページのソースコードが作成されるので、いろいろアレンジしやすいです。

Identityユーザー認証機能の追加

追加自体はとても簡単です。 スキャフォールディングによって作成されます。

ソリューションエクスプローラで右クリックしてコンテキストメニューから追加 > 新規スキャフォールディングアイテムを選択します。

f:id:shuhelohelo:20190612122308p:plain

スキャフォールディングを追加ウィンドウが開くので、左ペインからIDを選択して右下の追加ボタンを押します。

f:id:shuhelohelo:20190612122451p:plain

IDの追加ウィンドウが開く。

f:id:shuhelohelo:20190612122900p:plain

とりあえずやること。

Startup.csの編集

認証機能を使うことを指定する。

Configureメソッドに以下を追加

これはapp.UseMVCよりも前に置くこと。

app.UseAuthentication();

_Layout.cshtmlの編集

Identityのスキャフォールディングで_LoginPartial.cshtmlが作成されているので、これがサイトのヘッダ部分に表示されるようにする。

<partial name="_LoginPartial" />を埋め込む。

    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
・・・
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <partial name="_LoginPartial" />
・・・
                </div>
            </div>
        </nav>
    </header>

IdentityのLoginページやRegisterページはライブラリ内に保持されているので、直接編集などはできない。 困ったな。

わかった!!!

IDの追加画面で、オーバーライドする項目でチェックをつけたページがAreas/Identity/Pages/Account内に作成されて、オーバーライドという形で編集できるようになる。

OpenCvSharpでHSVで特定の色を取り出すときの簡易確認ツール

OpenCVで特定の色だけにしぼり込むときに、HSV(Hue,Saturation,Value)で範囲を指定するとします。

ある画像をサンプルとして、自分が絞り込みたい色の範囲はHSVでどの値の範囲なのかを確認したいです。

そんなちょっとした確認に便利なツールがあると便利です。ここでは、そんなちょっとしたツールをOpenCvSharpで作ります。

OpenCVでは独自のウィンドウに加え、スライダーをウィンドウに設置することができるため、こういった値を変えながら結果を確認したい、という用途にピッタリのツールを簡単に作ることができます。

f:id:shuhelohelo:20201123124148p:plain

参考

OpenCVのサンプルコードはPythonC++のものがたくさん見つかりますが、OpenCvSharpでも書き方はほとんど同じなので、他言語のサンプルを簡単な脳内変換でOpenCvSharpで使うことができます。

今回参考にさせてもらったのは以下の記事です。

qiita.com

ありがとうございました。

実装

流れとしては、

  1. 名前付きのウィンドウを作成する
  2. そのウィンドウにスライダー(trackbar)を配置する
  3. スライダーのOnChangedイベントにイベントハンドラを設定する
  4. イベントハンドラの中でスライダーの値をもとに画像を生成してウィンドウを更新

です。これがほぼすべてです。

using OpenCvSharp;
using System;

namespace OpenCvHsvChecker
{
    class Program
    {
        private static int _h_min = 0;
        private static int _h_max = 0;
        private static int _s_min = 0;
        private static int _s_max = 0;
        private static int _v_min = 0;
        private static int _v_max = 0;

        private static Mat src = new Mat();
        private static Mat hsv = new Mat();
        //private static Mat dst = new Mat();

        private const string WINDOW_NAME = "HSV Checker";
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            string inputImagePath = "Images/gauge-1.jpg";

            src = Cv2.ImRead(inputImagePath);
            if (src is null)
                return;
            Cv2.ImShow("src", src);

            Cv2.CvtColor(src, hsv, ColorConversionCodes.BGR2HSV, 3);
            Cv2.ImShow("hsv", hsv);

            //名前つきウィンドウを作成
            Cv2.NamedWindow(WINDOW_NAME);

            //ウィンドウ名を指定してスライダーを配置
            Cv2.CreateTrackbar("H_Min", WINDOW_NAME, 179, onChange: H_Min_Changed);
            Cv2.CreateTrackbar("H_Max", WINDOW_NAME, 179, onChange: H_Max_Changed);
            Cv2.CreateTrackbar("S_Min", WINDOW_NAME, 255, onChange: S_Min_Changed);
            Cv2.CreateTrackbar("S_Max", WINDOW_NAME, 255, onChange: S_Max_Changed);
            Cv2.CreateTrackbar("V_Min", WINDOW_NAME, 255, onChange: V_Min_Changed);
            Cv2.CreateTrackbar("V_Max", WINDOW_NAME, 255, onChange: V_Max_Changed);

            //初期画像を表示
            Cv2.ImShow(WINDOW_NAME, src);

            Cv2.WaitKey();
        }

        private static void V_Max_Changed(int pos, IntPtr userData)
        {
            _v_max = pos;
            Update();
        }

        private static void V_Min_Changed(int pos, IntPtr userData)
        {
            _v_min = pos;
            Update();
        }

        private static void S_Max_Changed(int pos, IntPtr userData)
        {
            _s_max = pos;
            Update();
        }

        private static void S_Min_Changed(int pos, IntPtr userData)
        {
            _s_min = pos;
            Update();
        }

        private static void H_Max_Changed(int pos, IntPtr userData)
        {
            _h_max = pos;
            Update();
        }

        private static void H_Min_Changed(int pos, IntPtr userData)
        {
            _h_min = pos;
            Console.WriteLine(_h_min);
            Update();
        }


        static void Update()
        {
            //HSV画像とスライダーの値からマスクを生成
            var scalar_min = new Scalar(_h_min, _s_min, _v_min);
            var scalar_max = new Scalar(_h_max, _s_max, _v_max);
            Mat mask = new Mat();
            Cv2.InRange(hsv, scalar_min, scalar_max, mask);

            //マスク画像を使って元画像にフィルタをかける
            Mat dst = new Mat();
            src.CopyTo(dst, mask);

            //ウィンドウの画像を更新
            Cv2.ImShow(WINDOW_NAME, dst);
        }
    }
}

OpenCvSharpのインストールについては以下の記事を。

shuhelohelo.hatenablog.com

ソースコード

github.com