[win8.1] ストアアプリで手軽に画像を加工しよう

Windows Store App Advent Calendar 2013
http://qiita.com/advent-calendar/2013/win-store-app/participants
の4日目です。

Windows 8のストアアプリの場合は、画像の加工が大変だったのですが、8.1の場合は「<a href=”http://msdn.microsoft.com/ja-jp/library/system.windows.media.imaging.rendertargetbitmap(v=vs.110).aspx”>RenderTargetBitmap クラス </a>」を使うと「手軽」にできますっ!!! というネタです。実際に手軽にできるかどうかは別として(!?)、DirectXを使ったり、Unityを使ったり、OpenCVを使ったり、いろいろ独自で画像処理をがんばったりしなくても、できる範囲ができました。って感じですね。8 の時は手が出しようがなかったのですが、まあ、できるようになった第一歩かと。ちなみに、この手の方法で画像加工をする場合は、WPFで組むほうが断然楽です。いろいろなエフェクトも用意されているし、図形なんかもいろいろあります。WinRTの場合は、結構画像まわりが削られてしまっているのでBlendを使ってもあまりうまくできなことが多いのです。
が、あえてストアアプリでやる利用があるとすれば、画像のプレビューとか、ちょっとしたフレームの加工とか、そんな感じですかね。本格的なところはDirectXを使ったほうがいい気がします。ちょっと、DirectX – C++/CXの調査は停滞していて進めていないのですが。

■できあがりイメージ

出来上がりは、こんな感じです。

画像の上に文字を張り付けたり(これが一番簡単)、切り抜きをしたり、透明度を変えて擬似的に色調を変えてみたり、マスクをかけてみたり。それぞれは、XAMLでキャンバス(canvas)上に重ね合わせています。重ねた後の画像をそのまま保存できるのが、RenderTargetBitmapクラスの利点です…つーか、WPFのほうがすんなり使えるんですがね。

■重ね合わせの方法

重ね合わせの部分をピックアップすると、下記のようにCanvasの上に貼り付けていきます。RenderTargetBitmap自体は、UIElementのレンダリングを保存できるので、GridでもOKですし、ImageそのままでOKです。

<Canvas 
        x:Name="panel4"
        Tapped="panel_Tapped"
        HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="200" Canvas.Left="224" Margin="783,173,0,0">

    <Image 
            x:Name="img4"
            Source="ms-appx:///images/haruna.jpg"
            HorizontalAlignment="Left" Height="200"  VerticalAlignment="Top" Width="200" Opacity="0.5"/>
    <Image 
            x:Name="img4mask"
            Source="ms-appx:///images/mask.png"
            HorizontalAlignment="Left" Height="200"  VerticalAlignment="Top" Width="200" Opacity="1"/>
    <TextBlock 
            FontSize="32"
            Canvas.Left="10" TextWrapping="Wrap" Text="マスク" Canvas.Top="151"/>
</Canvas>

ここではマスク画像を、mask.png であらかじめ作っておいて重ね合わせているわけですが、本当は動的に作りたいところですよね。

■ファイルの読み込み

定番のファイル読み込みは、こんな感じ。FileOpenPicker を使います。

/// <summary>
/// ファイルを選択
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Button_Click(object sender, RoutedEventArgs e)
{
    var picker = new FileOpenPicker();
    picker.CommitButtonText = "画像を選択する";
    picker.FileTypeFilter.Add(".jpg");
    picker.FileTypeFilter.Add(".jpeg");
    picker.FileTypeFilter.Add(".png");
    picker.ViewMode = PickerViewMode.Thumbnail;
    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    // 画像ファイルを指定する
    var file = await picker.PickSingleFileAsync();
    // 選択されなかった場合
    if (file == null)
    {
        return;
    }
    // 画面へ表示
    var stream = await file.OpenReadAsync();
    var bmp = new BitmapImage();
    bmp.SetSource(stream);
    this.img1.Source = bmp;
    this.img2.Source = bmp;
    this.img3.Source = bmp;
    this.img4.Source = bmp;
}

ペタペタと4つ分 Image を貼っていますが、実際のツールにする場合は配列をつかうとか諸々修正してください。

■ファイルへ保存

画像ファイルとして保存するときは、選択は FileSavePicker です。PanelSave.Save ってのが独自に作ったところで(実際は、Microsoft さんのサンプル集にあります)。

/// <summary>
/// パネルをクリック
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void panel_Tapped(object sender, TappedRoutedEventArgs e)
{
    var panel = sender as UIElement;
    // 保存先を選択する
    var savePicker = new FileSavePicker();
    savePicker.DefaultFileExtension = ".png";
    savePicker.FileTypeChoices.Add(".png", new List<string> { ".png" });
    savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    savePicker.SuggestedFileName = "snapshot.png";
    var saveFile = await savePicker.PickSaveFileAsync();
    // キャンセルされた場合
    if (saveFile == null)
        return;
    // ファイルに保存
    PanelSave.Save( panel, saveFile);
}

ざっと、該当箇所を整理しただけなので、こんな感じ。RenderTargetBitmapクラスで、レンダリングした後の画像バッファを取り出して、BitmapEncoderを使ってファイルを保存するという流れですね。メモリ上でやりたいときは、MemoryStream を使えばいいはずなのですが、これはまだ試していません。
StorageFile だと、ユーザーがファイルを指定しないといけないので、アプリケーションのデータフォルダやピクチャーフォルダに編集後のファイルを複数ファイル自動で保存できるといいので。このあたりは、また調べていく予定です。

public class PanelSave
{
    /// <summary>
    /// 指定した要素をレンダリングして保存
    /// </summary>
    /// <param name="el"></param>
    /// <param name="saveFile"></param>
    public static async void Save( UIElement el,  StorageFile saveFile )
    {
        // レンダリングをして画像バッファを取得
        RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
        await renderTargetBitmap.RenderAsync(el);
        var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();

        // Encode the image to the selected file on disk
        using (var fileStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
        {
            // PNG形式で保存
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fileStream);
            encoder.SetPixelData(
                BitmapPixelFormat.Bgra8,
                BitmapAlphaMode.Ignore,
                (uint)renderTargetBitmap.PixelWidth,
                (uint)renderTargetBitmap.PixelHeight,
                DisplayInformation.GetForCurrentView().LogicalDpi,
                DisplayInformation.GetForCurrentView().LogicalDpi,
                pixelBuffer.ToArray());
            await encoder.FlushAsync();
        }
    }
}

保存するとこんな感じ。

■どうやって「手軽」に作るか?

重ね合わせのキャンバスを手軽に保存できることはわかったのですが、じゃあ画像の加工は手軽にできるのか?ってのが問題ですね。ちょこっとやった感じでは、手軽に作れる範囲はかなり限られます。マスク画像や切り抜き画像は、あらかじめマスクや図形の合成を作っておかないといけないので、結構コツが要ります。思い通りできるまでにBlendと格闘、さらにはアドビ系のツールとかExpression Designerとの格闘が必要になるので、手軽にとはいきません。ああ、XAMLでお絵かきできる方ならば、あるいは「手軽」かもと思いもしますが。

  • 文字を画像に重ね合わせる(コピーライトとか日付を表示とか)
  • 画像をフレームに合わせる(あらかじめ、フレームを透過PNGで作っておく)
  • 透明度を変更させて、それっぽい感じに(セピア色とか)

画像自体に手を加えるのは、DirectX の範疇になってしまうので、あくまでXAMLの図形を重ね合わせてできる範囲ですね。画像をブラシにすれば図形の内側に塗り付けてという裏技もあるので結構なことができるはずなのですが、それはまた後日(まだ調べきれていない。WPFだとできるのはわかっているんですが)。

■サンプルコード

使ったサンプルコードは、こちら。
http://sdrv.ms/IpvVFF

カテゴリー: 開発, WinRT | [win8.1] ストアアプリで手軽に画像を加工しよう はコメントを受け付けていません

ノーマルにMSTestを使おう

TDD Advent Calendar 2013の参加状況確認・参加登録 – Qiita [キータ]
http://qiita.com/advent-calendar/2013/tddadventjp/participants

の一発目です。トップバッターなので、ノーマルに MSTest を使う話を1時間ほど書いていきます。TDDで何ができるの?とか、PHPUnitとか、NUnitとか、gtestとか、CppUnitとか、その他もろもろのお話は先行き出てくるはず(?)なので、そちらに譲るとして、Visual Studio 2013 の Express 2013 for Widnwos Desktop を使って、MSTest を使って見よう、という感じで進めていきます。

■いきなりUIを作る

image

こんな感じで、いきなりUIを作ってみます。最初のTextBox1とTextBox2の内容を足したら、TextBox3に入る、という簡単な計算機ですね。つーか、簡単すぎてあれなのですが、まあ、これを作っていきます。

■いきなり計算をする。

/// 計算する
private void button1_Click(object sender, EventArgs e)
{
	int x = int.Parse(textBox1.Text);
	int y = int.Parse(textBox2.Text);
	int z = x + y;
	textBox3.Text = z.ToString();
}

いきなりコードを書いていきます。これぐらいのコードならばUIにそのまま書いていいよね、って具合に書いていきますが、これじゃダメなのはわかりきってますね。だって、TextBox1.Text が空欄のときは、int.Parse でエラーになるし、なんか、いろいろ TDD 的にダメなところが満載です。

image

デバッグ実行して、ちまちまとエラー箇所を直すのもアリなのですが、ひと手間かけて、もう少しなんとかしましょう。

■ボタンクリックと計算する部分を分ける

せめて「UIとロジック」を分けようということで、ボタンをクリックしたところと、実際に計算するところを分けます。テキストボックスの文字列をそのまま渡して、文字列でもらってくると関数として楽ですよね、という発想です。

/// 計算するボタンを押す
private void button1_Click(object sender, EventArgs e)
{
	textBox3.Text = calc(textBox1.Text, textBox2.Text);
}

/// 計算する
private string calc(string x, string y)
{
	int xx = 0;
	int yy = 0;
	if (int.TryParse(x, out xx) == false) return "";
	if (int.TryParse(y, out yy) == false) return "";

	int zz = xx + yy;
	return zz.ToString();
}

まあ、これも悪くはないのですが…、ええと、MSTest を使うときは、private メソッドでは無理なので、public にしないとダメですね。

■テスト対象を public にする

calc メソッドを public にしてテストから呼び出せるようにします。

/// 計算する
public string calc(string x, string y)
{
	int xx = 0;
	int yy = 0;
	if (int.TryParse(x, out xx) == false) return "";
	if (int.TryParse(y, out yy) == false) return "";

	int zz = xx + yy;
	return zz.ToString();
}

おお、これなら calc メソッドがテストできそうですね。

■テストコードを書く

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WindowsFormsApplication1;

namespace UnitTestProject1
{
[TestClass]
	public class UnitTest1
	{
		[TestMethod]
		public void TestMethod1()
		{
			var frm = new Form1();
			string ans = frm.calc("1","2");
			Assert.AreEqual("3", ans);
		}
	}
}

こんな感じで、文字列の「1」と文字列の「2」を渡したら文字列の「3」を戻すコードを書きます。参照設定で、フォームのあるプロジェクトを設定するのと、System.Windows.Forms も参照設定するのを忘れずに。ちょっとややこしいですね。

image

無事、テストが成功するとこんな風に緑のチェックマークが出ます。

そうそう、テキストボックスが空欄の場合も追加して実行すると、

image

こんな風に次々とテストが書けます。めでたしめでたし…で終わると怒られそうですね。フォームをそのままnewするのは、まあそこそこ業務のテストとしてはあるのですが、しかしフォームにいろいろな部品が乗っかったり、フォームを初期化するときにデータベースに接続したりするときは、テストが重くなりそうです。まあ、実際重くなるし、初期化で失敗したりしてテストが自動化できなくなってしまいます。

■フォームとは別のクラスにする

フォームをnewしたくないので、calcメソッドを別のクラスに移します。別にLogicクラスというのを作って、

public class Logic
{
	/// 計算する
	public string calc(string x, string y)
	{
		int xx = 0;
		int yy = 0;
		if (int.TryParse(x, out xx) == false) return "";
		if (int.TryParse(y, out yy) == false) return "";

		int zz = xx + yy;
		return zz.ToString();
	}
}

フォームのボタンクリック部分を書き換えます。

/// 計算するボタンを押す
private void button1_Click(object sender, EventArgs e)
{
	var l = new Logic();
	textBox3.Text = l.calc(textBox1.Text, textBox2.Text);
}

そうして、テストも書き換えます。

namespace UnitTestProject1
{
	[TestClass]
	public class UnitTest1
	{
		[TestMethod]
		public void TestMethod1()
		{
			var l = new Logic();
			string ans = l.calc("1","2");
			Assert.AreEqual("3", ans);
		}

		[TestMethod]
		public void Test空欄の場合()
		{
			var l = new Logic();
			string ans = l.calc("", "2");
			Assert.AreEqual("", ans);
		}
	}
}

フォーム自体を呼び出さなくなったので、System.Windows.Forms の参照設定はいらなくなります。テスト対象のアセンブリ(この場合は、Form1を含むアセンブリ)を参照設定します。

image

ほどよく、いい感じになってきました。

■ロジックをシンプルにする

さて、calc メソッドは、足し算をするメソッドですが、引数が文字列になっています。文字列の足し算は文字列の連結に見えるし、数値だけを足すようにしたほうがロジックがシンプルになりますよね。なので、calc メソッドの引数を数値に変えましょう。

public int calc( int x, int y)
{
	int z = x + y;
	return z;
}

UI のほうは、数値を渡すように変更をします。

private void button1_Click(object sender, EventArgs e)
{
	string x = textBox1.Text;
	string y = textBox2.Text;
	int xx, yy;
	if (int.TryParse(x, out xx) == false) return ;
	if (int.TryParse(y, out yy) == false) return ;

	var l = new Logic();
	int ans = l.calc(xx, yy);
	textBox3.Text = ans.ToString();
}

次いでにテストのほうも書き換えます。

public void TestMethod1()
{
	var l = new Logic();
	int ans = l.calc(1,2);
	Assert.AreEqual(3, ans);
}

空欄の場合は、UIのところでチェックをしているので、テストを取り除きます。

image

calc メソッドは、必ず数値が渡されるので、ロジックがシンプルになっています。その分、UIで数値以外をチェックしているので、UIが複雑になっていますが。いやいや、これはこれで便利なのです。

■UIで空欄と数値以外でメッセージを表示する

UIの入力時に、空欄のときと、数値以外の2つのパターンでエラーメッセージを表示してみましょう。

private void button1_Click(object sender, EventArgs e)
{
	string x = textBox1.Text;
	string y = textBox2.Text;
	if (x == "")
	{
		MessageBox.Show("xが空欄です");
		return;
	}
	if (y == "")
	{
		MessageBox.Show("yが空欄です");
		return;
	}

	int xx, yy;
	if (int.TryParse(x, out xx) == false)
	{
		MessageBox.Show("xに数値を入れてください");
		return;
	}
	if (int.TryParse(y, out yy) == false)
	{
		MessageBox.Show("yに数値を入れてください");
		return;
	}

	var l = new Logic();
	int ans = l.calc(xx, yy);
	textBox3.Text = ans.ToString();
}

結構長くなりますが、テキストボックスに入力した値をひとつひとつチェックすることができますね(この部分をテスト可能にすることもできるのですが、ここではフォームを手動でテストするということで)。ロジックの calc メソッドでは「計算する」ことだけに集中できます。

■答えが10以上の時に、エラーにする

今度は、計算結果が10以上になったらエラーにする処理を追加してみましょう。10以上になることは、calc メソッド内でしかわからないので、例外処理を入れます。

public int calc(int x, int y)
{
	int z = x + y;
	if ( z >= 10 )
	{
		throw new Exception( &quot;答えが10以上です&quot;);
	}
	return z;
}

UI のほうでは、calc メソッドを呼び出したときに例外が発生したらメッセージを表示させます。

try
{
	var l = new Logic();
	int ans = l.calc(xx, yy);
	textBox3.Text = ans.ToString();
}
catch ( Exception ex)
{
	MessageBox.Show(ex.Message);
}

■例外をチェックするテストコードを書く

どうせなので、テストコードも追加します。

 [TestMethod]
public void Test例外発生()
{
	var l = new Logic();
	try
	{
		int ans = l.calc(5, 6);
	}
	catch
	{
		// 例外が発生すればOK
		return;
	}
	// 例外が発生ない場合
	Assert.Fail();
}

例外が発生しない場合に「失敗」になります。

image

■テストコードから書いてみる

今度は、テストコードのほうを先に書いてみましょう。calcメソッドの引数が0未満の場合はエラーにします。

public void Test引数チェック1()
{
	var l = new Logic();
	try
	{
		int ans = l.calc(-1, 6);
	}
	catch
	{
		// 例外が発生すればOK
		return;
	}
	// 例外が発生ない場合
	Assert.Fail();
}
public void Test引数チェック2()
{
	var l = new Logic();
	try
	{
		int ans = l.calc(0, -1);
	}
	catch
	{
		// 例外が発生すればOK
		return;
	}
	// 例外が発生ない場合
	Assert.Fail();
}

引数は2つあるので、テストは2つ必要です。これを実行すると2つ「失敗」します。

image

■テストが通るようにロジックを書き直す

ロジックのほうを直します。テストをしたときにオールグリーンになればOKです。

public int calc(int x, int y)
{
	if (x < 0 || y < 0)
	{
		throw new Exception(&quot;数値は0以上を指定してください&quot;);
	}
	int z = x + y;
	if ( z >= 10 )
	{
		throw new Exception( &quot;答えが10以上です&quot;);
	}
	return z;
}

image

■なぜ、ロジックをUIから分離させるのか?

ここまで試した方はわかると思いますが(いやいや、TDDだから周知の事実かもしれないけど)、分離したほうがテストがしやすいのです。逆にいえば、UIべったりにロジックを入れてしまうとテストしずらい。あるいは、テストできないコードになります。まあ、UI自体をテストすることも可能なので、テストできない訳ではないのですが。それはそれとして、TDDの基本の基本はここからスタートです。ケントベックの「テスト駆動開発入門」は残念ながら絶版扱いになっていますが、このチュートリアル(Phythonで書かれている) を1回通してみると、どこにTDDを使えば「効果的なのか」がわかります。逆に言えば、作業量を考えたときに効果的でないところにTDDを利用してもダメなんです、ということです。

で、私がプログラムを書くときに気を付けるのは、テストがしやすいクラスあるいは設計をする、ところに注力してます。テスト可能なクラス≒テストしやすい設計≒不具合対処がしやすい、というパターンですね。まあ、うまくいかないことも多々あるので、完全にという訳にはいきませんが、コアな部分(ライブラリ部分)をテスト可能にしておくのは必須かな、と常々思っています。まあ、ひとまず、1日目のチュートリアルということで。

カテゴリー: 雑談 | ノーマルにMSTestを使おう はコメントを受け付けていません

VS2013のWPFプロジェクトに「Blendで開く」を追加する方法

Visual Studio 2012からある問題なのですが、WPFプロジェクトからBlendを開くことができません。ストアアプリやSilverlightのXAMLファイルからは開けるのに、何故にWPFプロジェクトからは駄目なのか?は謎なのですが…多分、バグですよね、これ。

Blendで開く操作がプロジェクトの種類によって異なる | Microsoft Connect
https://connect.microsoft.com/VisualStudio/feedback/details/781581/blend
VS2012のWPFプロジェクトに、「Blendで開く」メニューを追加する – SourceChord
http://d.hatena.ne.jp/minami_SC/20130407/1365351086

上記のVS2012で、外部ツールを使ってコンテキストメニューに追加する方法はVS2013でも有効です。なので、まあ自分で使う分にはいいか、ってところなんですが…ストアアプリのプロジェクトには「Blendで開く(B)…」があるのだから、どっかで設定はしているハズなんですよ、と思い直して探してみました。

■コンテキストメニューのカスタマイズ

Visual Studio ではメニューやらコンテキストメニューは、カスタマイズが可能です。メニュー部分を右クリックして「カスタマイズ」を選択、そのあとに「カスタマイズ」ダイアログのコマンドタブを開くと、Visual Studio で利用するメニューを変更できます。

このコンテキストメニューの「プロジェクトとソリューション コンテキストメニュー|項目」に、アイテムを右クリックしたときにコンテキストメニューが割り当てられています。これをどうやって探すのかは不明なんですが…勘で見つけるしかないんですかね?

image

これを見ると、「Blend で開く」が3つ並んでいます。なんで3つ並んでいるのか分かりませんが、「B付き」と「B無し」と、「…無し」ですね。なんか理由があるのかな?と思って、この元ネタを探します。

元ネタを探すのが結構苦労したのですが、「コマンド追加」ボタンをクリックして、「表示」カテゴリの中にあります。ここには「B付き」と「B無し」はあるのですが「…無し」はありません。まあ、良しとしましょう。

image

さて、ここでストアアプリのコンテキストメニューを見てみると、「B付き」のメニューを使っていることが分かります。「B無し」は何処で使っているのかわかりませんが、ストアアプリのプロジェクトと同じものを使えば動くのかな?と想像ができます。

image

そんな訳で、先のカスタマイズで「B付き」のBlendで開くを追加すると、WPFプロジェクトにも「Blendで開く」が無事表示されます。めでたし、めでたしと思い気や、実は違うのです。WPFプロジェクトに「Blendで開く」が表示されるようになったのですが、今度はストアアプリのほうに「Blendで開く」が2つ表示されるようになってしまいます。

こんな風に新しく「Blendで追加」を追加すると…

image

ストアアプリのほうに二重にメニューが出てきてしまいます。

image

という訳で、なんとなくわかりますね。おそらく、最初の「Blendで開く(B)…」が変な登録の仕方をされているようなのです。

そんな訳で、

  1. もともとある、「Blend で開く(B)…」を削除する。
  2. コマンド追加で、「Blend で開く(B)…」を追加する。

という入れ替えをしてやると、無事 WPF でも Blendで開くが表示されます。

image

ちなみに、二重に「Blendを開く」を追加してしまった場合は「すべてリセット」ボタンで、リセットさせてください。何故か、カスタマイズのダイアログを開くと二重にあるはずの「Blendを開く」メニューがひとつしかなくて、目的のメニューを削除できません(これも多分、不具合)。まあ、2つとも同じ名称なのでどちらを削除していいか分からないので、リセットしかないのですが。

■プロジェクトのコンテキストメニュ―に追加

先の方法は、*.xaml の右クリックのコンテキストメニューなのですが、WPF プロジェクト自体のコンテキストメニューにも追加できます。

プロジェクトのほうは「プロジェクトとソリューション コンテキストメニュー|プロジェクト」のほうの「Bあり」のBlendで開くを、削除→追加します。

image

これで、ストアアプリやSilverlightのプロジェクトと同じように、WPFプロジェクトでもBlendと行き来が可能になります…って、これは、VS2013のUpdateで直してほしいところ。

カテゴリー: 開発 | VS2013のWPFプロジェクトに「Blendで開く」を追加する方法 はコメントを受け付けていません

艦これ 諜報員 ver.0.5.1

秘書艦の切り替えがうまくできていなかったので、バグフィックス。編集で「変更」をしたときのタイミングではなくて、執務室を開いたときの ship2 で取得するように変更しました。ブラウザで艦これを開いたあとに、諜報員を開く、そのあとポチポチと執務室と編成を移動させれば、秘書官が変わります…という妙な仕様ですね。

image

最近はデスクトップに五月雨が鎮座しておりふ。

最新の実行ファイルは こちら から。

カテゴリー: 艦これ | 艦これ 諜報員 ver.0.5.1 はコメントを受け付けていません

SQLとオブジェクト指向とO/Rマッピングの隙間で

ベンチャー社長で技術者で: オブジェクト指向言語で処理したら保守性が悪い!
http://el.jibun.atmarkit.co.jp/g1sys/2009/12/post-af96.html
SQLに依存することの危険性 ー 単体DBサーバでは終わらない時代の考え方 | 独り言v6
http://www.nonsensecorner.com/wp25/?p=4075

どちらも4年程前の記事ので、直接の言及は避けて、その延長上にメモを残して置かんと欲す…まあ、当時の状況とかもろもろの業務的な事情とか、その当時の「流行り」みたいなものがあるので、なんとも言えないところがあるのです。特に、オブジェクト脳とO/Rマッピング、さらに最近のNoSQLのあたりは数年たつと熱がおさまるもので、業務的にはうまく枯れてくれるとよいかなという分野です。どれも長く使うものですからね。

先に、先の2つの記事の欠けているところを補っておきます。

■ストアドプロシージャはロジックを含めたほうが効率が良い

私もやったことがあるのですが、一時期オブジェクト指向的にストアドプロシージャを使おうと思っていたことがあります。うまく再利用すると良いのでは?と考えていたのですが、ちまちまと小さなストアドプロシージャを作ると、先の「※なぜ、WHERE 売価 <> fn_標準価格(得意先コード, 商品コード)としてないか分かりますか?」に陥ります。答えはマッチングする件数が多くなって、ストアド fn_標準価格 を何度も呼び出してしまうから…なのですが、実はこのノウハウ自体が不要なのです。一見、共通化して fn_標準価格 がうまく隠蔽化しているように見えますが、これのために、インデックスの配置やらストアドを使ったときのクエリのノウハウやらが混在してしまい、データベースの性能をうまく引き出せなくなります。なので、ちまちまとした共通化をするよりも、がっつりとコピペをしてクエリを作ってしまったほうが、ストアド自体の性能が引き上げられるし、バッドノウハウを埋め込まなくて済みます(勿論、この記事にあるのは「例え」なので、その例えが悪かったってことなのですが)。

なので、ストアドを作るならば、100行位にわたる関数を作ったほうが、ストアド自体の本領を発揮できます。複数のクエリと内部変数と制御文を組み合わせて、普通の関数として使えるところがストアドの良いところなので、業務ロジックを含めたいのであれば、そうしたほうがよいでしょう。逆に、オブジェクト指向的なメソッドのまとまりとして作る場合には、ストアドは諦めて C# などのコードのほうにクエリを持ってきたほうがうまく動きます。これは私の経験上なので、それぞれの経験により、別々なのです。逆に言えば、ストアドをクエリから呼び出すのは極力避けたほうが良いでしょう。先の記事にあるように、最終的な SELECT の結果でストアドを使う(条件を絞ったあと)ってことになります。以前、WHERE 句に入れられて性能的にえらい目にあったという…私です。

ちなみに、ストアドは SQL を事前にコンパイルするから何度も使う時に早い、という以前の事情があったのですが、最近ではあまり変わりません。いや、1000行ぐらいのクエリなら意味があるかもしれませんが、最近のクエリ解析のほうでは、以前のクエリを内部で覚えていたりするので動的にクエリを作らない限り以前のクエリ解析のものが使われます。結果的に SQL 文を文字列で送っているのと変わらなくなります。ただし、文字列で構成するときにできるだけ変数を活用するといいです。これは変数以外のところはクエリ文が変わらないという利点があるのと、コマンドラインなどのチェックが楽になります。まあ、SQL インジェクションの問題もありますが、変数自体はプレコンパイルのクエリでもよいし、ストアドにしてもよいでしょう。ストアドにすると、ソース管理自体が DB 内とプログラム内に分離されてしまうので当時から避ける傾向にあるのですが、そこはプログラムコードを書く人の配置によりけりというところです。プレコンパイルのクエリを確認しても、なんらかの API 用のクラスを作ってひとまとめにしておけばソース管理が楽になります。単体テストもやりやすいですからね。

■老婆心ながら、SQL を知らずに O/R マッピングだけを使っていると危険

いま CakePHP を使っているのですが、O/R マッピングには色々なものがあります。.NET でも ADO.NET Entity Framework があるわけで、SQL 文を覚えなくてもプログラム内でこなすことができます。以前は、SQL を書くメリットは高速化にあった(O/R マッピングが遅いという意味で)わけですが、LINQ が内部的に SQL を作成していたり、Web API を使った場合はそもそものボトルネックはネットワーク帯域にあったりするわけで、O/R マッピング自体の速度性能だけが重視される時代は終わった気がします。勿論、内部インフラを高速化する必要がある場合は、なんらかのマッパーやSQLを調節するわけですが、8割以上のデータベース検索は、漠然とした O/R マッピングの機能で十分まかなえます。特に、CakePHP や RoR(ごめん使ったことがない)、Lightswitch などで作られたマスター定義の画面は、実にあっという間にできるので「生産性」が高いことこの上ないです。10年程前は、ちまちまと DB のマスター画面を作っていたのですが、顧客が操作を覚えてくれさえすれば、これらのチープなマスター定義の画面で十分です。逆に言えば、「顧客が操作を覚えてくれなかった」り、これらの O/R マッピングツールの自動生成では追いつけない画面は、なんらかの UI インターフェースを作らないと駄目ってことです。

O/R マッピングの join の仕方は色々あるのですが、やや面倒臭いところがあります。CakePHP の join は簡単な部分しか使ってなくて、面倒なところは query を使ってしまっている私なのですが、なんらかの SELECT を高速に回そうと思うと SQL 文を書いたほうが手早く書けます。SQL というのはそういう言語なのです。なので、O/R マッピングの join に直す場合は、SQL 文を書いた後に O/R マッピングの join を見直す、という手順にすると作業時間が少なくて済みます。このあたりは、相互に行き来できるほうが楽だったりします。よく、Access の ER 図を使って、テーブル間の連結を書いた後に SQL 文を書き出させて、それに WHERE 句を追加する、また Access の ER 図に戻す、という手順で学習をしていました。SQL 文は「文字」で構成されているために手早く書くには向いているのですが、全体を掴もうとすると難しいところがあります。しかし、サブクエリを使う場合には、あらかじめサブクエリを作ってチェックしてから FORM に入れ込むとか、そういう書き方ができます。さらっとした SQL 文であっても、1時間以上も時間が掛かったりします。これは、SQL 文自体に情報量が多いからなんですね。複雑怪奇なことをやる LINQ も似た位の時間が掛かってしまう(いや、複雑怪奇すぎるものは LINQ では無理だったりしますが)のですが、そんなときも SQL に立ち返ると以外とすんなりできたりします。

なので、いろいろな O/R マッピングがあるのですが、SQL 文は一通り覚えておくと作業効率が違いますし、試行錯誤がしやすくなります。なので、特定の O/R マッピング「だけ」を覚えてるのは避けておいたほうがよいでしょう。

■インピーダンスミスマッチの愚考

さて、本題に戻って、データベース(SQL) ?> O/R マッピング ?> オブジェクト指向 の変換を考え直してみます。オブジェクト脳と O/R マッピングが流行ったあたりなので、今となっては「うまく忘れ去れてている」ことを希望しているのですが、その中に出て来た「インピーダンスミスマッチ」という考え方は、あまり意味を持ちません。確かに、ミクロなレベルで、日付型だとか文字列だとか微妙な差異があってデータ変換的にはやらないといけないことなのですが、実は随分「些末なこと」です(と最近私は気づきました)。

例えば、オブジェクト指向的に次のように Person クラスを定義したとしましょう。

class Person {
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
}

このような場合、DB 的には次のようなマッチングを想定している訳です。

create table Person {
  ID int not null,
  Name varchar(256) not null,
  Age int not null, 
  PRIMARY KEY ( ID )
}

素直に DB とオブジェクト思考のマッピングができていますね。ID は更新用にプライマリーキーにあになる必要があります。大抵の「インピーダンスミスマッチ」は、このあたりの要素のレベルの議論になるのですが、実は現実はもっと大雑把/複雑です。

create table Person {
  ID int not null,
  Name varchar(256) not null,
  Birthday datetime, 
  PRIMARY KEY ( ID )
}

データベースのほうは、こんな風に誕生日(Birthday)が設定されているのです。当然ですよね。年齢は年ごとに代わるので年齢(Age)があるよりも、誕生日を入れておく方が永続化のデータベースとしては正しい設計です(勿論、当時の年齢を残すためにAgeを使う場合もありますが)。更に、誕生日が入力されないこともあるので、NULLが許容されています。

これを素直にオブジェクト思考にマッピングするときに、Birthday のプロパティを加えることができるのですが…ここで問題です。オブジェクト思考(プログラムのUI)のほうでは「年齢」が欲しいわけです。確かに、誕生日からいちいち計算することもできるのですが、それを Person クラスで使う側に設定させるのは変な話ですよね。オブジェクト指向のクラスがデータベースに寄りすぎています。

class Person {
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { 
    get { 
        if ( Birthday == null ) {
            return 0;
        } else {
            int age = DateTime.Now - Birthday.Year ;
            if ( Datetime.Now.AddYears(-age) < Birthday ) {
                age--;
            }
            retrun age;
        }
    }
  public DateTime? Birthday { get; set; }
}

こんな感じに、年齢(age)プロパティを書き換えます。Ageプロパティをnull許容にするかどうかは好みの問題で変えるとして、オブジェクト指向のクラスをもっと UI 層に寄せるならば、こんな書き方ができるはずです。これも「インピーダンスミスマッチ」の一種です。

もちろん、この作り方とは違って、O/R マッピング専用のクラス(DataPersonクラス)を作っておいて、UI で使う場合には Persosn クラスを使う、という方法もあります。大抵はそんな感じになっているのハズなんですが、やたら層ばっかりが多くなるし、DB が変更になったり、相互変換手順が多くなったりして面倒ですよね。バグの温床になりそうです。O/R マッパーを完全自動化するのであれば、DataPerson ?> Person タイプの二重変換が必要ですし、また、「完全に自動化」するのであれば、O/R マッパー自体のコードは手を入れないという方法もあります。実際、Entitiy Framework の場合には、完全自動化されているので手を入れません。別のメソッドを使ってクラスを拡張するし、そのほうがやりやすいでしょう。

なので、オブジェクト指向的なクラスを UI により寄り添う形にした場合は、必ずミスマッチが発生します。というか、発生しなほうがおかしいでしょう?ってな訳で、必ず「相互変換が必要」として考えるのが良いかと思います。逆に言えば、O/R マッピングを使っているから、クラスを自動生成できるし、そのまま UI に使えるから便利、という「直観的な」流れに固執すると危ないかな、ということです。

■しかし、状況によりけりで、O/R マッピングを使い分ける

とはいえ、巷に O/R マッピングのツールはあるし、作業効率上それを使ったほうが手早くできるし、間違いも少ないです。実際、Excel アプリ的なツールであれば、Lightswitch で簡単にできます(逆に言えば、それ以上のことをしようとすると難しいかも…ってことです)。それぞれの適用範囲があるので、それに逸脱しないように道具を使い分けていくのがベターでしょう。

SQL もできる。O/R マッピングツールも持っている。オブジェクト指向で画面を作れる(MVVMのバインドが作れる)という3つの道具が揃った段階で、それぞれの守備範囲をざっと書いておきます。経験則と、その時代のツールの状況次第なので、随時変わるかもしれませんが、現状ではという但し書きで。

  • ひとつのテーブルを扱うCRUD(マスター定義画面)は、O/R マッピングのツールで一気に作る。利用者は、それぞれの O/R マッパーの操作に慣れて頂く。
    → 利用者がIT素人の場合には、UI のみ自作。内部は、O/R マッパーで。
  • 2つ程度のテーブルの join ならば O/R マッピングで解決
    → 3つ以上ならば、SQL 文を書いたほうが無難。
  • テーブル定義とUIの表現が大きく異なる場合は、オブジェクト指向寄りで作成
    → 「データ」と「表現(プレゼンテーション)」の違いになるので、主にプレゼンテーション層を作るオブジェクト指向寄りにクラスを作ると、画面のテストがやりやすい。
    → 1対1のような場合は、O/R マッピングの CURD 画面にしてしまうのも良い。

まあ、ゲーム的のハードなホスティングの場合は別なんでしょうが、中小企業のWEB廻りとか社内の業務アプリなんかは、この方針でやると「手が抜ける/バグが少ない」っていうメリットがあります。

カテゴリー: 開発 | 1件のコメント

独自プロトコルを使って、コマンドラインからストアアプリへ通知する

先日の [WinRT] 独自プロトコルを使ってデスクトップアプリからストアアプリへ通知する – Moonmile Solutions Blog は、デスクトップアプリからプロトコルを通じて、ストアアプリへ通知した訳ですが、そうなると、コマンドラインからも(当然、powershell からも)起動ができますよね、ってことで、試してみました。

image

 C:Usersmasuda> start mm-message:送信する 

なるほど、普通に通知ができます。デスクトップアプリに WinRT を組み込むのは意外と手間なので、逆に、独自プロトコルを作って、ターゲットのストアアプリで処理をするほうが良いかもしれません。この方法を使うと、コマンドラインからストアアプリを起動するのも簡単。コマンドラインを使えるので、perl とか、他のスクリプト系の言語を使って、最終的にストアアプリに引き渡すことも可能です。

カテゴリー: WinRT | 独自プロトコルを使って、コマンドラインからストアアプリへ通知する はコメントを受け付けていません

[WinRT] 独自プロトコルを使ってデスクトップアプリからストアアプリへ通知する

デスクトップアプリからストアアプリに通信をするには、あらかじめストアアプリのほうからデスクトップアプリのほうにソケットを張っておく必要があり…と思っていたのですが、試してみるとストアアプリ間の独自プロトコルはデスクトップアプリとストアアプリ間にも使えるのですね。なるほど。

[WinRT] 独自プロトコルを使ってストアアプリ間で通信する – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/5023

で、作ったストアプリの「mm-message:」というプロコルを使って、デスクトップから通知を出せます。

■普通に Process.Start を使えば OK

実はブラウザを開く時のように普通に Process.Start メソッドが使えます。

private void button1_Click(object sender, EventArgs e)
{
    var text = textBox1.Text ;
    Process.Start("mm-message:" + text);
}

ちなみにストアアプリから通知する場合は、Launcher.LaunchUriAsync を使います。

private void Button_Click(object sender, RoutedEventArgs e)
{
    string text = this.text1.Text;
    var ret = Windows.System.Launcher.LaunchUriAsync(new Uri(
        "mm-message:" + text));
}

Windows 7 以前では独自プロトコルを登録するのは結構な手間だったのですが、Windows 8 の場合は、ストアアプリから登録してやると手軽にできるかも、という例として。このサンプルの場合は、ターゲットのストアアプリを OnActivated でわざとアクティブにしているので、アクティブにしないようにしてトーストだけ出すようにすれば、デスクトップアプリ→ストアアプリ→トースト通知、が手軽にできるかもしれません。デスクトップから直接出す方法は、艦これ諜報員で使っているのですが、意外と手間なのとスタート画面にタイルがデスクとトップアプリのアイコンになってしまいダサいので、ストアアプリとの組み合わせ技でできるならば、それで OK かも。

■サンプルコード

サンプルはこちら
http://sdrv.ms/17KCLxr

カテゴリー: C#, WinRT | [WinRT] 独自プロトコルを使ってデスクトップアプリからストアアプリへ通知する はコメントを受け付けていません

[WPF] 矩形をドラッグしてスイムレーンにスナップさせる

こちらは、仕事用のネタのために公開しておきます。

WPFでコントロールをドラッグ(1) – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/709
[win8] パンダ福笑いを作るために ManipulationDelta を使う – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3877

のところで、ManipulationDelta を WinRT(ストアアプリ)で使ったのですが、改めて WPF で作ってみると、ManipulationDelta 関係はタッチイベントでしか発生せず、マウスイベントでは発生しないのですね…勘違いしていました。ストアアプリの場合は、タッチイベントもマウスイベントも統合されているので、問題ないのですが、WPF の場合はやっぱりマウスの移動量を計算する方式ではないと駄目、ということで。

■デザイン時

デザイン時は、こんな風に Grid を使って描画をしておきます。この3つのスイムレーンに、赤、青、黄の3つのタスクを配置する、というイメージです。左下にある薄い赤の矩形は、ドラッグ時の描画用の矩形ですね。

■ドラッグ時のイベント処理

/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        // ドラッグ用の矩形は隠しておく
        manRect.Visibility = System.Windows.Visibility.Collapsed;
        // マウスイベントを割り付ける
        foreach (var el in grid1.Children)
        {
            var rec = el as Rectangle;
            if (rec != null)
            {
                if (rec.Fill == new SolidColorBrush( Colors.Beige)) continue;
                rec.MouseDown += manRect_MouseDown;
                rec.MouseMove += manRect_MouseMove;
                rec.MouseUp += manRect_MouseUp;
            }
        }
    }

    // ドラッグ時のフラグ
    private bool _isDrag = false;
    private Point _dragOffset;
    private Rectangle _capRect;

    /// <summary>
    /// マウスダウンイベント
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    private void manRect_MouseDown(object sender, MouseButtonEventArgs e)
    {
        UIElement el = sender as UIElement;
        if (el != null && e.LeftButton == MouseButtonState.Pressed )
        {
            // ドラッグ開始
            _isDrag = true;
            // オブジェクトとのオフセットを取得
            _dragOffset = e.GetPosition(el);
            // キャプチャ開始
            el.CaptureMouse();
            // ドラッグ対象の矩形(タスク)を保存しておく
            _capRect = (Rectangle)el;
            // ドラッグ用の矩形を表示開始
            manRect.Visibility = System.Windows.Visibility.Visible;
            // ドラッグ用の矩形のサイズを設定
            Point pt = Mouse.GetPosition(board);
            manRect.Width = ((Rectangle)el).ActualWidth;    // 描画時のサイズを取得
            manRect.Height = ((Rectangle)el).ActualHeight;
            manRect.Margin = new Thickness(
                pt.X - _dragOffset.X,
                pt.Y - _dragOffset.Y,
                0, 0);
            // ドラッグ対象のタスクを半透明に
            _capRect.Opacity = 0.5;

        }
    }

    /// <summary>
    /// マウス移動時
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    private void manRect_MouseMove(object sender, MouseEventArgs e)
    {
        if (_isDrag == true)
        {
            Point pt = Mouse.GetPosition(board);
            UIElement el = sender as UIElement;
            manRect.Margin = new Thickness(
                pt.X - _dragOffset.X,
                pt.Y - _dragOffset.Y,
                0, 0);
        }
    }

    /// <summary>
    /// マウスアップ時
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    private void manRect_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (_isDrag == true)
        {
            UIElement el = sender as UIElement;
            // マウスキャプチャをリリース
            el.ReleaseMouseCapture();
            // ドラッグ終了
            _isDrag = false;
            manRect.Visibility = System.Windows.Visibility.Collapsed;
            // タスクをスナッピングさせる
            Point pt = Mouse.GetPosition(grid1);
            // 3x3 の位置で特定
            int col = (int)( pt.X * 3 / grid1.Width);
            int row = (int)(pt.Y * 3 / grid1.Height);
            Grid.SetColumn(_capRect, col);
            Grid.SetRow(_capRect, row);
            _capRect.Opacity = 1.0;
        }
    }
}

元の矩形(タスク)をドラッグしても良いのですが、キャンセルや移動時にもともと何処にあったのかを分かりやすくするために、元の矩形は残したままドラッグさせます。なので、ドラッグ用の矩形(manRect)を移動させています。マウスキャプチャ el.CaptureMouse() をすると、元の矩形の位置からはみ出てもマウス移動を追ってくれるので、これを使います。元の矩形を移動させる場合にもマウスキャプチャを使ったほうが、マウスを高速に移動させたときにも追随します。

矩形のサイズを ActualWidth 等で取ってきているのは、スイムレーンを Grid で作って、これに完全にスナップさせているからです。このため、元の矩形は Width/Height ではサイズが取れないので(設定されていないので)、ActualWidth で実際の描画時のサイズを取得します。

■実行してみる

移動中のカラーを揃えていないのと、横長の青い矩形のスナップ判定がいまいちなのですが、ひとまずドラッグは綺麗に動いています。同じことを、ストアアプリでやる場合には、ManipulationDelta を使うとドラッグ中のフラグとかが必要なくなります。

サンプルはこちら http://sdrv.ms/17ZZ5UF

こんな風なタスクの置き換えツールは、マウスでやるよりもタブレット使ってタッチでやるほうが素早くできるので、Excel で PERT 的にタスク管理をするよりも、何らかのタブレットツールがあると計画の立て直しとかが手早くできるのではないかな…と思いつつ数年たっていますが。まあ、今回のタスク配置の画面で使うといとで、メモ的に。

 

カテゴリー: 開発, C# | [WPF] 矩形をドラッグしてスイムレーンにスナップさせる はコメントを受け付けていません

HttpClient を使って建造送信を偽装する…ってのは取り止め

艦これの「建造」をハックする記事は、一度取りやめておきます。
HttpClient の Content-type の設定方法のネタ絡みだったのですが、ちょっとまずいですね。反省しています。

ちなみに運営側の「建造」ハックの対抗策としては(他のコマンドもそうなんですが)、

  • コマンドの通番IDを振る。
  • コマンドと一緒にハッシュ値を送る。
  • Cookieでtokenを隠蔽化する。

などのガードの方法が考えられます。ひとまず、先のコードは Apache ログで一発で引っ掛けられます。

Content-Type のハックは、iPhone サイト等の偽装に変えて、公開しなおしましょう。

カテゴリー: C#, 艦これ | HttpClient を使って建造送信を偽装する…ってのは取り止め はコメントを受け付けていません

[WinRT] 画像を丸く切り抜いて表示する方法

アプリ開発企画 Spotlight スタート! – 高橋 忍のブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/shintak/archive/2013/09/03/10445963.aspx

の中に「艦これ」の単語があるので、そのまま戦略ボードを作るのはつまらないので、昨晩つくってみたのがこれです。

戦略ボードとはなんか違う…というか、全く違うものになってしまったのでアレですし、そのままで版権の問題があるからストアには出せない(と思う)ので、テクニック的に晒しておきます。
元画像から円形でクリッピングして、それを覗いてみる、というサーチライトみたな効果ですね。ひとつだけのサーチライトの場合、黒の透過画像を使ってクリッピングするほうが楽なのですが、今回のように複数の円をクリッピングして表示させる場合には、直接クリッピングが必要になります。でも、需要はあるのか?

■WinRTではClipが四角しかできない

画像を丸く切り抜くためには、WPF では EllipseGeometry を使うのですが残念ながらストアアプリの WinRT にはこれがありません。RectangleGeometry という矩形のクリッピングしかできないので、矩形以外のクリップには ImageBrush を使います。

クイック スタート: Image と ImageBrush (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/hh868203.aspx

WFP では、できていたものが、WinRT で使えないのはどうなの?という気もしますが、まあ、描画速度を考えた処置なのでしょう。ちなみに、8.1 でも RectangleGeometry 以外は使えないので、実装漏れというわけではなさそうです。

■円でクリップするためにはFillを使う

ここでは、動的に画像を設定したいので、コードで書いていますが、XAML だけで書くとこんな感じになりあmす。

<Ellipse StrokeThickness=&quot;4&quot; Stroke=&quot;Green&quot;
         Canvas.Left=&quot;60&quot; Canvas.Top=&quot;54&quot; Width=&quot;200&quot; Height=&quot;200&quot;>
    <Ellipse.Fill>
        <ImageBrush
            Stretch=&quot;None&quot;
            ImageSource=&quot;ms-appx:///images/64[8].png&quot;>
        </ImageBrush>
    </Ellipse.Fill>
</Ellipse>

Stretch=”None” にしてあるのは、元のサイズのまま表示させるためなのですが、None の時は画像の中心で表示されるのが曲者ですね。これを左上を原点にするために、画像のサイズが必要になります。

■拡大縮小のためにManipulationDeltaを使う

コントロールの移動とサイズ変更には、ManipulationDelta イベントを使います。拡大縮小の場合は、コントロールの中心を原点にして行いたいので、こんな風に x,y 座標を調節する必要あります。Canvas をつかうと計算が多少楽になります。

private void Grid_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var el = e.OriginalSource as UIElement;

    if (e.Delta.Scale == 1.0)
    {
        double x = Canvas.GetLeft(el) + e.Delta.Translation.X;
        double y = Canvas.GetTop(el) + e.Delta.Translation.Y;
        Canvas.SetLeft(el, x);
        Canvas.SetTop(el, y);
    }
    else
    {
        double w = ((Circle)el).Width;
        double h = ((Circle)el).Height;
        double x = Canvas.GetLeft(el);
        double y = Canvas.GetTop(el);
        x -= (e.Delta.Scale-1.0) * w /2.0;
        y -= (e.Delta.Scale-1.0) * h /2.0;
        w *= e.Delta.Scale;
        h *= e.Delta.Scale;
        Canvas.SetLeft(el, x);
        Canvas.SetTop(el, y);
        ((Circle)el).Width = w;
        ((Circle)el).Height = h;
    }
}

■画像の位置合わせをするためにTranslateTransformを使う

移動させるためのコントロールは、ユーザーコントロールとして作っておきます。
参照させたいところ(TranslateTransform の X,Y の座標)は、あらかじめ名前を付けておきます。TranslateTransform というのは、先の ImageBrush で描画するときの原点を設定する方法です。

    x:Class=&quot;SearchLight.Circle&quot;
    xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:local=&quot;using:SearchLight&quot;
    xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
    xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
    mc:Ignorable=&quot;d&quot;
    d:DesignHeight=&quot;200&quot;
    d:DesignWidth=&quot;200&quot;
    ManipulationDelta=&quot;UserControl_ManipulationDelta&quot; SizeChanged=&quot;UserControl_SizeChanged&quot;
    >
    <Grid>
        <Ellipse StrokeThickness=&quot;4&quot; Stroke=&quot;Red&quot; >
            <Ellipse.Fill>
                <ImageBrush
                    x:Name=&quot;ib&quot;
                    Stretch=&quot;None&quot;>
                    <ImageBrush.Transform>
                        <TranslateTransform
                            x:Name=&quot;tr&quot;
                            X=&quot;0&quot; Y=&quot;0&quot;></TranslateTransform>
                    </ImageBrush.Transform>
                </ImageBrush>
            </Ellipse.Fill>
        </Ellipse>
    </Grid>
</UserControl>

そんな訳で、コントロールを移動しても、元の画像を移動せずにクリッピングする、というイベントが作れます。原点からのコントロールの位置は、TransformPoint を使って取っていますが、実際は画像の原点から合わせたほうがよいので、もうちょっと違う書き方が必要かも。

private void UserControl_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var pt = this.TransformToVisual(null).TransformPoint(new Point(0, 0));
    marX = (_imageSize.Width - this.Width) / 2.0;
    marY = (_imageSize.Height - this.Height) / 2.0;
    tr.X = -pt.X + marX;
    tr.Y = -pt.Y + marY;
}

そんな訳で、戦略ボードのようにコントロールを追加して、円を移動させたり、拡大縮小させたりすることができたのですが、移動させているときに画像がずれるのが気に食わないですよね。ええ、このあたりは、ClipとImageBrushによるFillの違いかと。たぶん、WPFでClipを使ったほうがスムースに動くと思います。

20130915_03

■サンプルコード

サンプルコードは、以下からどうぞ。
http://sdrv.ms/1eAP7yp

 

カテゴリー: 開発, C#, WinRT | [WinRT] 画像を丸く切り抜いて表示する方法 はコメントを受け付けていません