アプリ開発企画 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="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 をつかうと計算が多少楽になります。
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="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 を使って取っていますが、実際は画像の原点から合わせたほうがよいので、もうちょっと違う書き方が必要かも。
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



