[Win8] スリープをした後にパスワードを入れなくてもログインできるようにする

Windows 8 の場合、Microsoft アカウントに紐づけるかローカルアカウントを設定するってことで、パスワードを入れておくのが普通です。特に Microsoft アカウントの場合は、パスワードなし、ってのができないので、ログインのときはセキュリティ的にパスワードが必須になります。

が、いざ、タブレット PC で使うと、スリープした後のパスワード入力ってのはちょっと面倒なんですよね。ソフトウェアキーボードを出して、ぽちぽち打ってもいいのですが、ハードウェアのキーボードでは打ちやすいけど、MS のソフトウェアキーボードではちょっと打ちづらい(数字が混じっているときは、いちいちモードを変えないといけないとか)ので、結構不便。

あと、2歳児にタブレットPCを遊ばせているときに、ふとスリープ状態になってしまって、パスワードが入れられなくて泣かれる、というパターンもあります。

そうなんです。家で使う分には他人が使うわけじゃないから、パスワードを入れなくてもいいようにしたい。しかし、SkyDrive やメールなどを使うために、Microsoft アカウントに紐づけているときは、必ずパスワードが必要だし、どうしたらよいか、ってときに。実は解決策があります。

image

チャームの「設定」をクリックした後で、下のほうにある「PC 設定の変更」をクリック。
そのあとに、 PC の設定から「ユーザー」をクリックして、「パスワードを持っている~」のところで「変更」ボタンをクリックします。

ちょっと文言がややこしいのですが、警告が出て OK をクリックすると、スリープの後ではパスワードの入力がいらなくなります。

image

なので、外に持って行ってしまったときはセキュリティ的によくないので(SkyDrive や Mail も見れてしまうので)すが、家で子供相手にしているときとか、家で自分しか使わないときは、これで十分ですね。

カテゴリー: windows 8 | [Win8] スリープをした後にパスワードを入れなくてもログインできるようにする はコメントを受け付けていません

[WinRT] Storyboard とユーザーコントロールでモーダル風ダイアログを作る

花札ゲームで、役ができたときに数秒間だけダイアログを出そうとしているので、その実験です。

■Blend で Storyboard を作る

ダイアログを開くときの sbOpen と閉じるときの sbClose という二つの Storyboard を作っておきます。

本当は枚数がいろいろなのですが、簡単にするために5枚だけ配置しておきます。タネやカスが成立したときは、別のユーザーコントロールを使うようにしようかなと。

長いですが、Blend で作った storyboard を晒します。

<UserControl.Resources>
	<Storyboard x:Name="sbOpen">
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pict1">
			<EasingDoubleKeyFrame KeyTime="0" Value="634"/>
			<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pict2">
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="565"/>
			<EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pict3">
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.6" Value="494"/>
			<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pict4">
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.6" Value="426"/>
			<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pict5">
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.6" Value="357"/>
			<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict1">
			<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
			<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict2">
			<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="0"/>
			<EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="1"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict3">
			<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.6" Value="0"/>
			<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict4">
			<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.6" Value="0"/>
			<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict5">
			<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
			<DiscreteDoubleKeyFrame KeyTime="0:0:0.6" Value="0"/>
			<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="textYaku">
			<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
			<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
		</DoubleAnimationUsingKeyFrames>
		<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="textYaku">
			<EasingColorKeyFrame KeyTime="0" Value="White"/>
			<EasingColorKeyFrame KeyTime="0:0:0.3" Value="#FFDAD002"/>
			<EasingColorKeyFrame KeyTime="0:0:0.5" Value="#FFFBFBFB"/>
		</ColorAnimationUsingKeyFrames>
	</Storyboard>
	<Storyboard x:Name="sbClose">
		<DoubleAnimation Duration="0:0:0.4" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="textYaku" d:IsOptimized="True">
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode="EaseIn"/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration="0:0:0.5" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict1" d:IsOptimized="True">
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode="EaseIn"/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration="0:0:0.5" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict2" d:IsOptimized="True">
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode="EaseIn"/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration="0:0:0.5" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict3" d:IsOptimized="True">
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode="EaseIn"/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration="0:0:0.5" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict4" d:IsOptimized="True">
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode="EaseIn"/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration="0:0:0.5" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pict5" d:IsOptimized="True">
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode="EaseIn"/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
	</Storyboard>
</UserControl.Resources>

花札は、右から左に流れる感じで出てきます。1枚1枚ちょっと違うタイミングで出てくるとゲームらしいですよね。あと、役の名前が出るときにちょっとだけ黄色から白に色が変化します。このあたりグラフィックの「アクセラレーター」が有効に働くように注意しないとダメなのよで、たとえば、フォントの大きさを変えようとするとパフォーマンスが落ちますという警告が出ます。

これに注意するとスムースなアニメーションが作れるかなと。

あと、札を表示するときに一瞬だけ光を付けたかったのですが(ちょっとだけ光るやつ)WinRT の XAML には、Effect 関係がなくなっているので(コードで追加するんだっけ?)、これはあとで調節します。代案としては、花札の大きさが固定なのであらかじめ、縁をぼかした画像を用意して貼り付けておくのがよいかなと思っています。

■ユーザコントロールを配置する

役を表示するためのダイアログと、テストようのボタンを配置します。

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <local:YakuModal
        x:Name="yakuDlg"
        HorizontalAlignment="Left" Margin="281,93,0,0" VerticalAlignment="Top"/>
    <Button 
        Click="StartClick"
        Content="開始" HorizontalAlignment="Left" Margin="65,55,0,0" VerticalAlignment="Top"/>
    <Button
        Click="EndClick"
        Content="終了" HorizontalAlignment="Left" Margin="134,55,0,0" VerticalAlignment="Top"/>
    <Button 
        Click="OpenClick"
        Content="開く" HorizontalAlignment="Left" Margin="65,93,0,0" VerticalAlignment="Top"/>
    <Button 
        Click="InoClick"
        Content="猪鹿蝶" HorizontalAlignment="Left" Margin="134,93,0,0" VerticalAlignment="Top"/>
</Grid>

■モーダルダイアログの呼び出しコードを書く

MessageDialog 風に、ShowAsync メソッドを使って await で非同期待ちをしたいので、こんな風にできたらいいなぁ、という感じで書きます。OpenClick のところでデバッグ出力をしているのは、start/end がきちんと待ちになっているかどうかのチェック用です。

/// <summary>
/// ダイアログを開くだけ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void StartClick(object sender, RoutedEventArgs e)
{
	await this.yakuDlg.OpenAsync();
}
/// <summary>
/// ダイアログを閉じるだけ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void EndClick(object sender, RoutedEventArgs e)
{
	await this.yakuDlg.CloseAsync();
}
/// <summary>
/// Open/Close が連続した ShowAsync メソッドを使う
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void OpenClick(object sender, RoutedEventArgs e)
{
	System.Diagnostics.Debug.WriteLine("start");
	await this.yakuDlg.ShowAsync(5000);
	System.Diagnostics.Debug.WriteLine("end");
}

/// <summary>
/// 猪鹿蝶の役が揃った場合
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void InoClick(object sender, RoutedEventArgs e)
{
	var lst = new List<Card>();
	lst.Add(new Card("G1"));
	lst.Add(new Card("J1"));
	lst.Add(new Card("F1"));
	this.yakuDlg.Cards = lst;
	this.yakuDlg.Message = "猪鹿蝶";
	await this.yakuDlg.ShowAsync(3000);
}

■モーダルダイアログ側のコードを書く

役を表示するモーダルダイアログのコードを書きます。
アニメーションが、sbOpen と sbClose でわかれているので、それを連続させるために Completed イベントを使っています。そして、コードを簡単にするために await Task.Delay(100); を使って完了待ちをします。
最適化するならば、sbOpen.Completed イベントの中で、sbClose.Begin() を呼び出せばよいのですが、そこは、ShowSync の実装で await の羅列を使いためにこうしています。

public sealed partial class YakuModal : UserControl
{
	public YakuModal()
	{
		this.InitializeComponent();

		// アニメーションの完了イベント
		this.sbOpen.Completed += (s, e) => { _completed = true; };
		this.sbClose.Completed += (s, e) => {
			_completed = true;
			this.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
		};

		_picts = new List<Image>();
		_picts.Add( this.pict1 );
		_picts.Add( this.pict2 );
		_picts.Add( this.pict3 );
		_picts.Add( this.pict4 );
		_picts.Add( this.pict5 );
	}

	private List<Card> _cards;
	/// <summary>
	/// 表示する花札を設定する
	/// </summary>
	public List<Card> Cards {
		get { return _cards; }
		set
		{
			_cards = value;
			if (value != null)
			{
				int i = 0;
				foreach (var c in value)
				{
					if (i < _picts.Count)
					{
						_picts[i].Visibility = Windows.UI.Xaml.Visibility.Visible;
						_picts[i].Source = CardUI.GetResName(c.ID);
					}
					i++;
				}
				for (; i < _picts.Count; i++)
				{
					_picts[i].Visibility = Windows.UI.Xaml.Visibility.Collapsed;
				}
			}

		}
	}
	string _message;
	/// <summary>
	/// 表示するメッセージを設定する
	/// </summary>
	public string Message
	{
		get
		{
			return _message;
		}
		set
		{
			_message = value;
			this.textYaku.Text = value;
		}
	}

	// アニメーションの完了フラグ
	private bool _completed = false;
	private List<Image> _picts;

	/// <summary>
	/// ダイアログを開く
	/// </summary>
	/// <returns></returns>
	public Task OpenAsync()
	{
		_completed = false;
		this.sbOpen.Begin();
		this.Visibility = Windows.UI.Xaml.Visibility.Visible;
		Task t = Task.Run(async () =>
		{
			while (_completed == false)
			{
				await Task.Delay(100);
			}
		});
		return t;
	}

	/// <summary>
	/// ダイアログを閉じる
	/// </summary>
	/// <returns></returns>
	public Task CloseAsync()
	{
		_completed = false;
		this.sbClose.Begin();
		Task t = Task.Run(async () =>
		{
			while (_completed == false)
			{
				await Task.Delay(100);
			}
		});
		return t;
	}

	/// <summary>
	/// 指定した時間表示して閉じる
	/// </summary>
	/// <param name="wait"></param>
	public async Task<Task> ShowAsync(int wait = 0)
	{
		await OpenAsync();
		await Task.Delay(wait);
		await CloseAsync();
		// await が使いたいので、空のタスクを返す
		return new Task(() => { }); 
		// 最後のタスクを返すのでも ok
		// return CloseAsync();
	}
}

ShowAsync メソッドでは指定したミリ秒数で表示待ちをします。こんな風に、OpenAsync, Task.Delay, CloseAsync の羅列で書けるから良いかなと。
このあたりの内部実装はさておき、外部から使うときは、

	await this.yakuDlg.ShowAsync(3000);

な風にしようというのが UIDD なところです。

■実行してみる

ローカルコンピュータで実際に実行してみます。


右からすすーっと花札が出てきて、真ん中で猪鹿蝶が確定。しばくすると、すっと消えます。というモーダルダイアログができます。カードゲームにありがちなアニメーションだし、業務アプリのメッセージでも取り入れできそうな雰囲気です(自画自賛)。ためしに、acer w500 なタブレットPC で動かしてみましたが、スムースに動きます。
ただ、これだとゲームとしてかなり寂しい感じなので、影とかぼかしのエフェクトが入れたいですね…ちょっと調査しますか。

カテゴリー: C#, WinRT, 花札ゲーム | [WinRT] Storyboard とユーザーコントロールでモーダル風ダイアログを作る はコメントを受け付けていません

[WinRT] Storyboard.Clone を作る

アニメーションの場合は、

  1. Storyboard の場合は、Blend でちまちま編集して動作確認
  2. プログラムコードに組み込んで、動作確認
  3. もう一度、Blend に戻って修正。
  4. またまた、プログラムコードに組み込んで、動作確認

っていう繰り返しになるので、Blend でデザイン、Visual Studio でビルドしてからシミュレーターで確認、ってのが定番…になると思うのですが、どうなんでしょう?そんなに複雑な Storyboard は作らないのかな?

■Storyboard.Clone を拡張メソッドで作る

リフレクションとか使って正確に書こうとも思ったのですが、さほど複雑な構造でもないし、入れ子になるクラスは決まっているのでだらだらと150行ほど書きます。

public static class StoryboardExtensions
{
	/// <summary>
	/// Storyboard をコピーする 
	/// </summary>
	/// <param name="src"></param>
	/// <returns></returns>
	public static Storyboard Clone(this Storyboard src)
	{
		var sb = new Storyboard();
		sb.AutoReverse = src.AutoReverse;
		sb.BeginTime = src.BeginTime;
		sb.Duration = src.Duration;
		sb.FillBehavior = src.FillBehavior;
		sb.RepeatBehavior = src.RepeatBehavior;
		sb.SpeedRatio = src.SpeedRatio;
		foreach (var tl in src.Children)
		{
			var tld = tl.Clone();
			sb.Children.Add(tld);
		}
		return sb;
	}

	public static Timeline Clone(this Timeline src)
	{
		Timeline dest = null;
		if (src is ColorAnimationUsingKeyFrames)
			dest = ((ColorAnimationUsingKeyFrames)src).Clone();
		if (src is DoubleAnimationUsingKeyFrames)
			dest = ((DoubleAnimationUsingKeyFrames)src).Clone();
		if (src is ObjectAnimationUsingKeyFrames)
			dest = ((ObjectAnimationUsingKeyFrames)src).Clone();
		if (src is PointAnimationUsingKeyFrames)
			dest = ((PointAnimationUsingKeyFrames)src).Clone();
		if (dest != null)
		{
			Storyboard.SetTargetProperty(dest, Storyboard.GetTargetProperty(src));
			Storyboard.SetTargetName(dest, Storyboard.GetTargetName(src));
		}
		return dest;
	}
	public static ColorAnimationUsingKeyFrames Clone(this ColorAnimationUsingKeyFrames src)
	{
		var dest = new ColorAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}

	public static ColorKeyFrame Clone(this ColorKeyFrame src)
	{
		ColorKeyFrame dest = null;
		if (src is LinearColorKeyFrame)
			dest = new LinearColorKeyFrame();
		if (src is DiscreteColorKeyFrame)
			dest = new DiscreteColorKeyFrame();
		if (src is EasingColorKeyFrame)
			dest = new EasingColorKeyFrame();
		if (src is SplineColorKeyFrame)
			dest = new SplineColorKeyFrame();
		if (dest != null)
		{
			dest.KeyTime = src.KeyTime;
			dest.Value = src.Value;
		}
		return dest;
	}

	public static DoubleAnimationUsingKeyFrames Clone(this DoubleAnimationUsingKeyFrames src)
	{
		var dest = new DoubleAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static DoubleKeyFrame Clone(this DoubleKeyFrame src)
	{
		DoubleKeyFrame dest = null;
		if (src is LinearDoubleKeyFrame)
			dest = new LinearDoubleKeyFrame();
		if (src is DiscreteDoubleKeyFrame)
			dest = new DiscreteDoubleKeyFrame();
		if (src is EasingDoubleKeyFrame)
			dest = new EasingDoubleKeyFrame();
		if (src is SplineDoubleKeyFrame)
			dest = new SplineDoubleKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
	public static ObjectAnimationUsingKeyFrames Clone(this ObjectAnimationUsingKeyFrames src)
	{
		var dest = new ObjectAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static ObjectKeyFrame Clone(this ObjectKeyFrame src)
	{
		ObjectKeyFrame dest = null;
		if (src is DiscreteObjectKeyFrame)
			dest = new DiscreteObjectKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
	public static PointAnimationUsingKeyFrames Clone(this PointAnimationUsingKeyFrames src)
	{
		var dest = new PointAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static PointKeyFrame Clone(this PointKeyFrame src)
	{
		PointKeyFrame dest = null;
		if (src is LinearPointKeyFrame)
			dest = new LinearPointKeyFrame();
		if (src is EasingPointKeyFrame)
			dest = new EasingPointKeyFrame();
		if (src is DiscretePointKeyFrame)
			dest = new DiscretePointKeyFrame();
		if (src is SplinePointKeyFrame)
			dest = new SplinePointKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
}

■アニメーションの開始点と終了点を変更する拡張メソッドを作る

アニメーションをする対象と、開始終了点を指定するのですが、色々ややこしいので拡張メソッドを用意しておきます。本当は別クラスのほうがいいんでしょうが、面倒なので Storyboard にくっつけてしまいます。

/// <summary>
/// 各種の値を変えるための拡張メソッド
/// </summary>
public static class StorybaordValueExtentions
{
	/// <summary>
	/// ターゲット名を指定する
	/// </summary>
	/// <param name="sb"></param>
	/// <param name="name"></param>
	public static void SetTarget(this Storyboard sb, UIElement el)
	{
		foreach (var tl in sb.Children)
		{
			Storyboard.SetTarget(sb, el);
		}
	}
	public static void SetTargetName(this Storyboard sb, string name)
	{
		foreach (var tl in sb.Children)
		{
			Storyboard.SetTargetName(sb, name);
		}
	}
	public static void SetMovePoint(this Storyboard sb, Point start, Point end)
	{
		((DoubleAnimationUsingKeyFrames)sb.Children[0]).KeyFrames[0].Value = start.X;
		((DoubleAnimationUsingKeyFrames)sb.Children[1]).KeyFrames[0].Value = start.Y;
		((DoubleAnimationUsingKeyFrames)sb.Children[0]).KeyFrames[1].Value = end.X;
		((DoubleAnimationUsingKeyFrames)sb.Children[1]).KeyFrames[1].Value = end.Y;
	}
}

storyboard は複数のターゲットを同時に動かすこともできるので、一律で変えてしまうのは困るのですが、まあ、そのときはそのときで考えるということで。

■実際に動かしてみる

あらかじめ、コピー元の storyboard を blend で作っておきます。

<Page.Resources>
    <Storyboard x:Name="sbMove">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pictAni">
            <EasingDoubleKeyFrame x:Name="sbStartX" KeyTime="0" Value="180.597"/>
            <EasingDoubleKeyFrame x:Name="sbEndX" KeyTime="0:0:1" Value="182.09"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="pictAni">
            <EasingDoubleKeyFrame x:Name="sbStartY" KeyTime="0" Value="-152.239"/>
            <EasingDoubleKeyFrame x:Name="sbEndY" KeyTime="0:0:1" Value="171.642"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Page.Resources>

アニメーションする対象は、あらかじめ、Image.RenderTransform と CompositeTransform を書いておきます。
これは Blend が出力する Storyboard の癖なので、手作業で作れば Margin とか Canvas.Left あたりを直接変更することも可能でしょう。今は、Blend と Visual Studio の行き来を簡単にするということで、Blend のほうにあわせておきます。

<Image x:Name='pict1' HorizontalAlignment="Left" Height="100" 
            VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"  Margin="288,151,0,0">
    <Image.RenderTransform>
        <CompositeTransform/>
    </Image.RenderTransform>

</Image>

自作した Clone と拡張メソッドを使って、pict1 を移動させます。ここでは、Clone した storyboard は使い捨てになっていますが、実際は Initialize 時にキープしておきます。

private void SbClone2Click(object sender, RoutedEventArgs e)
{
	Storyboard sb = this.sbMove.Clone();
	// ターゲットを変える
	sb.SetTarget( this.pict1 );
	sb.SetMovePoint(new Point(0, 0), new Point(100, 200));
	sb.Begin();
}

これで、storyboard の XAML を量産しないでよくなるので(特に開始終了点の名前付けとかが面倒なので)、花札ゲームのアニメーションに活用できそう。なので、再び花札ゲーム製作に戻るということで。

カテゴリー: C#, WinRT | [WinRT] Storyboard.Clone を作る はコメントを受け付けていません

[WinRT] XAML タグを XmlReader.Load を使ってコピーする

WPF の XAML には Clone メソッドがあって、Storyboard とか UI のタグをコピーできたのですが、WinRT の XAML には Clone がありません。継承関係とかすっきりしてメソッドの整理もされいるので、同じ XAML とはいえ挙動が異なる…のが微妙なところですが、まあ、すっきりした分 XAML の描画は高速になっているんだろうなッ!!! と実験してみたいものです。当時、非力…でもないマシンを使っても WPF アプリは結構重かったわけで、では、現在の PC ならばどうでしょうってのが疑問なところですが、描画として XAML と C++/CX の DirectX が直結しているのか否か?そのうえで、GPU は有効に使われているのか?(なんか、CPU のパワーだけを使っている感じがするので)ってのを確かめたうえで、XAML の描画部分を C# のみで頑張るか、C++/CX のパワーを使ったほうが(内部的に DirectX に直結しているという点で)を探っていきたいと思っています。

3Dのアクション系のゲームアプリのように描画速度に完全に依存する場合には、C++/CX と DirectX の組みわせは外せないところなのですが、花札ゲームのような、

  • それぞれのアニメーション以外、あまり画面が動かない
  •  → けれども、アニメーション部分は、それなりにリッチにしたい。

  • 3D ゲームのようにがっつり作るのではなく、手軽にリリースしたい(サンデープログラマー気分で)。
  •  → ロジック部分は、C# を活用するとか、描画じゃない部分をバージョンアップとか。

な形で、View と Logic を完全に分離させた上で、それぞれの速度(描画速度と開発速度)を追求したいということで。

それを踏まえたうえで、リッチな View と作ろうとすると、Blend を使ったデザインが欠かせないわけで、デザイナさまに作っていただいた XAML をいかに、きれにプログラムに組み入れていくのか?ってのが課題。いやいや、デザインするのは自分自身だったりするわけですがね。

■XamlReader.Load を使って、丸ごとコピーする

private void SbCloneClick(object sender, RoutedEventArgs e)
{
	string xaml = @"
<Image 
	xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
	HorizontalAlignment=""Left"" Height=""100"" 
    VerticalAlignment=""Top"" Width=""64"" Source=""/Images/FC097-2.png""  Margin=""288,151,0,0"">
</Image>
";
	for (int i = 0; i < 10; i++)
	{
		object obj = XamlReader.Load(xaml);
		UIElement el = obj as UIElement;
		Image img = obj as Image;
		img.Margin = new Thickness(70 * i, 100, 0, 0);
		grid.Children.Add(img);
	}
}

XamlReader.Load を使うときに注意しないといけないのが、

  • ルートは単一のタグであること。
  •  → Xml の Load と同じなので、ルートタグはひとつ。

  • xmlns で名前空間を指定しておく。
  • x:Name=”…” のような名前を外しておく
  •  → x の namespace が必須になるし、プログラムから参照できません。

     → FindName を使っても見つかられないので、意味ないし。

  • イベントハンドラは、後からプログラムで指定。

なところです。バインディングは、どうなるんだろう?今は試していません。
サンプルのコードでは、Image タグを10個作って、grid に追加しています。これぐらいならば new Image() で作っても手間は変わらないのですが、もうちょっと複雑な例があります。

■Storyboard を XamlReader.Load でコピーする

void SbClone()
{
	string xaml = @"
<Storyboard Name=""sbMove""
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(CompositeTransform.TranslateX)"" Storyboard.TargetName=""pictAni"">
        <EasingDoubleKeyFrame KeyTime=""0"" Value=""180.597""/>
        <EasingDoubleKeyFrame KeyTime=""0:0:1"" Value=""182.09""/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(CompositeTransform.TranslateY)"" Storyboard.TargetName=""pictAni"">
        <EasingDoubleKeyFrame KeyTime=""0"" Value=""-152.239""/>
        <EasingDoubleKeyFrame KeyTime=""0:0:1"" Value=""171.642""/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>
";
	object obj = XamlReader.Load(xaml);
	Storyboard sb = obj as Storyboard;
	// Storyboard.Target を書き換える
	foreach (var tl in sb.Children)
	{
		Storyboard.SetTarget(tl, this.pict1);
	}
}

ある点からある点まで移動するためのアニメーションですが、これをプログラムで書き直すとちょっと手間ですね。複雑な Storyboard を Blend で作成した後に、それをちまちまとプログラムコードに直すのは避けたいところです。
なので、XAML から該当する Storyboard を「手動」でコピーしてきて、コードに貼り付けるって作業ならば、まあ、それなりに許容範囲かと。
ただ、これも問題があって、アニメーションさせる要素を指定するのを、Storyboard.SetTarget を使ってちまちまと書き換えないとダメなんですよね(SetTargetName を使っても名前が設定変更されないので、SetTarget じゃないとダメみたいです)。このあたり、Setter を使う方法もあるんでしょうが、Setter を使うということは、元の Storyboard の XAML に手をいれないといけなくなって、Storyboard を再修正したきの作業手順が多くなってしまうのが難点です。

なので、Storyboard に Clone がないならば、作ってしまおう、ってのが次。

カテゴリー: C#, WinRT | [WinRT] XAML タグを XmlReader.Load を使ってコピーする はコメントを受け付けていません

View を遅延更新する MVVM を作ってみる

MVVM のメリットは、Model のプロパティを変更することによって、View の画面の更新が自動的にされる、というものがあります。まぁ、他にもメリットがあるんだけど、View のもろもろの構造は関係なく、好きなように Model を構築することができる(場合によってはデータベース周りに特化させても ok)ってのが良いのですが、その反面、Model のプロパティへの更新が、そのまま View に伝達されてしまうために、その「即時性」が、かえって View の描画の重さになってしまうってのが、デメリットといえばデメリットです。描画速度が遅くならない程度に、頻繁に Model を更新しなければよいだけの話なんですが…ふと、C++/CX のゲームアプリの説明では、描画のためのデータの更新は、描画をするタイミングまでためておいて、フレームレートの間で描画できる処理をする、というのがあったんので、では、MVVM の View の更新をフレームレート単位で更新するようにすれば、Model の頻繁な更新に耐えられるのでは?と思ったのが、次のアイデアです。

フレームレートっていうよりも、単純にタイマーで定期的に View の更新をしているだけなのです。ちょっとだけ、Model の INotifyPropertyChanged に手を加えます。

■ひとまず全文のコード

遅延用の Model と View のコードはこんな感じ。
スピードを重視するくせに、Queue のコードが遅すぎるだろう、という意見は却下で(苦笑)。

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();

        // 動的バインド
        this.textX.SetBinding( TextBlock.TextProperty, new Binding() { Path = new PropertyPath("X") });
        this.textMsg.SetBinding( TextBlock.TextProperty, new Binding() { Path = new PropertyPath("Msg") });

        // 遅延 View 更新タイマーを作成
        _timer = new DispatcherTimer();
        _timer.Interval = TimeSpan.FromSeconds(5.0);    // 5秒ごとに更新
        _timer.Tick += _timer_Tick;

        _model = new Model();
		// Queue クラスを作成
        _vq = new ViewQueue();
		// 遅延バインド
        _model.PropertyChangedQueue += _vq.PropertyChanged;
        _timer.Start();
        this.DataContext = _model;
    }

    DispatcherTimer _timer;
    Model _model;
    ViewQueue _vq;


    /// <summary>
    /// このページがフレームに表示されるときに呼び出されます。
    /// </summary>
    /// <param name="e">このページにどのように到達したかを説明するイベント データ。Parameter 
    /// プロパティは、通常、ページを構成するために使用します。</param>
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
    }

	/// <summary>
	/// View 更新用のタイマー
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
    void _timer_Tick(object sender, object e)
    {
        while (true)
        {
            ViewQueue.PropChange prop = _vq.GetProperty();
            if (prop == null)
                break;

			// model の更新を通知
            var model = (INotifyPropertyChangedQueue)prop.Sender;
            model.OnPropertyChanged(prop.Name);
        }
    }

	/// <summary>
	/// ボタンをクリックして Model を更新
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
    private void SetClick(object sender, RoutedEventArgs e)
    {
		// この時点では、View は更新されない。
        _model.X++;
        _model.Msg = string.Format("{0} カウント {1}", DateTime.Now, _model.X);
    }
}

/// <summary>
/// View の遅延更新用のインターフェース
/// </summary>
public interface INotifyPropertyChangedQueue : INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChangedQueue;
    void OnPropertyChanged(string name);
}

/// <summary>
/// モデル
/// </summary>
public class Model : INotifyPropertyChangedQueue
{
    private int _x;
    public int X
    {
        get { return _x; }
        set
        {
            _x = value;
			// Change イベントは、Queue のほうに通知
            OnPropertyChangedQueue("X");
        }
    }
    private string _msg;
    public string Msg
    {
        get { return _msg; }
        set
        {
            _msg = value;
            OnPropertyChangedQueue("Msg");
        }
    }

    public event PropertyChangedEventHandler PropertyChangedQueue;
    /// <summary>
    /// 遅延させるために Queue に配置
    /// </summary>
    /// <param name="name"></param>
    void OnPropertyChangedQueue(string name)
    {
        if (PropertyChangedQueue != null)
        {
            PropertyChangedQueue(this, new PropertyChangedEventArgs(name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Queue から View にイベントを発生させる
    /// </summary>
    /// <param name="name"></param>
    public void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged( this, new PropertyChangedEventArgs(name));
        }
    }
}
public class ViewQueue
{
    public class PropChange
    {
        public object Sender { get; set; }
        public string Name { get; set; }
    }

    List<PropChange> _lst = new List<PropChange>();
	/// <summary>
	/// Queue に溜め込んでおく
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="args"></param>
    public void PropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        var prop = _lst.Find(p => p.Name == args.PropertyName);
        if (prop == null)
        {
            _lst.Add(new PropChange() { Sender = sender, Name = args.PropertyName });
        }
    }
	/// <summary>
	/// View から更新依頼があったときに通知する
	/// </summary>
	/// <returns></returns>
    public PropChange GetProperty()
    {
        if (_lst.Count > 0)
        {
            PropChange prop = _lst[0];
            _lst.RemoveAt(0);
            return prop;
        }
        else
        {
            return null;
        }
    }
}

ボタンを押すと、Model を更新するのですが、View の更新タイミングは5秒間毎になっています。なので、ボタンをクリックしたときに「反応がにぶい」というかって変な感じになるのですが、Model の変更を即時更新させるか、遅延更新させるかで、プロパティ単位で即時反映(OnPropertyChanged)と遅延反映(OnPropertyChangedQueue)を選択すればよいかと。

■進捗描画に引きずられない MVVM が作れる?

作ってはみたけれど、いまいち使いどころはどうなのか?って疑問はあるのですが、まあ、実験として MVVM モデルで遅延描画できるかどうか?という答えとしては、意外とシンプルに実装できるっていう実例です。

for ( int i=0; i<MAX; i++ ) {
	// 何かの処理
	Run();
	// 進捗を通知
	Model.ProgressRatio = i*100/MAX;
}

な感じで進捗率を画面に表示するための Model を作ったとしましょう。あるいは、Run の中で Model のプロパティを更新しているとか。
この場合、Model のプロパティへの更新が、即座に View に OnPropertyChanged で通知されるために、View が更新されるまで Model が待たされます。これは View の描画処理のスピードもあるのですが、せっかく Run の処理を非常に高速にしているのに、MVVM を使っているがために、View の描画処理に引きずられてしまうのも、おかしな話ですね~、という具合です。DataGridView の DataSource の場合でも、列を Add するたびに View が更新されるものだから、妙に遅くなってしまうという現在の View の実装が問題というものあるのですが。

方法としては、描画している間は、View を更新させないとか、

// View を更新させない
View.Update = false;
for ( int i=0; i<MAX; i++ ) {
	// 何かの処理
	Run();
	// 進捗を通知
	Model.ProgressRatio = i*100/MAX;
}
// 処理がおわったら View を更新させる
View.Update = true;

Model のプロパティを更新するタイミングを間引きするとか、

for ( int i=0; i<MAX; i++ ) {
	// 何かの処理
	Run();
	// 進捗を通知を間引きする
	if ( MAX % 100 == 0 ) {
		Model.ProgressRatio = i*100/MAX;
	}
}

もろもろのテクニックがあるのですが、Model のどのプロパティが、どのように View に影響を与えるのか?逆に View の速度がどうやって、Model のコーディングに影響を与えてしまうのか?という問題が出てきてしまうこと自体が「問題」ではないか?と思ったわけです。Model と View を分離するのだから、Model を更新するタイミングと View を更新するタイミングはずれても構わないだろう、という思惑です。

View の遅延をさせるのですが、Model から View への通知を Queue に貯めますが、実際の値は View を更新するときに取ってきています。

List<PropChange> _lst = new List<PropChange>();
/// <summary>
/// Queue に溜め込んでおく
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
    var prop = _lst.Find(p => p.Name == args.PropertyName);
    if (prop == null)
    {
        _lst.Add(new PropChange() { Sender = sender, Name = args.PropertyName });
    }
}

なので、キューには「通知があった」ことだけを貯めればよいので、List を使う必要もないのですが、まあ、試作品というとで。
Model のプロパティを変更するためのボタンを連打しても、遅延更新の場合は最後のひとつだけが Quene にたまるので描画が1回しか行われません。CPU/GPU に負担をかけにくい実装、ってことなんですが、本当に負担を掛けないかどうかは、DataGridView とかで実装してみないとわかりません。

使いどころとしては、DataGridView に 2000行ぐらいのデータを表示しようとして、妙に遅くて困るとか、DataGridView を頻繁に更新しているためか画面が固まった感じがする、ってのが解消されるといいかなと。
DataSource プロパティに設定して、コレクションの内容を変えるたびに画面が切り替わる(描画的には、更新された描画部分だけ切り替わっているのでしょうが)ってのが解消されるかなと。

また、コーディング上としては、Model に View が連結されているか否かに関係なく、Model のプロパティを自由に設定できます。先の進捗率の表示のように、頻繁に Model の値を変える場合であっても、描画は定期的にしか再表示されないので、間引き処理とか、画面を描画しないとか、いうテクニックを使わずに済むのでコードが簡単になるかと。

カテゴリー: C# | 1件のコメント

連続したアニメーションをつなぐために、await/async を使う

連続したアニメーションをつなぐために、Completed イベントでつなげる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4071

の続きで、storyboard の Completed イベントで連続させるのではなくて、async/await を使ってつなげてみます。

■開始 Image と 終了 Image を渡してアニメする関数を作る

モーダルダイアログを作ったときと同じように、Completed イベントの発生待ち(アニメーションの完了待ち)をします。Task.Delay() をループさせて待つので、ダサいと言えばダサんですが。他によい方法があったら、また検討するということで。
Task.Run に渡すラムダ式に async がついているという不思議なコードですが、これで動くのだからたいしたものです。WinRT の場合には Thread.Sleep がなくて、非同期の Task.Delay を使うためにこんな風になっています。このあたり、Sleep 相当のブロッキング用のタイマーを自作すればよいのか?ちょっと思案中。

/// <summary>
/// アニメーションする関数
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
private async Task GoAnime( Image p1, Image p2 )
{
	SetMovePos(p1, p2);
	bool _complete = false;
	this.sbMove.Begin();
	this.sbMove.Completed += (s, e) => { _complete = true; };
	await Task.Run(async () =>
	{
		while (_complete == false)
		{
			await Task.Delay(100);
		}
	});
}

■アニメーションを羅列させる

アニメーションさせる GoAnime 関数ができたので、await を付けて羅列させてみます。Completed イベントで書くよりも、ちょっとは状態遷移がみやいかな、と。yeild return の場合はイテレーターを使う必要があって、ちょっとトリッキーな感じがするのですが、これだと自然かと。

/// <summary>
/// 札をクリックして連続アニメーションを開始
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void pictAniClick(object sender, TappedRoutedEventArgs e)
{
	if (_moving == false)
	{
		_moving = true;
		await GoAnime(this.pict1, this.pict2);
		await GoAnime(this.pict2, this.pict3);
		await GoAnime(this.pict3, this.pict4);
		await GoAnime(this.pict4, this.pict1);
	}
	else
	{
		this.sbMove.Stop();
		_moving = false;
	}
}
bool _moving = false;

■汎用的にクラスのメソッドにしてみる

GoAnime メソッドは、画面の内部メソッドなので汎用性がありません。なので、クラスとして括りだすのが良かろうってことで、クラス化してみたのが、これです。

public class AnimeWaitable
{
    public Storyboard sb { get; set; }
    public EasingDoubleKeyFrame startPosX { get; set; }
    public EasingDoubleKeyFrame startPosY { get; set; }
    public EasingDoubleKeyFrame endPosX { get; set; }
    public EasingDoubleKeyFrame endPosY { get; set; }
    public Image pictAni { get; set; }
    public Image pictStart { get; set; }
    public Image pictEnd { get; set; }

    public bool IsCompleted { get; set; }
    public int Result { get; set; }
    public AnimeWaitable()
    {
    }
    public async Task<int> RunAsync(Image pictS, Image pictE)
    {
        this.pictStart = pictS;
        this.pictEnd = pictE;

        var ptS = this.pictStart.TransformToVisual(null).TransformPoint(new Point(0, 0));
        var ptE = this.pictEnd.TransformToVisual(null).TransformPoint(new Point(0, 0));
        this.startPosX.Value = ptS.X;
        this.startPosY.Value = ptS.Y;
        this.endPosX.Value = ptE.X;
        this.endPosY.Value = ptE.Y;
        sb.Completed += (s, e) =>
        {
            this.IsCompleted = true;
            this.Result = 1;
        };
        this.IsCompleted = false;

        this.sb.Begin();
        // アニメーションの完了待ち
        while (this.IsCompleted == false)
        {
            await Task.Delay(100);
        }
        return this.Result;
    }
    public void Stop()
    {
        this.sb.Stop();
        this.IsCompleted = true;
        this.Result = 0;
    }
}

storyboard に座標を設定していた SetMovePos メソッドをクラス内に展開しているので、冗長になっていますが、基本は GoAnime メソッドと同じで、Task.Delay() を使ってアニメーションの完了待ちをしています。
クラス名が AnimeWaitable なのは、awaitable パターンにすればよいのでは?って時の名残りです。最初 awaitable パターンを使おうと思ったのですが、そうする必要がなかったってことですね。awaitable については別途書こうと思います。

アニメーションを連続させるところは、次のように await ani.RunAsync を羅列していきます。ここでは、ぐるぐると回るように List を使っていますが。

async void GoAnime()
{
	var ani = new AnimeWaitable();
	ani.sb = this.sbMove;
	ani.startPosX = this.sbStartX;
	ani.startPosY = this.sbStartY;
	ani.endPosX = this.sbEndX;
	ani.endPosY = this.sbEndY;
	ani.pictAni = this.pictAni;
	ani.pictStart = this.pict1;
	ani.pictEnd = this.pict2;

	var lstS = new List<Image>();
	var lstE = new List<Image>();
	lstS.Add(pict1); lstS.Add(pict2); lstS.Add(pict3); lstS.Add(pict4);
	lstE.Add(pict2); lstE.Add(pict3); lstE.Add(pict4); lstE.Add(pict1);
	this.textMsg.Text = "開始...";
	int i = 0;
	_go = true;
	_ani = ani;
	while (_go)
	{
		await ani.RunAsync(lstS[i], lstE[i]);
		i = ++i % 4;
		/* このように書き並べることができる
		await ani.RunAsync(pict1, pict2);
		await ani.RunAsync(pict2, pict3);
		await ani.RunAsync(pict3, pict4);
		await ani.RunAsync(pict4, pict1);
		*/
	}
	this.textMsg.Text = "終了...";
	_go = false;

}

bool _go = false;
AnimeWaitable _ani;

private void StartClick(object sender, RoutedEventArgs e)
{
	if (_go == false)
	{
		GoAnime();
	}
	else
	{
		_ani.Stop();
		_go = false;
	}
}

ここでちょっと奇妙なのは、GoAnime メソッドの中で、while ループでぐるぐるしている間でも StartClick イベントが発生します。ええ、Start ボタンを押すことができます。一見すると while ループで画面が固まるのでは?と思えるのですが、ani.RunAsync は非同期メソッドなので、途中でボタンクリックイベントなどを入れられるんですよね。このあたりが、非同期な書き方 async/await の面白いところです。

逆にいえば、シーケンス図としてどう書き表すのか、というのが難しいところです。RunAsync という名前を付けてあるので非ブロッキングということがわかるので、手続きっぽい書き方をしても UI の各種イベントを阻害しないという不思議な≒ある意味で直感的な、書き方ができるかと。

カテゴリー: C#, WinRT | 連続したアニメーションをつなぐために、await/async を使う はコメントを受け付けていません

連続したアニメーションをつなぐために、Completed イベントでつなげる

今、寄り道になってしまっているのは、WinRT でモーダルダイアログを出す場合には async/await が必須っぽいのと、アニメーション自体は非同期で動くのだから、それを連続させる場合には async/await を使ったほうが便利ではないか?という思惑と、ならば、Awaitable パターンを実装してみるのが良いのでは?というのと、MessageDialog は IAsyncOperation を実装していて…ってところで、非同期のパターンを同期っぽく書くために await を連続させれば、非同期処理を手続の連続で「わかりやすく」書けるのでは?っての確認のためです。ええ、何を言っているかややこしいのですが、これから一手ずつ試していきます。

■参考資料はここから

自分の資料がてら、だらだらと。

非同期メソッドの内部実装 (C# によるプログラミング入門)
http://ufcpp.net/study/csharp/sp5_awaitable.html
非同期メソッド入門 (8) ? コンパイラ要件 : xin9le note
http://xin9le.net/archives/147
WinRT と await を掘り下げる – Windows 8 アプリ開発者ブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/04/30/winrt-await.aspx
.NET タスクを WinRT 非同期処理として公開する – Windows 8 アプリ開発者ブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/06/25/net-winrt.aspx
WinRT と await を掘り下げる – Windows 8 アプリ開発者ブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/04/30/winrt-await.aspx

■アニメーションを連続させる

まずは、アニメーションの連続技をどう書けばよいのか?ってのがスタートです。

[WinRT] 指定位置からコントロールをアニメーションさせる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4066

なところをやっているのは、花札ゲームのアニメーションが、

  1. 手札から1枚、場に出すアニメーション
  2. 役ができれば、それを知らせるアニメーション
  3. マッチした札を取り札に加えるアニメーション
  4. 山から1枚、場に出すアニメーション
  5. 役ができれば、それを知らせるアニメーション
  6. マッチした札を取り札に加えるアニメーション

なんてのを一連の流れで動かすわけです。途中に2枚選択のモーダルダイアログも表示するので、このあたりは「状態遷移」の問題もあります。で、yeild return を使ってコード的に連続させる(イテレーションを使う)っていう技を知った

C# の yield return の使い道 – カタチづくり
http://d.hatena.ne.jp/u_1roh/20080302/1204471238
Hisuiチュートリアル / 直線作図機能を作ってみよう (1)
http://www.quatouch.com/products/hisui/hisui-1_5_0_0-20080204/doc/tutorial/putline.html

ので、これでも良いかも、と思ったものの、花札ゲームの場合はモーダルダイアログが間に入ってくるしどうしたものか?と悩むわけです。

で、モーダルダイアログの悩みはさておき、アニメーションを連続させる場合にはどう実装するのかを確かめてみます。
アニメーション自体は、storyboard を使って、あらかじめ blend で作っておくわけですが、1 から 6 までのアニメをあらかじめ作っておくことはできません。それぞれの部分に分けて storyboard を作って置くし、開始と終了の座標がことなるので、実行時に位置を変えないといけません。

本格的なゲームであれば、アニメーションの座標を動的に計算するのがいいんでしょうが、花札ゲームの場合は「XAML を活用すること」を(私が)主旨にしているので、できるだけ storyboard を活用したい…と言いますか、活用できるかどうかの実験っていう意味もあります。

■Storyboard の Completed イベントで連続させるアイデア

storyboard にはアニメーションが完了した時に Completed イベントが発生します。なので、それぞれの Completed イベントで次の storyboard を Begin させれば、それぞれの storyboard を連続で動作できるはずです。

sb1.Completed += (s,e) => { sb2.Begin(); };
sb2.Completed += (s,e) => { sb3.Begin(); };
sb3.Completed += (s,e) => { sb4.Begin(); };
sb4.Completed += (s,e) => { ; };

ラムダ式で書けばこんな風に、Completed イベントで連続させるわけです。なかなか壮観な感じがしますが、単純に連続させるだけならば、これでもいいかもしれません。

■実際に作ってみる

あらかじめ、こんな風に4つの Image を置いておきます。移動用の松の札が、ぐるぐる連続して回るようにするわけです。

テンプレート用の storyboard はこんな感じ。開始位置と終了位置を変更するために、sbStartX などの名前を付けておきます。

<Storyboard x:Name="sbMove">
	<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pictAni">
		<EasingDoubleKeyFrame x:Name="sbStartX" KeyTime="0" Value="180.597"/>
        <EasingDoubleKeyFrame x:Name="sbEndX" KeyTime="0:0:1" Value="182.09"/>
	</DoubleAnimationUsingKeyFrames>
	<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="pictAni">
        <EasingDoubleKeyFrame x:Name="sbStartY" KeyTime="0" Value="-152.239"/>
        <EasingDoubleKeyFrame x:Name="sbEndY" KeyTime="0:0:1" Value="171.642"/>
	</DoubleAnimationUsingKeyFrames>
</Storyboard>

アニメーションの位置計算を簡単にするために、移動用の Image は (0,0) に移動させてしまいます。
札自体をクリックしたらアニメーションを開始。もう一度クリックすると停止します。

public MainPage()
{
    this.InitializeComponent();

	// 最初の位置を(0,0) にしてしまう
	this.pictAni.Margin = new Thickness(0);
}


/// <summary>
/// 札をクリックする
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void pictAniClick(object sender, TappedRoutedEventArgs e)
{
	if (_moving == false)
	{
		// アニメーションを開始する
		SetMovePos(pict1, pict2);
		this.sbMove.Completed += sbMove_Completed;
		this.sbMove.Begin();
		_moving = true;
		_count = 0;
	}
	else
	{
		// アニメーションを停止する
		this.sbMove.Stop();
		this.sbMove.Completed -= sbMove_Completed;
		_moving = false;
	}
}
bool _moving = false;
int _count = 0;

/// <summary>
/// アニメーション完了時のイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void sbMove_Completed(object sender, object e)
{
	_count = (_count + 1) % 4;
	switch (_count)
	{
		case 0: SetMovePos(pict1, pict2); break;
		case 1: SetMovePos(pict2, pict3); break;
		case 2: SetMovePos(pict3, pict4); break;
		case 3: SetMovePos(pict4, pict1); break;
	}
	sbMove.Begin();
}

/// <summary>
/// 開始座標と終了座標の設定
/// </summary>
/// <param name="pictStart"></param>
/// <param name="pictEnd"></param>
void SetMovePos(Image pictStart, Image pictEnd)
{
	Point pt1 = pictStart.TransformToVisual(null).TransformPoint(new Point());
	Point pt2 = pictEnd.TransformToVisual(null).TransformPoint(new Point());

	this.sbStartX.Value = pt1.X;
	this.sbStartY.Value = pt1.Y;
	this.sbEndX.Value = pt2.X ;
	this.sbEndY.Value = pt2.Y ;

	Debug.WriteLine("{0},{1} -> {2},{3}",
		this.sbStartX.Value, this.sbStartY.Value,
		this.sbEndX.Value, this.sbEndY.Value);
}

ひとつの storyboard しか利用していないので、Completed イベントでは座標の切り替えしかしてません。この部分は、Completed が別々になるとなかなかややこしくなるかと。逆にいえば、複数の Completed イベントを同じイベントメソッドにしてしまって、そのイベントメソッドに状態遷移を書けばコードがややこしくならないかも。

で、このアニメーション部分は非同期なんだから、async/await で書いたらどうなるのか?ってのが次。

カテゴリー: C#, WinRT | 連続したアニメーションをつなぐために、Completed イベントでつなげる はコメントを受け付けていません

[WinRT] 指定位置からコントロールをアニメーションさせる

[WinRT] Gridに貼り付けたコントロールの絶対座標を取得する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4062

の続きです。この絶対座標を使ってアニメーションさせれば ok っだね、ってことで先に絶対座標を計算いたのですが、実はアニメーション自体は「相対座標」になるので、この手の Margin のややこしい計算は必要がなかったんですよね~、というオチです。

条件としては、適当に移動コントロールを用意しておいて、適当な開始位置から適当な終了位置までにアニメーションします。
アニメーション自体は、Blend を使って大体の位置で作成しておきます。「大体」でよいのは、開始位置と終了位置はプログラムのほうで制御するためです。

<Page.Resources>
	<Storyboard x:Name="sbMove1">
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pict1">
			<EasingDoubleKeyFrame x:Name="startPosX" KeyTime="0" Value="405.97"/>
			<EasingDoubleKeyFrame x:Name="endPosX" KeyTime="0:0:3" Value="441.791"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="pict1">
			<EasingDoubleKeyFrame x:Name="startPosY" KeyTime="0" Value="277.612"/>
			<EasingDoubleKeyFrame x:Name="endPosY"  KeyTime="0:0:3" Value="-11.941"/>
		</DoubleAnimationUsingKeyFrames>
	</Storyboard>
</Page.Resources>

あらかじめ作った storyboard に対して変更部分に名前をつけていきます。開始位置(startPosX,startPosY) と終了位置(endPosX,endPosY) だけで ok です。アニメーションする時間を変更したい場合は、endPosX.KeyTime, endPosY.KeyTime で指定できるので汎用的に使えます。

■アニメーションを開始する

これを使ってアニメーションさせるのが次のコードです。

private void Button3Click(object sender, RoutedEventArgs e)
{
	// 移動対象の絶対座標
	var pt1 = _pt1;
	// 開始位置の絶対座標
	var pt2 = pict2.TransformToVisual(null).TransformPoint(new Point(0, 0));
	// 終了位置の絶対座標
	var pt3 = pict3.TransformToVisual(null).TransformPoint(new Point(0, 0));
	// 開始、終了位置を相対座標で設定
	startPosX.Value = pt2.X - pt1.X;
	startPosY.Value = pt2.Y - pt1.Y;
	endPosX.Value = pt3.X - pt1.X;
	endPosY.Value = pt3.Y - pt1.Y;
	// アニメーション開始
	sbMove1.Begin();
	sbMove1.Completed += (_, __) => { ; };
}

startPosX.Value に設定するのは、相対座標なので、pt2.X – pt1.X になります。元の pt1 を取っておかないとダメなのがちょっとダサいですが、移動用のコントロールの座標を(0,0)にして左上にしておけば、pt1 の値はいりません。

アニメーションの終了は、Completed イベントで拾えるので、ラムダ式で設定しておくとコードが簡単になるかと。移動用のコントロールを消すとか、元の位置に戻すとかを、

	sbMove1.Completed += (_, __) =>
	{
		sbMove1.Stop();
		grid1.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
	};

のようにしておくとよいかと。

カテゴリー: C#, WinRT | [WinRT] 指定位置からコントロールをアニメーションさせる はコメントを受け付けていません

[WinRT] Gridに貼り付けたコントロールの絶対座標を取得する

さて、手持ちの札を場に捨てるところのアニメーションを作成開始、ってことでちょっと悩んだのは、札の座標はどうやって取るのだろうか?ってことです。XAML では Grid か Canvas にコントロールをペタペタはりつけて位置決めをしていくのですが、この Margin の値は親の Grid からの相対座標なんですよね。移動するコントロールをルートの grid 上に張り付けられてば計算は簡単なのですが、Grid の中に Grid があって、その中にさらに Grid があって、その中に Image がある、っていう入れ子になっている場合には、ちょっと計算が大変。さらに言えば、花札の場合には、手札コントロールとか場コントロールとか、それぞれが別々のユーザーコントロールになっているので、複数のコントロール間の座標はどうやって取るの?ってのが、疑問でありました、って話です。

アニメーションとしては、

  1. 手札コントロールのクリックした札の絶対座標(pt1)を取得
  2. 場コントロールのマッチする札の絶対座標(pt2)を取得
  3. pt1 から pt2 に札をアニメーション

させたいわけです。手札と場のコントロールが別々になっているのは、UI として自由にデザインしたいからです。いやいや、花札ゲームコントロールとして全体をコントロールするという手もあるのですが、なんかごちゃごちゃしたので、コントロール毎に分けたんですよね。アニメーションがない場合は、これのほうが使いやすいのですが、相互に座標を参照するようなアニメーションの場合には結構手間 orz になってしまったということです。このあたり、XAML の場合は、リソースベースで別の XAML を読み込むってほうが素直なのかも。このあたりは後で検討してみます。

で、アニメーションをさせるのはいいけれど、手札の座標はどうやって取るのか?ってことです。

■指定したコントロールの絶対座標を取る。

var pt = pict.TransformToVisual(null).TransformPoint(new Point(0, 0));

tips っぽいですが、pict という Image コントロールの絶対座標は、TransformToVisual メソッドと TransformPoint メソッドの組み合わせで取れます。TransformToVisual メソッドはベースとなる座標軸を決めるってな具合で、null を指定すると画面全体が対象になります。普通は座標軸の原点を指定するために親の grid などを指定するのでしょう。TransformPoint メソッドは、現在の座標軸から変換先の座標軸へ移すために使います。この場合は、Image コントロール(pict)の左上の座標(0,0)を変換したいので、new Point(0,0) としています。
まあ、原理はさておき、こうやると grid の入れ子とか関係なく画面の左上からの座標が取れますね。

■コントロールを、指定した絶対位置に移動する。

左上からの絶対座標が取れたので、これで gird とか canvas とかの相対座標が関係なく移動できます…って訳にもいかず grid の margin に指定するのは grid に対しての「相対座標」なんですよね。なので、コントロールを任意の位置に移動させるためには、対 grid の相対座標値を計算し直さないといけません。この手の計算がいやな場合は、あらかじめ左上を原点として grid や canvas を配置しておくとよいです。たぶん、そっちのほうが簡単です。

アニメーションさせるためのコントロールを、適当な grid の中に適当な Image を貼り付けます。計算が面倒な場合には、左上に接するように作ればいいのですが、ここでは実験のために「あえて適当な」場所に配置しています。

<Grid x:Name="grid1" 
	HorizontalAlignment="Left" 
	VerticalAlignment="Top" Height="289" Width="335" Margin="343,200,0,0">
    <Image x:Name="pict1" 
	HorizontalAlignment="Left" Height="100" 
	Margin="56,44,0,0" 
	VerticalAlignment="Top" Width="64" 
	Source="/Images/FC001.png"/>
</Grid>

これを見ると、Margin=”56,44,0,0″ となっているので、Image は Grid に対して(56,44)という位置にありますね。正確に言うと Margin は外側の「余白」になるので、0,0 のところは、右と下の余白を意味しています…と言いますか、この 0,0 ってなんなんでしょうね?

で、いろいろやって試しに、Image を Grid からはみ出るように配置させます。

<Grid x:Name="grid1" 
	HorizontalAlignment="Left" 
	VerticalAlignment="Top" Height="289" Width="335" Margin="343,200,0,0">
    <Image x:Name="pict1" HorizontalAlignment="Left" Height="100" 
		Margin="356,135,-85,0" 
		VerticalAlignment="Top" Width="64" 
		Source="/Images/FC001.png" />
    </Image>
</Grid>

Image の Margin が “356,135,-85,0” になっています。356,135 は、x座標, y座標として計算できそうですが、-85 って何なんでしょうね?ってのをかなり悩みました。どうやら、Grid 内に含まれている間は、right と bottom は 0,0 なんですが、grid からはみ出た場合ははみ出た分の「余白」をマイナス値で設定しているようなのです。ためしに、ここを 0 にして「”356,135,0,0″」にすると、

な感じで Image が表示されなくなります。いろいろ試すと表示領域のクリッピングと関係があるようなので、このマイナス値の余白も真面目に計算しないとダメなんですね。

private void Button1Click(object sender, RoutedEventArgs e)
{
	// 移動対象の絶対座標
	var pt1 = pict1.TransformToVisual(null).TransformPoint(new Point(0, 0));
	// 開始位置の絶対座標
	var pt2 = pict2.TransformToVisual(null).TransformPoint(new Point(0, 0));

	// 開始位置に移動
	double dx = pt2.X - pt1.X;
	double dy = pt2.Y - pt1.Y;
	double l = pict1.Margin.Left + dx;
	double t = pict1.Margin.Top + dy;
	double r = grid1.Width - (l + pict1.Width );
	double b = grid1.Height - (t + pict1.Height);
	pict1.Margin = new Thickness(l,t,r,b);
}

移動元と移動先の絶対値をあらかじめ pt1, pt2 で取得しておいて、grid の対して相対値で margin を計算するってことをやります。margin の right と bottom の値は grid からはみ出た分を計算するのです。なんかややこしいですね。

で、実はこのコードではダメなんです。一度 margin を設定してしまうと、pict1.Margin.Left の値が変わってしまうので、実はもともとの margin の値を使わないダメというオチが(相対値を計算すればよいのかな?)

なので、最初の margin と pt1 の値を起動時にキープしておきます。

public MainPage()
{
    this.InitializeComponent();

	_ma1 = pict1.Margin;
	_pt1 = pict1.TransformToVisual(null).TransformPoint(new Point(0, 0));
}

Thickness _ma1;
Point _pt1;

そして、元の位置から計算しなおすというダサい方法が…

private void Button1Click(object sender, RoutedEventArgs e)
{
	// 移動対象の絶対座標
	var pt1 = _pt1;
	// 開始位置の絶対座標
	var pt2 = pict2.TransformToVisual(null).TransformPoint(new Point(0, 0));

	// 開始位置に移動
	double dx = pt2.X - pt1.X;
	double dy = pt2.Y - pt1.Y;
	double l = _ma1.Left + dx;
	double t = _ma1.Top + dy;
	double r  = grid1.Width - (l + pict1.Width );
	double b = grid1.Height - (t + pict1.Height);
	pict1.Margin = new Thickness(l,t,r,b);
}

まあ、これでも動くといえば動くのですが、カプセル化するにしても元の値を取っておくところがひどすぎるので、grid と image の位置を計算が簡単になるように左上の(0,0)にしておきます。
すると、次のように元の値を取っておく必要がなくなります。

private void Button1Click(object sender, RoutedEventArgs e)
{
	// 開始位置の絶対座標
	var pt2 = pict2.TransformToVisual(null).TransformPoint(new Point(0, 0));

	// 開始位置に移動
	double dx = pt2.X;
	double dy = pt2.Y;
	double l = dx;
	double t = dy;
	double r  = grid1.Width - (l + pict1.Width );
	double b = grid1.Height - (t + pict1.Height);
	pict1.Margin = new Thickness(l,t,r,b);
}

まあ、これはこれで動くから良いかな、と思っているわけで、さてこれをアニメーションに応用するのは…続きます。

カテゴリー: C#, WinRT | [WinRT] Gridに貼り付けたコントロールの絶対座標を取得する はコメントを受け付けていません

[WinRT] 自動的に閉じるダイアログを作る

モーダルダイアログ風なものができたので、これを応用して自動的に閉じるダイアログを作ります。

のようにメッセージを表示して、しばらくしたら閉じるっていうメッセージダイアログです。よくゲームであるパターンだし、他のアプリでも応用が利きます。
フォームアプリの場合にはタイマーを使ってイベント待ちをするのが定番ですが、WinRT の場合(というか XAMLの場合)には、storyboard を使って、3秒後の閉じる、ってのがコード的に楽です。

自動的に閉じるダイアログは、Popup でも Canvas でもいいのですが、ここでは、popup を使って実現してみます。

■popup でダイアログを作る

popup コントロールを使って、ダイアログを作成。

<Popup x:Name="popYaku" 
        Margin="320,160,-320,328" Grid.Row="1">
    <Grid Background="Red" Height="213" Width="399">
        <TextBlock 
            x:Name="popYakuText"
            FontSize="40"
            HorizontalAlignment="Left" Margin="34,32,0,0" TextWrapping="Wrap" Text="役ができました" VerticalAlignment="Top"/>
        <Image x:Name="pictYaku1" HorizontalAlignment="Left" Height="100" Margin="34,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku2" HorizontalAlignment="Left" Height="100" Margin="103,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku3" HorizontalAlignment="Left" Height="100" Margin="172,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku4" HorizontalAlignment="Left" Height="100" Margin="241,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku5" HorizontalAlignment="Left" Height="100" Margin="310,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
    </Grid>
</Popup>

この場合には、花札の役を表示するので、あらかじめ image コントロールを貼り付けておきます。

■blend で storyboard を追加する

3秒経ったらダイアログを閉じるタイムラインを、blend を使って作ります。

<Page.Resources>
    <!-- TODO: Delete this line if the key AppName is declared in App.xaml -->
    <x:String x:Key="AppName">花札 こいこい</x:String>
    <Storyboard x:Name="sbPopYaku">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="popYaku">
        	<EasingDoubleKeyFrame KeyTime="0:0:2.5" Value="1"/>
        	<EasingDoubleKeyFrame KeyTime="0:0:3" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Page.Resources>

プログラムから storyboard を起動するので名前をつけておくのと、単に消えると WinRT らしくないので、最後の 0.5 秒だけは透明度を変更するという方式にします。この手のアニメーションは blend を使うとひじょうに楽ですね。

■アニメーション終了時の処理を追加

ポップアップを開くときは、IsOpen プロパティを true にすれば ok。ストリーボードの開始は sbPopYaku.Begin() な感じで実行ができます。
で、忘れちゃいけないのが、アニメーションが終了した時に、ストリーボードを Stop で終了させておくことと、ポップアップを IsOpen = false で閉じておくこと。

/// <summary>
/// 役を表示
/// </summary>
/// <param name="y"></param>
void dispPopYaku( Yaku y )
{
	this.pictYaku1.Visibility = Windows.UI.Xaml.Visibility.Visible;
	this.pictYaku2.Visibility = Windows.UI.Xaml.Visibility.Visible;
	this.pictYaku3.Visibility = Windows.UI.Xaml.Visibility.Visible;
	this.pictYaku4.Visibility = Windows.UI.Xaml.Visibility.Visible;
	this.pictYaku5.Visibility = Windows.UI.Xaml.Visibility.Visible;
	if (y.GoKou != null)
	{
		this.popYakuText.Text = "五光";
		this.pictYaku1.Source = CardUI.GetResName(y.GoKou[0].ID);
		this.pictYaku2.Source = CardUI.GetResName(y.GoKou[1].ID);
		this.pictYaku3.Source = CardUI.GetResName(y.GoKou[2].ID);
		this.pictYaku4.Source = CardUI.GetResName(y.GoKou[3].ID);
		this.pictYaku5.Source = CardUI.GetResName(y.GoKou[4].ID);
	}
// 略

	this.sbPopYaku.Completed += sbPopYaku_Completed;
	this.popYaku.IsOpen = true;
	this.sbPopYaku.Begin();
}

/// <summary>
/// タイマー完了時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void sbPopYaku_Completed(object sender, object e)
{
	this.sbPopYaku.Stop();
	this.popYaku.IsOpen = false;
}

これで、適当なところで dispPopYaku メソッドを呼び出せばポップアップが表示されます。

アニメーションは非同期で行われるので、下記のように、dispPopYaku でポップアップを表示させた後は、_curPlayer.GetCard などのメソッドは即時実行されます。さきゆきは、場からマッチしたときのアニメーションも追加するので、このあたりの整合性(画面の動き的な整合性)をあわせる必要がありますね。

/// <summary>
/// 場に出した札をクリックしてマッチさせる
/// </summary>
/// <param name="obj"></param>
void baControl1_ClickPutCard(Card obj)
{
	var lst = _board.Game.MatchBa(_board.Ba, _board.Ba.PlayerCard, _curPlayer);
	if (lst != null)
	{
		// カードが決定した場合、画面を更新
		_board.Ba.GetCard(lst);
		// 役の計算
		Yaku y = _board.Game.CalcYaku(lst, _curPlayer.TakenCards);
		if (y != null)
		{
			// 一定時間、役を表示
			dispPopYaku(y);
		}
		// 手持ちに加える
		_curPlayer.GetCard(lst);
		// 画面の更新
		ScrUpdate();
	}
}

■ダイアログをタップした瞬時の閉じる処理を追加

自動的にポップアップは閉じるのですが、タップしたときに即時終了させる処理も追加しておきます。

ポップアップの grid 部分に Tapeed イベントを追加しておきます。ためしに Popup 自身に Tapped を追加してみたのですが呼び出されませんでした。

<Popup x:Name="popYaku" 
        Margin="320,160,-320,328" Grid.Row="1">
    <Grid Background="Red" Height="213" Width="399"
            Tapped="popClick"
            >
        <TextBlock 
            x:Name="popYakuText"
            FontSize="40"
            HorizontalAlignment="Left" Margin="34,32,0,0" TextWrapping="Wrap" Text="役ができました" VerticalAlignment="Top"/>
        <Image x:Name="pictYaku1" HorizontalAlignment="Left" Height="100" Margin="34,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku2" HorizontalAlignment="Left" Height="100" Margin="103,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku3" HorizontalAlignment="Left" Height="100" Margin="172,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku4" HorizontalAlignment="Left" Height="100" Margin="241,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>
        <Image x:Name="pictYaku5" HorizontalAlignment="Left" Height="100" Margin="310,96,0,0" VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"/>

    </Grid>
</Popup>

アニメーションの完了イベントを発生させるために、SkipToFill メソッドを使って最終点まで移動させます。これでタップしたときには即時終了します。

/// <summary>
/// ポップアップをタップした時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void popClick(object sender, TappedRoutedEventArgs e)
{
	this.sbPopYaku.SkipToFill();
}

他のアニメーションとの整合性もあるのですが(札を手持ちに加えるアニメーションとか)、ひとまず役をできたときのポップアップはこれで ok ということで。

カテゴリー: C#, WinRT | [WinRT] 自動的に閉じるダイアログを作る はコメントを受け付けていません