A certain engineer "COMPLEX"

Xamarinメモ その7 Timerを使ってみる

前回はMVVMを使ってみました。

Problem


Xamlを使ってみたので、色々簡単にできると思います。
なので、Timerを使ってストップウォッチを作ってみました。

ソースは
https://github.com/takuya-takeuchi/Demo/tree/master/Xamarin.Forms.Portable2です。

この手の話題はエクセルソフトの田淵様JXUGC #9 Xamarin.Forms Mvvm 実装方法 Teachathon を開催しましたで実施済みですが、あえて自分でやってみます。

課題はTimerをどうするか、ですが普通にXamarinのページにXamarin.Forms.Device.StartTimerがありました。
けど、WinFormsやWPFでお世話になったSystem.Timers.Timerとかと違います。
インスタンスを作ってそこで管理するのではなく、Staticメソッドにコールバックを渡して、指定した間隔で処理をするようです。

タイマーを停止する方法は

While the callback returns true, the timer will keep recurring.

訳:コールバックがtrueを返す間は、タイマーは繰り返し発生し続けます。

とあるので、第二引数のコールバックで不要になったらfalseを返せば良いだけのようです。

Resolution


Timerさえわかれば、あとはちょいちょい、って感じかと思ったそうでもありませんでした。
Xamlで作れるし、WPF、UWPとクラスが似ているので直感的に作れますが、細かいところが違います。
まず、WidthやHeightがReadOnlyなので、要素をおおまかなおおきさにして配置するのが面倒です。
あと、Xamlのインテリセンスが少し残念です。プロパティ要素構文でインテリセンスの推測が効かないので、最初は焦りました。
最もきついのは、Xaml構文でエラーになってもビルドが通ることです。
最初、間違えてResoueceプロパティの子要素に、下記のようにリソース要素を追加していました。


<ContentPage.Resources>
<converters:BooleanInvertConverter x:Key="BooleanInvertConverter" />
</ContentPage.Resources>

でも、これでビルドが通って、長い時間かけて起動したiPhone Simulatorで実行時エラー、となると悲しいです。
正しくは当然下記のよう。


<ContentPage.Resources>
<ResourceDictionary>
<converters:BooleanInvertConverter x:Key="BooleanInvertConverter" />
</ResourceDictionary>
</ContentPage.Resources>

これは改善して欲しいです。

辛いのはこれくらいですが、あとは普通です。WPFやUWPがきちんと使えればどうとでもなります。
VisiblityプロパティがIsVisibleでbool型なのはWinFormsを思い出して、(´ω`)ホッコリしました。

最終的に、

MainPage.xaml


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="Xamarin.Forms.Portable2.Views.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:Xamarin.Forms.Portable2.Converters;assembly=Xamarin.Forms.Portable2"
xmlns:viewModels="clr-namespace:Xamarin.Forms.Portable2.ViewModels;assembly=Xamarin.Forms.Portable2">
<ContentPage.BindingContext>
<viewModels:MainPageViewModel />
</ContentPage.BindingContext>

<ContentPage.Resources>
<ResourceDictionary>
<converters:BooleanInvertConverter x:Key="BooleanInvertConverter" />
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="48" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>

<Grid VerticalOptions="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0"
Grid.Column="1"
Style="{StaticResource LabelStyle}"
Text="{Binding Minute}" />
<Label Grid.Row="0"
Grid.Column="2"
Style="{StaticResource LabelStyle}"
Text=":" />
<Label Grid.Row="0"
Grid.Column="3"
Style="{StaticResource LabelStyle}"
Text="{Binding Second}" />
<Label Grid.Row="0"
Grid.Column="4"
Style="{StaticResource LabelStyle}"
Text="." />
<Label Grid.Row="0"
Grid.Column="5"
Style="{StaticResource LabelStyle}"
Text="{Binding Millisecond}" />
<Button Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="5"
Command="{Binding StartCommand}"
HorizontalOptions="Center"
IsVisible="{Binding IsRunning,
Converter={StaticResource BooleanInvertConverter}}"
Text="Start"
VerticalOptions="Center" />
<Button Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="5"
Command="{Binding StopCommand}"
HorizontalOptions="Center"
IsVisible="{Binding IsRunning}"
Text="Stop"
VerticalOptions="Center" />
</Grid>
</ContentPage>

MainPageViewModel.cs


using System;
using System.Windows.Input;
using Microsoft.Practices.Prism.Mvvm;

namespace Xamarin.Forms.Portable2.ViewModels
{
public sealed class MainPageViewModel : BindableBase
{

public MainPageViewModel()
{
this.IsRunning = false;
this.TimeSpan = new TimeSpan(0, 0, 0, 0, 0);
}

private TimeSpan _TimeSpan;

public TimeSpan TimeSpan
{
get
{
return this._TimeSpan;
}
private set
{

this.Minute = this.TimeSpan.Minutes.ToString("00");
this.Second = this.TimeSpan.Seconds.ToString("00");

var ms = this.TimeSpan.Milliseconds / 10;
this.Millisecond = ms.ToString("00");
this.SetProperty(ref this._TimeSpan, value);
}
}

private string _Minute;

public string Minute
{
get
{
return this._Minute;
}
private set
{
this.SetProperty(ref this._Minute, value);
}
}

private string _Second;

public string Second
{
get
{
return this._Second;
}
private set
{
this.SetProperty(ref this._Second, value);
}
}

private string _Millisecond;

public string Millisecond
{
get
{
return this._Millisecond;
}
private set
{
this.SetProperty(ref this._Millisecond, value);
}
}

private bool _IsRunning = false;

public bool IsRunning
{
get
{
return this._IsRunning;
}
private set
{
this.SetProperty(ref this._IsRunning, value);
}
}

private ICommand _StartCommand;

public ICommand StartCommand
{
get
{
if (this._StartCommand == null)
{
this._StartCommand = new Command(() =>
{
this.IsRunning = true;

Device.StartTimer(new TimeSpan(0, 0, 0, 0, 10),
() =>
{
this.TimeSpan = this.TimeSpan.Add(new TimeSpan(0, 0, 0, 0, 10));
return this.IsRunning;
});
});
}

return this._StartCommand;
}
}

private ICommand _StopCommand;

public ICommand StopCommand
{
get
{
if (this._StopCommand == null)
{
this._StopCommand = new Command(() =>
{
this.IsRunning = false;
});
}

return this._StopCommand;
}
}

}

}

になりました、
BooleanInvertConverterとかは省略します。簡単すぎますので。
出来上がった結果がこれ。

どこかでみたようなストップウォッチ

シンプルなストップウォッチ...

Startを押せば、Stopボタンに変わり、経過時間が変化します。
が、このストップウォッチ問題があって、非常に精度が悪いです。10ms間隔がまずいのか非常に遅い。
ひょっとしたらTimerは別のAPIがあるのかもしれません。
ネイティブのAPIを直接呼ぶ方がよいか?

Conclusion


実用度と精度はともかくアプリと呼べるアプリを作ってみました。
XAMLでの開発経験があるならこれほど取っつきやすいものはないと思います。

Source Code


https://github.com/takuya-takeuchi/Demo/tree/master/Xamarin.Forms.Portable2

コメントを残す

メールアドレスが公開されることはありません。

%d人のブロガーが「いいね」をつけました。