WPFでReactiveProperty入門 ~Bing画像検索ビューアを作る

ReactiveProperty を使ってみたい

と思いつつ、なかなか手を付けられなかった ReactiveProperty に入門すべく、WPF + ReactiveProperty で、サンプルアプリケーションを作ってみました。

ReactiveProperty

github.com

ReactivePropertyの概要はこちら。この解説を読むだけでもReactivePropertyの素晴らしさが分かります。
MVVMとリアクティブプログラミングを支援するライブラリ「ReactiveProperty v2.0」オーバービュー - かずきのBlog@hatena

作ったもの

Bingで画像検索をして結果を閲覧するWPFアプリケーションです。
検索した画像は Microsoft Project Oxford Computer Vision APIs を使って画像解析も行います。

f:id:pierre3:20150809110334p:plain

機能

  • 入力した検索ワードでBing画像検索を行います。
  • 検索結果をサムネイルで一覧表示します。
  • サムネイルを選択すると、その画像をフルサイズでダウンロードしてウィンドウ中央に表示します。
  • 同時に、Computer Vision API で画像解析を行い、その結果を画像プロパティとしてテキスト表示します。 ― さらに、Computer Vision APIで人の顔として認識されたものがある場合、顔領域の矩形と、その顔から推定される年齢と性別を画像に重ね合わせて表示します。
  • ステータスバーに、検索の進捗と現在の状況を表示します。
  • 画像の検索、ダウンロードなどで例外が発生した場合、エラーの内容を通知パネルで表示します。

f:id:pierre3:20150809224845p:plain

マナカナ以外の画像も集める!

このアプリケーションでは、以下のAPIを使用しています。

APIの概要、導入方法等は以下の記事を参考にさせていただきました。
(というか、完全にこちら↓の記事の二番煎じです。)

マナカナを集める - かれ4

マナカナの画像からProejctOxfordとimagemagickで顔を切り出す。 - かれ4

ソースコード

今回のサンプルコードは Githubに置いてあります。

ReactiveProperty はもちろん、 WPFやReactive Extensionsについても勉強しつつ探り探り実装しております。
「ここの使い方間違っているよ」や「これもっといい実装方法あるよ」等々ありましたらご指摘いただけると非常に助かります。

github.com

開発環境、ライブラリ

使用方法

試してみたい方がいらっしゃいましたら、GitHubでClone してコンパイルするか、Release からバイナリのzipをダウンロードしてみてください。

但し、実行するには Bing Search API と Project Oxford Computer Vision API の アクセスキーが必要になります。 Azure Market Place でアカウントを登録して、キーを取得してください (この辺りの手順も、前述のマナカナ画像の記事が詳しいです。)

アクセスキーを入手しましたら、App.config (ReactiveBingViewer.exe.config)のapplicationSettings に入力します。
"BingApiAccountKey" および"VisionApiSubscriptionKey" をそれぞれ入手したアクセスキーで置き換えてください。

<configuration>
    <applicationSettings>
        <ReactiveBingViewer.Properties.Settings>
            <setting name="BingApiAccountKey" serializeAs="String">
                <value>Input your account key.</value>
            </setting>
            <setting name="VisionApiSubscriptionKey" serializeAs="String">
                <value>Input your subscription key.</value>
            </setting>
        </ReactiveBingViewer.Properties.Settings>
    </applicationSettings>
</configuration>

次回

次回から、プログラムの解説的なものを 書いて行く予定です。

PowerShell の入力補完にGoogleサジェストの結果を表示する

↑ みたいな事って出来るのかな、となんとなく調べてみたところ、どうやら TabExpansion++ というモジュールを使うと簡単に出来そう!
という事で、ちょっと試してみました。

f:id:pierre3:20150625215746p:plain

TabExpansion++

github.com

PowerShellのTab補完、インテリセンスをより賢く、便利にするモジュールで、
コンテキストに応じた入力候補を動的に生成してくれるようです。

さらには、入力候補の生成処理を自作して組み込むことが出来るとのことで、今回はこの機能を使ってGoogleサジェストの結果をインテリセンスに表示させてみたいと思います。

インストール

iex (new-object System.Net.WebClient).DownloadString('https://raw.github.com/lzybkr/TabExpansionPlusPlus/master/Install.ps1')

でダウンロードして、Install.ps1 を実行するか、PsGet がインストールされていれば以下でインストールできます。

Install-Module -ModuleUrl https://github.com/lzybkr/TabExpansionPlusPlus/zipball/master/ -ModuleName TabExpansion++ -Type ZIP

後は、プロファイルに Import-Module TabExpansion++ を追加しておけば準備完了です。

Search-Google コマンドレット

まずは、Google でWeb検索するコマンドレットを作成します。
パラメータ($SearchWords)に検索ワードを渡すと、検索クエリ付のURLを生成してブラウザに投げるだけの簡単なものです。

Search-Google -$SearchWords あ のように入力して、[Tab]または[Ctrl]+[Space]を押すと、
Googleサジェストから取得した入力候補が表示されるようになればOKです。

入力候補を動的に生成する関数の定義

以下の要件を満たした関数を定義するだけで、自作の入力補完処理が使用できるようになります。
(すごい!)

  • 関数に ArgumentCompleter 属性を付ける。

    • ここで、入力補完のターゲットとしたいコマンドとそのパラメータ名を指定します。
    • コマンドは配列で複数指定することが可能です。
  • 入力パラメータは以下の様に定義する。

    • param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
    • $wordToComplete に補完対象となる入力中の文字列が入ってきます。
  • System.Management.Automation.CompletionResult クラスのオブジェクトを出力する

    • New-ComoletionResult コマンドを使用して生成します。引数には順に以下を渡します。
      • CompletionText:補完結果の文字列
      • ToolTip: ツールチップに表示される文字列(省略可。省略時は第1引数と同じになる)
      • ListItemText: 入力候補一覧に表示される文字列(省略可。省略時は第1引数と同じになる)
      • CompletionResultType : 入力候補の種別 (一覧の左側に表示するアイコンを指定できる)(省略可。規定値'ParameterValue')
      • NoQuotes : 文字列の中の変数を展開するか否かを指定?(違うかも)(省略可。規定値False)

では、実際に関数を作ってみましょう。
SearchWoerdsパラメータの一部として入力された文字列をGoogleサジェストに投げて、帰ってきた結果から CompletionResult を生成して出力します。

(2015/12/5 追記) PowerShell v5.0からは、Register-ArgumentCompleter コマンドレットで処理を登録できるようになりました。こちらを使用したサンプルコードを追記します。(以下2番目のコード)

後は、これを記述したスクリプトファイルを 以下の何れかに配置しておくだけでOKです。

  • $env:PSModulePath に定義されているPath の1階層下のフォルダ
  • 任意のフォルダに置いて、そのパスを $env:PSArgumentCompleterPath に設定する

実行結果

"powershell" と入力して[Ctrl]+[Space]

f:id:pierre3:20150627002308p:plain

"powershell" と入力して[Tab]

f:id:pierre3:20150625220854p:plain

まとめ

思った以上に簡単に実現できてしまって驚きです。

他にも工夫次第でいろいろ便利なものが出来そうな予感がします。

TabExpansion++ が提供する入力補完も相当数あるようですので、自作しなくても十分かもしれませんが。

SignalR HubProxyのTypeScript型定義を自動生成してくれるT4テンプレート 「Hubs.tt」

前回は、ASP.NET + SignalRのクライアントサイドにTypeScriptを導入してみました。

今回は、サーバー側のコードも見てみることにします。

サーバー側が呼び出すクライアント側のメソッドを静的型付けにする

サーバー側で記述するクライアント側のメソッドは、dynamicなオブジェクトに生やしています。

public class ChatHub : Hub
{
    public void Send(string name , string message)
    {
        //クライアント側のメソッド呼び出しは、実行時に解決
        Clients.All.AddNewMessageToPage(name, message);
    }
}

しかし、これではコンパイル時のチェックやインテリセンスの恩恵に預かれません。 ほとんどのケースでは、使用するメソッドの定義はコンパイル時には分かっているのですから、静的に型付けをしたいところです。

ジェネリック版Hubクラス

実は、Hubクラスにはジェネリック版が用意されています。 ジェネリックの型引数にクライアント側のインターフェースを指定することが出来ます。

class ChatHub : Hub<IChatHubClient>

クライアント側のメソッドを定義したインターフェース(IChatHubClient)を用意します。

public interface IChatHubClient
{
    void AddNewMessageToPage(string name,string message);
}

このインターフェースをHubクラスの型引数に指定します。(Hub<IChatHubClient>)
インテリセンスも効きますし、定義していないメソッドを書けばエラーになります。 f:id:pierre3:20150422221546p:plain

HubProxyを自動生成する

サーバー側のコードが固まったところで、クライアント側に視点を戻します。
前回の記事では、HubProxyの型定義を毎回書かなくてはならないのは、面倒だと書きました。

  • サーバー側とクライアント側で、同じ内容を2度定義しなければならない
  • クライアント側の型定義を記述する際に、誤りがあってもコンパイラによるチェックができない

そこで、サーバー側のコードから、HubProxyの型定義を自動生成できないだろうか?

サーバー側のこのコードから

public class ChatHub : Hub<IChatHubClient>
{
    public void Send(string name,string message)
    {
        Clients.All.AddNewMessageToPage(name,message);
    }
}

public interface IChatHubClient
{
    void AddNewMessageToPage(string name,string message);
}

クライアント側のこのコードを生成したい

//chatHubProxy.d.ts
/// <reference path="signalr/signalr.d.ts" />
/// <reference path="jquery/jquery.d.ts" />

interface SignalR {
    chatHub : ChatHub;
}

interface ChatHub {
    server : ChatHubServer;
    client : ChatHubClient;
}

interface ChatHubServer {
    send(name : string, message : string) : JQueryPromise<void>;
}

interface ChatHubClient
{
    addNewMessageToPage : (name : string, message : string) => void;
}

そう思って探したら、ありました!

Hubs.tt

Hubs.tt というT4 テキストテンプレートです。

ソースコードが Gist: https://gist.github.com/htuomola/7565357 にあります。

詳細は、以下の記事をご参照ください。

上で書いた、サンプルコードの変換はもちろん、メソッドの引数にオブジェクトを使用した場合は、その型定義もしてくれます。

class ChatHub: Hub 
{
    public void SendTo(ChatMessage message)
    {
        //...略
    }
}
public class ChatMessage
{
    public string Name { get; set; }
    public string Message { get; set; }
    public string ConnectionId { get; set; }
}

SendTo() の引数に使用している ChatMessage クラスの定義もちゃんと変換されています。

/**
  * Data contract for SignalR_TypeScript_BasicChat.hubs.ChatMessage
  */
interface ChatMessage {
    Name : string;
    Message : string;
    ConnectionId : string;
}
interface ChatHubServer {
  /** 
    * Sends a "sendTo" message to the ChatHub hub.
    * Contract Documentation: ---
    * @param message {ChatMessage} 
    * @return {JQueryPromise of void}
    */
    sendTo(message : ChatMessage) : JQueryPromise<void>;
}

しかも、コメントまで生成してくれます。
さらに、C#側でドキュメントコメントを書いて、コメントのXMLを出力するようにしておけば、そのコメントまで反映してくれるようです。素晴らしい!

Hubs.tt の導入

Web Essentials 2013 for Update 4 extension がインストールされていれば、ソリューションエクスプローラから追加できます。

f:id:pierre3:20150425135141p:plain

追加後、場合によっては、アセンブリの参照を自分の環境に合わせて書き換える必要がありますが、基本的にはこの1ファイルを追加するだけでOK!

Hubクラスを増やしたり、メソッド名を変更したりする場合でも、サーバー側のC#のコードを変更するだけで済むようになりました。

SignalRのクライアントサイドをTypeScript で強い型付けにする。

ASP.NET MVC5 + SignalR 2.0 + TypeScript 1.4 でリアルタイムWeb入門

最近、SignalRを使ったWebアプリケーションを作りたいと思い、お勉強を始めました。
クライアントサイドには(こちらも入門したばかりの)TypeScriptを使おうかと考えています。

という事で、今回は ASP.NET MVC + SignalR の組み合わせにTypeScriptを導入する方法をまとめたいと思います。

ASP.NET/SignalR チュートリアル

サンプルコードとして、ASP.NET公式サイトのチュートリアル Tutorial: Getting Started with SignalR 2 and MVC 5 | The ASP.NET Site を使用します。
このサンプルコードのクライアントサイドをTypeScriptに置き換えて行こうと思います。

なお、チュートリアルではVisual Studio2012を使用していますが、今回はVisual Studio2013(Ultimate)で実装と動作確認を行っています。

TypeScriptのインストール

Visual Studio 2013 に Update4 を当てていればv1.3 がインストールされていると思いますが、最新版にアップデートしておきます。

[ツール]-[拡張機能と更新プログラム...]で"TypeScript"を検索して最新版(現時点ではv1.4)をインストールします。 f:id:pierre3:20150407222334p:plain

SignalRの型定義 signalr.d.ts のインストール

NuGetでインストールします。

PM> Install-Package signalr.TypeScript.DefinitelyTyped

(この時、SignalRが依存しているJQueryの型定義(jquery.d.ts)も同時にインストールされます。)

ここで、TypeScriptのバージョンが古いと、以下の記事にあるようなエラーが発生するようです。orzmakoto.hatenablog.com

Hub による接続

チュートリアルの詳細はリンク先を見て頂く事にして、ここではHubを使用した通信の実装部分をざっくりと確認しておきます。

サーバー側(C#)

サーバー側では、Hubクラスを継承したクラスを定義します。

  • Client側から呼んでもらうメソッドを定義
    • ChatHub.Send()
  • サーバーから呼ぶClient側の処理は、実行する箇所を記述するだけ。記述したメソッドは実行時に解決される。
    • Clients.All.AddNewMessageToPage()
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SignalRChat.Hubs
{
    public class ChatHub : Hub
    {
        //クライアントからチャットメッセージを受け取る
        public void Send(string name , string message)
        {
            //受け取ったメッセージを接続している全てのクライアントへ送信
            Clients.All.AddNewMessageToPage(name, message);
        }
    }
}

クライアント側(JavaScript)

クライアント側では、自動生成されるHubProxyを使ってサーバーとの通信を行います。

window.onload = function (ev: Event) {
    $('#displayname').val(prompt('Enter your name:', ''));
    $('#message').focus();

    var chatHub = $.connection.chatHub;
    //サーバーから呼ばれるメソッドをここで定義
    chatHub.client.addNewMessageToPage = function (name: string, message: string){
        appendMessage("#discussion", name, message);
    };
    //サーバーとの接続が完了したタイミングでClickイベントにハンドラを設定
    $.connection.hub.start().done(function (){
        $("#sendmessage").click(function(ev) {
            //サーバーにメッセージを送信
            chatHub.server.send($("#displayname").val(), $("#message").val());
            $("#message").val("");
        });
    });
};
// メッセージをHtmlに埋め込む
function appendMessage(selector, name, message) {
    $(selector).append(
        '<li>'
        + '<strong>'
        + $('<div />').text(name).html()
        + '</strong>: '
        + $('<div />').text(message).html()
        + '</li>');
}

クライアントサイドをTypeScriptに置き換える。

プロジェクトに空のTypeScriptファイルを追加(App.ts とします)したら、ファイルの先頭にSignalRとJQueryの型定義への参照を追加します。

コードは上のJavaScriptのコードをそのままコピペします。

//(App.ts)
/// <reference path="../typings/signalr/signalr.d.ts" />
/// <reference path="../typings/jquery/jquery.d.ts" />

window.onload = function (ev: Event) {
    $('#displayname').val(prompt('Enter your name:', ''));
    $('#message').focus();

    //...(略)...

ひとまずは、これで動くはず...
と思ったのですが、「型SignalRにchatHubなんてプロパティはありません。」と怒られてしまいました。

f:id:pierre3:20150410211403p:plain

HubProxy (chatHub) 自体は、実行時に$connection のプロパティとして実装されるのですが、TypeScriptでの型の定義が無いためにコンパイルエラーとなります。

HubProxyの型定義を記述する

では、chatHub の型を定義してみましょう。 SignalR という型ですが、$.connection の型としてsignalr.d.ts で定義されています。

//(chatHubProxy.d.ts)
/// <reference path="../typings/signalr/signalr.d.ts" />
/// <reference path="../typings/jquery/jquery.d.ts" />

//$.connection の型。 ここにHubProxyが追加される
interface SignalR {
    chatHub : ChatHub;
}
//HubProxyの定義。プロパティにserver, client を持つ。
interface ChatHub {
    server : ChatHubServer;
    client : ChatHubClient;
}
//サーバー側のメソッドを定義
interface ChatHubServer {
    send(name : string, message : string) : JQueryPromise<void>;
}
//クライアント側のメソッドを定義
interface ChatHubClient
{
    addNewMessageToPage : (name : string, message : string) => void;
}

このファイル(chatHubProxy.d.ts)の参照を App.tsに記述します。

/// <reference path="hubproxy/chatHubProxy.d.ts" />

これで、無事コンパイルできるようになりました。
インテリセンスもちゃんと効きます。

f:id:pierre3:20150417214344p:plain

補足

サーバー側のメソッドは非同期実行

サーバー側のメソッド(sendメソッド)は、戻り値にPromise(JQueryPromise<T>)を返す非同期メソッドとなっています。 チュートリアルのサンプルでは同期的に実行されているように見えますが、実際は、非同期実行の結果を受け取らずに投げっぱなしにしているだけです。

試しに、以下の様に記述してみると、ボタンクリック直後に"start"が表示され、サーバーからの結果を受信後に"complete"が表示されるはずです。

$("#sendmessage").click(ev => {
    chatHub.server
        .send($("#displayname").val(), $("#message").val())
        .then(() => $("#message").val("complete"));
    $("#message").val("start");
});
型定義を書かない方法

以下の様に、HubProxyの基底クラス が持つ汎用的なメソッドを使う方法もあります。
但し、Hub名やメソッド名を文字列で指定する必要があります。

//HubProxyの取得
var hub = $.connection.hub.createHubProxy("chatHub");
//サーバーに呼んでもらうメソッドの定義
hub.on("addNewMessageToPage",(name: string, message: string) : void => {
    appendMessage("#discussion", name, message);
});

$.connection.hub.start().done(() => {
    $("#sendmessage").click(ev => {
        //サーバー側にメッセージを送信
        hub.invoke("send", $("#displayname").val(), $("#message").val());
        $("#message").val("");
   });
});

まとめ

これで、クライアント側のコードをTypeScriptに置き換えることが出来ました。

なのですが、型定義をHub毎に書かなくてはならないのは面倒です。
しかも、型定義自体の誤りはチェックされませんので、動的に記述するのと大して変わりません。 サーバー側とクライアント側で同じ定義を2度記述しなくてはならないのもイケてないです。

サーバー側のコード(C#)から、型定義を自動生成出来たらいいのに! という事で、次回へ続く

文字列で指定する既知のパラメータをTypeScriptで型付けする

1. Enumに置き換える

JavaScriptでは、ライブラリ等に渡すパラメータを文字列で指定することが多いのですが、指定可能な値が分からなかったり、タイプミスによるバグを作り込む可能性があったりで嫌ですよね。

例えば以下のような場合、lineCap には"butt" / "round" / "square" が指定可能なのですが、ドキュメントを調べないと分かりませんし、 Typoしても気付き難いです。

//Canvasに描画する線の終端部分の形状を指定する
var rc = canvas.getContext("2d");
rc.lineCap = "round"; //"butt" or "round" or "square"

このような場合、TypeScriptにあるEnum型が使えそうです。

//設定可能な値をEnumで定義
enum LineCap { butt, round, square }

var rc = canvas.getContext("2d");
//enumの要素名を文字列で取得
rc.lineCap = LineCap[LineCap.round]; 

enumの要素名(butt, round, square)を取得するには、LineCap[lineCap.round]の様に記述する必要があるのですね。
C#enum に慣れていると、LineCap.round.toString();とやりたくなるのですが、LineCap.roundは単なる数値(1)なので、この場合"1"が設定されてしまいます。)

あんまり嬉しくない?

上記サンプルでは、記述量が多くなるだけであまり有難味が無いように思えます。
rc.lineCap = LineCap[LineCap.round]のような記述も不細工です。

実際に使う際には、enum ⇔ 文字列パラメータ の変換部分をラッパークラス等で隠ぺいして、enum の受け渡しだけで済むようにした方が良いでしょう。

例として、Canvasに描画するストロークのスタイルを指定するためのクラス(Penクラス)を作ってみます。

enum LineCap { butt, round, square }
enum LineJoin { bevel, round, miter }

class Pen {
    constructor(public color: Color, 
        public width: number= 1,
        public lineDash: number[]= [],
        public lineCap: LineCap= LineCap.butt,
        public lineJoin = LineJoin.bevel,
        public miterLimit: number = 10.0) { }

    //ストロークのスタイルをまとめて設定する。
    public applyTo(rc: CanvasRenderingContext2D) {
        rc.strokeStyle = this.color.cssColor;
        if (rc.setLineDash != undefined) {
            rc.setLineDash(this.lineDash);
        }
        rc.lineWidth = this.width
        rc.lineCap = LineCap[this.lineCap];
        rc.lineJoin = LineJoin[this.lineJoin];
        rc.miterLimit = this.miterLimit;
    }
}

このクラスを使う側では、コンストラクタで各種パラメータを指定するのですが、
その際、(IDEを使用していることが前提ですが、)LineCap および LineJoin では入力補完が利き、一覧から選択するだけでOKとなります。

var rc = canvas.getContext("2d");
//IDEを使用していれば、`LineCap.`や`LineJoin.` と入力して、入力候補から選ぶだけ!
var pen = new Pen(Color.Red, 1, [], LineCap.Round, LineJoin.Round);
pen.applyTo(rc);

TypeScriptのenum について

enumJavaScriptコンパイルすると、以下のような連想配列に展開されるようです。

var LineCap;
(function (LineCap) {
    LineCap[LineCap["butt"] = 0] = "butt";
    LineCap[LineCap["round"] = 1] = "round";
    LineCap[LineCap["square"] = 2] = "square";
})(LineCap || (LineCap = {}));

/*
LineCap[0] = "butt";
LineCap[1] = "round";
LineCap[2] = "square";
LineCap["butt"] = 0;
LineCap["round"] = 1;
LineCap["square"] = 2;
*/

//LineCap.round は、コンパイルするとNumberのリテラルに置き換えられる。
//var a = LineCap.round;
var a = 1 /* round */;
//var b = LineCap[LineCap.round];
var b = LineCap[1 /*round*/];

シンボルとしては使えない文字列を使用したい場合

引用符で囲めば、enumの要素名として使用できるようですが、インデクサからでしかアクセスできなくなってしまう為、残念ながら今回のような用途には使えそうにありません。

//これはNGだけど
enum Test{
   1ab = 0,     //数字から始まる
   abc def = 1, //スペースを含む
}

//これならOK、コンパイルが通る
enum Test{
   "1ab" = 0,
   "abc def" = 1,
}

但し、アクセスはインデクサからのみとなります。入力候補にも出てきません。

Test.1ab     //コンパイルエラー
Test."1ab"   //コンパイルエラー
Test["1ab"]  //OK

2. Static フィールドに置き換える

以下のように、Staticなフィールドを持つクラスに置き換える方法でも良いかもしれません。 これであれば、置き換えたいパラメータがどんな文字列でも(シンボルとして使えなくても)関係なく使えます。

class LineCap {
    constructor(private _index:number, private _value:string){}
    public get index():number { return this._index;}
    public get value():string { return this._value}

    static Butt:LineCap = new LineCap(0,"butt");
    static Round:LineCap = new LineCap(1,"round");
    static Round:LineCap = new LineCap(2,"square");
}

var lineCapIndex = LineCap.Round.index;   //1
var lineCapString = LineCap.Round.value;  //"round"

以下のような基底クラスを用意しておけば、定義が楽になりますし、文字列以外の型も使えて便利です。

class EnumBase<TValue> {
    constructor(private _index:number, private _value:TValue){}
    public get index():number { return this._index;}
    public get value():TVale{ return this._value}
}

class LineCap extends EnumBase<string>{
    static Butt:LineCap = new LineCap(0,"butt");
    static Round:LineCap = new LineCap(1,"round");
    static Round:LineCap = new LineCap(2,"square");
}

最後に

今回のような用途で、enumを使うのはちょっと無理があるような気がしてきました。

以下の様にEnumの値を文字列で指定出来たらいいのに

enum Test{
    None = "",
    Hoge = "hoge",
    Piyo = "piyo piyo"
}

TypeScript で作った、System.Drawingっぽい HTML Canvas ライブラリ

MSCC用に作ったアプリでは、HTML Canvasのお絵かきツールをTypeScriptでつくりました。
その中からCanvas面を操作する部分をライブラリに切り出してGitHubで公開してみました。

pierre3/DrawingTs · GitHub

System.Drawing 風?

Canvasへの図形等の描画処理を .Net Framework の System.Drawing名前空間にあるようなGDI操作っぽく作ってみました。

  • 描画面への書き込み処理は、CanvasクラスのonPaintイベントハンドラに記述
  • onPaint の引数に渡されるGraphicsオブジェクトに用意された各種描画用メソッドを使用
  • ストロークや塗りつぶしのスタイルはPenBrushなどの(それっぽい)オブジェクトで指定
  • Canvasクラスには、マウスやタッチ操作に対応したイベントハンドラを用意(現状、Down、Move、Upしかありませんが)
window.onload = (ev: Event) => {
    var canvas = new Canvas("canvas1");
    
    //描画処理はonPaint イベントハンドラで    
    canvas.onPaint = (g: DrawingTs.Graphics): any => {
        g.clear(true);
        g.drawString(message,
            Font.Create("sans-serif", 18),
            new SolidBrush(new Color("blue")),
            4, 18);
    };      
    //Pointerイベントは、Mouse,Touch,Pen の差異を吸収しいてる(はず。たぶん)
    canvas.onPointerDown = (ev: DrawingTs.CanvasPointerEvent) => {
        message = "PointerDown (" + ev.position.x + ", " + ev.position.y + ")"
        canvas.paint();
    };
    canvas.onPointerMove = (ev: DrawingTs.CanvasPointerEvent) => {
        message = "PointerMove (" + ev.position.x + ", " + ev.position.y + ")";
        canvas.paint();
    };
    canvas.onPointerUp = (ev: DrawingTs.CanvasPointerEvent) => {
        message = "PointerUp (" + ev.position.x + ", " + ev.position.y + ")"
        canvas.paint();
    };
    //Canvas面への書き込みはpaintメソッドで実行
    canvas.paint();

どんなクラスやメソッドがあるかは、こちらの定義ファイル(drawingTs.d.ts)で確認できます。
読み込んだ画像を表示する drawImage() はまだ未実装だったり、まだまだ機能不足ですが。。。

サンプルコード

サンプルコードと実行結果がこちらで確認できます。

http://pierre3net.azurewebsites.net/Drawing

ブラウザ上で描いた絵をTwitterで共有するWebアプリを作りました

ピクエスト(α)

MSCC 提出作品です。
現在α版としてテスト公開中です。

ピクエスト(α)

  • リクエストされたテーマに沿って絵を描きます。
  • 描いた絵を画像付きツイートとしてTwitterへ投稿します。
  • リクエストは、ユーザーが自由に作成して追加することが出来ます。

f:id:pierre3:20150115220322p:plain

描いた絵はTwitterで共有

画像付きツイートとして投稿しますので、フォロワーさんのタイムライン上に描いた絵を表示させることが出来ます。

f:id:pierre3:20150116220450p:plain

Twitterとの連携について

リクエスト一覧内で[リクエストに応える]ボタンをクリックするとTwitterの認証画面に移動します。ここで、

  • Twitterの使用を許可した場合は、許可したユーザーのアカウントで投稿します。
  • 許可せず「キャンセル」した場合は、Piquestの公式アカウント @piquest で投稿します。
    • 但し、Twitterにログインしていない状態で「キャンセル」しても「ユーザー名、パスワードに誤りが..」と表示され、再度ログインを求められてしまいます。
      (Piquest側に戻ってくれません。したがって、連携を許可しない場合でも、絵の投稿にはTwitterのアカウントが必須となります。この辺どうにかしたい)

ASP.NET MVC

このWebアプリケーションは、ASP.NET MVC5で実装し、Azure Webサイト上で動いています。

Webアプリとしての基本的な動作は、ほとんどASP.NETが面倒を見てくれるため、この部分のコーディング量は驚くほど少なくて済みました。

CoreTweet

また、Twitterとの連携機能にはCoreTweetを使用させていただきました。こちらも数行書き加えるだけの簡単なお仕事でした。ありがとう CoreTweet。

HTML Canvas + TypeScript

お絵かきツール部分は、HTML Canvasの操作をTypeScriptで記述しました。
機能的には、最低限の物しか実装できていませんが、コーディング量としてはここが一番大きく、時間もかかりました。

TypeScriptは初めて触りましたが、思った以上に違和感なく使えていい感触でした。

最後に

機能的にはまだまだ貧弱で、間に合わせた感は否めませんが、コンテスト後も少しずつ手を加えて改良して行きたいと思います。

どなたか、使ってみてくだされ~