アプリ開発企画 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す。
1 2 3 4 5 6 7 8 9 | < Ellipse StrokeThickness = "4" Stroke = "Green" Canvas.Left = "60" Canvas.Top = "54" Width = "200" Height = "200" > < Ellipse.Fill > < ImageBrush Stretch = "None" ImageSource = "ms-appx:///images/64[8].png" > </ ImageBrush > </ Ellipse.Fill > </ Ellipse > |
Stretch=”None” にしてあるのは、元のサイズのまま表示させるためなのですが、None の時は画像の中心で表示されるのが曲者ですね。これを左上を原点にするために、画像のサイズが必要になります。
■拡大縮小のためにManipulationDeltaを使う
コントロールの移動とサイズ変更には、ManipulationDelta イベントを使います。拡大縮小の場合は、コントロールの中心を原点にして行いたいので、こんな風に x,y 座標を調節する必要あります。Canvas をつかうと計算が多少楽になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 で描画するときの原点を設定する方法です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | x:Class="SearchLight.Circle" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SearchLight" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="200" d:DesignWidth="200" ManipulationDelta="UserControl_ManipulationDelta" SizeChanged="UserControl_SizeChanged" > < Grid > < Ellipse StrokeThickness = "4" Stroke = "Red" > < Ellipse.Fill > < ImageBrush x:Name = "ib" Stretch = "None" > < ImageBrush.Transform > < TranslateTransform x:Name = "tr" X = "0" Y = "0" ></ TranslateTransform > </ ImageBrush.Transform > </ ImageBrush > </ Ellipse.Fill > </ Ellipse > </ Grid > </ UserControl > |
そんな訳で、コントロールを移動しても、元の画像を移動せずにクリッピングする、というイベントが作れます。原点からのコントロールの位置は、TransformPoint を使って取っていますが、実際は画像の原点から合わせたほうがよいので、もうちょっと違う書き方が必要かも。
1 2 3 4 5 6 7 8 | 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を使ったほうがスムースに動くと思います。
■サンプルコード
サンプルコードは、以下からどうぞ。
http://sdrv.ms/1eAP7yp