おでんはじめました。

required ちくわぶ and 巾着,optional はんぺん.

SkiaSharpがXamarin.MacでもWPFでも動くよという話

Xamarin その1 Advent Calendar 2017 - Qiitaの22日目です。

去年のアドベントカレンダーXamarin.Forms+SkiaSharpで縦組みの記事を書いたので、今回はこれを.NET Standard化してAndroidiOSとXamarin.MacWPFで動かしてみたよというお話です。

作成したサンプルはGitHubに置いたのでよければビルドしてみてください。

SkiaSharpとは

SkiaSharpとはC#と.Netで使用できるオープンソースの2Dグラフィックスのライブラリです。

github.com

現在はPCLと以下のプラットフォームに対応しています。

こちらに詳しく書かれています。 qiita.com

去年のプロジェクト構成

1年前はSkiaSharpがSharedプロジェクトのみに対応で、PCLプロジェクトに対応していなかったので途中で対応してたけど書き換える能力がなかったのでXAML用に専用のカスタムビュー(TategumiView)を作成し、カスタムレンダラーをiOSAndroidにそれぞれゴリゴリ書いて実装しました。

f:id:masatoru:20171220193746p:plain

今回のプロジェクト構成

現在のSkiaSharpはPCLプロジェクトにも対応しているので、専用のNativeViewを作る必要がなくなり(カスタムレンダラーが不要になり)XAMLでSkiaSharp標準のビューコントローラ(SKCanvasView)を使って描画まで実装が可能です。Xamarin.Mac / WPFも同様にSkiaSharp標準のビューコントローラが用意されています。

さらに.NET Standardに対応しているので描画ロジックなどを.Net Standard対応のライブラリ化をすることでXamarin.MacWPFからも共通のライブラリとして使用することができます。

f:id:masatoru:20171220194316p:plain

去年のSharedプロジェクトに比べてかなりシンプルな構成になっています。

.NET Standard2.0とは

.NET Standardの詳しい説明は賢者の方々のブログを見ていただくとして、僕の唯一の理解は

NET Standard2.0 は .NET Core 2.0 と .NET Framework 4.6.1 と Mono と Xamarin.iOS と Xamarin.Android と Xamarin.Mac と UWP を(ほぼ)サポートしている(ただし、OS やランタイム依存の処理がある場合を除く)

ということです。

.NET Framework4.5以降と.net standard1.xが共存できるということに関してはよくわかりません勉強中です。

要するに.NET Standardは複数のOSでソースを共有できる仕組みなので、 この仕組みのおかげで最近のXamarin.Formsや.NET Framework4.6.1以上のXamarin.MacWPFから.NET Standard2.0のライブラリを使用することができるというわけです。

「ほぼ」とあるのは.Net Standard2.0で作成したライブラリで例えば.NET Framework4.6.1のライブラリを参照すると完全な互換性がない可能性があると警告が表示されるのでそういうことです。

warning NU1701: パッケージ 'xxxxx 2.0.1' はプロジェクトのターゲット フレームワーク '.NETStandard,Version=v2.0' ではなく '.NETFramework,Version=v4.6.1' を使用して復元されました。このパッケージは、使用しているプロジェクトとの完全な互換性がない可能性があります。

僕の数少ない経験上では完全に互換性があります。

各プロジェクトのポイント

各プロジェクト作成にあたり一番特筆すべきは.NET Starndard2.0対応のために.csprojファイルを編集する必要がない!(2017.12.21時点)ということです。

参考までに現在のバージョンを記載しておきますが、少なくともこのバージョン以降であればIDEが提供するプロジェクト作成の手順だけで作成できます。

.NET Standard2.0ライブラリ

UIはXamarin.Forms / Xamarin.Mac / WPFそれぞれで作る必要がありますが、反対にそれ以外に関しては(上記条件を満たせば)共通の .NetStandard2.0ライブラリとしてまとめられるということになります。

今回のサンプルでは(フォントファイルなどリソースの取得に関する箇所を除く)描画ロジックをすべてをライブラリ化して、Xamarin.Forms / Xamarin.Mac / WPFから使用しています。

作成方法はとても簡単で、プロジェクト作成→.NET Standard→クラスライブラリ(.NET Standard)から作成できます。

f:id:masatoru:20171221111721p:plain

Xamarin.Formsプロジェクト

プロジェクト作成に関しては特筆すべきことはありません。プロジェクト作成から、Cross-Platform→Cross-Platform App(Xamarin Forms)→.NET Standardを選択で作成できます。

f:id:masatoru:20171221112156p:plain

nugetからSkiaSharpとSkiaSharp.Views.Formsを取得し、XAMLにSKCanvasViewを配置します。

<views:SKCanvasView x:Name="canvas" PaintSurface="OnPaintSample"/>

SKCanvasViewのPaintSurfaceのイベントハンドラに描画処理を記述します。

void OnPaintSample(object sender, SKPaintSurfaceEventArgs e)
{
  // SKCanvasを取得する
  var canvas = e.Surface.Canvas;
  // 描画処理
  DrawText(canvas);
}

Xamarin.Macプロジェクト

Visual Studio For Macで、Macアプリ→Cocoaアプリを選択します。

SkiaSharpを使用する場合でのはまりどころとしては、ターゲットフレームワーク(オプション→全般)をXamarin.Mac Modernを選択するということです。 .Net Framework4.6.1を選択すると、パッケージの取得でWindowsと認識してしまうのか(SkiaSharp.Mac.dllではなく)SkiaSharp.WPF.dllを持ってきてしまうので(仕組みがよくわからないので誰か教えてください)。

f:id:masatoru:20171221113820p:plain

nugetからSkiaSharpとSkiaSharp.Viewsを取得します。

Storyboardを使用してSKCanvasViewを配置してプロパティを関連付けます。

  1. Main.Storyboardをダブルクリック(XCodeが開く)
  2. ViewControllerにCustomViewを配置する
  3. CustomClassをSKCanvasViewにする
  4. 右上のAssitant editorを開く
  5. 作成したCustomViewをCtrlキーを押しながらViewController.hにドラッグ&ドロップする
  6. Nameをcanvasにする

f:id:masatoru:20171221121513p:plain

上記作業後にXCodeを保存するとコードビハインド(ViewController.designer.cs)にコントロールとプロパティが関連付けされていることがわかります。

[Register ("ViewController")]
partial class ViewController
{
    [Outlet]
    SkiaSharp.Views.Mac.SKCanvasView canvas { get; set; }
    
    void ReleaseDesignerOutlets ()
    {
        if (canvas != null) {
            canvas.Dispose ();
            canvas = null;
        }
    }
}

Xamarin.Forms同様に、ViewController.csにイベントハンドラを記述します。

// 画面が初期化される時に呼ばれる
public override void ViewWillAppear()
{
  base.ViewWillAppear();

  // SKCanvasViewにイベントハンドラを関連付ける
  canvas.PaintSurface += OnPaintCanvas;
}
private void OnPaintCanvas(object sender, SKPaintSurfaceEventArgs e)
{
  // SKCanvasを取得する
  var canvas = e.Surface.Canvas;
  // 描画処理
  DrawText(canvas);
}

Storyboardの操作方法などXamarin.Macのことが詳しく書かれているEssential Xamarinをぜひご一読ください。

WPFプロジェクト

Windowsクラッシックデスクトップ→WPFアプリ(.NET Framework)→.NET Framework4.6.1以上で作成します。

XAMLへの記述以降はほとんどXamarin.Formsと同じです(クラス名が少し違うので注意)。 nugetからSkiaSharpとSkiaSharp.Viewsを取得し、XAMLにSKElementを配置します。

<views:SKElement x:Name="canvas" PaintSurface="OnPaintSample"/>

SKCanvasViewのPaintSurfaceのイベントハンドラに描画処理を記述します。

void OnPaintSample(object sender, SKPaintSurfaceEventArgs e)
{
  // SKCanvasを取得する
  var canvas = e.Surface.Canvas;
  // 描画処理
  DrawText(canvas);
}

画面キャプチャ

最後に今回作成したサンプルの画面キャプチャをのせておきます。

f:id:masatoru:20171221103637p:plain

f:id:masatoru:20171221130955p:plain

f:id:masatoru:20171221130554p:plain

f:id:masatoru:20171221103656p:plain

最後に

.NET Standard2.0がより一般的になることでWinFormsなどのレガシーなC#の資産をXamarinやMacで再利用する流れがそろそろより一層広がればいいなと思います。

今後の課題として、(今回はすべてコードビハインドで書いていますが)WPFやXamarinでPrismやReactivePropertyなどのフレームワークを使用した場合に、MacCocoa Bindingなど別のフレームワークを利用することになるので、全体としてどういう構成(落としどころ)が望ましいのか引き続き勉強したいです。

以上です。 今宵もハッピーアドベント!(言わない?)

追伸、 あ!サンプルのボタンがどれも機能してないです(汗。随時実装していきますので。

明日は tafuji さんです。よろしくお願いします!