takataka430’s blog

.NET系を中心に勉強したことのまとめを書きます

【Xamarin.Forms】マテリアルデザインを使ってみた

数日前にXamarin.FormsでButtonやEntryなどのUIをマテリアルデザインにできる機能が追加されたようなので早速試してみました。

環境

Visual Studio Community 2017 for Mac
Xamarin.Forms (3.6.0.220655)

手順

まずは空のXamarin.Formsアプリを作り、Nugetパッケージ【Xamarin.Forms.Visual.Material】を各プロジェクト(共通、AndroidiOS)にインストールします。また、全てのNugetパッケージは最新にしておきます。

f:id:takataka430:20190314214130p:plain

  
次にAndroidプロジェクトのTarget FrameworkをAndroid9.0に変更します。(ここで8.1などの低いバージョンになっているとこの後のコードでエラーになります)

f:id:takataka430:20190314220840p:plain

最後にAndroidiOSの各プロジェクトのファイルにコードを追加します。

Android(MainActivity.cs)

protected override void OnCreate(Bundle savedInstanceState)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    base.OnCreate(savedInstanceState);
    global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

    //ここを追加
    global::Xamarin.Forms.FormsMaterial.Init(this, savedInstanceState);

    LoadApplication(new App());
}

iOS(AppDelegate.cs)

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();

    //ここを追加
    global::Xamarin.Forms.FormsMaterial.Init(); 

    LoadApplication(new App());
    return base.FinishedLaunching(app, options);
}

これで準備は完了です。あとはXAMLに記述するだけです。UIのVisualプロパティを「Material」とすればOKです。例えばButtonだと以下のようになります。

<Button Visual="Material"
        Text="マテリアルデザインのボタン" />

Buttonを例にしてデフォルトとマテリアルデザインの比較を実際の画面で確認しましょう。

f:id:takataka430:20190314221117p:plain:w250f:id:takataka430:20190314221008p:plain:w250
左:iOS   右:Android

iOSは結構変わりますね!

使ってみて一つ気になる点としては、マテリアルデザインにするとXAMLのプレビューでエラーが発生するようで、表示されなくなってしまうことです。うーん、これは困る。何か解決方法はないものか・・・。

といったところで今回の記事はこれまで!

参考ページ

https://blog.xamarin.com/beautiful-material-design-android-ios/

Xamarin.Forms マテリアル Visual - Xamarin | Microsoft Docs

【Xamarin.Forms】ToolbarItemを有効化・無効化する方法

以前Xamarin.Formsで画面の操作をロックする方法(ナビゲーションバー含む)という記事を書きました。その記事では画面全体をContentPageで覆ってもナビゲーションバーが隠れないのでナビゲーションバーを含めた画面全体を覆う方法を考えました。しかし、よく考えたらナビゲーションバーのボタン、つまりToolbarItemを無効化する方法もあるのでは?と思い、その方法を調べました。

結論から言いますと、ToolbarItemのCommandプロパティをバインドすることで実現することができました。コマンドを実行できる場合はボタンが有効、実行できない場合はボタンが無効になるようです。
(ちなみに、ToolbarItemはIsEnableプロパティも持っているようなので最初はこちらを使おうと思いましたが、うまくいきませんでした。)

環境

Visual Studio Community 2017 for Mac
Xamarin.Forms (3.6.0.220655)

コード

画面は次の通りです。コードビハインドはInitializeComponent()のみの記載です。ToolbarItemのCommandプロパティをバインドします。

MainPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_Overlay" 
             x:Class="XF_Overlay.MainPage">
    <ContentPage.BindingContext>
        <local:MainPageViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Test"
                     Command="{Binding TBCommand}"/>  <!-- ここでバインド -->
    </ContentPage.ToolbarItems>
    <AbsoluteLayout>
        <StackLayout AbsoluteLayout.LayoutFlags="All"
                     AbsoluteLayout.LayoutBounds="0,0,1,1"
                     VerticalOptions="Center">
            <Button Command="{Binding OverlayClicked}"
                    Text="OverlayVer2"/>
        </StackLayout>
        <ContentView x:Name="bglayer"
                     BackgroundColor="Black"
                     Opacity="0.4"
                     IsVisible="{Binding IsBusy}"
                     AbsoluteLayout.LayoutFlags="All"
                     AbsoluteLayout.LayoutBounds="0,0,1,1"/>
            <Frame  x:Name="frame"
                    IsVisible="{Binding IsBusy}"
                    AbsoluteLayout.LayoutFlags="PositionProportional"
                    AbsoluteLayout.LayoutBounds="0.5,0.5,AutoSize,AutoSize">
                <StackLayout>
                    <ActivityIndicator  Color="Black"
                                        IsRunning="true"/>
                    <Label Text="処理中です" />
                </StackLayout>
            </Frame>
    </AbsoluteLayout>
</ContentPage>

  
ビューモデルは次のようになります。有効・無効の値を入れるためにIsEnableプロパティを作成します。Commandの第2引数であるChangeCanExecuteにIsEnableプロパティを指定し、このプロパティの値を変更するたびにコマンドの実行可否を再評価します。前述の通りコマンドが実行可能な場合はボタンが有効に、実行不可能な場合はボタンが無効になるようなので、それを利用しています。

MainPageViewModel.cs

using System.ComponentModel;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace XF_Overlay
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public MainPageViewModel()
        {
            IsEnable = true;
            IsBusy = false;

            OverlayClicked = new Command(async() =>
            {
                IsEnable = false;
                IsBusy = true;
                TBCommand.ChangeCanExecute();  //コマンドの実行可否を再評価
                await Task.Delay(2000);
                IsEnable = true;
                IsBusy = false;
                TBCommand.ChangeCanExecute();  //コマンドの実行可否を再評価
            });

            //コマンドの実行可否をIsEnableプロパティによって決定
            TBCommand = new Command( () =>
            {

            }, ()=> IsEnable);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public Command OverlayClicked { get; }
        public Command TBCommand { get; set; }

        public bool IsEnable { get; set; }

        private bool isBusy;
        public bool IsBusy
        {
            get { return isBusy; }
            set
            {
                if(isBusy != value)
                {
                    isBusy = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsBusy)));
                }
            }
        }
    }
}

動きは下のような感じです。

f:id:takataka430:20190307214501g:plain:w200

処理中は右上のTestボタンが押せなくなっていますね!

参考にしたサイト

ToolbarItem を Disable 状態にする方法 | Xamarin.Forms - ITブログ時々なんでもブログ

Xamarin.Forms の Command でも CanExecute でボタンを制御 - Xamarin 日本語情報

【Xamarin.Forms】データバインディングとMVVMを学ぶ

今回の記事ではデータバインディングによってLabelやEntryの表示・非表示を行い、さらにMVVMっぽくするところまでをやってみたいと思います。題材として以下のような動きをするアプリを考えてみることにします。

f:id:takataka430:20190302231209g:plain:w200

初めはユーザーIDとパスワードを入力する入力欄2つとログインボタン1つを表示しておき、ボタンを押すとログアウトするボタンのみが表示されます。これをクリックするごとに切り替えることとします。

データバインディング無しでコードビハインドに書く

まずは特に何も考えずにコードビハインドに記述してみましょう。

  
MainPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_MVVM"
             x:Class="XF_MVVM.MainPage">
    <StackLayout VerticalOptions="Center">
        <Entry x:Name="entry1" 
               Placeholder="ユーザーIDを入力してください"/>
        <Entry x:Name="entry2"
               Placeholder="パスワードを入力してください"/>
        <Button x:Name="button1"
                Text="ログインする"
                Clicked="Handle_Clicked"/>
        <Button x:Name="button2"
                Text="ログアウトする"
                Clicked="Handle_Clicked"
                IsVisible="false"/>
    </StackLayout>
</ContentPage>

  

MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace XF_MVVM
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            entry1.IsVisible = !entry1.IsVisible;
            entry2.IsVisible = !entry2.IsVisible;
            button1.IsVisible = !button1.IsVisible;
            button2.IsVisible = !button2.IsVisible;
        }
    }
}

ボタンを押すごとにLabel、EntryのIsVisibleプロパティを否定します(Trueの場合はFalseに、Falseの場合はTrueにします)。一応これでも目的の動きを達成していますが、Handle_Clicked内の処理を見ると各要素に同じような処理をしていることがわかります。entry1、entry2、button1は常に同じ値になるので共通の値を参照するような形にしたほうが良さそうです。そこでデータバインディングを活用します。

データバインディングを設定する

データバインディングとは2つのオブジェクト間において、一方のプロパティが変更されたらもう一方のプロパティも変更する仕組みのようです。詳しくは以下をご覧ください。

docs.microsoft.com

データバインディングではソースターゲットの二つの役割があります。基本的にはソースのプロパティの値が変更されたらターゲットのプロパティの値も自動的に変更されるようです。

先ほどのコードをデータバインディングするように変更してみます。

MainPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_MVVM"
             x:Class="XF_MVVM.MainPage">
    <StackLayout VerticalOptions="Center">
        <Entry Placeholder="ユーザーIDを入力してください"
               IsVisible="{Binding IsLogin}"/>  <!-- 追加 -->
        <Entry Placeholder="パスワードを入力してください"
               IsVisible="{Binding IsLogin}"/>  <!-- 追加 -->
        <Button Text="ログインする"
                Clicked="Handle_Clicked"
                IsVisible="{Binding IsLogin}"/>  <!-- 追加 -->
        <Button Text="ログアウトする"
                Clicked="Handle_Clicked"
                IsVisible="{Binding IsLogout}"/>  <!-- 追加 -->
    </StackLayout>
</ContentPage>

「追加」となっている場所ではターゲットを指定しています。それぞれのIsVisibleプロパティが、ソースIsLoginまたhIsLogoutプロパティと同期するようにしています。
次にコードビハインドを見てみましょう。   

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace XF_MVVM
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            
            //ソースの指定
            BindingContext = this;
        }

        public bool IsLogin { get; set; } = true;

        public bool IsLogout { get; set; } = false;

        //ボタンを押すと各プロパティの値が否定される
        void Handle_Clicked(object sender, EventArgs e)
        {
            IsLogin = !IsLogin;
            IsLogout = !IsLogout;
        }
    }
}

BindingContextによってソースとなるオブジェクトの指定を行っています。
以上をまとめると、2つのEntry要素と「ログインする」LabelはIsLoginプロパティを基に、「ログアウトする」LabelはIsLogoutプロパティを基に値が決まるという設定をしました。
動きを確認してみましょう。

f:id:takataka430:20190304002514g:plain:w200

ボタンを押しても何も変化しません。しかしVisualStudioで確認するとIsLoginIsLogoutそれぞれのプロパティはボタンを押すと値が変化します。
なぜこうなるかといいますと、今回の場合、ソースの値が変化してもターゲットに通知されないためです。そのため変更を通知するためにソースにINotifyPropertyChangedを実装する必要があります。

INotifyPropertyChangedの実装

実装すると以下のようになります。(MainPage.xamlは変更なしのため省略)

MainPage.xaml.cs

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XF_MVVM
{
    public partial class MainPage : ContentPage
    {
        Control control = new Control();

        public MainPage()
        {
            InitializeComponent();
            BindingContext = control;
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            control.IsLogin = !control.IsLogin;
            control.IsLogout = !control.IsLogout;
        }
    }

    public class Control : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private bool isLogin = true;
        public bool IsLogin
        {
            get { return isLogin; }
            set
            {
                isLogin = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogin)));
            }
        }

        private bool isLogout;
        public bool IsLogout
        {
            get { return this.isLogout; }
            set
            {
                isLogout = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogout)));
            }
        }
    }
}

INotifyPropertyChangedを継承したControlクラスを新しく作ります。
  
※INotifyPropertyChangedについて詳しくは以下をご覧ください。

docs.microsoft.com

MainPage.xaml.csクラスでControlクラスのインスタンスを作り、このインスタンスをソースに指定します。こうすれば変更が通知されるはずです。

f:id:takataka430:20190304004022g:plain:w200

うまく動きました!

MVVMに書き換えてみる

以上のようなコードでも良さそうですが、ビューのファイルに画面と画面を操作するコードが混在して書かれているので、これを分離してみたいと思います。   

MainPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_MVVM"
             x:Class="XF_MVVM.MainPage">
    <StackLayout VerticalOptions="Center">
        <Entry Placeholder="ユーザーIDを入力してください"
               IsVisible="{Binding IsLogin}"/>
        <Entry Placeholder="パスワードを入力してください"
               IsVisible="{Binding IsLogin}"/>
        <Button Text="ログインする"
                IsVisible="{Binding IsLogin}"
                Command="{Binding LoginLogout}"/>  <!-- 追加 -->
        <Button Text="ログアウトする"
                IsVisible="{Binding IsLogout}"
                Command="{Binding LoginLogout}"/>  <!-- 追加 -->
    </StackLayout>
</ContentPage>

クリック時の処理をコードビハインド以外に記述する場合はClickedが使えないので代わりにCommandを使います。
  
*Commandについて詳しくは以下をご覧ください docs.microsoft.com   

  

MainPage.xaml.cs

using Xamarin.Forms;

namespace XF_MVVM
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            BindingContext = new MainPageViewModel();
        }
    }
}

  

MainPageViewModel.cs

using System.ComponentModel;
using Xamarin.Forms;

namespace XF_MVVM
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private bool isLogin = true;
        public bool IsLogin
        {
            get { return isLogin; }
            set
            {
                isLogin = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogin)));
            }
        }

        private bool isLogout;
        public bool IsLogout
        {
            get { return this.isLogout; }
            set
            {
                isLogout = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogout)));
            }
        }

        public Command LoginLogout { get; }

        public MainPageViewModel()
        {
            LoginLogout = new Command(() =>
            {
                IsLogin = !IsLogin;
                IsLogout = !IsLogout;
            });
        }
    }
}

コードビハインドに書いていたControlクラスに変わってMainPageViewModelクラスを新しく作ります。
そしてMainPage.xaml.csMainPageViewModelクラスをBindingContextに指定すれば完了です。これによってコードビハインド(MainPage.xaml.cs)がスッキリしましたね。

まとめ

以上のように役割によってクラスを分けると理解しやすいコードになるのではないかと思いました。今まではコードビハインドにほとんどのコードを書いていたのですが、ファイルを分けた方が自分の頭が混乱しなくていいですね。

参考サイト

今さら入門するMVVMに必要な技術要素(Xamarin.Forms & UWP) - かずきのBlog@hatena

Xamarin.Forms のデータ バインディング - Xamarin | Microsoft Docs

【Xamarin.Forms】ページ間での値の受け渡し方法を考えてみた(コードビハインド編)

最終更新:2019年3月19日

Xamarin.Formsでページ間で値を受け渡しするいい方法がないかと色々考えてみました。今回は次のような簡単な状況を想定します。

最初のページ(MainPage)でEntryに値を入力する

次のページ(SecondPage)で、最初のページのEntryに入力した値をLabelに表示する
  
考えた方法は以下の3通りです。MainPageからSecondPageのどこに値を渡すかで場合分けしています。

1.コンストラクタに値を渡す
2.プロパティに値を渡す
3.メソッドに値を渡す
4.遷移先のページのBindingContextに渡す(2019/3/19 追記)

なお、1~3の画面は共通なので先に掲載します。

MainPage.xaml(値を渡すページ)

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_NavigationPage" 
             x:Class="XF_NavigationPage.MainPage">
    <StackLayout VerticalOptions="Center">
        <Entry x:Name="entry"
               HorizontalOptions="FillAndExpand" />
        <Button x:Name="button"
               Text="To Second Page"
               HorizontalOptions="Center"
               Clicked="Handle_Clicked" />
    </StackLayout>
</ContentPage>

     

SecondPage.xaml(値を受け取るページ)

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="XF_NavigationPage.SecondPage">
    <ContentPage.Content>
        <StackLayout VerticalOptions="Center">
            <Label Text="前のページから受け取った値"
                   HorizontalOptions="Center"/>
            <Label x:Name="label"
                   HorizontalOptions="Center"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

1.コンストラクタに値を渡す

コードビハインドに以下のように記述します。
  
MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace XF_NavigationPage
{
    public partial class MainPage : ContentPage
    {

        public MainPage()
        {
            InitializeComponent();
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            var name = entry.Text;

            Navigation.PushAsync(new SecondPage(name));
        }
    }
}

  
SecondPage.xaml.cs

using Xamarin.Forms;

namespace XF_NavigationPage
{
    public partial class SecondPage : ContentPage
    {
        public SecondPage(string name)
        {
            InitializeComponent();

            label.Text = name;
        }
    }
}

NavigationPageでSecondPageのインスタンスを作る時にコンストラクタに値を渡しています。
それでは動きを見てみましょう。

f:id:takataka430:20190227224120g:plain:w200

うまくいっていますね!・・がこの方法では困ることがあります。SecondPageのXAMLのプレビューがエラーになるのです。

f:id:takataka430:20190227224330p:plain:w200

これでは画面を作る時に不便なので、この方法はやめたほうが良さそうです。

2.プロパティに値を渡す

それならSecondPageにプロパティを定義して、インスタンスを作る時にプロパティに値を渡せば良いのでは?と考えました。

MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace XF_NavigationPage
{
    public partial class MainPage : ContentPage
    {

        public MainPage()
        {
            InitializeComponent();
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            var name = entry.Text;

            //SecondPageクラスのNameプロパティに値を渡す
            Navigation.PushAsync(new SecondPage {Name=name});
        }
    }
}

  
SecondPage.xaml.cs

using Xamarin.Forms;

namespace XF_NavigationPage
{
    public partial class SecondPage : ContentPage
    {
        //コンストラクタ
        public SecondPage()
        {
            InitializeComponent();
        }

        //画面が表示される時に実行
        protected override void OnAppearing()
        {
            NameSet(Name);
        }
        
        //プロパティを追加
        public string Name { get; set; }

        //ラベルのテキストを設定するメソッドを追加
        public void NameSet(string name)
        {
            label.Text = name;
        }
    }
}

最初はOnAppearingなしで、コンストラクタにNameSetメソッドを入れていましたが、それでは値を受け取れませんでした。この対策のためOnAppearingを使っています。
それでは動きを確認してみましょう。

f:id:takataka430:20190227230014g:plain:w200

一応、値は受け渡しできていますが、SecondPageで受け取った値が遅れて表示されるのでちょっと微妙ですね。

3.メソッドに値を渡す

それならメソッドに直接値を渡してしまえばいいのでは?と思ってコードを以下のようにしてみました。

MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace XF_NavigationPage
{
    public partial class MainPage : ContentPage
    {

        public MainPage()
        {
            InitializeComponent();
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            var name = entry.Text;

            var secondPage = new SecondPage();
            Navigation.PushAsync(secondPage);
            secondPage.NameSet(name);
        }
    }
}

  
SecondPage.xaml.cs

using Xamarin.Forms;

namespace XF_NavigationPage
{
    public partial class SecondPage : ContentPage
    {
        public SecondPage()
        {
            InitializeComponent();
        }

        public void NameSet(string name)
        {
            label.Text = name;
        }
    }
}

先ほどよりもコードがスッキリしました。動きを確認してみましょう。
  
f:id:takataka430:20190227230923g:plain:w200

いい感じです。今回行った方法の中で一番良さそうです。   

4.遷移先のページのBindingContextに渡す(2019/3/19 追記)

以下のようにSecondPageのBindingContextにMainPageのentryを渡し、SecondPageのLabelにバインドする方法でもできました。

MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace XF_NavigationPage
{
    public partial class MainPage : ContentPage
    {

        public MainPage()
        {
            InitializeComponent();
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            Navigation.PushAsync(new SecondPage()
            {
                BindingContext = entry
            });
        }
    }
}

  
SeconaPage.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="XF_NavigationPage.SecondPage">
    <ContentPage.Content>
        <StackLayout VerticalOptions="Center">
            <Label Text="前のページから受け取った値"
                   HorizontalOptions="Center"/>
            <Label Text="{Binding Text}"
                   HorizontalOptions="Center"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

動きは3と同じだったので省略します。

まとめ

今回のような簡単な場合はコードビハインドに書くだけでいいのかもしれませんが、やはりしっかりやるならMVVMを使ってやった方がいいのではないかと思いました。それについては調べてみようと思います。

【Xamarin.Forms】ProgressBarを使い、繰り返し処理の進捗を表示してみた

モバイルアプリで何か処理をするために数十秒くらいの時間がかかる場合、ユーザーからするといつ終わるのか目安みたいなものが欲しいですよね?このような場合にどうすればいいのか調べていたところ、Xamarin.Formsには「ProgressBar」というものがあることを知りました。

ProgressBarとは

進捗を表すコントローラのことです。このクラスはProgressというプロパティを持っており、ここに値(最大値は1)を入れることによって進捗バーの色の領域が変わります。例えば、Progressプロパティが0.4の場合は以下の画像のようになります。

f:id:takataka430:20190221203806p:plain:w200

画像の通り、左から40%が色付きの領域になっていることがわかります。
詳しくは以下のドキュメントをご覧ください。

docs.microsoft.com

繰り返し処理の進捗をProgressBarで表示してみた

それでは本題です。今回はコレクションから要素を一つずつ取り出し、何らかの処理を繰り返す場合を考えてみました。コードは以下の通りです。

画面

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_ProgressBar" 
             x:Class="XF_ProgressBar.MainPage">
    <StackLayout VerticalOptions="Center">
        <Button x:Name="button"
                Text="Button"
                Clicked="Handle_Clicked"/>
        <ProgressBar x:Name="progress"/>
        <Label x:Name="label"
               HorizontalOptions="Center"/>
    </StackLayout>
</ContentPage>

  
コードビハインド

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace XF_ProgressBar
{
    public partial class MainPage : ContentPage
    {
        //1が1000個あるコレクション
        IEnumerable<int> numbers = Enumerable.Repeat(1, 1000);

        public MainPage()
        {
            InitializeComponent();
        }

        async void Handle_Clicked(object sender, EventArgs e)
        {
            label.Text = "";
            int sum = 0;
            var amount = numbers.Count();    //コレクションの要素数
            var operated = 0;                //処理済みの回数を初期化
            foreach(var number in numbers) 
            {
                sum = sum + number;
                operated += 1;
                progress.Progress = (double)operated / amount;  //ここで進捗率を計算
                await Task.Delay(1);                            //一瞬で終わってしまうので見やすいように1ミリ秒待つようにしています
                label.Text = sum.ToString();
            }
        }
    }
}

1が1000個入っているコレクションから値を一つずつ取り出し加算するという処理を行い、ProgressBarで進捗を表しています。また、加算の結果は随時ラベルで表示するようにしています。
実行すると以下のようになります。

f:id:takataka430:20190221203709g:plain:w200

いい感じですね!
今回行った加算処理を他の処理に置き換えれば色々な進捗の表示に使えるのではないかと思います。

【Xamarin.Forms】Behaviorを使って入力値検証(その2:ボタンと連動させる)

前回の記事ではEntryの文字の色を変えるところまでやりました。今回はそれに加えてボタンと連携し、条件を満たす文字列の場合にのみボタンが有効になるようにしてみます。
  
目標は「Entryに入力した文字列がhttps://で始まれば文字は黒、ボタンは有効化される。そうでない場合は文字は赤、ボタンは有効化されない」です。
  
これを実現するため、Xamarin.FormsのMessagingCenterという機能を使いました。

docs.microsoft.com

MessagingCenterとは、簡単に言いますとメッセージの送受信ができる機能です。Sendでメッセージを送信し、Subscribeでメッセージを受信して処理を行います。

では実際にどのようにコードを書いたのか見てみましょう。(前回の記事からの変更部分のみコメントをしています)   
  
UrlValidationBehavior.cs

using System;
using Xamarin.Forms;

namespace XF_EntryCheck
{
    public class UrlValidationBehavior : Behavior<Entry>
    {
        //設定した条件を満たすかどうか判定
        public bool IsValid { get; set; }

        protected override void OnAttachedTo(Entry bindable)
        {
            bindable.TextChanged += OnEntryTextChanged;
            base.OnAttachedTo(bindable);
        }

        protected override void OnDetachingFrom(Entry bindable)
        {
            bindable.TextChanged -= OnEntryTextChanged;
            base.OnDetachingFrom(bindable);
        }

        void OnEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            var m = args.NewTextValue.StartsWith("https://", StringComparison.Ordinal);

            ((Entry)sender).TextColor = m ? Color.Default : Color.Red;

            //判定結果を代入
            IsValid = m;

            //メッセージの送信
            MessagingCenter.Send(this, "IsValid");
        }
    }
}

  

MainPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_EntryCheck" 
             x:Class="XF_EntryCheck.MainPage">
    <StackLayout VerticalOptions="Center">
        <Entry x:Name="entry"
               Placeholder="Entry">
            <Entry.Behaviors>
                <local:UrlValidationBehavior x:Name="urlValidationBehavior"/>
            </Entry.Behaviors>
        </Entry>
        
        <!-- ボタンを追加 -->
        <Button x:Name="button"    
                Text="Button"
                HorizontalOptions="Center" />
        
    </StackLayout>
</ContentPage>

  
MainPage.xaml.cs

using Xamarin.Forms;

namespace XF_EntryCheck
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            //メッセージの受信
            MessagingCenter.Subscribe<UrlValidationBehavior>(this, "IsValid", (sender) =>
            {
                EntryChanged();
            });
        }

        //ボタンの有効・無効を判定
        void EntryChanged()
        {
            button.IsEnabled = urlValidationBehavior.IsValid;
        }
    }
}

UrlValidationBehavior.csOnEntryTextChangedイベントが実行された時にメッセージを送信し、MainPageクラスがメッセージを受信後、EntryChanged()を実行するという動きになっています。

それでは早速動かしてみましょう。
目標は「Entryに入力した文字列がhttps://で始まれば文字は黒、ボタンは有効化される。そうでない場合は文字は赤、ボタンは有効化されない」です。

f:id:takataka430:20190216203054g:plain:w250

実現したかった動作は出来ていますね!

参考にしたページ

Xamarin.Forms の MessagingCenter - Xamarin | Microsoft Docs
Xamarin.Formsでビヘイビアーを使用するには? - Build Insider

【Xamarin.Forms】Behaviorを使って入力値検証(その1:文字列の色を変える)

Xamarin.FormsでEntryに文字列を入力する時に入力した値によって色を変えたり下にあるボタンを有効化・無効化するという動きを実現したいと思い、方法を調べました。今回の記事は「条件によってEntryの文字列の色を変える」というのをやってみたいと思います。

やり方

これを実現するにはXamarin.FormsのBehaviorという機能を使えばできるようです。

docs.microsoft.com

早速実装してみましょう。

まずはBehaviorクラスを継承したUrlValidationBehaviorクラスを作ります。

using System;
using Xamarin.Forms;

namespace XF_EntryCheck
{
    public class UrlValidationBehavior : Behavior<Entry>
    {
       //ビヘイビアを追加
        protected override void OnAttachedTo(Entry bindable)
        {
            bindable.TextChanged += OnEntryTextChanged;
            base.OnAttachedTo(bindable);
        }

        //ビヘイビアを削除
        protected override void OnDetachingFrom(Entry bindable)
        {
            bindable.TextChanged -= OnEntryTextChanged;
            base.OnDetachingFrom(bindable);
        }

        //Entryに入力された値が変わった時の動作
        void OnEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            //「https://」で始まる文字列かどうかを判定
            var m = args.NewTextValue.StartsWith("https://", StringComparison.Ordinal);

            //mがtrueなら文字はデフォルトの色、falseなら赤色に変更
            ((Entry)sender).TextColor = m ? Color.Default : Color.Red;
        }
    }
}

コメントにあるように、Entryに入力した文字列が「https://」で始まっていれば文字の色が黒に、そうでない場合は赤に変更する動きになります。

次に画面を作ります。

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:XF_EntryCheck" 
             x:Class="XF_EntryCheck.MainPage">
    <StackLayout VerticalOptions="Center">
        <Entry x:Name="entry">

            <!-- このEntryに「UrlValidationBehavior」をアタッチする -->
            <Entry.Behaviors>
                <local:UrlValidationBehavior />
            </Entry.Behaviors>

        </Entry>
    </StackLayout>
</ContentPage>

これでEntryに入力した値は先ほど作ったUrlValidationBehaviorクラスで定義した通り、「https://」で始まっているかどうかによって文字の色が変わります。

動作確認

f:id:takataka430:20190214205151p:plain:w250    f:id:takataka430:20190214205148p:plain:w250

ちゃんと色が変わっていますね!

次にButtonと連携して、条件を満たしている場合のみButtonが有効化されるようにしたいと思います。・・・が、続きは次回にしたいと思います。 (以下、次回のリンクです)

takataka430.hatenablog.com

参考ページ

Xamarin.Forms のビヘイビアー - Xamarin | Microsoft Docs
Xamarin.Formsでビヘイビアーを使用するには? - Build Insider