おでんはじめました。

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

Xamarin.Macでアプリ起動時にコードでStoryboardを切り替える方法

Xamarin.Macの起動時に複数のストーリーボードからコードで選択する方法がわからなかったのでそのメモ。

デフォルトで起動時に表示されるインターフェイスはinfo.plistのMain Interfaceで指定されています(さらにInterface Builderの[is Initial Controller]にチェックが入っているWindow Controllerが表示対象)。

f:id:masatoru:20170828125045p:plain

この起動時に表示されるインターフェイスをコードで変更する方法です。

そもそもストーリーボードの使い方がよくわかってない*1のでストーリーボードの追加方法から。

  • ストーリーボードを追加する

    • ファイル→新しいファイル→ストーリーボードでSub.storyboardを作成する
    • 作成したSub.storyboardをInterfaceBuilderで開いてWindowControllerを追加する
    • InterfaceBuilderでWindowControllerを選択→IdentityInspectorを選択→StoryboardIDにMainWindowと入力する
    • 保存する

f:id:masatoru:20170828173725p:plain

  • AppDelegate.csでDidFinishLaunchingメソッドをオーバーライド*2して以下を追加する*3
public override void DidFinishLaunching(NSNotification notification)
{
    // 呼んじゃだめ!!
    //base.DidFinishLaunching(notification);

    // StoryBoardを呼び出し
    var storyboard = NSStoryboard.FromName("Sub", null);

    // StoryBoardからViewControllerを呼び出し
    var controller = storyboard.InstantiateControllerWithIdentifier("MainWindow") as NSWindowController;

    // 表示
    controller.ShowWindow(this);
}

以上っす。

*1:Ctrlキー押しながらdragとか無理っと指つりながら作業してます。

*2:インテリセンスで入力するとbase.DidFinishLaunching()も追加されるけどこれは呼んじゃだめ(「陰」のP24参照)

*3:コードは Working with Storyboards - XamarinのLoading from codeそのまま。

iOSのSimulatorからParallels越しのVisualStudioで起動したASP.NETを参照する方法

バックエンドのテストをしていて、Parallels越しのWindowsのVisualStudioで立ち上げたASP.NETIIS)を、XamarinStudioでビルドしたiOSアプリから参照する方法がわからなかったので調べてみました。

www.barelycompetent.co.za

VisualStudioでASP.NETを起動するとローカルの任意のポート(例:http://localhost:10897/)で実行されます。 このポートはWindowsの外側からは参照できないのでMac側のiOSのSimulator(やAndroidのEmulator)からは使用することができません。そこでSharpProxyというソフトを使ってポート番号を切り替える(すり替える?)ことで実現しています。以下その手順になります。

  • WIndows側でSharpProxyをクローンしてVisualStudioでビルド&起動します。
git clone https://github.com/jocull/SharpProxy.git
  • Windows側のVisualStudioでASP.NETを起動してポート番号を確認します(例:http://localhost:10897/)。

  • WindowsIPアドレスをipconfigで調べます(例:172.xx.xx.xx)。SharpProxyでも[Your IP Address]で表示されています。

  • SharpProxyの[InternalPort]に起動したASP.NETのポート番号(10897)を入力して[Start]をクリックします。

f:id:masatoru:20170404205444p:plain

これでiOS側からParallels越しのWindowsASP.NETを参照できます。

他にもっとスマートな方法があればぜひ教えて下さい。

追伸: Xamarinはいいぞ!!(今回はXamarinに限った話じゃないけど)

Xamarin.Formsで縦組み

Xamarin Advent Calendar 2016(その2)の21日目です。

はじめに

10月にJXUGC #17 お前の Xamarin アプリを見せてみろ!に登壇させていただいたのでそのまとめです。

goo.gl

なぜXamarin.Formsで縦組みか

最近のHTML+CSS縦組み機能が充実しており大抵のブラウザで表示可能です。ただしブラウザによってその実装はまちまちで、端末に依存することなく同じイメージで表示したいというのが始めたきっかけです。自分で実装することで例えば左にルビを表示する(そんな人はほとんどいないでしょうけど)など自由に機能追加できることが(当たり前ですが)魅力の一つです。

SkiaSharp

縦組みの描画は2Dの描画フレームワークSkiaSharpを使用しています。Xamarinが推奨してるわりに日本語情報は少ないのですが、最近になって記事も増えてきたので今後に期待しています。今回のサンプルではRendererを使ってAndroidiOSをそれぞれ実装していますが、最新バージョンではXamarin.Formsに対応しています。ということで早く書き直さないと(汗)。

日本語フォント

日本語フォントを表示するためにIPAIPAex明朝フォントを使用しています。サイトからダウンロードしたipaexm.ttfをリソースフォルダ(AndroidはAssetsフォルダ、iOSはResourcesフォルダ)へ追加しています。

縦組みエンジン(Hanako)

タグの読み込みと組版(Compose)はHanakoという縦組みエンジンで実装しています。この中で行末の折り返しやページごとに組版をおこなうといった処理をおこなっています。

縦組み描画コントロール(TategumiView)

組版されたページをTategumiViewコントロールで表示しています。SkiaSharpで用意されたCanvasにDrawTextで1文字ずつXYを指定して描画しています。

//文字の描画
static void drawHonbunChar(SKCanvas canvas, SKPaint paint, WaterTrans.TypeLoader.TypefaceInfo tfi, HKWaxBase ch)
{
  //文字をGlyphIdに変換
  var glyphs = stringToVerticalGlyphs(ch.Char, paint, tfi);
  //描画の文字サイズを設定
  paint.TextSize = ch.FontSize;
  //本文を描画
  drawText(canvas, glyphs, ch.DevX, ch.DevY, paint);
}
static unsafe void drawText(SKCanvas canvas, ushort[] glyphs, float x, float y, SKPaint paint)
{
  paint.TextEncoding = SKTextEncoding.GlyphId;
  fixed (ushort* p = glyphs)
  {
    canvas.DrawText((IntPtr)p, glyphs.Length * 2, x, y, paint);
  }
}

縦組み用の字形

上記の処理でstringやcharではなくGlyphIdで描画していますが、拗促音(ゃゅょっ)や括弧は縦組み用の字形に切り替える必要があり、これは@espresso3389さんが作った(というかこのために作ってもらった)WaterTrans.TypeLoaderを使用していて、ここでstringをGlyphIdに変換しています。

//縦組み用の文字に変換している
static ushort[] stringToVerticalGlyphs(string text, SKPaint paint
  , WaterTrans.TypeLoader.TypefaceInfo typefaceInfo)
{
  ushort[] glyphs;
  paint.Typeface.CharsToGlyphs(text, out glyphs);
  var conv = typefaceInfo.GetVerticalGlyphConverter();
  for (int i = 0; i < glyphs.Length; i++)
  {
    if (conv.CanConvert(glyphs[i]))
      glyphs[i] = conv.Convert(glyphs[i]);
  }
  return glyphs;
}

XAML

作成したTategumiViewをXAMLで配置してます。WidthやHeightをBindingしていてViewの向きが変わると再処理(Compose)→描画(View)処理がおこなわれます。

<local:TategumiView VerticalOptions="FillAndExpand"
 HorizontalOptions="FillAndExpand"
 Width="{Binding TateviewWidth.Value}"
 Height="{Binding TateviewHeight.Value}"
 CurrentPage="{Binding CurrentPage.Value}"
 PageIndex="{Binding Path=PageIndex.Value,Mode=TwoWay}">

データ形式

HTML形式で<p>タグと<ruby>タグを読み込むようにしています。タグのパースはHtmlAgilityPackを使用しています。

<p><ruby>私<rt>わたくし</rt></ruby>はその人を...</p>

サンプルデータ

青空文庫から直接スクレイピングしようとしたのですが、文字コードやタグの使い方が統一されていなかったり(HtmlAgilityPackのPCLではXPATHが使えなかったり)できれいに取得できなかったので、元のHTMLからルビと本文だけのHTMLに加工しました。作成したHTMLリソースフォルダ(AndroidのAssetフォルダ、iOSのResoucesそれぞれ)に置いて読み込む形になっています。

表示!!

目次から書名をタップするとこんな感じで表示されます。左右のタップでページ移動します。

f:id:masatoru:20161220184545p:plain

GitHub

というわけで(不十分なところが多々ありますが)今回の縦組みサンプルをGitHubに上げました(現在Android版のみ動作します。iOSでなぜか動かないので調査中です)。奇特な方々の優しいダメだしを頂ければ大変幸いです。

最後に

今年もXamarinおよびXamariの関係者の方々と楽しく時間を過ごせました。ありがとうございました。来年もより一層よろしくお願いします。よいお年を。

.Net Framework2.0から4.6.1に上げたけどSystem.BadImageFormatExceptionで動かない

昔のプログラムを.Net Framework2.0から4.6.1に上げたけどSystem.BadImageFormatExceptionとかで動作しない。

下記をチェックしたけどどれも該当しない。

例外のトラブルシューティング : System.BadImageFormatException

わかったことはメインのexeのapp.configに

<supportedRuntime version="v2.0.50727"/>>

とあったのでこれを下記に書き換えたら動いた。

<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>

すっかりはまってしまった。

DbSetのAddをMockでTestする

書きなれないブログでタイトルからつまづいてますが。。。
EF6をServiceとMockを使っていい感じでテスト&モッキューできないかなと。
元ネタはなかじさんのブログ。

blog.nakajix.jp

ここの記事にあるEFのTestをMoqを使ってやる記事があります。

msdn.microsoft.com

この中の「Testing query scenarios」が、サンプルデータをServiceを通してGetAllBlogsで取得するとorder byされて返ってくるというTESTです。

[TestMethod] 
public void GetAllBlogs_orders_by_name() 
{ 
    var data = new List<Blog> 
    { 
        new Blog { Name = "BBB" }, 
        new Blog { Name = "ZZZ" }, 
        new Blog { Name = "AAA" }, 
    }.AsQueryable(); 

    var mockSet = new Mock<DbSet<Blog>>(); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(0 => data.GetEnumerator()); 

    var mockContext = new Mock<BloggingContext>(); 
    mockContext.Setup(c => c.Blogs).Returns(mockSet.Object); 

    var service = new BlogService(mockContext.Object); 
    var blogs = service.GetAllBlogs(); 

    Assert.AreEqual(3, blogs.Count); 
    Assert.AreEqual("AAA", blogs[0].Name); 
    Assert.AreEqual("BBB", blogs[1].Name); 
    Assert.AreEqual("ZZZ", blogs[2].Name); 
} 

次にServiceでAddBlogすると追加されるTestをおこなうために下記を追加。
ここでなぜか(当然?)4件にならない、ということで本題に入ります。

service.AddBlog("CCC", "http://blogs.msdn.com/adonet");
var blogs = service.GetAllBlogs(); 
Assert.AreEqual(3, blogs.Count); //4件にならない

ServiceでAddBlogしたときに追加されるようにしたいわけですが、見つけたのが下記のAnswerの箇所。
要するに、DbSetでAddされる時のMockがないので追加されないとのこと。

stackoverflow.com

...Callbackがよくわからないのでちょっと勉強してから。

github.com

というわけで試行錯誤した結果一番最後の行ができたやつ。

//コピペのままではコンパイルされず
//mockSet.Setup(m => m.Add(It.IsAny<Blog>())).Callback(blog => data.Add(blog));

//IQueryrableにAddはない
//mockSet.Setup(m => m.Add(It.IsAny<Blog>())).Callback<Blog>(blog => data.Add(blog));   

//AddがないのでConcatで配列を作り直す→なぜかダメ
//mockSet.Setup(m => m.Add(It.IsAny<Blog>())).Callback((Blog blog) => data = data.Concat<Blog>(new[] { blog }))

//ListとIQueryableを上で使い分ける
mockSet.Setup(d => d.Add(It.IsAny<Blog>())).Callback<Blog>((s) => source.Add(s));

動いたソースを一応全部のっけておきます。

[Test]
public void GetAllBlogs_with_mock()
{
  var source = new List<Blog>
  {
    new Blog { Name = "BBB" },
    new Blog { Name = "ZZZ" },
    new Blog { Name = "AAA" },
  };
  var data = source.AsQueryable();
  
  var mockSet = new Mock<DbSet<Blog>>();
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

  //DbSetのAddをモックする
  //ListとIQueryableを上で使い分ける(これは仕方ない?ここをもうちょいきれいにしたい)
  mockSet.Setup(d => d.Add(It.IsAny<Blog>())).Callback<Blog>((s) => source.Add(s));
  
  var mockContext = new Mock<BloggingContext>();
  mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);
  
  var service = new BlogService(mockContext.Object);
  var blogs = service.GetAllBlogs();
  
  Assert.AreEqual(3, blogs.Count,"追加前");
  Assert.AreEqual("AAA", blogs[0].Name);
  Assert.AreEqual("BBB", blogs[1].Name);
  Assert.AreEqual("ZZZ", blogs[2].Name);
  
  //もう一件追加する
  service.AddBlog("CCC", "http://blogs.msdn.com/adonet");
  blogs = service.GetAllBlogs();

  Assert.AreEqual(4, blogs.Count, "追加後");
  Assert.AreEqual("AAA", blogs[0].Name);
  Assert.AreEqual("BBB", blogs[1].Name);
  Assert.AreEqual("CCC", blogs[2].Name);
  Assert.AreEqual("ZZZ", blogs[3].Name);
}

で、下記に回答がありましたというオチです。

stackoverflow.com

追伸、 もっきゅーもっきゅーって居酒屋のメニューにしか聞こえないのは自分だけ?

Bitmapとbyte[]の変換

いざ書くといつも忘れてしまうので。

画像ファイル(Bitmap)からbyte[]に変換

Bitmap bmp = new Bitmap(画像のPATH);  //using System.Drawing
MemoryStream ms = new MemoryStream();
bmp.Save(ms,ImageFormat.Png);   //using System.Drawing.Imaging;

byte[]から画像ファイル(Bitmap)に変換

MemoryStream ms = new MemoryStream(byte[]のデータ);
Bitmap bmp = new Bitmap(ms);
ms.Close();
bmp.Save(画像のPATH);

byte[]って何者かいまだによく知らない。

「しょうゆ味のだし汁で煮ただいこんが最高」を英語で言うと

Daikon radish simmered in soy broth is the best!!

The broth in Kanto(関東のだし汁) is made with bonito flakes(かつお節) and "kombu" seaweed.

In Hokkaido, dried baby fish(煮干し) are added to enrich the flavor.

日経より引用。