じゃがいも畑

開発ネタの記録

WPFのMVVMでイベントを処理する方法いろいろ

最近PrismやらReactive Propertyやら勉強中なので忘れないように書いていきます

  • コードビハインド
  • Prism
  • Reactive Property

を使ってイベントを処理するサンプルを作成しました

サンプルコードはこちら github.com

開発環境はVisual Studio Community 2019で対象のフレームワークは.NET Core 3.0です

サンプルの中身

サンプルアプリはこんな感じの画面になっていて

f:id:whitedog0215:20200315134648p:plain

  • Textboxに入力したものと同じ内容をTextBlockにコピーする
  • ボタンを押したらTextBlockの内容をすべて消去する

という動作になっています

サンプルコードではButtonのClickイベントとTextBoxのPreviewTextInputイベントを処理しています

TextBox クラス (System.Windows.Controls) | Microsoft Docs

Button クラス (System.Windows.Controls) | Microsoft Docs

 

コードビハインドでのイベント処理

MVVMじゃなくなっちゃいますがコードビハインドを使うことでWinFormっぽくイベントを処理することができます

xamlでViewの部品それぞれに名前とイベントの処理先を書いて、コードビハインドに処理を実装します

  • CodeBehind.xaml (UIのレイアウト部分のみ)
    <StackPanel Margin="5, 5" >
        <TextBlock Text="CodeBehind"/>
        <Button x:Name="button" Content="ボタンです" Click="button_Click"/>
        <TextBox x:Name="textBox" PreviewTextInput="textBox_PreviewTextInput"/>
        <TextBlock x:Name="textBlock" Text="TextBoxに入力した文字をここに表示します&#10;ボタンを押すと文字をすべて消去します"/>
    </StackPanel>
  • CodeBehind.xaml.cs
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            textBlock.Text = "";
        }

        private void textBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
        {
            textBlock.Text += e.Text;
        }
    }

Prismでのイベント処理

PrismはMVVMを実現するためのサポートライブラリです

prismlibrary.com

サンプルプログラムにはPrism.Unityパッケージが使われています f:id:whitedog0215:20200315143244p:plain

Prism.xamlでViewを作成し、PrismViewModelでイベントの処理や入力されたテキストの制御を行います

ソースコードは以下になります

<UserControl x:Class="EventSample.Views.Prism"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <StackPanel Margin="5, 5">
        <TextBlock Text="Prism"/>
        <Button Content="ボタンです" Command="{Binding ButtonClickCommand}"/>
        <TextBox Text="{Binding InputText}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="PreviewTextInput">
                    <prism:InvokeCommandAction Command="{Binding PreviewTextInputCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>
        <TextBlock Text="{Binding OutputText}"/>
    </StackPanel>
</UserControl>
  • PrismViewModel.cs
    public class PrismViewModel : BindableBase
    {
        private string inputText = "";
        public string InputText
        {
            get { return inputText; }
            set { SetProperty(ref inputText, value); }
        }

        private string outputText = "TextBoxに入力した文字をここに表示します\r\nボタンを押すと文字をすべて消去します";
        public string OutputText
        {
            get { return outputText; }
            set { SetProperty(ref outputText, value); }
        }

        private DelegateCommand<RoutedEventArgs> buttonClickCommand;
        public DelegateCommand<RoutedEventArgs> ButtonClickCommand =>
            buttonClickCommand ?? (buttonClickCommand = new DelegateCommand<RoutedEventArgs>(ExecuteButtonClickCommand));

        private DelegateCommand<TextCompositionEventArgs> previewInputTextCommand;
        public DelegateCommand<TextCompositionEventArgs> PreviewTextInputCommand =>
            previewInputTextCommand ?? (previewInputTextCommand = new DelegateCommand<TextCompositionEventArgs>(ExecutePreviewInputText));


        public PrismViewModel()
        {

        }

        void ExecuteButtonClickCommand(RoutedEventArgs e)
        {
            OutputText = "";
        }

        void ExecutePreviewInputText(TextCompositionEventArgs e)
        {
            OutputText += e.Text;
        }

    }

ソースコードのポイントは以下のような感じ

  • Prism.xaml 5行目
// 5:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

イベントの処理に使用するためのライブラリをi:のプレフィックスに割り当てています

  • Prism.xaml 9行目
// 9:
<Button Content="ボタンです" Command="{Binding ButtonClickCommand}"/>

ボタンが押された時に呼び出すViewModel側のButtonClickCommand関数を呼び出すようにしています

  • Prism.xaml 10~17行目
// 10:
        <TextBox Text="{Binding InputText}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="PreviewTextInput">
                    <prism:InvokeCommandAction Command="{Binding PreviewTextInputCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>
        <TextBlock Text="{Binding OutputText}"/>
  • ポイント

    • 10行目、17行目:TextBox, TextBlockに表示するテキストをViewModelのInputText, OutputTextプロパティにバインドしています
    • 12行目:TextBoxで処理したいイベントの設定をしています(今回はPreviewTextInputイベント)
    • 13行目:イベントが発生したときに呼び出すViewModelの関数を設定しています(PreviewTextInputCommand関数が呼び出されます)
  • PrismViewModel.cs 13~25行目

// 13:
        private string inputText = "";
        public string InputText
        {
            get { return inputText; }
            set { SetProperty(ref inputText, value); }
        }

        private string outputText = "TextBoxに入力した文字をここに表示します\r\nボタンを押すと文字をすべて消去します";
        public string OutputText
        {
            get { return outputText; }
            set { SetProperty(ref outputText, value); }
        }

TextBox, TextBlockのバインド先を定義しています

Visual Studio拡張機能にPrism Template Packをインストールしていてスニペットの設定もできていれば"propp" + Tabで自動入力できます

  • PrismViewModel.cs 27~33行目
// 27:
        private DelegateCommand<RoutedEventArgs> buttonClickCommand;
        public DelegateCommand<RoutedEventArgs> ButtonClickCommand =>
            buttonClickCommand ?? (buttonClickCommand = new DelegateCommand<RoutedEventArgs>(ExecuteButtonClickCommand));

        private DelegateCommand<TextCompositionEventArgs> previewInputTextCommand;
        public DelegateCommand<TextCompositionEventArgs> PreviewTextInputCommand =>
            previewInputTextCommand ?? (previewInputTextCommand = new DelegateCommand<TextCompositionEventArgs>(ExecutePreviewInputText));

ボタンが押された時とテキストが入力された時のイベントのバインド先を定義しています

※こちらも"cmdg" + Tabで自動入力できます

  • PrismViewModel.cs 41~49行目
// 41:
        void ExecuteButtonClickCommand(RoutedEventArgs e)
        {
            OutputText = "";
        }

        void ExecutePreviewInputText(TextCompositionEventArgs e)
        {
            OutputText += e.Text;
        }
  • ポイント
    • 43行目:ボタンが押された時の処理を書いています。OutputTextはViewのTextBlockにバインドされているのでプロパティの変更と同時に表示が更新されます
    • 48行目:TextBoxに文字が入力された時の処理を書いています。入力された文字はe.Textに入っているのでそれをOutputTextに設定しています

Reactive Propertyでのイベント処理

Reactive PropertyはReactive ExtensionsをベースとしたMVVMのサポートライブラリです

Reactive Extensionsの知識があると理解しやすいですが、なくても簡単にイベントを処理することができます

github.com

NugetパッケージにReactivePropertyを追加すれば使用可能です

f:id:whitedog0215:20200315171227p:plain

Prismと同様にReactiveProperty.xamlでViewを作成し、ReactivePropertyViewModel.csでイベントの処理や入力されたテキストの制御を行います

ソースコードはこちら

  • ReactiveProperty.xaml
<UserControl x:Class="EventSample.Views.ReactiveProperty"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"
             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
             xmlns:rp="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NETCore"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <StackPanel Margin="5, 5">
        <TextBlock Text="ReactiveProperty" />
        <Button Content="ボタンです" Command="{Binding ButtonClickCommand}"/>
        <TextBox Text="{Binding InputText.Value}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="PreviewTextInput">
                    <rp:EventToReactiveCommand Command="{Binding PreviewTextInputCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>
        <TextBlock Text="{Binding OutputText.Value}"/>
    </StackPanel>
</UserControl>
  • ReactivePropertyViewModel.cs
    public class ReactivePropertyViewModel : BindableBase
    {
        public ReactivePropertySlim<string> InputText { get; } = new ReactivePropertySlim<string>("");

        public ReactiveProperty<string> OutputText { get; } = new ReactiveProperty<string>("TextBoxに入力した文字をここに表示します\r\nボタンを押すと文字をすべて消去します");

        public ReactiveCommand<RoutedEventArgs> ButtonClickCommand { get; } = new ReactiveCommand<RoutedEventArgs>();

        public ReactiveCommand<TextCompositionEventArgs> PreviewTextInputCommand { get; } = new ReactiveCommand<TextCompositionEventArgs>();
        public ReactivePropertyViewModel()
        {
            this.ButtonClickCommand.Subscribe(e =>
            {
                this.OutputText.Value = "";
            });

            this.PreviewTextInputCommand.Subscribe(e =>
            {
                this.OutputText.Value += e.Text;
            });
        }
    }

以下ポイント

  • ReactiveProperty.xaml 5行目
// 5:
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

prismと同じようにイベントの処理に使用するためのライブラリをi:のプレフィックスに割り当てています

ただし、prismと参照するライブラリが違うので注意

// 6:
xmlns:rp="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NETCore"

ReactivePropertyをrp:のプレフィックスに割り当てています(イベントの処理をバインドするときに使う)

  • ReactiveProperty.xaml 10行目
// 10:
<Button Content="ボタンです" Command="{Binding ButtonClickCommand}"/>

ボタンが押された時に呼び出すViewModel側のButtonClickCommand関数を呼び出すようにしています(Prismと同じ)

  • Prism.xaml 11~18行目
// 11:
        <TextBox Text="{Binding InputText.Value}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="PreviewTextInput">
                    <rp:EventToReactiveCommand Command="{Binding PreviewTextInputCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>
        <TextBlock Text="{Binding OutputText.Value}"/>
  • ポイント

    • 11行目、18行目:表示するテキストをViewModelのInputText, OutputTextプロパティにバインド。Prismと違って.Valueを付ける必要があるので注意
    • 13行目:TextBoxで処理したいイベントの設定をしています(Prismと同じ)
    • 14行目:イベントが発生したときに呼び出すViewModelの関数を設定しています(PreviewTextInputCommand関数が呼び出されます)
  • ReactivePropertyViewModel.cs 14~16行目

// 14:
        public ReactivePropertySlim<string> InputText { get; } = new ReactivePropertySlim<string>("");

        public ReactiveProperty<string> OutputText { get; } = new ReactiveProperty<string>("TextBoxに入力した文字をここに表示します\r\nボタンを押すと文字をすべて消去します");

TextBox, TextBlockのバインド先を定義しています

※ReactivePropertyのスニペットの設定もできていれば"rprop" + Tabで自動入力できます

  • ReactivePropertyViewModel.cs 18~20行目
// 18:
        public ReactiveCommand<RoutedEventArgs> ButtonClickCommand { get; } = new ReactiveCommand<RoutedEventArgs>();

        public ReactiveCommand<TextCompositionEventArgs> PreviewTextInputCommand { get; } = new ReactiveCommand<TextCompositionEventArgs>();

ボタンが押された時とテキストが入力された時のイベントのバインド先を定義しています

※こちらも"rcommg" + Tabで自動入力できます

  • ReactivePropertyViewModel.cs 25~33行目
// 25:
            this.ButtonClickCommand.Subscribe(e =>
            {
                this.OutputText.Value = "";
            });

            this.PreviewTextInputCommand.Subscribe(e =>
            {
                this.OutputText.Value += e.Text;
            });
  • ポイント
    • 定義したコマンドをSubScribeするとイベントが発生したときに、ラムダ式内の処理が呼び出されるようになります
    • SubScribeの中で処理をしているだけで27行目、32行目の処理はPrismとほとんど同じです

まとめ

以上、WPFのMVVMでイベントを処理する方法いろいろでした

個人的にはReactive Propertyが一番ViewModelをすっきり書ける気がするので自分はReactivePropertyをよく使っています

おわり

Self hostedのVS Codeに接続できなくなったときの対策

whitedog0215.hatenablog.jp

 前記事で作った環境を使ってiPadVS Code使って遊ぶぞー

 

と思ったらこんなエラーが出て繋がらなくなった

f:id:whitedog0215:20200227230831p:plain

 

どうやってVSCode復帰させればいいのか調べてたら公式のTroubleshootingのページにあった

docs.microsoft.com

 

セルフホスト環境が使えなくなったら、リストアを試してみてね

環境を右クリックして「Restore Local Environment」を選んでね

 

と書いてあるので

左メニューの「リモートエクスプローラー」を開いて、Self-hosted environmentの作成した環境を右クリックして「Restore Local Environment」を実行

f:id:whitedog0215:20200227231602p:plain

 

リストア実行中のダイアログが出てくるので結構長く待つ

(自分の環境では2,3分待った)

 

f:id:whitedog0215:20200227231803p:plain


これで再びVS Codeに繋がるようになりました

 

PCの電源入れるだけで環境立ち上がるようにできないかなあ...

 

Visual Studio OnlineでiPadからVSCodeを使えるようにした

iPadでコーディングをしたかったのでVS Codeを使えるようにした。

 

以下手順。

 

〇必要なもの

Microsoftのアカウント

・Azureのアカウント(無料アカウントでOK)

 

1.以下のURLを開いて「Sign in」を押してサインイン

https://online.visualstudio.com/login

 

Microsoftアカウントの名前に漢字が使われているとログインページで一生ループする現象が起こるようです。自分はローマ字表記に修正しました。

https://github.com/MicrosoftDocs/vsonline/issues/184#issuecomment-554621731

 

 2.「Create environment」を押して環境作成。設定はこんな感じ。

f:id:whitedog0215:20200226000653p:plain

次のCreate Environmentはキャンセルでおk。

 

3.VS Codeを立ち上げて拡張機能Visual Studio Online」をインストール

Ctrl+Shift+xを押して、検索してこれをInstall

f:id:whitedog0215:20200226001445p:plain

 

4.自分のPCをSelf Hostedに登録

Ctrl+Shift+pを押して、「VS Online: Register Local Environment」を選択。

f:id:whitedog0215:20200226001916p:plain

Azureのサインインを促されるのでサインインする。

プランの選択はさっき作成したプランを選択。

ワークフォルダは自PC内の適当なフォルダを選択。

環境名は適当な名前を入力(デフォルトでおk)

 

ここまでやってRegisterが終われば準備完了。

 

環境ができたので今度はiPadのほうでVisual Studio Onlineにサインイン。

 さっき作った環境が表示されている。(画質悪くてスンマセン...)

f:id:whitedog0215:20200226004133j:plain

 

「あんたのブラウザサポートされてないよ!でもすぐサポートするからね!」ってメッセージが出てるけどキニシナイ。

 

右下のハンバーガーを押して「Connect」をタップ。

f:id:whitedog0215:20200226004151p:plain

ちゃんとVSCodeの画面が表示された。しゅごい。

 

2020/2/27

PCシャットダウンしたら繋がらなくなったので、対策を追記

whitedog0215.hatenablog.jp

 

 

 

 

 

iPadをサブディスプレイ化したぞい

1,220円払ってDuet Displayを買って、iPadWindowsのサブディスプレイとして使えるようにした。

 

ja.duetdisplay.com

 

快適快適。

 

サブディスプレイを買うお金も置くスペースもないのでとても助かる。

 

f:id:whitedog0215:20200224171713j:plain

 

 

 

 

 

プライバシーポリシー

当サイトに掲載されている広告について

当サイトでは、第三者配信の広告サービス(Googleアドセンス)を利用しています。
このような広告配信事業者は、ユーザーの興味に応じた商品やサービスの広告を表示するため、当サイトや他サイトへのアクセスに関する情報 『Cookie』(氏名、住所、メール アドレス、電話番号は含まれません) を使用することがあります。
またGoogleアドセンスに関して、このプロセスの詳細やこのような情報が広告配信事業者に使用されないようにする方法については、こちらをクリックしてください。

当サイトが使用しているアクセス解析ツールについて

当サイトでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。
このGoogleアナリティクスはトラフィックデータの収集のためにCookieを使用しています。
このトラフィックデータは匿名で収集されており、個人を特定するものではありません。

この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。
この規約に関して、詳しくはこちら、またはこちらをクリックしてください。

当サイトへのコメントについて

当サイトでは、スパム・荒らしへの対応として、コメントの際に使用されたIPアドレスを記録しています。

これはブログの標準機能としてサポートされている機能で、スパム・荒らしへの対応以外にこのIPアドレスを使用することはありません。

また、メールアドレスとURLの入力に関しては、任意となっております。
全てのコメントは管理人であるjagapokoが事前にその内容を確認し、承認した上での掲載となりますことをあらかじめご了承下さい。

加えて、次の各号に掲げる内容を含むコメントは管理人の裁量によって承認せず、削除する事があります。

  • 特定の自然人または法人を誹謗し、中傷するもの。
  • 極度にわいせつな内容を含むもの。
  • 禁制品の取引に関するものや、他者を害する行為の依頼など、法律によって禁止されている物品、行為の依頼や斡旋などに関するもの。
  • その他、公序良俗に反し、または管理人によって承認すべきでないと認められるもの。

免責事項

当サイトで掲載している画像の著作権・肖像権等は各権利所有者に帰属致します。権利を侵害する目的ではございません。記事の内容や掲載画像等に問題がございましたら、各権利所有者様本人が直接メールでご連絡下さい。確認後、対応させて頂きます。

当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。

当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。

プライバシーポリシーの変更について

 当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。

運営者:jagapoko

Vue.jsでアプリを作成してFirebaseにデプロイする

最近やったので忘れないうちにメモ。環境はwindows 10です。

Node.jsのインストール

以下からインストーラーをダウンロードしてインストールする。
この記事を書いたときは10.14.1 LTSでした。

nodejs.org

Vue-cliのインストール

Node.jsのインストールが完了したらコマンドプロンプトを起動して以下を入力。

$ npm i vue-cli -g

とりあえずインストールの確認。

$ npm list --depth=0 -g
vue-cli@2.9.6

Vue.jsアプリの作成

コマンドプロンプトでどこか適当なフォルダを指定(とりあえず"D:\Vue"に)

$ D:
$ mkdir Vue
$ cd Vue

そして以下のコマンドを実行。初期設定をいろいろ聞かれますがとりあえず何も考えずに全部Enterで大丈夫そうです。

$ vue init webpack hellovue

? Project name hellovue
? Project description A Vue.js project
? Author XXXXXXX
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recommended) npm

いろいろ初期化された後、こんな感じの画面が出ると思います。指示通りにコマンドを打ってみましょう。
f:id:whitedog0215:20181203224956p:plain

$ cd hellovue
$ npm run dev

すると、http://localhost:8081でアプリケーションが動いてるよ!とメッセージが出ます。
(普通の人はhttp://localhost:8080かも?)
開いてこんなページが出れば成功です。
f:id:whitedog0215:20181203225500p:plain

Firebaseのプロジェクトを作成する

以下のページ右上からGoogleアカウントでログインしてコンソールへ移動。
firebase.google.com

新しくプロジェクトを追加する。下の画像のような感じでいいと思います。
f:id:whitedog0215:20181203230104p:plain

Firebase CLIのインストールとログイン

せっかくコンソールまで開いたところですが、コマンドプロンプトに戻ります。
以下のコマンドからFirebase CLIをインストール。

$ npm install -g firebase-tools

インストールが終わったらログイン。Googleアカウントへの許可を求めてくるので許可します。

$ firebase login

無事にログインできました。
f:id:whitedog0215:20181203230850p:plain

Vueプロジェクトのビルド

特にいじっていなければ、コマンドプロンプトのカレントのフォルダ構成は以下みたいな感じだと思います。
f:id:whitedog0215:20181203231336p:plain

この状態から、まずはデプロイするためにVueプロジェクトをビルドします。

$ npm run build

そうすると、distというフォルダが生成されると思います。
このdistフォルダをFirebaseにデプロイしていきます。

Firebaseの初期化とデプロイ

まずはFirebaseの初期化を行います。以下のコマンドを入力。

$ firebase init hosting

Are you ready to proceed? と聞かれるのでEnter。
次にプロジェクトを選択するように言われるので先ほど作成したプロジェクトを選択。
(hellovueと書かれたやつがあると思います)

そして、"What do you want to use as your public directory?"と聞かれるので
先ほどビルドしたdistフォルダを指定します。そしてEnter。f:id:whitedog0215:20181203232820p:plain

後の質問はすべてEnterで大丈夫です。
完了したら以下のコマンドでデプロイしましょう。

$ firebase deploy

ページの確認

あとはコンソールに表示されているURLにアクセスして、作成したVue.jsアプリの画面が表示されれば完了です。

デプロイされたアプリはFirebaseコンソールのHostingメニューから確認できます。
f:id:whitedog0215:20181203233450p:plain

用が済んだら以下のコマンドでアプリを停止できます。

$ firebase hosting:disable

とっても簡単