Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on Android編)

Xamarin.Forms でドラッグを実装しよう(のうりん編)
http://www.moonmile.net/blog/archives/7653
Xamarin.Forms でドラッグを実装しよう(Android編)
http://www.moonmile.net/blog/archives/7667

の続きで、前回の Xamarin.Android を Xamarin.Forms で動くようにします。
Android だけの場合であれば Xamarin.Android で閉じてしまえばよいのですが、iOS も同時に動かしたいのと、View に XAML の構文を使いたい(最終的には MVVM パターンに落とし込みたい)ので、Xamarin.Forms を使います。
ただし、ここでは Android の Touch イベントを持ってくるだけなので iOS では動きません…が、インターフェースは揃えておきましょう。

本来ならば、

Xamarin.Formsでタッチイベントを処理するには?(iOS/Androidの各種ジェスチャー対応)
http://www.buildinsider.net/mobile/xamarintips/0035

にあるように GestureDetector.SimpleOnGestureListener のスタイルを取りたいところなのですが、なぜかタップ位置が消えてしまっているので自作をします。このリスナークラスには GestureDetector.IOnGestureListener というインターフェースが用意されているので、頑張ればこのスタイルに沿って自作できないこともないのですが…どうも、なんか回りくどいので、別な方式をとります。
Xamarin.Forms の場合、PCL にあるコントロールと、それぞれのプラットフォームにあるコントロールのレンダラーがワンセットになっているので、直接レンダラーから PCL のコントロールにコールバックしてしまいます。こうすると無駄な Listener クラスが無くなって構造が簡単になります。まあ、Listener パターンは汎用性を広げるために使っているので、場合によりけりということです。

サンプルコード

サンプルコードは、https://github.com/moonmile/BoxDrag にあります。
BoxDragXF フォルダが Xamarin.Forms で作ったテストアプリ一式になります。

PCL に MainPage.xaml を作る

共通化される PCL プロジェクトに MainPage.xaml を追加します。

<?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?>
<ContentPage xmlns=&quot;http://xamarin.com/schemas/2014/forms&quot;
             xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
             xmlns:local=&quot;clr-namespace:BoxDragXF;assembly:BoxDragXF&quot;
             x:Class=&quot;BoxDragXF.MainPage&quot;>
  <StackLayout>
    <Button Text=&quot;初期化&quot; x:Name=&quot;button1&quot; ></Button>
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width=&quot;1*&quot; />
        <ColumnDefinition Width=&quot;1*&quot; />
        <ColumnDefinition Width=&quot;1*&quot; />
        <ColumnDefinition Width=&quot;1*&quot; />
      </Grid.ColumnDefinitions>
      <Button Text=&quot;上&quot; x:Name=&quot;buttonUp&quot;></Button>
      <Button Text=&quot;下&quot; x:Name=&quot;buttonDown&quot; Grid.Column=&quot;1&quot;></Button>
      <Button Text=&quot;左&quot; x:Name=&quot;buttonLeft&quot; Grid.Column=&quot;2&quot;></Button>
      <Button Text=&quot;右&quot; x:Name=&quot;buttonRight&quot; Grid.Column=&quot;3&quot;></Button>
    </Grid>
    <AbsoluteLayout
      x:Name=&quot;layout&quot;
      BackgroundColor=&quot;Blue&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalOptions=&quot;FillAndExpand&quot;>
      <local:BoxViewEx
        BackgroundColor=&quot;Red&quot;
        x:Name=&quot;box1&quot; WidthRequest=&quot;60&quot; HeightRequest=&quot;60&quot; AbsoluteLayout.LayoutBounds=&quot;100,100,60,60&quot; />
    </AbsoluteLayout>
  </StackLayout>
</ContentPage>

ドラッグ対象のコントロールは BoxView を継承した BoxViewEx コントロールを使います。このカスタムコントロールを参照するために、”clr-namespace:BoxDragXF;assembly:BoxDragXF” でロードできるようにします。

最初の BoxViewEx クラスは BoxView を継承しただけの空っぽのクラスです。

public class BoxViewEx : BoxView
{
}

Android プロジェクトにレンダラーを追加する

BoxViewEx コントロールに対応するレンダラーを追加します。レンダラーは各プラットフォームの描画をすると同時に、各プラットフォーム特有の動作を記述できます。

最初の BoxExRenderer クラスは、こんな感じになります。


 [assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.Droid
{
    class BoxExRenderer : BoxRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
        }
    }
}

ExportRenderer 属性は、PCL 側にあるコントロールの本体とレンダラーを結び付ける属性で記述が必要です。
この時点で、一度ビルドと実行をして、エラーが無いようにしておきます。

  • XAML の local:BoxViewEx の記述
  • BoxViewEx クラスの記述
  • BoxExRenderer クラスの記述

の3つがワンセットになります。

レンダラーに、タッチイベントを追加する

Xamarin.Android で作成した Touch イベントをそのまま移植します。
レンダラーから、本体のコントロールクラスを参照するときは Element プロパティを参照します。ここでは BoxView クラスになっていますが、実体は BoxViewEx です。
Android で描画する View オブジェクトは、sender として渡されてくるので、これを Android.Views.View にキャストをします。もちろん、必要であれば対応する Android の View コントロールにキャストして大丈夫です。

class BoxExRenderer : BoxRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
    {
        base.OnElementChanged(e);
        this.Touch += BoxExRenderer_Touch;
    }

    float _gx, _gy; // 初期の相対値
    float _ox, _oy; // 前回の絶対位置
    /// <summary>
    /// タッチイベント
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    private void BoxExRenderer_Touch(object sender, TouchEventArgs e)
    {
        var box = sender as Android.Views.View;
        switch (e.Event.Action)
        {
            case MotionEventActions.Down:
                // 初期の相対値を保存
                _gx = e.Event.GetX();
                _gy = e.Event.GetY();
                break;
            case MotionEventActions.Move:
                // 移動距離を計算
                float dx = e.Event.RawX - _ox;
                float dy = e.Event.RawY - _oy;
                // コールバック呼び出し
                // TODO: delta 方式なのか誤差が大きい
                var el = this.Element as BoxViewEx;
                el.OnManipulationDelta(el, new ManipulationDeltaRoutedEventArgs(sender, dx, dy));
                break;
            case MotionEventActions.Up:
                break;
        }
        // 現在の絶対位置を保存
        _ox = e.Event.RawX;
        _oy = e.Event.RawY;
    }
}

先の Xamarin.Android と異なるのは、PCL 側にある BoxViewEx クラスの OnManipulationDelta メソッドを直接呼び出していることです。ここでリスナーパターンを使うこともできるのですが、面倒なので直接メソッドを呼び出します。C# の場合は event を使ってもよいでしょう。

引数として渡す ManipulationDeltaRoutedEventArgs クラスは共通で利用する PCL プロジェクトに記述します。このクラスはプラットフォームに依存しないように作ります。名前が UWP 風にしてあるのは、最終的に Windows ユニバーサルアプリ風に MVVM を作るためです。

PCL プロジェクト内の BoxViewEx クラスでドラッグ処理をする

疑似的な ManipulationDeltaRoutedEventArgs クラスを作っておいて、Android 側から通知される OnManipulationDelta メソッドを用意しておきます。

public class BoxViewEx : BoxView
{
    public virtual void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
    {
        var rc = this.Bounds;
        rc.X += e.Delta.Translation.X;
        rc.Y += e.Delta.Translation.Y;
        this.Layout(rc);
    }
}

public class ManipulationDeltaRoutedEventArgs
{
    public ManipulationDeltaRoutedEventArgs(object source, double deltaX, double deltaY)
    {
        this.OriginalSource = source;
        this.Delta = new Delta_()
        {
            Translation = new Delta_.Translation_()
            {
                X = deltaX,
                Y = deltaY
            }
        };
    }
    public Delta_ Delta { get; set; }
    public object OriginalSource { get; set; }
    public class Delta_
    {
        public Translation_ Translation { get; set; }
        public class Translation_
        {
            public double X { get; set; }
            public double Y { get; set; }
        }
    }
}

Xamarin.Forms 側でコントロールの位置設定は Layout メソッドを使えば ok です。移動距離が Delta.Translation.X,Y として渡されるのは UWP と同じです。というか、そういう風に作ります。

実行してみると

実行してみると、マウスの位置とコントロールがかなりズレていますがきちんとドラッグできていることがわかります。このずれは、Xamarin.Android の時と同じように差分移動をさせているための誤差です。

デバッグ出力をさせるとわかりますが、Touch イベントが Xamarin.Android の時よりも頻繁に入ります。このためなのか、Android 側の GetX,Y の値が float 型であるものの、Xamarin.Forms が double 型になっていること、最終的な描画(レンダラーのほう)は int 型になっていることなどに影響されていると思います。このあたり、誤差が出ない方式をあとで考えていきます。

ちなみに UWP(Windows のユニバーサルアプリ)で Delta.Translation を使ったときはずれが発生しません。できることならば、うまく調節していきたいところです。

カテゴリー: C#, Xamarin | 1件のコメント

Xamarin.Forms でドラッグを実装しよう(Android編)

Xamarin.Forms でドラッグを実装しよう(のうりん編)
http://www.moonmile.net/blog/archives/7653

の続きで「2.Xamarin.Android 上で Touch イベントを使って実装」のところをメモ書きします。一応、Xamarin.Forms on Android までは動いたので、あとは微調整(ドラッグしているときの位置に誤差が含まれているので)と iOS 対応版を作るところですね。

どうも Xamarin.Forms の場合はリスナーを使ってあれこれやるっぽいのですが、C# の場合はリスナーを使うよりもイベントやデリゲートを使う方が楽なので、そっちのほうに流れます。さらにいうと、Xamarin.Forms でネイティブのイベントハンドラを拾う(iOS/Android編) でネイティブのコントロールに直接バインドしてしまって、レンダラー無しのパターンでもよいかもしれません。こっちのほうは先行き実装ということで。

Xamarin.Android を Touch イベントでドラッグ

Xamarin.Forms を直接弄る前に、Xamarin.Android を使って C# でのドラッグを確認しておきます。コードとしては Java から C# へコンバートすれば良いので比較的簡単ではあるのですが、Xamarin.Forms で動かすことを考えて、Android/iOS/WinPhone のイベントを共通化するために、疑似的に ManipulationDelta を使って動かせるようにします。ストアアプリ/UWP の場合には、コントロールのドラッグイベントを ManipulationDelta を使って移動差分を利用するとコントロールの位置指定簡単になるのです。

画面的にこんな風になります。

  • 初期化ボタンで、最初の位置にジャンプさせる。
  • 上下左右ボタンで、20ドットずつ移動
  • 黄色のコントロールをドラッグする

初期化と上下左右ボタンは、Layout の位置チェックに使っています。最終的には移動先の座標を保存しておいて復元することも視野にいれるので、ドラッグ先の位置や整列の方法も調べておく必要があります。

TextViewで GetX,Y と RawX,Y を表示させていますが GetX,Y は正確な値ではありません。
ちなみに

  • GetX(), GetY() がコントロールに対する相対位置
  • RawX, RawY がタップの絶対位置(画面の左上が基準点)

という違いがあります。

ややこしいですが、こんな図の関係になります。box がドラッグするコントロールで、layout が box を載せているコンテナコントロールです。ここでは、RelativeLayout を使います。
タップした位置は RawX,Y で取れますが、ドラッグするコントロールは box.Left, box.Top のように layout の相対位置に直さねばいけません。また、タップした相対位置は、Touch イベントでとれるのですが、GetX,GetY はタップ対象コントロールの相対位置になるので、この場合は、box に対しての相対位置になります。

相対位置で計算するがベストなのですが、タップしたときの相対位置が、ドラッグさせて移動させてしまうコントロールそのものに対しての相対位置なので話がややこしくなります。このあたり本来ならば、コンテナのほううで Touch イベントを取るほうがよいのですが、Xamarin.Forms のレンダラーの制限のために、この方法になっています。まあ、ちょっとこのあたりはあとで調節ですね。

画面の axml

TextView コントロールをドラックさせるため、RelativeLayout の中に張り付けます。
コンテナに対しての相対位置は layout_marginTop と layout_marginLeft で指定しておきます。

<?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?>
<LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:orientation=&quot;vertical&quot;
    android:layout_width=&quot;fill_parent&quot;
    android:layout_height=&quot;fill_parent&quot;>
    <Button
        android:text=&quot;初期化&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/button1&quot; />
    <LinearLayout
        android:orientation=&quot;horizontal&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/linearLayout1&quot;>
        <Button
            android:text=&quot;上&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonUp&quot; />
        <Button
            android:text=&quot;下&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonDown&quot; />
        <Button
            android:text=&quot;左&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonLeft&quot; />
        <Button
            android:text=&quot;右&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonRight&quot; />
    </LinearLayout>
    <TextView
        android:text=&quot;GetX,Y: 000,000&quot;
        android:textAppearance=&quot;?android:attr/textAppearanceMedium&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/textView1&quot; />
    <TextView
        android:text=&quot;RawX,Y: 000,000&quot;
        android:textAppearance=&quot;?android:attr/textAppearanceMedium&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/textView2&quot; />
    <RelativeLayout
        android:minWidth=&quot;25px&quot;
        android:minHeight=&quot;25px&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:id=&quot;@+id/relativeLayout1&quot;>
        <TextView
            android:text=&quot;Box&quot;
            android:textAppearance=&quot;?android:attr/textAppearanceLarge&quot;
            android:layout_width=&quot;60dp&quot;
            android:layout_height=&quot;60dp&quot;
            android:id=&quot;@+id/box1&quot;
            android:layout_marginTop=&quot;100dp&quot;
            android:layout_marginLeft=&quot;100dp&quot;
            android:background=&quot;#ffbb33&quot; />
    </RelativeLayout>
</LinearLayout>

OnCreate で初期化する

OnCreate メソッドで、画面が作成されたときにボタンのイベントを追加しておきます。
RelativeLayout 内での座標の指定は、RelativeLayout.LayoutParams と SetMargins メソッドを使います。このあたり使い方がややこしいですよね。RelativeLayout.LayoutParams の部分は、コンテナで利用する layout によって変える必要があります。

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    // Set our view from the &quot;main&quot; layout resource
    SetContentView(Resource.Layout.Main);

    // Get our button from the layout resource,
    // and attach an event to it

    var text1 = this.FindViewById<TextView>(Resource.Id.textView1);
    var text2 = this.FindViewById<TextView>(Resource.Id.textView2);
    var box = this.FindViewById<View>(Resource.Id.box1);
    var btn1 = this.FindViewById<Button>(Resource.Id.button1);

    // 位置を初期化
    btn1.Click += (s, e) =>
    {
        setPos(50, 50);
        dispPos();
    };
    // 位置移動
    this.FindViewById<Button>(Resource.Id.buttonLeft).Click += delegate
    {
        setPos(box.Left-20, box.Top);
        dispPos();
    };
    this.FindViewById<Button>(Resource.Id.buttonRight).Click += delegate
    {
        setPos(box.Left + 20, box.Top);
        dispPos();
    };
    this.FindViewById<Button>(Resource.Id.buttonUp).Click += delegate
    {
        setPos(box.Left, box.Top - 20);
        dispPos();
    };
    this.FindViewById<Button>(Resource.Id.buttonDown).Click += delegate
    {
        setPos(box.Left, box.Top + 20);
        dispPos();
    };
    box.Touch += Box_Touch;
    // OnManipulationDelta を設定
    this.ManipulationDelta += OnManipulationDelta;
}
// 位置指定
void setPos( int x, int y )
{
    var box = this.FindViewById<View>(Resource.Id.box1);
    // var lp = new RelativeLayout.LayoutParams(box.Width, box.Height);
    if ( _box_w == 0 || _box_h == 0 )
    {
        _box_w = box.Width;
        _box_h = box.Height;
    }
    var lp = new RelativeLayout.LayoutParams(_box_w, _box_h);
    lp.SetMargins(x,y, 0, 0);
    box.LayoutParameters = lp;
}

ちなみに、box.Top, box.Left は get/set の両方があるので設定も可能なのですが、これに値を入れると Width/Height も自動的に変わってしまいます。Width/Height は get しかないので謎な仕様です。

タップは、Touch イベントに結び付けます。ManipulationDelta のほうはテスト用に作ったものです。

Touch イベント

ドラッグ時のタップイベントは、MotionEventActions.Down/Move/Up の処理をします。
box の新しい位置を決定するときに、直接、絶対座標の GetX,GetY を使って指定すれば良いのですが、コンテナやドラッグ対象のコントロールの位置指定が相対座標のためうまくいきません。仕方がないので、前回の絶対位置を保持しておいて差分を使ってコントロールの位置決めをします。

private void Box_Touch(object sender, View.TouchEventArgs e)
{
    var box = sender as View;
    switch( e.Event.Action )
    {
        case MotionEventActions.Down:
            // 初期の相対値を保存
            _gx = e.Event.GetX();
            _gy = e.Event.GetY();
            _box_w = box.Width;
            _box_h = box.Height;
            break;
        case MotionEventActions.Move:
            // 移動距離を計算
            float dx = e.Event.RawX - _ox;
            float dy = e.Event.RawY - _oy;
            // 移動
            // TODO: 誤差で少しずれるが実用上問題ない
            // setPos((int)box.Left + (int)dx, (int)box.Top + (int)dy);
            // OnManipulationDelta(sender, new ManipulationDeltaRoutedEventArgs(sender, (int)dx, (int)dy));
            // イベント呼び出し
            if ( ManipulationDelta != null )
            {
                ManipulationDelta(sender, new ManipulationDeltaRoutedEventArgs(sender, (int)dx, (int)dy));
            }

            break;
        case MotionEventActions.Up:
            break;
    }
    // 現在の絶対位置を保存
    _ox = e.Event.RawX;
    _oy = e.Event.RawY;
}

差分を使ったときの欠点は、誤差が蓄積されてしまうことです。絶対座標の GetX,GetY は float 型なのですが、box の Left/Top は int 型です。このあたりがずれているために、前回の位置(int型)+ 変化量(float型)が、位置(int型)に丸められてしまって、だんだんと誤差が溜まってしまいます。タップした位置に合わせるように誤差を補正する方法が必要でしょう。これはあとで考えます。

疑似的な ManipulationDelta

移動量を差分で計算する場合は、誤差が溜まるという欠点はあるのですが、設定する座標の計算が楽になります。単純に前回の位置に移動量を足せば、今回の位置になるからです。これが、Windowsストアアプリ/UWP で使われている方法で、ManipulationDelta を使って加算していきます。

どうせ、Xamarin.Forms で 3つのプラットフォームを共通化しなければいけない(WPFも合わせると4つのプラットフォームになる)ので、疑似的に ManipulationDelta を使えるようにしてしまいます。
名前だけ合わせるために、無理矢理 ManipulationDeltaRoutedEventArgs クラスを作り、OnManipulationDelta メソッドが扱えるようにします。どうせ C# なのだから、コーディングスタイルも UWP に合わせてしまうほうがいいですよね。

    public class ManipulationDeltaRoutedEventArgs
    {
        public ManipulationDeltaRoutedEventArgs( object source, int deltaX, int deltaY )
        {
            this.OriginalSource = source;
            this.Delta = new Delta_()
            {
                Translation = new Delta_.Translation_()
                {
                    X = deltaX,
                    Y = deltaY
                }
            };
        }
        public Delta_ Delta { get; set; }
        public object OriginalSource { get; set; }
        public class Delta_
        {
            public Translation_ Translation { get; set; }
            public class Translation_
            {
                public int X { get; set; }
                public int Y { get; set; }
            }
        }
    }
    public virtual void OnManipulationDelta( object sender, ManipulationDeltaRoutedEventArgs e )
    {
        var el = e.OriginalSource as View;
        int x = el.Left + e.Delta.Translation.X;
        int y = el.Top + e.Delta.Translation.Y;
        // left, top は同時に設定する必要あり
        // RelativeLayoutHelper.SetLeft(el, x);
        // RelativeLayoutHelper.SetTop(el, y);
        RelativeLayoutHelper.SetXY(el, x, y);
    }

    void setPos(int x, int y)
    {
        var box = this.FindViewById<View>(Resource.Id.box1);
        // var lp = new RelativeLayout.LayoutParams(box.Width, box.Height);
        if (_box_w == 0 || _box_h == 0)
        {
            _box_w = box.Width;
            _box_h = box.Height;
        }
        var lp = new RelativeLayout.LayoutParams(_box_w, _box_h);
        lp.SetMargins(x, y, 0, 0);
        box.LayoutParameters = lp;
    }
}
class RelativeLayoutHelper
{
    public static void SetLeft( View el, int left)
    {
        var lp = new RelativeLayout.LayoutParams(el.Width, el.Height);
        lp.SetMargins(left, el.Top, 0, 0);
        el.LayoutParameters = lp;
    }
    public static void SetTop(View el, int top)
    {
        var lp = new RelativeLayout.LayoutParams(el.Width, el.Height);
        lp.SetMargins(el.Left, top, 0, 0);
        el.LayoutParameters = lp;
    }
    public static void SetXY(View el, int left, int top)
    {
        var lp = new RelativeLayout.LayoutParams(el.Width, el.Height);
        lp.SetMargins(left, top, 0, 0);
        el.LayoutParameters = lp;
    }
}

ManipulationDelta 方式を使うときには、キャンバス(Canvas)を使って、Canvas.SetLeft と Canvas.SetTop が頻発するので同じようなものを作っておきます。RelativeLayoutHelper.SetXY とすることで、座標を指定できます。試しに SetLeft と SetTop を作ってみたのですが片方ずつ設定することができませんでした。たぶん、設定するタイミングと View で反映されて再計算されるタイミングが違うからですね。仕方がないので、両方設定するときは SetXY を使います。

サンプルコード

そんな訳で、Xamarin.Android を使って TextView をドラッグさせることができました。更に、疑似的に ManipulationDelta を使うようにしたので Xamarin.Forms で共通化できそうです。

https://github.com/moonmile/BoxDrag/tree/master/BoxDrag.Android

では、引き続き Xamarin.Forms のほうも。

カテゴリー: C#, Xamarin | Xamarin.Forms でドラッグを実装しよう(Android編) はコメントを受け付けていません

ガンダムUCを観ながらXamarin.Formsをver2.0に上げると、ブルースクリーンになるのか

という訳で、ブルースクリーンになりました。単にガンダムUCをビデオ再生しながら、Visual Studio 2015 で Xamarin.Forms 2.0 にアップデートしながら、Android on Hyper-V でデバッグをしていただけなんでけどね。なぜか、2回ほどやって、2回ともなるからこれが不思議なのです。

「自動的に再移動します」とありますが、なかなか進まないので電源をぶちっと切って、再起動させます。すると、画面が真っ黒になって BIOS 画面すら出ないというパターンに。暫く待っても ping すら通らない冷や汗状態になりました。この写真は、次の日の昼頃に復旧させようとして、またブルースクリーンになった状態なので、前日の夜中に2回、そのあとに3回ほど同じ現象にあたっています。

あとの3回は、

  • Android on Hyper-V を使って、画面キャプチャ中に落ちる。
  • FireFox でツイッターに入力中に落ちる。
  • なんだかわからないが、起動して Core Temp を起動したら落ちる。

とい体たらくです。これはあまりひどいので原因究明をしていった記録を少し。

再起動で立ち上がらない

再起動で BIOS の画面も出ずに起動画面がでないので、ビデオボードの不具合か?と思ったのですが、BIOS すら出ないのは変な感じです。実は Window 10 の場合、高速起動が働いていて BIOS 画面をすっ飛ばしてロゴの画面が表示されます。このときに、ビデオドライバー等に不具合があれば真っ黒になりますね。Windows 10 の場合、低解像度とかにならなくて真っ黒になるのが困りものなのですが…ひとまず、電源コードを抜いて 5 分ぐらい経って起動すると、無事 BIOS 画面が表示されるようになりました。ひとまず、ビデオボードのドライバーは問題なさそうです。

ビデオ再生の不具合か?

ユニコーン UC は、標準の「ビデオ」アプリで再生させているのですが、たまにこのアプリが落ちます。ビデオドライバーの辛みもあって、エラーを起こしたりするのですが、再生によるエラーが原因でブルースクリーンではないようです。

Hyper-V の不具合か?

ブルースクリーンということはカーネル回りで落ちているので(実際「KMODE Exception not handled」になってる)、Hyper-V ともども落ちるということは考えられます。が、先の3回はともかくとして、あとの 2 回は Hyper-V は関係ありません。いや、バックグラウンドで動いているパターンもあるので、これが原因かもしれないのですが、もうちょっと原因を探っていきます。

ブルースクリーンが :( ではない

そういえば、Windows 10 のブルースクリーンは :( の英語パターンなのですが、なぜかこのブルースクリーンは日本語だし :( がありません。なぜか?

CPU のパフォーマンスや、キャッシュ破壊などの OS 絡みのカーネルパニックならば :( の絵が出るはずですよね。ってことは、OS 関係なく、ハード絡みかもしれません。

Core Temp で CPU の温度を見る

ひょっとすると単純な熱暴走なのでは?と思いつつ Core Temp を立ち上げると、CPU の温度が 70度近くになっています。いやいや、冬だしこんなには高くなかったはずだし、そもそも高い水冷式にしたのに、こんなに高くなるはずがありません。ファンが止まってしまったか、それとも水冷式って寿命があるのか?

水冷式のファンを掃除する

空冷式の場合は、ファンにほこりがたまってしまって PC 内が熱くなってしまうことがあるのですが、水冷式の場合は下のように冷却液がめぐっていて、PC 筐体が熱くなることはありません。

だからこその、水冷式なんですけど、70度近くってのはあまりにも上がりすぎ。冷蔵庫のガスが抜けてしまったように冷却液が抜けてしまったのでしょうか?

ひょっとしたら、ファンのビスを外して、ラジエーターの掃除をしました。そう、クーラーのような細かいフィンの間にほこりが溜まっていて冷却効率が悪くなってしまったのではないか、と。

正解は、水冷のラジエーターでした

どうやら、これが正解でした。ラジエーターにたまっていたほこりが、冷却効率を悪くしてしまっていて冷却液が冷えなかったという状態…ううう、これは落とし穴ですね。

image

PC の筐体は、何回か掃除はしていたのですが、ラジエーター自体の目詰まりは考えませんでした。そんなわけで、原因は「ラジエーターの目詰まりによる CPU 熱暴走。そして、ブルースクリーンに至る」というわけです。

カテゴリー: 雑談 | ガンダムUCを観ながらXamarin.Formsをver2.0に上げると、ブルースクリーンになるのか はコメントを受け付けていません

Xamarin.Forms でドラッグを実装しよう(のうりん編)

今年は申年ということで、何故か正月から「のうりん」を読んでいます。申年は Xamarin に関係ありますが「のうりん」には関係ありません。いや、猿の回があったから少しは関係するかも。と、会社を辞/〆て数年経ちますが、フリーでITプログラム屋さんをやっていると月々の収入がえらい上下します。自営業とはいえ小売りや飲食店をやっている訳ではないので、数か月間収入がなかったり、どかっとまとめて入ってきたりします。最初の頃は、この上下が心理的に苦しくて(だからこそ、会社員や契約社員になったりするんですけど…というのは後で気づきました)あれこれ継続的な収入を考えていたのですが、いやいや「アワビ業者」と同じですねと考えてからあれこれすっきりしました。計画的なあれこれはさておき、1年のうちの数回しか収入がないものでそれを1年間で分配すればよいわけです。不確定な年棒制みたいなものですが、程よく貯金(金銭的なバッファ)があれば、多少の浮き沈みは問題ではありません。そんな訳で「のうりん」を読んでいるわけですが、先日のポスター騒ぎをさておき原作を読めば、「ああ、『もやしもん』と『おせん』が混ざっている」わけで、時には『夏子の酒』も入っていたりします。農業あれこれは、板橋区で畑の貸し出しを数年やっただけですが、先に手間をかける、そして収穫するという意味では、フリーのITプログラム屋さんも似たようなものです。農業の場合、農機具を借金して買うパターンになるので農協の話とか台風の話のほうが深刻ですが、自給自足(地産地消)できる分だけ、お金のやり取りが少なくなる分だけお農業のほうが良いかもと思ったり、さすがに午前4時起きで作業を続けるのはちょっと私には無理、と思ったりと読み進めるわけですが。ちなみに、5巻ぐらいになると「おっぱい」の割合が減ります。蘊蓄部分が多くなって、読むスピードが落ちるわけですが、そのあたりのバランスはラノベならではですね。漫画っぽい引きと挿絵の挿入は、他のラノベでもそうなのか分かりませんが、筒井康隆風でもあります。まあ、それ以前に、黄表紙とかタイポグラフィとかあるわけですが。

そんな訳で「のうりん」を読みながら、他人様の時間と自分の作業する時間とをあまり交差させないように、OpenCCPM の構想を立てているところです。OpenCCPM では、

  • タスクカード抽出
  • PERT図
  • ガントチャート

が、ひとつのタスクデータを共有します。いわば、タスクの三面図みたいなものを作るわけで、UML も同じ発想です。これにアジャイル的な変化を組み込む(実は CCPM のプロジェクトバッファは、アジャイル的な変化を畳みこむので考慮しない)ようにします。プロジェクトバッファは、タスクの遅延には強いのですが、タスクそのものの増減には関与しないんですよね。というわけで、IT 特有の時間経過によるタスクの変化(いわゆる状況/要件の変化)を織り込むのが OpenCCPM の目標です。ちなみに CCPM(クリティカルチェーン プロジェクトマネージメント)は、PMBOK に含まれています(発祥は TOC ですがね)。

タスク抽出をタブレットで実装したい

タスク抽出にはいろいろな方法があります。

  • トップダウン方式の WBS
  • ゴールから後戻りをする方式

PMBOK では WBS という作業単位を出していますが、いきなり WBS で出しててはダメです。あれこれ、トップダウン的にプロジェクトマネージメント的にマインドマップ的にあれこれ出しているものがありますが、全部ダメです。プロジェクトマネジメント関係は、以前 TOC を知ったころにあれこれやて考えた時があるのですが、政治的な話に踏み込んでしまうので途中で止めました。もっと技術的な職人的なことに労力を突っ込んだほうがいいだろう、と思ったわけです。その裏ではちょこちょことマネジメント的なテクニックを個人的に使ってはいましたが、外側で使うことはありませんでした。が…どうやら、この歳になって、あれこれとダメダメなマネージメントに引きずられるのが嫌になってきました。ダメダメなのはまあ、いいんですが、ダメダメなスケジュールと進行に自分の時間を浪費させられるのはちょっと嫌~な感じですね。というわけで、少しだけ好き勝手できるようにします。

無駄を省くために、ゴールからタスクを抽出する方法もありますが、実は時間が掛かりすぎます。なにも一から泥縄式にタスクを抽出することはありません。究極に無駄を省くこともできますが、何も最適値である必要はありません。準最適であるだけで十分だし、更に言えば、競争力としては「他の会社でやっているダメダメプロジェクト管理」よりもちょっとだけ良ければいいのです。

ある程度経験があれば、タスク抽出にはテンプレートや以前の経験が便利ということが分かります。逆に言えば、新人 PM には無理です。不可能です。以前やったことがある経験値としてのタスク抽出と、どこかのストーリーで読んだことがある(本を読むことは疑似体験として重要です)タスクを取り入れることは、最適ではなにせよ準最適になり得ます。

さらに、最初のタスク抽出に固辞する必要はなく、状況が変化するのであれば(プロジェクトを進める間に、新人 PM は、新人ではない PM に進化しますよね)、それに従ってタスク自体も組み替えていけばよいわけです。由緒正しいスクラムプロセスの場合は、2週間単位のスプリントで決めますが(最近のスクラムのそれになっている?)ほどよい人数になれば、コミュニケーションコストがかさむし、全体の統括が必要になってきます。「統括」自体は、マイルストーンという形でスケジュールを縛っていくのでプロジェクトのバッファの意味がなくなってきます。そういう欠点も含めて、状況に沿って動く「予測が可能なプロジェクト運営」を目的とします。

で、タスク抽出自体は付箋を使うのが一番よいのですが、手書きだけだと共有ができないし、たくさんのタスクになると検索性も悪くなります。また、タスクを PERT 図はガントチャートに自動的に直すことができません。このあたりも含めて、タブレットでタスク抽出ができるとよいわけです(天井にカメラを据えて、付箋の画面キャプチャ、OCR 認識も考慮したいですよね)。

Xamarin.Forms で Drag する

ふと、大型タブレットを探したのですが、13インチぐらいあるタブレットは、iPad か Windows タブレットになっていますね。何故か Android タブレットは 7 インチぐらいで止まっています。大型タブレットの需要が少ないのもあるのでしょうが、スマートフォーンではないタブレットという分野は閲覧を抜群に上げるために重要な分野です。バッテリー故に重たくなってしまうタブレットですが、液晶の電力を下げたり、薄い有機液晶も出てきているので、5年以内にはでそうです。というか、スマートフォンは無くなるんじゃないかなと思っています。

それはさておき、Java/Objective-C/Swift であれこれするよりは、C# で一括で作ったほうがよいだろうという発想と、MVVM の VM-M を共通化するために View を Xamarin.Forms に渡したほうがよいだろう、という労力的な問題があります。OpenCCPM のツール群は、VM-M のほうに注力するので、できるだけ View は省力化したいのです。マンパワー的に。ちなみに OpenCCPM の作成自体は OpenCCPM で行っています…ということにします。

Xamarin.Forms を使う時に不便なのは、タップしてドラッグ関係の機能がないことです。以前から何故ないんだろう、と思っているわけですが今でもありません。Xamarinでコントロールのドラッグに対応する で作ってみたのは、Windowsストア/Android/iOS の三種類です。これを、Xamarin.Formsでタッチイベントを処理するには?(iOS/Androidの各種ジェスチャー対応) にあるロングタップを参考にして、 OnLongPress をコントロールの Renderer を使って実装すればよいかと思っていたのですが、あれ? GestureDetector.SimpleOnGestureListener って、タップ位置を渡していないよ、なぜかタップの Down はあるけど Move/Up が消されちゃってるよ orz ってことで、どうやら自分で実装しないといけないようです。確かに、Android/iOS とドラッグのやり方は異なるので、実装をどちらに合わせるか悩むところなのですが。

でもって、調べる Android でもドラッグの方法がいくつかあるので、最終的な手順まで計画を立てます。

  1. Xamarin.Android 上で OnDragEvent を使って実装
  2. Xamarin.Android 上で Touch イベントを使って実装
  3. Xamarin.iOS 上で実装
  4. インターフェースを ManipulationDelta 風に変更する
  5. Renderer を使って、Xamarin.Forms にコールバックする

のように手順を踏まないと作れないっぽいです。

という訳で、このあたりからちまちまと。

Android Drag の参照先

カテゴリー: Xamarin | Xamarin.Forms でドラッグを実装しよう(のうりん編) はコメントを受け付けていません

WPF でコントロールをドラッグする

以前、WPF でコントロールのドラッグコードを書いたのですが、業務的に少し書き直してみます。WPFでコントロールをドラッグ(1) では、マウスの down/up/move のイベントを直接つけていますが、コントロールが増えたときには面倒ですね…というか、動的にイベントを追加すればよいのですが。ふと、[WPF]枠なしでリサイズ&ドラッグ移動可能なウィンドウを作る に行き当たると、WindowChrome クラス を使うと枠なしのウィンドウが作れて、MouseLeftButtonDown イベント内に DragMove イベントを呼び出せばよいようです。この方式だと簡単ですね。

というわけで、マウスダウンのイベントだけあらかじめ付けておいて、マウスの移動とアップイベントは動的に追加します。また、クラスの継承ではなくて拡張を使って実装します。

 
public static class ControlDragExtenstions
{
    /// <summary>
    /// コントロールのドラッグを有効にする
    /// 親コントロールが Canvas であること
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    public static void DragMove(this Control t, object sender, MouseButtonEventArgs e)
    {
        var el = sender as Control;
        var canvas = el.Parent as Canvas;
        var dragOffset = e.GetPosition(el);
        el.CaptureMouse();

        MouseEventHandler mouseMove = null;
        MouseButtonEventHandler mouseUp = null;

        mouseMove = new MouseEventHandler((_, __) => {
            Point pt = Mouse.GetPosition(canvas);
            Canvas.SetLeft(el, pt.X - dragOffset.X);
            Canvas.SetTop(el, pt.Y - dragOffset.Y);
        });
        mouseUp = new MouseButtonEventHandler((_, __) => {
            el.ReleaseMouseCapture();
            el.MouseMove -= mouseMove;
            el.MouseUp -= mouseUp;
        });
        el.MouseMove += mouseMove;
        el.MouseLeftButtonUp += mouseUp;
    }
}

利用するときは、次のように sender と MouseButtonEventArgs 引数をそのまま渡します。MouseButtonEventArgs のほうは、マウスの相対位置を調べるのに使います。

 
// マウスダウンのイベント処理
card.MouseLeftButtonDown += (sender, e) =>
{
    this.DragMove(sender, e);
};

 

これを動かすと、こんな感じにドラッグができるようになります。

image

カテゴリー: C#, WPF | WPF でコントロールをドラッグする はコメントを受け付けていません

Windows IoT Core と MVVM の関係

先月の .NETラボで Windows IoT Core のデモをやりましたが、そこで使ったソースコードを公開します。

moonmile/WinIoT
https://github.com/moonmile/WinIoT/tree/master/HelloSample

HelloServo のサンプルは Raspberry Pi では動作しない(他では動作している?)のでなんとも言えませんが、他のコードはほどよく Hello 的に使えるものです。当日はコードの解説はせず、概要と動作だけだったので、ちょっとコードのほうも解説しておきます。

環境を整える

木澤さんの I/O ネタにも突っ込みを入れたのですが、現在 Winodws IoT の開発環境づくりは結構な困難を伴います。有償(とされる)Enterprize 版の場合もこのような状況なのかはわかりませんが、少なくとも開発者が自由に使える無償版は、現状こんな感じです。

  • 最新の Windows IoT Core 10.0.10585 を micro SD カードに焼きこんだ時は、Windows 10.0.10585 + Visual Studio 2015 Update 1 の環境が必要になる。
  • 最新の Windows IoT Core 10.0.10585 には、リモート環境が含まれておらず、直前のバージョンから RDBG フォルダをコピーする必要がある。

Windows IoT Core の最新バージョンは Get Started with Windows IoT から Windows IoT Core Dashboard を使ってインストールします。最初の頃はファイルをダウンロードしてコマンドラインからインストール、というややこしい手順だったのですが、最近のものはえらい変わりました。ついでに言うと、先日の発表をした頃よりも変わっていて、自動的に micro SD カードに焼きこむファイルをダウンロードするようです。
image

ちょっと前の従来のものは Download Windows 10 IoT Core からダウンロードができます。

トップページのドキュメント自体が Windows 10 (version 10.0.10240) or better. となっているので、Windows 10 の細かいバージョンが怪しいのですが、デバッグの関係上 Visual Studio で作成するユニバーサルアプリのバージョンと Raspberry Pi 上で動く Windows IoT Core のバージョンは揃えないといけません。

Visual Studio 2015 から Raspberry Pi へプログラムを送り込むにはリモートデバッグが便利なのですが、何故か10585 版の環境には肝心なリモートデバッグの環境が含まれていません。入れ忘れたのか、有償版には入っていて無償版にはないのか、は定かではありませんが 10585 版はそのままではリモートデバッグができません。このあたりは Windows IoT Core ver.10.0.10586 のデバッグを有効にする | Moonmile Solutions Blog を参考にして、RDBG フォルダをコピーしてください。直前のバージョンの RDBG フォルダは http://1drv.ms/1QqAKz4 にあります。

時計を動かす

Windows IoT Core はユニバーサルアプリ(UWP)で作りますが、これの最大のメリットはユーザーインターフェースのテストを普通の PC で行えることです。

HelloSample プロジェクトは、画面に時刻を表示するアプリですが特に Raspberry Pi の GPIO などをアクセスしている訳ではないので、PC 上で動きます。もちろん、Windows Phone 上でも動きます。

image

また、画面は XAML で作成するので MVVM パターンを使うことができます。HelloSample プロジェクトでは、時計の表示部分を1秒ごとに更新していますが、これは TextBlock を直接更新しているわけではなくて、わざわざ Binding を使っています。この程度だとバインディングを使わなくていいのですが、まあ、先の Xamarin + MVVM の流れでこうしています。

 
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += (s, e) =>
        {
            this.DataContext = _vm = new ViewModel(this.Dispatcher);
            _vm.TimeStart();
        };
        this.Unloaded += (s, e) => { _vm.TimeStop(); };
    }
    ViewModel _vm;
}

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    Windows.UI.Core.CoreDispatcher _disp;

    public ViewModel(Windows.UI.Core.CoreDispatcher disp )
    {
        _disp = disp;
    }

    private DateTime _time = DateTime.Now;
    public DateTime Time
    {
        get { return _time; }
        set { this.SetProperty(ref _time, value); }
    }

    Task _task;
    bool _loop = false;
    public void TimeStart()
    {
        _loop = true;
        _task = new Task(async () => {
            while( _loop )
            {
                await _disp.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                {
                    this.Time = DateTime.Now;
                });
                await Task.Delay(1000);
            }
        });
        _task.Start();
    }
    public void TimeStop()
    {
        _loop = false;
    }


    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

タイマーは Task クラスで別スレッドにしているので、RunAsync メソッドを使ってスレッド越えをして UI にアクセスする必要があります。このあたり、プロパティアクセスと UI アクセスが混在している VM の弱点でもありますが、逆に言えば、Model にスレッド絡みをいれてしまうと View の結合が強くなってしまうので本末転倒、そうなると ViewModel に押し込むのがベターか、といったところです。

Lチカとタクトスイッチ

HelloLED では、GPIO で Lチカ をするのと、タクトスイッチという物理ボタンをブレッドボードに乗せてスイッチに反応するパターンです。いわば、アウトプットが Lチカで、インプットがタクトスイッチです。

image

アウトプットのほうは、LED だけでなく、液晶ディスプレイとか、モーターへの出力とか、出力全般を扱うための基礎になります。インプットは、温度センサーや、マウスやタッチパネル、角速度センサーの検出などの基礎です。インプットは、OS の割り込みのようなものですが、Windows IoT Core では適当なイベントにして戻してくれます。このイベントの内部動作は、後々詳しく説明したいと思うのですが(結局のところ Windows のイベント駆動と同じなので)、ここでは「便利に」イベントとして返してくれるところに注目してください。ちなみに、Arduino IDE でタクトスイッチを扱うときは意外と面倒です。

public MainPage()
{
    this.InitializeComponent();

    var gpio = GpioController.GetDefault();
    this.ledPin = gpio.OpenPin(LED_PIN);
    this.buttonPin = gpio.OpenPin(BUTTON_PIN);

    ledPin.SetDriveMode(GpioPinDriveMode.Output);
    buttonPin.SetDriveMode(GpioPinDriveMode.InputPullUp);

    ledPin.Write(GpioPinValue.Low);
    _isLED = false;

    buttonPin.ValueChanged += ButtonPin_ValueChanged;

}

private async void  ButtonPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
{
    await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
        // if (buttonPin.Read() == GpioPinValue.High )
        if ( args.Edge == GpioPinEdge.FallingEdge)
        {
            ellipse1.Fill = new SolidColorBrush(Colors.Red);
        }
        else
        {
            ellipse1.Fill = new SolidColorBrush(Colors.Gray);
        }
    });
}

タクトスイッチのボタンイベントは、ValueChanged として扱います。これは Read だけで読み取ると、押している瞬間は電気信号として +/- をうろうろすることがあるからです。タクトスイッチとはいえ、電留が流れる部分はアナログ的な電気接点ですから、物理的に接点がちょっと離れたり押されたりするわけです。ですが、ソフトウェアのほうでは 0/1 のデジタルで判断したいので、途中の揺れの部分は必要ありません。むしろ、0/1 のあたりをうろうろしているのは邪魔ですよね。というわけで、ValueChanged イベントは、一定ので電圧以下から以上になるタイミング、あるいは一定の電圧以上から以下になるタイミングでイベントを発生しています…ってはずなのですが、内部はあとで調べてみます。少なくとも、微妙な接点によるうろいろしたところを削って、0/1 で反応させることができます。

また、一見、UI のボタンイベントのように見えますが、ハードウェアからイベントなのでスレッド越えのときと同じように Dispatcher.RunAsync を使う必要があります。このあたりのスレッドを意識しなければいけないのは、Windows IoT Core の難しいところなのか、不備なのかは謎です…が、ワタクシ的には「不備」ですね。もうちょっと MVVM パターンも含めてスレッド越えを意識しないようなコードにしたいところです。

モーターを動かす

本当はサーボモーターの制御を試したかったのですが、Windows.Devices.Pwm の中身がからっぽっぽいので、急遽ブラシモータに変更しました。Windows.Devices.Pwm のほうはいずれ中身を作っていきたいと思います。

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += MainPage_Loaded;
        this.Unloaded += (s, e) => _motor.Stop();
    }

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        var gpio = GpioController.GetDefault();
        _motor = new Motor(gpio.OpenPin(22), gpio.OpenPin(27));
    }

    Motor _motor;

    private void clickFront(object sender, RoutedEventArgs e)
    {
        _motor.GoFront();
    }
    private void clickBack(object sender, RoutedEventArgs e)
    {
        _motor.GoBack();
    }
    private void clickStop(object sender, RoutedEventArgs e)
    {
        _motor.Stop();
    }
}

public class Motor
{
    GpioPin _front, _back;
    public Motor( GpioPin front, GpioPin back )
    {
        _front = front;
        _back = back;

        _front.SetDriveMode(GpioPinDriveMode.Output);
        _front.Write(GpioPinValue.Low);
        _back.SetDriveMode(GpioPinDriveMode.Output);
        _back.Write(GpioPinValue.Low);
    }

    public void GoFront()
    {
        _front.Write(GpioPinValue.High);
        _back.Write(GpioPinValue.Low);
    }
    public void GoBack()
    {
        _front.Write(GpioPinValue.Low);
        _back.Write(GpioPinValue.High);
    }
    public void Stop()
    {
        _front.Write(GpioPinValue.Low);
        _back.Write(GpioPinValue.Low);
    }
}

モーターを前後に動かすためには、+ と – を交換すればよいだけです。ただし、ふつうのモーターを動かすためにはある程度の電流が必要になるので、Raspberry Pi の GPIO に直接さして動かすわけにはいきません。別途モータードライバが必要になります。ちなみに、小さなモーター(携帯用の振動モーターなど)は直接さしても動きます。

+/- を逆転させるために、2本のGPIOを使います。それぞれの GPIO に対して HIGH/LOW を逆転させてもよいのですが、自前で Motor クラスを作成しています。この Motor クラスを使って制御ができるのがオブジェクト指向のよいところですよね。ここにハードウェア制御だけでなく、液晶ディスプレイに状態をあらわすような VM を追加していけば、うまく V-VM-M が分離できるてシミュレータで動かしたり、インターネット経由で動かしたりというアスペクト指向的な実装も可能になります。

カテゴリー: C#, RaspberryPi | Windows IoT Core と MVVM の関係 はコメントを受け付けていません

ソフトウェア開発者がハードウェア/IoTをやる意味

12月の .NET ラボ勉強会では「これから始める Windows IoT Core」ということで発表してきました。

[slideshare id=56308093&doc=windowsiotcore-151220031915]

その中で、さらっと流していきましたが「ソフトウェア開発者がIoTをやる意味」のところをもう少し掘り下げて解説しておきます。

垣根を超えるために

私がプログラミングを始めたのは30年以上前ですが、既にソフトウェア開発というジャンルは確立されていました。まだ N88Basic の頃だったり、MS-DOS や DR-DOS だったり、したものの直接「ハードウェア」に触るためには、ハードウェアにアクセスするための馬鹿高いインターフェースボードが必要でした。逆に言えば、ハードウェアとソフトウェアの垣根があって、その馬鹿高いインターフェースを買わない限り直接ハードを触ることはなかったのです。当然、一方では組込みシステムの開発者(当時はアセンブラ)が居たわけで、基板設計とあわせてハードの I/O を地道にたたくハードなソフトウェア屋さんもいらっしゃったわけです。

ともあれ、大学の頃は主に炉心のシミュレーションをやっていたので、ほとんどハードウェアに触ることがありませんでした。学生実験で計測器で測定するぐらいですね。おそらく工学系でも機械工学の方は、もっとハードウェア寄りのことを知っていると思います。

コンピュータ上で何でもできる。インターネットや仮想空間も含めて、コンピュータ内のソフトウェアの世界でかなりのことができます。昨今の人工知能や機械学習、VR なども含めればソフトウェアの世界で突っ切ることも可能な時代になってきています。

逆に言えば、「ハードウェアの世界」と「ソフトウェアの世界」という形で、垣根が現前に存在します。子供の科学などで、少しでも電子回路を組んだことがあれば、半田付けとかコンデンサの使い方とか計測誤差とか、そういう手を使ってアナログ的なところをうまく補完していく方法もあることが分かったのでしょうが、こう 30 年間もソフトウェア内だけでやっていると、ここのところがうまくわからなかったのですよ。

ハードウェアは誤差を許容する世界

ソフトウェアも小数点以下は正確に表せないので、誤差を許容するように作りますが。ハードウェア≒現実な世界では、誤差は範囲を以て対応させます。抵抗値にしても、教科書には 330Ω と書かれていても、だいたい 1kΩ ~ 100Ω ぐらいのを持ってきても大丈夫です。更に半田や銅線の内部抵抗があるので、ぴっちりと計算で決まるわけではありません。抵抗値では2,3倍違ってもだいたい動きます。また、有効桁数は1桁か2桁ぐらいしかありません。ソフトウェアの計算では有効桁数は8桁ぐらい簡単に取れるのですが(doubleの間ならば)のですが、そのあたりもハードウェアとソフトウェアの世界では値の扱いが異なります。

このあたり、即値でも幅があるし、合計値にも幅があります。しかし、RC回路を組んで振動させると分かるのですが、Δt の要素が入ってきて時間軸のある物理現象がかかわってくると、理想状態(定常状態)とは違ったものを見えてきます。また、細かいこところでは誤差が入っていても、全体の値は目の前にある物理現象が絶対なのですがから、合計値が計算で違っていたとしても「誤差」として考えなければいけません。これは、設計をいくらうまくやってもコードの動きが絶対だ、というところと似ています。理論上、あれこれと言い訳をしても、現実で動いているものが絶対的な「現実」ですからね(ああ、量子力学的な説明はまた別なので、これは別途)。

垣根が低くなっている

巷の IoT, Maker Faire などの展覧会に行ってみると、ソフトウェアとは一変した世界があります。到底、ソフトウェア開発者としては到底追いつけないような個人的な出展があって、ちょっと尻込みしてしまうぐらいです。

しかし、一部の高度なロボット技術者の方は別として、ソフトウェア的にはあまり複雑なことをやっている訳ではないことが分かります。データシートというハードウェアの仕様を読み込まないとうまく作れないものがたくさんありますが、ある意味 .NET Framework のような大量なクラスライブラリと同じような形で、ソフトウェア設計の知識を導入すればもう少し扱いやすくなるのではないか?と思われるものもあります。そのあたりは、どちらが主という訳でもなく、相互に乗り入れが可能ではないか、と思っています。

私は Mesh のようなソフトウェアから手軽に扱えるもの、をあえて持っていません。これは、去年の夏から、ロボット戦車みたいなものを作りたかったので、もっとハードウェア寄りな考え方が必要だったので「ソフトウェアから扱う手軽さ」よりも「ハードウェアを扱う手軽さ」が自分にとって必要だったからです。このために、PIC やユニバーサル基板での半田付けなどを試していきました。

そこで、ちょうどよく、ハードウェアの扱いとソフトウェアの扱いとして自分の中で手足になっていきそうなものが、

  • Arduino Nano/Mini
  • Arduino Nano 3.1 (ATmega328P搭載)--販売終了

  • Raspberry Pi 2

商品画像1

  • mbed

です。組み立てのブロック周りとしては LEGO Mindstorms EV3 も良いのですが、いかんせん個人で買うには値段が高いので、あくまで教育用ですね。私の考えている「数を揃える」のが難しいのでちょっと後回しです。

 

これらの 3つは、ある程度値段も安く情報も手に入りやすいものです。通信の有線LAN/WiFi/Bluetooth も扱いやすくなっています。

かつては、PIC とユニバーサル基板で電子工作をしなければいけなかったことを考えると(参考用に、昔の電子工作用の本は数冊買いました)、だんぜん楽です。半田付けをしなくても、ブレッドボードで十分だし、固定したいのであればグルーガンで接着する方法もあります。ブレッドボードも安いものであれば200円前後ですから、試作ならばこれで十分です。かつ、ソフトウェア開発者からハードを触るだけだったら試作で十分です。

Lチカとモーターを回した後に進む場所

最初は、マイコンだけ買って Lチカする訳ですが、それも1時間ほどチカチカさせれば飽きてしまいます(クリスマスツリーとか、LED 制御も色々あるので実は奥が深いのですが)。じゃあ、その後は何か動かすものが必要かな、と思うと機械工学の知識が必要になってきます。アルミ板を曲げたり、切ったり、ドリルで穴をあけたりと専用の工具が必要になってきます。ソフトウェア開発の場合は、パソコンとスマートフォンぐらいで十分だったのですが、色々取り揃えないといけないわけです。

これが垣根になっています。

まあ、昨今の3Dプリンタを買って一気に工作環境を整えてしまうという方法もありますが、初期投資が大変ですよね。それに 3D ソフトウェアを覚えないといけません。

その前にお勧めなのが、既存のおもちゃを作ったり、改造したりすることです。イスペット社のグリッパーアームは5,000円程度と低価格なロボットアームです。関節にはサーボモーターじゃなくて普通のブラシモーターが使われていますが、試しに動かしてみるにはこれで十分でしょう。PC で動かせるバージョンもありますが、あえてコントローラだけ安価なものを買って Arduino などで動かしてみるとよいです。

http://ecx.images-amazon.com/images/I/51NmTEqRgEL.jpg

http://pds.exblog.jp/pds/1/201301/21/06/b0158406_134562.jpg

他にも、ダイソーの100円電車とか、電池で動くおもちゃは大抵改造が可能です。タミヤのギアボックスを買えばプラモデルの戦車を Bluetooth でラジコン化することもできます(Bluetooth には安い HC0-05 のシリアル通信を使えばよいのです)。

そういう安くて簡単なところから入るとよいでしょう。IoT というと、クラウドとセンサーの組み合わせがほとんどで、センサーの類も結構高価なものだったり、サーバーが月額で有料だったりしますが、いえいえ、そういうものを使う必要はありません。センサーの類は中華 Arduino と安価なセンサーの組み合わせで十分だし、サーバーは安価なホスティングサービスを借りて PHP で組む、というパターンでも良いわけです。

その先にあるもの

私のこれからになりますが、ハードウェアの世界を垣間見たら、ソフトウェアの世界に戻ってきましょう。ハードウェアの誤差やΔtの世界を、うまくモデル化してやるのがソフトウェアの役目です。ロボットを扱うための ROS でもよいし、組込みの RTOS を使ってもいいし、スマートフォンやタブレットからうまく制御できる仕組みを使ってもよいでしょう。カメラ機能と画像認識、各種センサーを組み合わせて人工知能的に動く iRobot のような制御も自作できると思います。これを数台作って相互通信させるところが目標です。

ハードウエア屋さんの良いところを持ってきて、うまくソフトウェアでモデル化して、それでハードウェアをうまく制御できるのが目標ですよね。来年こそ暇を見つけて蓮根に行きます。

カテゴリー: 開発 | ソフトウェア開発者がハードウェア/IoTをやる意味 はコメントを受け付けていません

大規模プロジェクトで MVVM を導入するときの注意

先日の JXUG の MVVM の補足 を書いたので、どうせだからもうちょっと突っ込んだ話題を残しておきます。Xamarin.Forms などを使う場合は、そんなに多くの人は関わらないでしょう。勉強会でもちらっと話しましたが、ひとりで MVVM すべてを作ったり、数名で MVVM を分け合ったりするのがほとんです。

せっかく V-VM-M と分かれているのだから、ある程度規模が大きくなれば分担しないといけません。最初に2つのパターンが考えられて、

  • 縦割りにして、画面ごとに MVVM を一貫して作る。
  • 横割りにして、V-VM-M を別々の人に割り当てる。

ことが考えられます。理想的には View をデザイナが受け持ち、VM をプログラマが、M をデータベーススペシャリストが担当するのが良いのですが、人数的な問題や規模的な問題、開発者のスキルのばらつき、遠地なのか近地なのか、同じ会社なのか別会社なのか、という様々な「政治的な」問題が絡み合って、理想的にはできません。となれば、プロジェクトマネジメントも含めて、何らかの妥協点を見つけて、ソフトウェア開発を進めていくことになります。まあ、それこそが「マネジメント」なのですが。

20160114_02

かれこれ5年前の話から

NDA 的には5年経っているので話しても大丈夫なのですが、まあ細かい話は無しにして、Silverlight + MVVM モデルで DataGrid をバインドの落とし穴 をやっていたころです。Silverlight が流行っていて、ActiveX コントロールではなくてブラウザ上に高機能なインターフェースを乗せようとしていた時期ですね。折しも、ちょうど MVVM が発表されたあたりで初期のブームだったのです。

経理系のシステムで、結構規模が大きくかかわっていたのはマネージャ等を除いても30名ぐらいいます。銀行系の大規模とは違うのですが、最近のスマートフォンの開発や、WEB開発に比べれば十分に大きく、そして混乱しやすい人数でもあります。そこに新技術としての Sliverlight と MVVM を同時にぶち込んだところに悲劇があったわけですが…まあ、仕事なのであれこれのトラブルがあってもそれなりに収めるわけなんですがね。

コンサルティング的な立場で入ったはずなのですが、複数の画面を担当することになりました。これもよくありがちで、技術的な点よりも人手でなんとかしようとする人海戦術パターンです。初期の頃から入っていれば設計的になんとかできたのでしょうが、結果的にみれば火消的な時期に入ってしまったため既存の設計に引きずられています。

MVVM パターンのライブラリが別会社だった

ある程度の画面数を超えると、人海戦術的にモノ作りをするパターンにおちいります。まあ、その手のプログラミング自体、労働集約的なところから抜け出せないので、仕方がないといえば仕方ないところです。いまだと、画面数をぐっと減らしてしまうか、共通化することで手数を減らしますよね。あと画面生成を自動化するとか。

そういうものはなくて(今でもそういうものがない会社はたくさんあります)、ひたすら手作業であ画面を作っていきます。Silverlight で画面を作るのですが、画面の部品は別の会社が作っていました。どうやら、ほかのプロジェクトで使ったらしい Sliverlight の画面部品を今回のプロジェクトに流用しようとしたらしく、そこに MVVM のバインドを入れ込んだという訳です。

いやいや、この別会社の部品がひどくて、そもそも Silverlight の Binding は元から揃っているにも関わらず、さらにそれをラップするような同じコントロールをひとつひとつ提供するという技をやってきました。当時、私も MVVM をやり始めたばかりなので、なんでこんな変な形になっているのかわからなかったのですが、あとから考えれば根本的に設計を間違えていたのでしょう。なんだかよくわからない Binding を付加したなんだかよくわからないラップされただけのコントロールで画面を組み合わせると、なんだかよくわからない形で画面部品にアクセスしないといけなくなります。だから、余計混乱するし、別会社のコントロールに不具合が発生するたびにあれこれと時間をロスしていました。

まあ、この手の共通部品は社内で作るのがベストですよね。あるいは、綿密なサンプルと設計をしたうえで社内に持ち込むのがベターです。

画面ごとに縦割りの MVVM は効率が悪い

画面部品を作っている別会社の MVVM の知識も足りないし、まして使う側の社内のメンバの MVVM の知識も足りません。そういう足りない上に、従来の画面ごとに担当を分けていったので更に混乱しています。よくわからない MVVM の知識は、それぞれ V-VM-M に分けて考えてインターフェースをきちっと作ると作業分担をして効率よく進めることができるのですが、従来通り縦割りに MVVM を作りました。このため V-VM-M のすべての知識がないと画面がうまく作れないわけです。

MVVM の社内講習会などはありませんでした。理由としては、MVVM が「解らなくても使いやすい」とされていた画面の部品を別会社から購入したことにより、それをクリアする話だったのですが、実際には VM の Binding が必須なコードになっているので、全然ラップされていません。というか、MVVM の構造を理解せずに画面を作るのであれば、MVVM パターン自体を導入する必要はありませんよね。本末転倒です。

  • MVVM を完全にラップする → 従来のイベントベースでコーディングできる
  • MVVM を使いやすくラップする → View の Binding 記述などを減らす

のどちらかの方法に進むべきだったのですが、結果的にどちらの悪いところを取ってしまって、MVVM をラップしているけども Binding がむき出しなコントロールを使う羽目に陥りました。そんなややこしいことをするならば、そのまま標準コントロールを使ったほうが楽です。

なので、MVVM をある程度の多人数で作る場合には、

  • 全員が MVVM の構造を理解して、画面ごとに縦割りで作る
  • MVVM の概要だけ理解して、インターフェースを守って V-VM-M で担当を分ける

というパターンになります。一見、V-VM-M で分けたほうが効率が良いように見えますが、現実は異なります。というのも、構造的に V-VM-M と分けえるとそれぞれの層に特化することはできるのですが、画面ごとに異なるドメインの知識が必要になってしまいます。このため、プロジェクト全体の業務知識がないと、VM が作れないというところに陥ります。

5名ぐらいで分担している場合は、プロジェクト全体の業務知識はさほどではないのですが、30名レベルになると結構なボリュームになります。分業するならば、業務知識はそれぞれで分担できるほうが効率がよいので、MVVM パターンを画面単位で作るのが良いのですが、これ先に言った通りに MVVM パターン全体の知識が必要となってしまいます。そういうジレンマが MVVM パターンにはあるということを理解して MVVM パターンを導入してください。

テスト可能な VM, M を作る

xUnit を使ってテスト可能なコードを書くことにより、コードの品質は上がるしリファクタリングがやりやすくなります。これも数名で書いている分にはちょこちょこと変えられる程度なのでしょうが、30名レベルになるとかなり混雑してきます。

先のプロジェクトでは Model の部分は WCF を通じて SQL Server にアクセスしていました。このため DBA チームがあって、SQL Server の構造にあった Model を返すのですが、これが曲者です。当然、View は Model に合うような構造になっていないので、VM あたりで構造を変えないといけません。いわゆるインピーダンスミスマッチというやつですね。

MVVM パターンのひとつの回避策としては、画面に即した Model をもうひとつ作り、データベースの Model をうまく組み合わせることです。多少冗長にはなりますが、VM が比較的に簡単になるので、これは有効な手段です。ただし、実装中にコロコロとデータベースのテーブル構造が変わるたびに画面用の Model も変更しないといけないためインテグレーション的に不利なところがあります。

もうひとつのパターンは、VM 内で変換する方法です。もとも View と Model のつなぎを担当する VM なのですから、業務ロジック(画面の操作ロジック)も含めて ViewModel をコーディングしていくことも可能です。ただし、これもデータベースのテーブル構造が変わると VM に手を入れなければならず、さらに VM 自体が画面と密着しているために、テーブル構造の変化が VM –> View にまで伝播してしまうことが多く再テストが大変です。

このようなことから、Model の自動テストだけでなく、ViewModel の自動テストも行えると、上記の2つのパターンの複雑さが多少改善されます。Model だけのテストだと VM の再テストが画面操作からしかできなくなって膨大な打鍵試験が発生します。この打鍵試験を減らすことが xUnit の目的なので、このままは本末転倒です。そこで、自動テストがしやすいように VM を作っているスタイルが必須になります。

まあ、担当した画面だけは自動テストっぽい VM の作りをしていたのですが、もともとそういう文化がない会社だったので、そのあたりはダメダメでした。Excel な設計書をきっちりと書いて、なぜか別の人がコーディングして、なぜか別な人がテストするという従来の方法でやっていたので炎上は必須です、といいますか、炎上していました。設計した会社とコーディングをしていた会社がかなり険悪な状態になっていたんですよね。設計する会社が親会社で、コーディングが子会社だったので猶さらその無茶ぶりな圧力は異常なところがありました。別会社ではありますがパワハラに近いものです。

で、オチはどうなったか?

この話、実はオチがあります。

設計するのが親会社、下請けが子会社だったのですが、ある日突然、子会社が親会社を買収することになってですね、プロジェクト間の立場が一転したのです。へこへことコーディングしていた会社が一夜明けて、設計してた会社の上に立つことになって、進捗会議なども一遍しました。なんだかなー、笑えます。会議のパワハラも、いままでのストレスも相まって、えらいことになってました。

で、私はどうなったかというと、元親会社の契約更新ができなくなって2か月程度でお払い箱ですよ。それ以来、その手の技術支援っぽい派遣には手を付けないことにしています スマイル 安い派遣で働くよりも、うちで地道に勉強していたほうが将来的に有利ってことですね。

カテゴリー: 開発 | 大規模プロジェクトで MVVM を導入するときの注意 はコメントを受け付けていません

JXUG で話した MVVM の活用の解説を

JXUGC #9 Xamarin.Forms Mvvm 実装方法 Teachathon – connpass
http://jxug.connpass.com/event/22840/

にて、田淵さんコードに「マサカリ」を投げまくって来ました。実は、ピアレビューという手法があって、できるだけきめ細かくコードをレビューしていくという手法があります。本来ならばインスペクションの形式を使うのですが、「人を攻撃する」のではなくて、コードのみをバシバシと叩いて向上させる方法ですね。コードを個人の成果物ではなくて、共同の成果物として仕立てあげることが最終目標です。

ひとまず、MVVM とは何ぞ?なり、Xamarin.Forms とは?という話はすっ飛ばして直接コードから入ったのは良かったと思います。ペアプロとか、M-VM-M の分業体制なんかはあんな感じで進めるとうまくいくでしょう。

私の発表したサンプルコードは以下にあるので、ざっと解説をつけておきます。

http://github.com/moonmile/JXUG

プロジェクトの構造

コードにはちょっとだけでも単体テスト用のコードを付けるようにしています。私が TDD を使う目的としては、テストの効率化の他に、「オブジェクト/ライブラリの使い方」を示すためにも作っています。そのクラスをどのように使うのか、かつ、クラスを使う時にどのようなインターフェースにしたら使いづらくはなならないか、の検討用に単体テストコードを使っています。
また、実際にクラスを動かすときのサンプルとして、いきなり Xamarin のコードでは大変(実機でしか動作しないパターンなど)なので、最初に WPF や Windows フォームを使った小さなサンプルを用意します。これでいくつか実験した後で、実際のモバイルコードに直す、あるいは組み合わせていいきます。こうすると、プロジェクトの最後のほうになって実機コードが複雑になっても、実験アプリによって少しテストをしながら、という手法が取れます。

  • /Test/MStopWatch.Test — 単体テストコード
  • /Test/MStopWatch.WPF — WPF による実験コード
  • /ViewModel/MStopWatch.VM — WPF/Xamarin.Forms の共通の VM
  • /ViewModel/MStopWatchFsharp.VM — 試しに F# で書き直した VM
  • MStopWatch — Xamarin.Froms の共通 PCL
  • MStopWatch.Droid — Android 用
  • MStopWatch.iOS — iOS 用
  • MStopWatch.WinPhone — Windows Phone 用

最後の、Droid/iOS/WinPhone は Xamarin.Forms を使うと手を入れずに済みます。シミュレータの場合は、Windows Phone が Hyper-V を使って一番早く動きます。

これで、MVVM パターンを形作るわけですが、いくつかの仕掛けが入っています。ストップウオッチのタイマの場所を何処に置くのかによって VM の書き方が変わります。勉強会のときにも強調しましたが、特に正解があるわけではありません。とあるコードやプロジェクトによって、「そこが最適であろう」という推測はできますが、実際に置かなければならないということではありません。ふさわしい場所がある、というだけです。

ストップウォッチタイマを ViewModel に置く

StopWatchVM.cs

public class StopWatchVM : BindableBase
{
...
    public void Start()
    {
        _now = DateTime.Now;
        _startTime = _now;
        _items.Clear();
        _loop = true;
        Mode = 1;
        _task = new Task(async () => {
            while (_loop)
            {
                await Task.Delay(100);
                _now = DateTime.Now;
                if (OnTimer != null)
                    OnTimer();
                else
                {
                    this.NowSpan = _now - _startTime;   // 画面を更新
                }
            }
        });
        _task.Start();
    }

タイマは、100 msec で動かしています。非常に遅いように見えますが、画面に表示させるときは 100 msec で十分で、Lap ボタンを押したときには改めて DateTime.Now から取っているので正確な時刻が取得できます。このあたりが、表示用の VM とデータとしての Model の違いになりますね。

ここではスレッド越えを許すためにコールバック関数 OnTimer を定義させていますが、Android でもコールバック関数は必要ではありませんでした。NowSpan プロパティと更新すると、INotifyPropertyChanged で画面に通知されます。

ストップウォッチタイマを Model に置く

StopWatchVM2.cs

public class StopWatchModel
{
...    public DateTime StartTime { get; set; }
    public void Start()
    {
        StartTime = Now = DateTime.Now;
        Items.Clear();
        _loop = true;
        _task = new Task(async () => {
            while (_loop)
            {
                await Task.Delay(100);
                Now = DateTime.Now;
                if (OnTimer != null)
                    OnTimer();
            }
        });
        _task.Start();
    }

Model のほうにタイマを用意した例です。この意図としては、計測機器の割り込みイベントや、外部から定期的に割り込みが入るようなパターンを想定しています。この場合、イベントが Model -> VM -> View へと数珠つなぎになるので、MVVM のまま使うよりも Rx のような方法を取ったほうが楽です。

ストップウォッチタイマを View に置く

ちょっとサンプルには書き忘れましたが、View 自身にタイマーを持たせることもできます。ストップウォッチの場合には、

  • 定期的に人の目に触れる View の時刻を切り替える
  • 内部で持つ時刻データを正確に持つ

の2つに分離できることがわかります。このため、内部データは Lap ボタンを押したタイミングで DateTime.Now を取得すればよいわけで、何も定期的に内部データを更新する必要はありません。なので、画面の表示させる View だけタイマー更新を使うという方法が考えられます。これはちょうどゲームの画面更新(スプライト機能など)を行う場合に、描画はキャラの更新タイミングに合わせるのではなくて、垂直同期にあわせるという方法ですね。たいていのゲームは 50fps 程度あれば十分なので、20 msec 程度で更新させれば十分です。
なので、Lap タイムは msec 単位で持っていても、画面更新は 20 msec 単位程度で十分ということです。

View 単体の更新では、WPF の場合は Storyboard の更新タイミングを使う方法もあります。これらは機会を見てサンプルに付け加えていきましょう。

VM を F# で書く

VM や Model に単体テストが入れば開発効率は非常に上がります。画面であれこれテストしたり、インタプリタで一時的なテストを繰り返すよりも、自動テストができる作り方にするのです。

F# で書いた VM の全文が次になります。これらは、単体テスト MStopWatch.Test でテストが可能です。

type StopWatchVM() = 
    let ev = new Event<_,_>()
    let mutable _mode = 0
    let mutable _startTime = DateTime()
    let mutable _now = DateTime()
    let mutable _nowSpan = TimeSpan()
    let mutable _items = new ObservableCollection<LapTime>()
    let mutable _loop = false
    let mutable _task:Task = null

    member this.StartButtonText 
        with get() = 
            match _mode with
            | 0 -> &quot;Start&quot;
            | 1 -> &quot;Stop&quot;
            | 2 -> &quot;Restart&quot;
            | _ -> &quot;&quot;
    member this.Mode 
        with get() = _mode
        and set(value) = 
            if ( _mode <> value ) then
                _mode <- value
                ev.Trigger(this, PropertyChangedEventArgs(&quot;StartButtonText&quot;))
                ev.Trigger(this, PropertyChangedEventArgs(&quot;Mode&quot;))
    member this.Items 
        with get() = _items
        and set(value) = 
            _items <- value
            ev.Trigger(this, PropertyChangedEventArgs(&quot;Items&quot;))
    member this.NowSpan
        with get() = _nowSpan
        and set(value) = 
            _nowSpan <- value
            ev.Trigger(this, PropertyChangedEventArgs(&quot;NowSpan&quot;))


    member this.ClickStart() =
        match _mode with
        | 0 -> this.Start()
        | 1 -> this.Stop()
        | 2 -> this.Reset()
        | _ -> ()
    member this.ClickLap() = this.Lap()

    member this.Start() = 
        _now <- DateTime.Now
        _startTime <- _now
        _items.Clear()
        _loop <- true
        this.Mode <- 1
        _task <- new Task( fun () ->
                while ( _loop ) do
                    ( Async.Sleep(100) |> Async.StartAsTask ).Wait()
                    _now <- DateTime.Now
                    this.NowSpan <- _now - _startTime
            )
        _task.Start()

    member this.Stop() = 
        _now <- DateTime.Now
        this.Items.Add( LapTime( this.Items.Count+1, _now, _now-_startTime))
        _loop <- false
        this.Mode <- 2

    member this.Reset() = 
        _now <- DateTime.Now
        _startTime <- _now
        this.NowSpan <- TimeSpan(0,0,0)
        this.Items.Clear()
        this.Mode <- 0

    member this.Lap() =
        _now <- DateTime.Now
        this.Items.Add( LapTime( this.Items.Count+1, _now, _now-_startTime))

    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member this.PropertyChanged = ev.Publish

VM を単体テストする

Model を自動テスト化すると頑丈なコードが書けます。さらに画面に近い VM をテストするコードを書くことも可能です。

/// <summary>
/// ラップを実行する
/// </summary>
 [TestMethod]
public void TestOneLap()
{
    var vm = new StopWatchVM();
    vm.Start();
    Assert.AreEqual(&quot;Stop&quot;, vm.StartButtonText);
    System.Threading.Thread.Sleep(1000);

    vm.Lap();
    Assert.AreEqual(&quot;Stop&quot;, vm.StartButtonText);
    // ひとつだけ追加されている
    Assert.AreEqual(1, vm.Items.Count);

    System.Threading.Thread.Sleep(1000);
    vm.Stop();
    Assert.AreEqual(&quot;Restart&quot;, vm.StartButtonText);
}

VM の構造を、UI/View から触るメソッドにうまく対応させてやれば、このようにユーザーのアクションをエミュレートできます。最近では Test Cloud のように実機/エミュレータを使って UI ベースのテストをすることも可能です。全ての UI イベントをエミュレートする必要はありませんが、おまかな動作がテストできると、実機を使った打鍵チェックを減らすことができます。

カスタムコントロールの利用

MVVM パターンを使うと、何にでも Binding を使って表そうとしてしまいますが、その分 View が冗長になってしまいます。勉強会でも話しましたが、本来は XAML をデザイナが記述し、コードビハイドをプログラマが記述するという分業ができる、というのが当時の売りでした。ですが、最初の頃に XAML をデザインするにはすべてをコードでみるしかないという状態に陥っていたため、XAML 自体もプログラマが書くようなスタイルになってしまいました。

Xamarin.Forms で、Button クラスを継承して Mode プロパティで表示が変えられるようなカスタムコーントロールを作ります。こうすることで、コントロール自体をより高機能な部品にすることができます。ここではボタンの表示を Mode プロパティで切り替えているだけですが、画像ファイルを張り付けたり、アニメーションをしたりすることができます。これらの動きを全て XAML で書くような Setter な方法もありますが、カスタムコントロールを作ってしまったほうが XAML の View が複雑にならなくて済みます。

public class CustomButton : Button
{
    public static BindableProperty ModeProperty =
        BindableProperty.Create<CustomButton, int>(
            p => p.Mode,
            0,
            defaultBindingMode: BindingMode.TwoWay,
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var uc = bindable as CustomButton;
                switch (newValue)
                {
                    case 0: uc.Text = &quot;開始&quot;; break;
                    case 1: uc.Text = &quot;停止&quot;; break;
                    case 2: uc.Text = &quot;リセット&quot;; break;
                }
                ((CustomButton)bindable).Mode = newValue;
            });
    public int Mode
    {
        get { return (int)GetValue(ModeProperty); }
        set { SetValue(ModeProperty, value); }
    }
}

WPF の場合は、DependencyProperty を使うため、若干 Xamarin.Forms と書き方が違うので注意が必要です。

class CustomButton : Button 
{
    /// <summary>
    /// モードを指定
    /// </summary>
    public static readonly DependencyProperty ModeProperty =
        DependencyProperty.Register(
            &quot;Mode&quot;,     // プロパティ名
            typeof(int),    // プロパティの型
            typeof(CustomButton),  // コントロールの型
            new FrameworkPropertyMetadata(   // メタデータ                   
                0,
                new PropertyChangedCallback((o, e) =>
                {
                    var uc = o as CustomButton;
                    if (uc != null)
                    {
                        int v = (int)e.NewValue;
                        switch ( v )
                        {
                            case 0: uc.Content = &quot;開始&quot;; break;
                            case 1: uc.Content = &quot;停止&quot;; break;
                            case 2: uc.Content = &quot;リセット&quot;; break;
                        }
                    }
                })));
    // 依存プロパティのラッパー
    public int Mode
    {
        get { return (int)GetValue(ModeProperty); }
        set { SetValue(ModeProperty, value); }
    }
}

ひとつの VM に複数の View を割り当てる

本来ならば、View と VM はきれいに分離するはずなので、動的に View をロードすることも可能です。インターネット経由で View(XAML)をロードすることも可能なのですが、これは結構難しいです。しかし、一定の View のパターンを持っていて、場合によって XAML 全体を切り替えるということができます。
この方法は、権限の違うユーザ(管理ユーザ、一般ユーザー)では画面をダイナミックに切り替える、ということができます。

Xamarin.Forms の MStopWatch プロジェクトには MyPage.xaml と MyPageV.xaml という2つの View があります。VM が同じであっても、インスタンスを生成するときに Page クラスを切り替えることができます。
ちなみに、MyPageV.xaml は、すべて View のコードビハイドにロジックを入れてしまった例です。

こんな感じで、ちょこちょこと業務ノウハウっぽいものも入れてある Xamarin.Forms の MVVM サンプルコードですので、ぜひ活用してください。

カテゴリー: Xamarin | JXUG で話した MVVM の活用の解説を はコメントを受け付けていません

Windows IoT Core ver.10.0.10586 のデバッグを有効にする

土曜日の .NET ラボ勉強会のために、やっとこさ Windows IoT Core の新しいバージョン 10586 を入れて Visual Studio 2015 からデバッグ実行しようと思ったのだけど「できない」。なぜか?と思いつつ探しつつ、結構ややこしい状態になっていたのでメモ書きしておきます。

必要な環境

Windows IoT – Console App Sample
http://ms-iot.github.io/content/en-US/win10/samples/ConsoleApp.htm

を見ると、Windows 10 の 10586 と Visual Studio 20156 Update 1 と Windows IoT Core の 10586 を揃えないといけないようです。結果的には揃えてバージョンが違ったときにはどうなるのかわかりませんが、揃えておきます。

が、揃えたもののデバッグ実行ができません。

新しい環境ではリモートデバッグ(msvsmon.exe)が実行されていない

デバッグメニューの中に「Start」ボタンが増えています。どうやら、普段はリモートデバッグができない環境で、この Start ボタンを押すとリモートデバッグできるようになるらしいのですが…

image

これを押しても、エラーがでます。

Failed to start the Visual Studio Remote Debugger. Make sure the remote debugger is copied to the device using Visual Studio For more information please visit www.windowsondevices.com

image

リンク先を見ても解決しないので、なんだかなー、という感じなのですが、あれこれと調べていくと、そもそもリモートデバッグ環境の /RDBG フォルダがないことが解ります。どうやら、それ以前の 10556 版にはあるのですが、なぜか最新版から落ちている。

解決策は、10556 から RDBG をコピーする

Getting Visual Studio Debugger running on latest build (10586) of Windows 10 IoT – TechNet Articles – United States (English) – TechNet Wiki
http://social.technet.microsoft.com/wiki/contents/articles/32718.getting-visual-studio-debugger-running-on-latest-build-10586-of-windows-10-iot.aspx

あれこれ探した挙句、TechNet に手順が書いてありました。元の 10556 版を入れて、RDBG をローカルにコピーして、最新の 10586 にコピーしなおして、再起動すれば ok。っていう手順なのですが、元のファイルなんかないよー、ってことで、http://1drv.ms/1QqAKz4 に 10556 版の RDBG を置いておきますので、USB メモリにコピーして Windows IoT Core にコピーしてください。直接、SD カードに書き込むと Win IoT から見えないので、RPi に USB メモリを差し込んでコマンドラインでコピーします。

このあと、7. Use the Web UI -> process -> Run command and run the below command:
c:\RDBG\RegisterOneCoreRdbg.cmd と c:\RDBG\StartOneCoreRdbg.cmd を動かします。以前、やったときは「Start」そのままで大丈夫な気がしたのですが、このレジストが必要な模様。

再起動すれば、プロセスに msvsmon.exe が現れて従来通りリモート実行できます。

image

 

MemoryStatus Console Application Sample のサンプルは C++ なのでリモート実行の手順がややこしくなっていますが、ユニバーサルアプリを C# で作れば、「プロジェクト」→「デバッグ」で、対象のリモートコンピュータを指定すれば ok です(デバイスのほうは Windows Phone になるので使いません)。

image

デバッグ実行

こんな風にミニ液晶を使って動かすことができます。

image

土曜日までに、サーボモーターと Lチカを準備する予定。

カテゴリー: Win IoT | 2件のコメント