まず初めに、このプログラムで実装する機能の動作を、下記に解説しておきます。
上段に少し大きめの画像が、下段には複数の小さめの画像が配置されています(図1)。上段の画像を下段の画像の集団にドラッグ&ドロップすると、画像のサイズが、RippleEffect効果を伴って、下段内の画像のサイズと同じになります(図2)。 RippleEffectは、画像に波紋をシミュレートする効果です。上段に画像をドラッグ&ドロップした場合は、画像サイズは元の大きいサイズのままになっています。
|
図1:上段に少し大きめの画像が配置され、下段には複数の小さめの画像が配置されている(クリックで拡大) |
|
図2:上段の少し大きめの画像を、下段にドラッグ&ドロップすると、RippleEffect効果を伴って、画像サイズが同じになる(クリックで拡大) |
今回のサンプルは以下よりダウンロードできます。
→ 今回のサンプルファイル(2.43MB)
新規プロジェクトの作成
早速サンプルを作っていきましょう。本稿では開発言語にVisual Basicを用います。
VS 2010のメニューから[ファイル(F)/新規作成(N)/プロジェクト(P)]を選択します。次に、「Silverlight アプリケーション」を選択して、「名前(N)」に任意のプロジェクト名を指定します。ここでは「SL4_ImageDropRipple」という名前を付けています。
ソリューションエクスプローラー内にImageというフォルダを作成して、4枚の画像も追加しておきます。ダウンロードされたサンプル・ファイルには画像は追加済みです。
コントロールの配置
<UserControl>要素のWidthに800、Heightに600と指定します。ツールボックスからCanvasコントロールを配置します。Widthが800、Heightが300のCanvasコントロールを2個、上下に配置します。先に下のCanvasを配置し、x:NameにTargetと指定します。次に、上にCanvasを配置し、x:NameにSourceと指定します(図3)。書き出されるコードはリスト1のようになります。
|
図3:Canvasを上下に2個配置する(クリックで拡大) |
リスト1 書き出されたXAMLコード(MainPage.xaml)
01 | <UserControl x:Class="SL4_ImageDropRipple.MainPage" |
07 | d:DesignHeight="300" d:DesignWidth="400" Width="800" Height="600"> |
08 | <Grid x:Name="LayoutRoot" Background="White"> |
09 | <Canvas Height="300" HorizontalAlignment="Left" Margin="0,300,0,0" x:Name="Target" VerticalAlignment="Top" Width="800" /> |
10 | <Canvas Height="300" HorizontalAlignment="Left" x:Name="Source" VerticalAlignment="Top" Width="800" /> |
次にSourceのCanvas内にImageコントロールを1個配置します。Widthに320、Heightに240を指定し、SourceプロパティにImageフォルダ内の画像を指定します。ここでは「菜の花.jpg」を指定しています。
同様にTargetのCanvas内に3個のImageコントロールを適当な位置に配置し、SourceプロパティにImageフォルダ内の画像を指定します。この場合のImageコントロールのWidthは160、Heightは120としておきます(図4)。
|
図4:2つのCanvas内にImageコントロールを配置し画像を読み込む(クリックで拡大) |
書き出されるコードはリスト2のようになります。
リスト2 書き出されたXAMLコード(MainPage.xaml)
01 | (1)x:NameがTargetの<Canvas> 要素内に3個の<Image>要素を配置しています。Widthは160、Heightは120としています。 |
02 | (2)x:NameがSourceの<Canvas>要素内に1個の<Image>要素を配置しています。Widthは320、Heightは240としています。 |
03 | <UserControl x:Class="SL4_ImageDropRipple.MainPage" |
09 | d:DesignHeight="300" d:DesignWidth="400" Width="800" Height="600"> |
11 | <Grid x:Name="LayoutRoot" Background="White"> |
12 | <Canvas Height="300" HorizontalAlignment="Left" Margin="0,300,0,0" x:Name="Target" VerticalAlignment="Top" Width="800"> ■(1) |
13 | <Image Canvas.Left="614" Canvas.Top="17" Height="120" Name="Image2" Stretch="Fill" Width="160" Source="/SL4_ImageDropRipple;component/Image/桜.jpg" /> |
14 | <Image Canvas.Left="335" Canvas.Top="163" Height="120" Name="Image3" Stretch="Fill" Width="160" Source="/SL4_ImageDropRipple;component/Image/黄色い花.jpg" /> |
15 | <Image Canvas.Left="31" Canvas.Top="17" Height="120" Name="Image4" Stretch="Fill" Width="160" Source="/SL4_ImageDropRipple;component/Image/ポピー.jpg" /> |
17 | <Canvas Height="300" HorizontalAlignment="Left" x:Name="Source" VerticalAlignment="Top" Width="800"> ■(2) |
18 | <Image Canvas.Left="230" Canvas.Top="27" Height="240" Name="Image1" Stretch="Fill" Width="320" Source="/SL4_ImageDropRipple;component/Image/菜の花.jpg" /> |
ソリューションエクスプローラー内の、MainPage.xamlを選択し、マウスの右クリックで表示されるメニューの、「Expression Blendを開く(X)」を選択し、Blend4を起動します。
Blend4でのImageのDropShadowEffec(陰影)の設定
表示されている4枚の画像を全て選択し、プロパティの[外観]パネル内のEffectの横にある[新規作成]ボタンをクリックします。「オブジェクトの選択」画面が表示されますので、DropShadowEffectを選択します(図5)。
|
図5:DropShadowEffectを選択する(クリックで拡大) |
全てのImageコントロールを選択していたのを解除し、一番大きい画像のImage1を選択します。プロパティの[外観]パネル内のEffectの左に、矢印アイコンが表示されますので、これをクリックするとDropShadowEffectの各種プロパティが設定できます。BlurRadiusには影のぼかし程度の値を指定します。ここでは12を指定します。Colorには影の色を指定します。ShadowDepthにはImageと影との距離の値として、20を指定します。そのほかはデフォルトのままです(図6)。ほかの3枚のImageにも同じ値を指定します。
|
図6:DropShadowEffectのプロパティを設定する |
Blendの画面上では、DropShasowEffectが適用した形では表示されませんが、Blend4のメニューの「プロジェクト(P)/プロジェクトの実行(R)」で実行すると、DropShadowEffectが適用されているのがわかります(図7)。またVS2010画面上では、DropShadowEffectが適用されて表示されます。
|
図7:DropShadowEffectが適用されている(クリックで拡大) |
Canvasの背景色の設定
次に、2つのCanvasの背景色を設定します。「オブジェクトとタイムライン(B)」から、Sourceを選択し、プロパティの[ブラシ]パネルにあるBackgroundを選択します。「グラデーションブラシ」アイコンをクリックします(図8)。
|
図8:Canvas(Source)の背景色に「グラデーションブラシ」を適用する |
上から下に向かってグレー系統色のグラデーションがかかります。上の方が暗くなり、下に向かって明るくなっているのを逆にします。色の「エディター」内の2つのカラーストップを左右入れ替えることで、上下の明暗のグラデーションが逆になります(図9)。上の方が明るく下の方が暗いグラデーションになります。
|
図9:カラーストップを左右逆にする(クリックで拡大) |
同じようにTargetのCanvasにも背景色にグラデーションを設定します。グラデーションの明暗の向きはそのままで構いません。全体に、中心が暗く外に向かって明るくなるグラデーションがかかります(図10)。
|
図10:2つのCanvasの背景色にグラデーションを指定した(クリックで拡大) |
状態(S)の設定
「状態(S)」パネルの「状態グループの追加」アイコンをクリックし(図11)、さらに「状態の追加」アイコンをクリックします(図12)。
「●VisualState状態 記録オン」に変わり、アートボード上の画面全体が赤の枠線で囲まれます。この状態でタイムラインの記録が可能になります。
「ImageSmall」という名前を入力します(図13)。「タイムラインを表示する」アイコンをクリックして、タイムラインを表示します(図14)。
|
図11:「状態グループの追加」アイコンをクリックする |
|
図12:「状態の追加」アイコンをクリックする |
|
図13:「ImageSmall」という名前を入力する |
|
図14:タイムラインを表示する |
「オブジェクトとタイムライン(B)」からImage1を選択します。黄色の再生ヘッドが0の位置で、楕円マークと+の追加された、「キーフレームの記録」アイコンをクリックします。Image1の再生ヘッドが0の位置に楕円マークが表示されます(図15)。
|
図15:Image1の再生ヘッドが0の位置に楕円マークが表示された |
次に再生ヘッドを0.5の位置に移動し、プロパティの[変換]パネルにある、RenderTransformの「拡大縮小」アイコンをクリックし、Xに0.5、Yに0.5と入力します。Image1の画像が縮小されます(図16)。
|
図16:RenderTransformの「拡大縮小」のXに0.5、Yに0.5と指定する(クリックで拡大) |
「●ImageSmall状態記録オン」の●をクリックして、タイムラインの記録をオフにします。
同様な手順で、ImageLargeという状態を追加します。タイムラインの記録をオフの状態にして、Image1を選択し、再生ヘッドが0の位置で、プロパティの[変換]パネルにあるRenderTransformの「拡大縮小」のXとYの値に0.5と入力します。
「●ImageLarge記録オフ」の●をクリックして、記録オンにします。再生ヘッドが0の位置で、「キーフレームの記録」アイコンをクリックします。再生ヘッドを0.5の位置に移動し、RenderTransformの「拡大縮小」のXとYの値に1と入力します(図17)。ここまでの手順はImage1が選択された状態で行ってください。
|
図17:ImageLargeのタイムラインを記録する。Image1のRenderTransformの「拡大縮小」のXとYに1を入力し画像が元のサイズに戻ってる(クリックで拡大) |
「●ImageLarge状態記録オン」の●をクリックして、記録オフにしてください。Image1が、元のサイズの状態である、再生ヘッドが0.5の位置で記録をオフにしてください。
Ripple特殊効果の設定
Blend4の[アセット(T)]パネルをクリックし、「コントロール」からRippleを検索して表示させます。「検索」欄に「Ripp」と入力すると、Rippleが表示されます(図3)。
|
図18:「検索」欄に「Ripp」と入力して、Rippleが表示された |
表示されたRippleを「オブジェクトとタイムライン(B)」内のTarget要素上にドラッグ&ドロップします(図19)。
|
図19:RippleをTarget上にドラッグ&ドロップした |
Targetの子として追加された、RippleEffectを選択して表示されるプロパティの「名前」に、myRippleEffectと指定し、Frequencyに0、 Magnitudeに0、 Phaseに0と指定しておきます(図20)。Frequencyにはシェーダー内の周波数の値を指定します。Magnitudeにはシェーダー内の振幅の値を指定します。Phaseにはシェーダー内のフェーズ値を指定します。
|
図20:RippleEffectのプロパティを設定する |
Storyboardの作成
「オブジェクトとタイムライン(B)」の下にあるストーリーボードの「新規作成」アイコン(+)をクリックし、RippleStoryboardというストーリーボードを作成します(図21)。
|
図21:RippleStoryboardという名前のストーリーボードを作成する(クリックで拡大) |
アートボード上の画面全体が赤の枠線で囲まれ、「●RippleStoryboardタイムライン記録オン」に変わります。この状態でタイムラインの記録が可能になります。
「タイムラインとオブジェクト(B)」内のRippleEffectを選択し、タイムラインの黄色い再生ヘッドを0秒に合わせます。RippleEffectのプロパティ「Frequency」に40と指定します。「Magnitude」には0、Phaseには0と指定します。既に0が初期値として入力されている場合も、上書きで0と入力してください。Centerの値はデフォルトの0.5のままです。
「オブジェクトとタイムライン(B)」内のRippleEffectのEffect内に、いま設定したプロパティが追加されます(図22)。
|
図22:各プロパティが追加された(クリックで拡大) |
次に、RippleEffectを選択した状態で、再生ヘッドを0.1秒の位置に移動し、「Frequency」に30、「Magnitude」に0.02、「Phase」に0と指定します。
次に、RippleEffectを選択した状態で、再生ヘッドを1.3秒の位置に移動し、「Frequency」に0、「Magnitude」に0、「Phase」に0と指定します。既に、0が入力されていても上書きで再入力してください。
0と上書き入力したプロパティにも、楕円のマークが追加されます(図23)。
|
図23:何も指定しなかったプロパティにも楕円のマークが追加されている |
ここまでの手順をまとめると、表1のようになります。
表1 RippleStoryboard
プロパティ名 | 再生ヘッドの位置(秒) |
---|
0 | 0.1 | 1.3 |
---|
Frequency | 40 | 30 | 0 |
Magnitude | 0 | 0.02 | 0 |
Phase | 0 | 0 | 0 |
「●RippleStoryboardタイムライン記録オン」の●をクリックしてオフとし、Blend4を終了してVS2010に戻ります。
ソリューションエクスプローラー内のMainPage.xamlを展開して表示されるMainPage.xaml.vbをダブルクリックしてリスト3のコードを記述します。
ロジックコードを記述する
リスト3 (MainPage.xaml.vb)
02 | Partial Public Class MainPage |
09 | マウスの左ボタンが押されたか、離されたかを判別するブール型メンバ変数、myMouseCaptureを宣言します。 |
10 | Dim myMouseCaputer As Boolean |
11 | マウスポインタのY座標を格納するメンバ変数myYPosを宣言します。 |
13 | マウスポインタのX座標を格納するメンバ変数myXPosを宣言します。 |
15 | ImageがDropされた時のY座標を格納するメンバ変数myYDropPos変数を宣言します。 |
16 | Dim myYDropPos As Double |
19 | Image1のMouseLeftButtonUpイベント時に、Image1Droppedプロシージャを実行します。 |
20 | Image1のMouseLeftButtonDownイベント時に、Image1Largedプロシージャを実行します。 |
21 | Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded |
22 | AddHandler Image1.MouseLeftButtonUp, AddressOf Image1Dropped |
23 | AddHandler Image1.MouseLeftButtonDown, AddressOf Image1Larged |
26 | ■Image1上でマウスの左ボタンが押された時の処理 |
27 | カーソルを手の形にします。senderオブジェクトの持っているImageの情報を、DirectCastでImage型に変換し、変数myImageで参照します。マウスポインタの左上隅にあるY座標の値をメンバ変数myYPosに格納します。マウスポインタの左上隅にあるX座標の値をメンバ変数myXPosに格納します。Image1上でマウスの左ボタンが押されたため、ブール型の変数myMouseCaptureにTrueを指定しておきます。CaptureMouseメソッドで、Image1のマウスキャプチャを有効にします。 |
28 | Private Sub Image1_MouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Image1.MouseLeftButtonDown |
29 | Image1.Cursor = Cursors.Hand |
30 | Dim myImage As Image = DirectCast(sender, Image) |
31 | myYPos = e.GetPosition(Nothing).Y |
32 | myXPos = e.GetPosition(Nothing).X |
34 | myImage.CaptureMouse() |
37 | ■Image1上でマウスの左ボタンが離された時の処理 |
38 | カーソルを矢印の形にします。senderオブジェクトの持っているImageの情報を、DirectCastでImage型に変換し、変数myImageで参照します。Image1上でマウスの左ボタンが離されたため、ブール型の変数myMouseCaptureにFalseを指定しておきます。ReleaseMouseCaptureメソッドで、Image1のマウスキャプチャを無効にします。Image1がDropされた時のY座標を格納するメンバ変数myYDropPos変数に、現在のマウスポインタのY座標の値を指定します。 |
39 | Private Sub Image1_MouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Image1.MouseLeftButtonUp |
40 | Image1.Cursor = Cursors.Arrow |
41 | Dim myImage As Image = DirectCast(sender, Image) |
42 | myMouseCaputer = False |
43 | myImage.ReleaseMouseCapture() |
47 | ■Image1をマウスでドラッグして移動した時の処理 |
48 | senderオブジェクトの持っているImageの情報を、DirectCastでImage型に変換し、変数myImageで参照します。Image1上でマウスの左が押されている場合に、以下の処理を実行します。 |
49 | 移動したマウスポインタのY座標の値から、マウスの左ボタンが押された時点で取得されたY座標の値(myYPos)を減算した値を、変数myVerticalに格納します。 |
50 | 移動したマウスポインタのX座標の値から、マウスの左ボタンが押された時点で取得されたX座標の値(myXPos)を減算した値を、変数myHorizontalに格納します。 |
51 | myVerticalの値に、GetValueメソッドで取得したImage1の、CanvasのTopの値を加算した値を、変数myNewTopに格納します。 |
52 | myHorizontalの値に、GetValueメソッドで取得したImage1の、CanvasのLeftの値を加算した値を、変数myNewLeftに格納します。 |
53 | SetValueメソッドで、Image1のTopとLeftの値に、myNewTopとmyNewLeftの値を指定します。 |
54 | 現在のマウスポインタのY座標の値を、myYPosメンバ変数に格納しておきます。同様に、現在のマウスポインタのX座標の値を、myXPosメンバ変数に格納しておきます。 |
55 | Private Sub Image1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Image1.MouseMove |
56 | Dim myImage As Image = DirectCast(sender, Image) |
57 | If myMouseCaputer = True Then |
58 | Dim myVertical As Double = e.GetPosition(Nothing).Y - myYPos |
59 | Dim myHorizontal As Double = e.GetPosition(Nothing).X - myXPos |
60 | Dim myNewTop As Double = myVertical + DirectCast(myImage.GetValue(Canvas.TopProperty), Double) |
61 | Dim myNewLeft As Double = myHorizontal + DirectCast(myImage.GetValue(Canvas.LeftProperty), Double) |
62 | myImage.SetValue(Canvas.TopProperty, myNewTop) |
63 | myImage.SetValue(Canvas.LeftProperty, myNewLeft) |
64 | myYPos = e.GetPosition(Nothing).Y |
65 | myXPos = e.GetPosition(Nothing).X |
69 | ■Image1上でマウスの左ボタンが離された時の処理 |
70 | マウスが離された時のマウスポインタのY座標の値が300より大きかった場合、つまり、Targetキャンバス内にImageがドロップされた時の処理です。 |
71 | VisualStateManager.GotoStateメソッドで、画像が縮小するImageSmallの VisualSateを実行します。VisualStateManager.GotoStateメソッドの書式は下記の通りです。 |
72 | VisualStateManager.GotoState(状態を遷移させるコントロール,状態名,VisualTransitionを使うかどうかのBoolean値(使用する場合はTrue、それ以外はFalse) |
73 | Targetキャンバス内にある現在のマウスポインタの位置をmyCP変数に格納し、XとY座標の位置を取得します。XとY座標の値を取得したmyCP変数の値を、RippleEffectのCenterプロパティの値に指定します。RippleEffectのストーリーボードを開始します。 |
74 | マウスが離された時のマウスポインタのY座標の値が300以外の場合は、画像が元の状態に戻る(拡大する)、ImageLargeのVisualStateを実行します。 |
75 | Private Sub Image1Dropped(ByVal sender As Object, ByVal e As MouseButtonEventArgs) |
76 | If myYDropPos > 300 Then |
77 | VisualStateManager.GoToState(Me, "ImageSmall", True) |
78 | Dim myCP As Point = e.GetPosition(Target) |
79 | myCP.X = myCP.X / Target.ActualWidth |
80 | myCP.Y = myCP.Y / Target.ActualHeight |
81 | myRippleEffect.Center = myCP |
82 | RippleStoryboard.Begin() |
84 | VisualStateManager.GoToState(Me, "ImageLarge", True) |
88 | ■Image1のMouseLeftButtonDownイベント時に実行される処理 |
89 | マウスが押下された時のマウスポインタのY座標の値が、300より小さい場合は(マウスポインタがSourceという名前のCanvas内にあった時(図3参照))、処理を中止します。マウスポインタがSourceという名前のCanvas内にあった時には、画像をドラッグ&ドロップしても画像は何も変化しません。それ以外は、画像が拡大するImageLargeのVisualStateを実行します。 |
90 | Private Sub Image1Larged(ByVal sender As Object, ByVal e As MouseButtonEventArgs) |
91 | If myYDropPos < 300 Then |
94 | VisualStateManager.GoToState(Me, "ImageLarge", True) |