WPF でアニメーションを操作する場合、アニメーションのパフォーマンスを向上させ、フラストレーションを解消できるヒントやテクニックがいくつかあります。
一般的な問題
スクロール バーまたはスライダーの位置をアニメーション化するとフリーズする
FillBehaviorのHoldEnd (既定値) を持つアニメーションを使用してスクロール バーまたはスライダーの位置をアニメーション化すると、ユーザーはスクロール バーまたはスライダーを移動できなくなります。 これは、アニメーションが終了しても、ターゲット プロパティの基本値をオーバーライドしているためです。 アニメーションがプロパティの現在の値をオーバーライドしないようにするには、アニメーションを削除するか、FillBehaviorをStopとして指定します。 詳細と例については、「 ストーリーボードでアニメーション化した後にプロパティを設定する」を参照してください。
アニメーションの出力をアニメーション化しても効果はありません
別のアニメーションの出力であるオブジェクトをアニメーション化することはできません。 たとえば、ObjectAnimationUsingKeyFramesを使用してFillからRectangleにRadialGradientBrushのSolidColorBrushをアニメーション化する場合、RadialGradientBrushまたはSolidColorBrushのプロパティをアニメーション化することはできません。
アニメーション化後にプロパティの値を変更できない
場合によっては、アニメーションが終了した後でも、プロパティの値を変更できない可能性があります。 これは、アニメーションが終了しても、プロパティの基本値をオーバーライドしているためです。 アニメーションがプロパティの現在の値をオーバーライドしないようにするには、アニメーションを削除するか、FillBehaviorをStopとして指定します。 詳細と例については、「 ストーリーボードでアニメーション化した後にプロパティを設定する」を参照してください。
タイムラインを変更しても効果がない
ほとんどの Timeline プロパティはアニメーション化可能であり、データバインドできますが、アクティブな Timeline のプロパティ値を変更しても効果がないようです。 これは、 Timeline が開始されると、タイミング システムが Timeline のコピーを作成し、それを使用して Clock オブジェクトを作成するためです。 元のファイルを変更しても、システムのコピーには影響しません。
Timelineが変更を反映するには、そのクロックを再生成し、以前に作成したクロックを置き換えるために使用する必要があります。 クロックはあなたのために自動的に再生成されることはありません。 タイムラインの変更を適用する方法を次に示します。
タイムラインが Storyboardであるか、またはに属している場合は、 BeginStoryboard または Begin メソッドを使用してストーリーボードを再適用することで、変更を反映させることができます。 これは、アニメーションを再起動する副作用もあります。 コードでは、 Seek メソッドを使用してストーリーボードを前の位置に戻すことができます。
BeginAnimation メソッドを使用してプロパティにアニメーションを直接適用した場合は、BeginAnimation メソッドをもう一度呼び出し、変更されたアニメーションを渡します。
クロック レベルで直接作業している場合は、新しいクロック セットを作成して適用し、それらを使用して、生成された以前のクロック セットを置き換えます。
タイムラインとクロックの詳細については、「 アニメーションとタイミング システムの概要」を参照してください。
FillBehavior.Stop が期待どおりに動作しない
一つのアニメーションがFillBehavior設定をStopにして別のアニメーションに移行する場合など、HandoffBehaviorプロパティをSnapshotAndReplaceに設定しても効果がないように見えることがあります。
次の例では、 Canvas、 Rectangle 、および TranslateTransformを作成します。 TranslateTransformがアニメーション化され、Rectangleの周囲にCanvasが移動します。
<Canvas Width="600" Height="200">
<Rectangle
Canvas.Top="50" Canvas.Left="0"
Width="50" Height="50" Fill="Red">
<Rectangle.RenderTransform>
<TranslateTransform
x:Name="MyTranslateTransform"
X="0" Y="0" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
このセクションの例では、上記のオブジェクトを使用して、 FillBehavior プロパティが予期したとおりに動作しないいくつかのケースを示します。
FillBehavior="Stop" と HandoffBehavior の複数のアニメーションとの動作
アニメーションが 2 つ目のアニメーションに置き換えられると、 FillBehavior プロパティが無視されるように見えることがあります。 次の例では、2 つの Storyboard オブジェクトを作成し、それらを使用して前の例で示したのと同じ TranslateTransform をアニメーション化します。
最初のStoryboardB1は、XのTranslateTransformプロパティを 0 から 350 までアニメーション化し、四角形を 350 ピクセル右に移動します。 アニメーションが継続時間の終了に達し、再生が停止すると、 X プロパティは元の値 0 に戻ります。 その結果、四角形は右の 350 ピクセルに移動し、元の位置に戻ります。
<Button Content="Start Storyboard B1">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard x:Name="B1">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
From="0" To="350" Duration="0:0:5"
FillBehavior="Stop"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
2 番目のStoryboardB2は、同じXのTranslateTransform プロパティもアニメーション化します。 このToのアニメーションのStoryboard プロパティのみが設定されているため、アニメーションはアニメーション化するプロパティの現在の値を開始値として使用します。
<!-- Animates the same object and property as the preceding
Storyboard. -->
<Button Content="Start Storyboard B2">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard x:Name="B2">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
To="500" Duration="0:0:5"
FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
最初の Storyboard の再生中に 2 つ目のボタンをクリックすると、次のような動作が発生する可能性があります。
アニメーションにはFillBehaviorのStopがあるため、最初のストーリーボードは終了し、四角形を元の位置に戻します。
2 番目のストーリーボードは有効になり、現在の位置 (現在は 0) から 500 にアニメーション化されます。
しかし、それは起こるものではありません。 代わりに、四角形は戻りません。右に移動し続けます。 これは、2 番目のアニメーションは、最初のアニメーションの現在の値を開始値として使用し、その値から 500 にアニメーション化するためです。 SnapshotAndReplace HandoffBehaviorが使用されているために 2 番目のアニメーションが 1 つ目のアニメーションに置き換えられる場合、最初のアニメーションのFillBehaviorは関係ありません。
FillBehavior と Completed イベント
次の例では、 StopFillBehavior が影響を受けないような別のシナリオを示します。 ここでも、ストーリーボードを使用して、XのTranslateTransform プロパティを 0 から 350 までアニメーション化します。 ただし、今回の例では、 Completed イベントを登録します。
<Button Content="Start Storyboard C">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard Completed="StoryboardC_Completed">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
From="0" To="350" Duration="0:0:5"
FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Completed イベント ハンドラーは、同じプロパティを現在の値から 500 にアニメーション化する別のStoryboardを開始します。
private void StoryboardC_Completed(object sender, EventArgs e)
{
Storyboard translationAnimationStoryboard =
(Storyboard)this.Resources["TranslationAnimationStoryboardResource"];
translationAnimationStoryboard.Begin(this);
}
Private Sub StoryboardC_Completed(ByVal sender As Object, ByVal e As EventArgs)
Dim translationAnimationStoryboard As Storyboard = CType(Me.Resources("TranslationAnimationStoryboardResource"), Storyboard)
translationAnimationStoryboard.Begin(Me)
End Sub
リソースとして 2 番目の Storyboard を定義するマークアップを次に示します。
<Page.Resources>
<Storyboard x:Key="TranslationAnimationStoryboardResource">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
To="500" Duration="0:0:5" />
</Storyboard>
</Page.Resources>
Storyboardを実行すると、XのTranslateTransformプロパティが 0 から 350 にアニメーション化され、完了した後 (FillBehaviorのStop設定があるため) 0 に戻り、0 から 500 にアニメーション化することが予想される場合があります。 代わりに、 TranslateTransform は 0 から 350、次に 500 にアニメーション化されます。
これは、WPF がイベントを発生させる順序と、プロパティ値がキャッシュされ、プロパティが無効でない限り再計算されないためです。 Completed イベントは、ルート タイムライン (最初のStoryboard) によってトリガーされたため、最初に処理されます。 現時点では、 X プロパティはまだ無効になっていないため、アニメーション化された値を返します。 2 番目の Storyboard では、キャッシュされた値を開始値として使用し、アニメーション化を開始します。
[パフォーマンス]
ページから移動した後もアニメーションが引き続き実行される
実行中のアニメーションを含む Page から移動すると、それらのアニメーションは、 Page がガベージ コレクションされるまで再生され続けます。 使用しているナビゲーション システムによっては、移動先のページが無期限にメモリに残り、そのアニメーションでリソースが消費される場合があります。 これは、ページに常に実行中の ("アンビエント") アニメーションが含まれている場合に最も顕著です。
このため、ページから移動するときに、 Unloaded イベントを使用してアニメーションを削除することをお勧めします。
アニメーションを削除するには、さまざまな方法があります。 次の手法を使用して、 Storyboardに属するアニメーションを削除できます。
イベント トリガーで開始した Storyboard を削除するには、「 方法: ストーリーボードを削除する」を参照してください。
コードを使用して Storyboardを削除するには、 Remove メソッドを参照してください。
アニメーションの開始方法に関係なく、次の手法を使用できます。
- 特定のプロパティからアニメーションを削除するには、 BeginAnimation(DependencyProperty, AnimationTimeline) メソッドを使用します。 アニメーション化するプロパティを最初のパラメーターとして指定し、2 番目のパラメーターとして
nullします。 これにより、プロパティからすべてのアニメーション クロックが削除されます。
プロパティをアニメーション化するさまざまな方法の詳細については、「 プロパティ アニメーション手法の概要」を参照してください。
Compose HandoffBehavior を使用すると、システム リソースが消費されます
Storyboard AnimationTimelineを使用してプロパティにAnimationClock、Compose、またはHandoffBehaviorを適用すると、そのプロパティに以前に関連付けられたClock オブジェクトはシステム リソースを消費し続けます。タイミング システムはこれらのクロックを自動的に削除しません。
Composeを使用して多数のクロックを適用するときにパフォーマンスの問題を回避するには、完了した後にアニメーション化されたプロパティから作成クロックを削除する必要があります。 クロックを削除するには、いくつかの方法があります。
プロパティからすべてのクロックを削除するには、アニメーションオブジェクトの ApplyAnimationClock(DependencyProperty, AnimationClock) または BeginAnimation(DependencyProperty, AnimationTimeline) メソッドを使用します。 アニメーション化するプロパティを最初のパラメーターとして指定し、2 番目のパラメーターとして
nullします。 これにより、プロパティからすべてのアニメーション クロックが削除されます。クロックの一覧から特定のAnimationClockを削除するには、ControllerのAnimationClock プロパティを使用してClockControllerを取得し、RemoveのClockController メソッドを呼び出します。 これは通常、クロックの Completed イベント ハンドラーで行われます。 ClockControllerによって制御できるのはルート クロックのみであることに注意してください。子クロックのController プロパティは
nullを返します。 また、クロックの有効時間が永遠の場合、 Completed イベントは呼び出されないことにも注意してください。 その場合、ユーザーは Removeを呼び出すタイミングを決定する必要があります。
これは主に、有効期間が長いオブジェクトのアニメーションの問題です。 オブジェクトがガベージ コレクションされると、そのクロックも切断され、ガベージ コレクションされます。
クロック オブジェクトの詳細については、「 アニメーションとタイミング システムの概要」を参照してください。
こちらも参照ください
.NET Desktop feedback