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

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

の続きです。

Xamarin.Forms は 2.0 からユニバーサルアプリ(UWP)を扱えるようになったハズなので、これを作ってみます。

Adding a Universal Windows Platform (UWP) App – Xamarin
https://developer.xamarin.com/guides/xamarin-forms/windows/getting-started/universal/

を参考にして、

– App.xaml.cs に Xamarin.Forms.Forms.Init(e); を追加
– MainPage.xaml に using:Xamarin.Forms.Platform.UWP を追加
– MainPage.xaml.cs に LoadApplication(new BoxDragXF.App()); を追加

すると動きます。

Android や iOS と同じようにドラッグさせたいので、Windows Phone 8.1 と同じように BoxExRenderer クラスを作ります。

[assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.UWP
{
    class BoxExRenderer : Xamarin.Forms.Platform.UWP.BoxViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
            this.ManipulationDelta += BoxExRenderer_ManipulationDelta1;
            this.ManipulationMode = Windows.UI.Xaml.Input.ManipulationModes.All;
        }

        private void BoxExRenderer_ManipulationDelta1(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)
        {
            var el = this.Element as BoxViewEx;
            el.OnManipulationDelta(el, new BoxDragXF.ManipulationDeltaRoutedEventArgs(el, e.Delta.Translation.X, e.Delta.Translation.Y));
        }
    }
}

ほとんど、WinPhone 版と同じですね。
これが動くと、Windows IoT Core の UWP も同じように動くので、自作タブレットを作ったり、Surface を使った場合でも共通化できます。Android/iPhone/Surface の UI が Xamarin.Forms で一括で作ることができます。

動かしたときの動画はこちらです。

サンプルコード

https://github.com/moonmile/BoxDrag

にあります。

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

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

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

の続きです。

最後は Windows Phone で動かします。
もともとの Xamarin.Forms のテンプレートを使っているので、Windows Phone 8.1 版になっていますが、おそらく Windows 10 用の UWP プロジェクトでも動くはずです。

Windows Phone のレンダラーを作る

BoxDragXF.WinPhone プロジェクトに BoxExRenderer クラスを追加します。Windows Phone/Mobile が一番簡単で(簡単なようにしたので)、そのまま ManipulationDelta メソッドを渡せば ok です。渡すときの引数は、疑似的なほうの ManipulationDeltaRoutedEventArgs オブジェクトなので、そこだけコンバートします。

 [assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.WinPhone
{
    class BoxExRenderer : Xamarin.Forms.Platform.WinPhone.BoxViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
            this.ManipulationDelta += BoxExRenderer_ManipulationDelta;
        }
        
        private void BoxExRenderer_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
        {
            var el = this.Element as BoxViewEx;
            el.OnManipulationDelta(el, new BoxDragXF.ManipulationDeltaRoutedEventArgs(el, e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y));
        }
    }
}

実行してみる

Hyper-V のエミュレーターで動かしてみます。これで、同じ Xamarin.Forms を使って、三機種(Android/iPhone/Windows Phone)で同じようにコントロールをドラッグできることがわかります。

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

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

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

の続きです。iOS 版は比較的簡単にできたので、Xamarin.iOS 版と Xamarin.Forms 版をいっぺんに作っていしまいます。

サンプルコード

https://github.com/moonmile/BoxDrag にあります。

Xamarin.iOS でドラッグを実装する

iOS のみで動くバージョンは、BoxDragApple.iOS というプロジェクトのほうです。以前は、ViewController にタップイベント(TouchesBegan等)を送っていたのですが、今回は Android と動きを合わせるために自前のコントロール BoxViewEx を作ります。

partial class BoxViewEx : UIView
{
    public BoxViewEx (IntPtr handle) : base (handle)
    {
    }
    public override void TouchesBegan(NSSet touches, UIEvent evt)
    {
        base.TouchesBegan(touches, evt);
        UITouch touch = touches.AnyObject as UITouch;
    }
    public override void TouchesMoved(NSSet touches, UIEvent evt)
    {
        base.TouchesMoved(touches, evt);
        UITouch touch = touches.AnyObject as UITouch;
        var newPoint = touch.LocationInView(this);
        var previousPoint = touch.PreviousLocationInView(this);
        nfloat offsetX = previousPoint.X - newPoint.X;
        nfloat offsetY = previousPoint.Y - newPoint.Y;
        this.Frame = new CoreGraphics.CGRect(
            this.Frame.X - offsetX,
            this.Frame.Y - offsetY,
            this.Frame.Width,
            this.Frame.Height);
    }
    public override void TouchesEnded(NSSet touches, UIEvent evt)
    {
        base.TouchesEnded(touches, evt);
    }
}

ちょっとはまりどころがあって、Xamarin.iOS でユーザーコントロールを作るときは、

  1. Storyboard で、BoxViewEx を記述してコントロールを作る。
  2. コードのほうで、partial 付きの BoxViewEx クラスが作られる。
  3. 生成された BoxViewEx.cs を使う

という手順が必要です。先に BoxViewEx クラスを作ったり、partial 無しで作ったのですがうまく動きません。おそらく Storyboard のデザイナで自前コントロールを作ったときに、何らかの ID が割り振られるのでしょう。

BoxViewEx クラスは、UIView を継承しただけなので普通に Storyboard で扱えます。

タッチイベントによるドラッグは、TouchesMoved メソッドをオーバーライドすれば ok です。移動したときの差分を取って Frame プロパティに位置を設定します。

Xamarin.Forms on iOS でドラッグを実装する

iOS オンリー版ができたので、Xamarin.Forms で動くようにします。
Forms のある PCL プロジェクトは Android で作ったときのままにしておいて、BoxDragXF.iOS プロジェクトに、
BoxExRenderer クラスを追加します。

using BoxDragXF;
using BoxDragXF.iOS;
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Foundation;
using UIKit;

 [assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.iOS
{
    class BoxExRenderer : BoxRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
        }
        public override void TouchesBegan(NSSet touches, UIEvent evt)
        {
            base.TouchesBegan(touches, evt);
            UITouch touch = touches.AnyObject as UITouch;
        }
        public override void TouchesMoved(NSSet touches, UIEvent evt)
        {
            base.TouchesMoved(touches, evt);
            UITouch touch = touches.AnyObject as UITouch;
            var newPoint = touch.LocationInView(this);
            var previousPoint = touch.PreviousLocationInView(this);
            nfloat dx = newPoint.X - previousPoint.X;
            nfloat dy = newPoint.Y - previousPoint.Y;
            var el = this.Element as BoxViewEx;
            el.OnManipulationDelta(el, new ManipulationDeltaRoutedEventArgs(el, dx, dy));
        }
        public override void TouchesEnded(NSSet touches, UIEvent evt)
        {
            base.TouchesEnded(touches, evt);
        }
    }
}

書き方は、Android の時と同じですね。iOS の場合は、移動距離の差分がすでに計算されているので、そのまま OnManipulationDelta メソッドを呼び出せば ok です。

実行してみる

これを動かしてみたのが、この動画です。

Renderer を切り替えるだけで、Android と iOS が同じように Xamarin.Forms で動くことがわかります。似たような感じで、独自のコントロールを作って、PCL 側で共通で動かすということができる訳です。

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

ふと GoF のデザインパターンを再考しておく

懐かしの パタンランゲージ を眺めつつ、ふと 再考: GoF デザインパターン が気になったのでさらっと流しておこう。

[amazonjs asin=”4306041719″ locale=”JP” title=”パタン・ランゲージ―環境設計の手引”]

そもそもが GoF 発祥自体が 90年頃で、日本語の書籍は Java が発生した頃と前後してしまっているので、オブジェクト思考ブーム、UML, パターンのブームよりも随分前からある。C# や Java よりも前になので、今さら再考する意味もないのではないか?と思われるのだが(そもそも Qiita の「再考~」の記事自体が 2014 年だ)、歴史経緯から考えるのと、GoF の反省(あるいはドツボ?)からできあがった現在の C#, Java の言語拡張をみていくと、これらのパターンが言語仕様やライブラリに含まれてしまっていることが分かる。逆に言えば、.NET ライブラリを使うときに「?」とか「何でこんなややこしい使い方になっとるんやッ!!!」というときには、ここの GoF を踏まえたライブラリづくりがあって現在があるという経緯をを知ると、なんとなく納得がいくと思う。良くも悪くも、最適化(準かもしれないけど)されつつあるということだ。

それは、アルゴリズムのテクニックとして、双方向リストやらソートやらが流行っていた時期があって、今だと、List<> や Sort() で十分な訳で、実用的にはそれでよいし、でも知識として知っておくとその延長がわかるという仕掛けが含まれている。

というわけで、GoF を現在の C#, .NET クラスライブラリと合わせてさらっていこうと思う。

Abstract Factory

Factory パターンの汎用版で、C# だとストリームを作るときに使ってる。UWP の File.Open あたりがそれ。

20160111_03

Builder

なんとか Builder が好きな人が多くて StringBuilder とかに出てくる。

20160111_04

Factory Method

いわゆるファクトリーパターンで、Java だと結構頻発する。C# だと TaskFactory.StartNew にみられる。new するときにパラメータが多くなるのを嫌がったパターンと、DOM で Parent を Document にするときに作るときに使われる。

Abstract Factory と比べると、Factory/Creator クラスが二重化されていることがわかる。

20160111_31

Prototype

見かけたことがない…というか、普通にインターフェース。

20160111_06

Singleton

static で十分やろ、ってな感じで使われている。

20160111_32

Adapter

継承を使ってクラスを拡張する代わりに、あらかじめ拡張するメソッドをインターフェースにしておく手法。メッセージングのときに、後付けで必要なインターフェースを追加するときに使う。データ自体と操作するメソッドを分離するときによこう使われる。が、最初からわかっていれば、Interface で十分な気も。

20160111_08

Bridge

今だと普通に委譲を使うのだが、GoF の発祥自体が Java, C# よりも前なのだ。C++ と同時期だと思えば、そういうパターンがあっても仕方がない。継承地獄を避けるためにつくられる。今となっては常識になったパターン…だが、.NET クラスライブラリには歴史的にか継承地獄が頻発する。

20160111_10

Composit

今でいえば、DOM そのもの。ツリー構造を使いたいときは、このパターンを使うか、DOM を使う。

20160111_11

Decorator

元クラスがぴよぴよクラスのときに、継承して拡張するか、メンバにして拡張するか、のパターン。

20160111_12

Facade

残念ながら .NET クラスライブラリにはこの発想はない。既定クラスからばらばらとメソッド&プロパティが公開されている。Android の ViewGroup へのキャストがそれ。あるいは、ごちゃごちゃになったクラスをクライアントから見れば整理するパターン。ユーティリティクラスがそれ。

20160111_13

Flyweight

データベースのコネクションプーリングで使われている。

20160111_14

Proxy

いわゆる、アスペクト指向のパターンなのだが、C# の UI コントロールのメソッドが常に override と event を持っているのが proxy の役割になっている。あらかじめ仕込んでおかないといけないのがミソ。

20160111_15

Chain of Responsibility

複数のオブジェクトを連ねていくパターン。クリップボードのチェーンや、昔の常駐プログラムのキーチェーンで使われていた。いわゆる協調パターン。

20160111_16

Command

MVVM パターンにも出て来るおなじみの、ICommand インターフェースになる

20160111_17

Interpreter

いわゆる、式木だが、普通の人は使わない。動的にスクリプトを使うとか一部の関数型言語マニアが使う。F# だと簡単に作れる。

20160111_18

Iterator

いわゆる C++ のイテレーターだし、C# の IEnumerator だ。今だと、LINQ を使ったほうが楽。

20160111_20

Mediator

リモートメッセージで使われる SOAP とか JSON とかがそれにあたる。DCOM, CORBA を知っていればシリアライズという形で現れる。

20160111_23

Memento

いわゆる、Undo/Redo に使われるパターンなのだが、これを使った実装は見たことがない。そもそも Undo/Redo 自体が難しくて、今だとメモリのある限り丸ごと保存してしまう。例えば、Visual Studio をコンテナとして Undo/Redo メソッドとして利用する、ような感じ。

20160111_24

Observer

これも MVVM パターンで使われる ObservableCollection<T> の元ネタになる。

20160111_25

State

switch や if で実装しないために State パターンを使う。インターフェースを作っておくと、無限種類でオブジェクトを作れるので、State パターンは便利なのですよ。5 個程度ならば swtich、それ以上であれば State パターンを使えばよい。

20160111_26

Strategy

かつて openssl で使っていたパターンで、暗号コードの構造体を切り替える。ちなみに openssl は C言語だ。部分的に切り替えるのが State で、ごっそり切り替えるのが Strategy になる。ただし、.NET クラスライブラリに Strategy という単語はでてこなくて、Factory あたりでごっそりクラス名を切り替えたりする。ストリームクラスあたりがそれ。

20160111_27

Template Method

interface だったり、abstract だったりするが、言語仕様として Interface は内部フィールドを持たず、Abstract は内部データを持てる、という違いがあったりする。言語の都合なのと、IDE の補完機能を使って自動メソッドを使うときに便利なパターンだ。

20160111_28

Visitor

このあたりは、ほとんど delegate と一緒だったり、Action<>, Func<> で大丈夫だったりするが、異なるスレッドの場合はディスパッチが発生するため、このパターンが使われる?たぶん、C# だと Invoke になると思う。

20160111_29

 

 

改めて見直すと、コンテナとオブジェクトのパターンが多い。「パターン」自体が、頻出されるモデルという意味合いなので、かつてのオブジェクト指向以前であれこれと悩んだ末に出てきた定型パターンと考えられえる。定型だからこそ、現在の言語仕様やライブラリではそれらのパターンを取り入れてしまって、外側に出ないようにする。そういう意味では、GoF のパターンは形骸化しているといってよい。が、新しいプログラム環境で作ろうと思ったり、もっと大きい範囲でパターンを考える(ロボット制御の ROS とか、スマートフォンや PC などのメッシュ的な通信とか)ときには、これらのパターンが有効に働くので、知識として覚えておくとよいだろう。

おまけ

astah で作った GoF の図を置いてきます。

参考先

[amazonjs asin=”B000SEIBB8″ locale=”JP” title=”Design Patterns: Elements of Reusable Object-Oriented Software (Adobe Reader)”]

 

カテゴリー: 開発 | ふと GoF のデザインパターンを再考しておく はコメントを受け付けていません

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="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:BoxDragXF;assembly:BoxDragXF"
             x:Class="BoxDragXF.MainPage">
  <StackLayout>
    <Button Text="初期化" x:Name="button1" ></Button>
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
      </Grid.ColumnDefinitions>
      <Button Text="上" x:Name="buttonUp"></Button>
      <Button Text="下" x:Name="buttonDown" Grid.Column="1"></Button>
      <Button Text="左" x:Name="buttonLeft" Grid.Column="2"></Button>
      <Button Text="右" x:Name="buttonRight" Grid.Column="3"></Button>
    </Grid>
    <AbsoluteLayout
      x:Name="layout"
      BackgroundColor="Blue" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
      <local:BoxViewEx
        BackgroundColor="Red"
        x:Name="box1" WidthRequest="60" HeightRequest="60" AbsoluteLayout.LayoutBounds="100,100,60,60" />
    </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="sender"></param>
    /// <param name="e"></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="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:text="初期化"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button1" />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/linearLayout1">
        <Button
            android:text="上"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonUp" />
        <Button
            android:text="下"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonDown" />
        <Button
            android:text="左"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonLeft" />
        <Button
            android:text="右"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonRight" />
    </LinearLayout>
    <TextView
        android:text="GetX,Y: 000,000"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView1" />
    <TextView
        android:text="RawX,Y: 000,000"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView2" />
    <RelativeLayout
        android:minWidth="25px"
        android:minHeight="25px"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/relativeLayout1">
        <TextView
            android:text="Box"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:id="@+id/box1"
            android:layout_marginTop="100dp"
            android:layout_marginLeft="100dp"
            android:background="#ffbb33" />
    </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 "main" 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="sender"></param>
    /// <param name="e"></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 の関係 はコメントを受け付けていません