WPF データトリガーとストーリーボード
-
09-06-2019 - |
質問
ViewModel/Presentation Modelがビジー状態のときに進捗アニメーションをトリガーしようとしています。IsBusy プロパティがあり、ViewModel が UserControl の DataContext として設定されています。IsBusy プロパティが true の場合に「progressAnimation」ストーリー ボードをトリガーする最善の方法は何ですか?Blend では、UserControl レベルでのみイベント トリガーを追加でき、データ テンプレートでプロパティ トリガーのみを作成できます。
「progressAnimation」はユーザー コントロール内のリソースとして定義されます。
DataTriggers を UserControl のスタイルとして追加しようとしましたが、StoryBoard を開始しようとすると、次のエラーが発生します。
'System.Windows.Style' value cannot be assigned to property 'Style'
of object'Colorful.Control.SearchPanel'. A Storyboard tree in a Style
cannot specify a TargetName. Remove TargetName 'progressWheel'.
ProgressWheel はアニメーション化しようとしているオブジェクトの名前なので、ターゲット名を削除することは明らかに私が望んでいることではありません。
コードを通じてイベントを公開したりアニメーションを開始/停止したりするのではなく、データ バインディング技術を使用して XAML でこれを解決したいと考えていました。
解決
ProgressWheel 自体でアニメーションを宣言することで、必要なことを実現できます。XAML:
<UserControl x:Class="TriggerSpike.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<UserControl.Resources>
<DoubleAnimation x:Key="SearchAnimation" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:4"/>
<DoubleAnimation x:Key="StopSearchAnimation" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:4"/>
</UserControl.Resources>
<StackPanel>
<TextBlock Name="progressWheel" TextAlignment="Center" Opacity="0">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsBusy}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<StaticResource ResourceKey="SearchAnimation"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<StaticResource ResourceKey="StopSearchAnimation"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
Searching
</TextBlock>
<Label Content="Here your search query"/>
<TextBox Text="{Binding SearchClause}"/>
<Button Click="Button_Click">Search!</Button>
<TextBlock Text="{Binding Result}"/>
</StackPanel>
コードビハインド:
using System.Windows;
using System.Windows.Controls;
namespace TriggerSpike
{
public partial class UserControl1 : UserControl
{
private MyViewModel myModel;
public UserControl1()
{
myModel=new MyViewModel();
DataContext = myModel;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
myModel.Search(myModel.SearchClause);
}
}
}
ビューモデル:
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace TriggerSpike
{
class MyViewModel:DependencyObject
{
public string SearchClause{ get;set;}
public bool IsBusy
{
get { return (bool)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
public static readonly DependencyProperty IsBusyProperty =
DependencyProperty.Register("IsBusy", typeof(bool), typeof(MyViewModel), new UIPropertyMetadata(false));
public string Result
{
get { return (string)GetValue(ResultProperty); }
set { SetValue(ResultProperty, value); }
}
public static readonly DependencyProperty ResultProperty =
DependencyProperty.Register("Result", typeof(string), typeof(MyViewModel), new UIPropertyMetadata(string.Empty));
public void Search(string search_clause)
{
Result = string.Empty;
SearchClause = search_clause;
var worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
IsBusy = true;
worker.RunWorkerAsync();
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IsBusy=false;
Result = "Sorry, no results found for: " + SearchClause;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
}
}
}
お役に立てれば!
他のヒント
アニメーション化する要素にアニメーションを直接アタッチすることを提案する回答は、単純な場合にはこの問題を解決しますが、複数の要素をターゲットにする必要がある複雑なアニメーションがある場合、これは実際には機能しません。(もちろん、各要素にアニメーションを添付することもできますが、管理がかなり面倒になります。)
したがって、これを解決する別の方法があります。 DataTrigger
名前付き要素をターゲットとするアニメーションを実行します。
WPF ではトリガーをアタッチできる場所が 3 つあります。要素、スタイル、テンプレート。ただし、最初の 2 つのオプションはここでは機能しません。WPF は使用をサポートしていないため、最初のものは除外されます。 DataTrigger
要素上で直接。(私の知る限り、これには特に大きな理由はありません。私が覚えている限り、何年も前にこのことについて WPF チームの人々に尋ねたとき、彼らはそれをサポートしたかったが、それを機能させる時間がなかったと言っていました。) そして、スタイルは廃止されました。あなたが報告したエラー メッセージには、スタイルに関連付けられたアニメーション内の名前付き要素をターゲットにすることはできないと書かれています。
したがって、テンプレートが残ります。これには、コントロール テンプレートまたはデータ テンプレートを使用できます。
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<ControlTemplate.Resources>
<Storyboard x:Key="myAnimation">
<!-- Your animation goes here... -->
</Storyboard>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<DataTrigger
Binding="{Binding MyProperty}"
Value="DesiredValue">
<DataTrigger.EnterActions>
<BeginStoryboard
x:Name="beginAnimation"
Storyboard="{StaticResource myAnimation}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard
BeginStoryboardName="beginAnimation" />
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
<!-- Content to be animated goes here -->
</ControlTemplate>
</ContentControl.Template>
<ContentControl>
この構造により、WPF はアニメーションがテンプレート内の名前付き要素を参照できるようになります。(ここではアニメーションとテンプレートのコンテンツの両方を空のままにしています。明らかに、そこに実際のアニメーションとコンテンツを入力することになります。)
これがテンプレートでは機能するがスタイルでは機能しない理由は、テンプレートを適用すると、テンプレートで定義されている名前付き要素が常に存在するため、そのテンプレートのスコープ内で定義されたアニメーションがそれらの要素を安全に参照できるためです。スタイルは複数の異なる要素に適用でき、それぞれがまったく異なるビジュアル ツリーを持つ可能性があるため、これは一般にスタイルには当てはまりません。(必要な要素が確実に存在するシナリオでもこれができないのは少しイライラしますが、おそらくアニメーションを右側の名前付き要素にバインドするのを非常に困難にする何かがあるのでしょう。時間。WPF には、スタイルの要素を効率的に再利用できるようにするために非常に多くの最適化が行われていることはわかっています。そのため、おそらくその 1 つが、これをサポートすることを困難にしている原因です。)
IsBusy プロパティの代わりに RoutedEvent を使用することをお勧めします。OnBusyStarted および OnBusyStopped イベントを発生させ、適切な要素でイベント トリガーを使用するだけです。
DataObject クラスの PropertyChanged イベントをサブスクライブし、Usercontrol レベルから RoutedEvent を起動できます。
RoutedEvent が機能するには、DependancyObject から派生したクラスが必要です。
Trigger.EnterAction を使用すると、プロパティが変更されたときにアニメーションを開始できます。
<Trigger Property="IsBusy" Value="true">
<Trigger.EnterActions>
<BeginStoryboard x:Name="BeginBusy" Storyboard="{StaticResource MyStoryboard}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="BeginBusy" />
</Trigger.ExitActions>
</Trigger>