web-dev-qa-db-ja.com

WPFデータトリガーとストーリーボード

ViewModel/Presentation Modelがビジー状態のときに進行状況アニメーションをトリガーしようとしています。 IsBusyプロパティがあり、ViewModelがUserControlのDataContextとして設定されています。 IsBusyプロパティがtrueのときに「progressAnimation」ストーリーボードをトリガーする最良の方法は何ですか? Blendは、medにUserControlレベルでのイベントトリガーの追加のみを許可し、データテンプレートでのみプロパティトリガーを作成できます。

「progressAnimation」は、ユーザーコントロールのリソースとして定義されます。

DataControlを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でこれを解決することを望んでいました。

24
Jonas Follesø

アニメーションは、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);
        }
    }
}

お役に立てれば!

40
Dabblernl

アニメーション化する要素に直接アニメーションをアタッチすることを提案する答えは、単純なケースでこの問題を解決しますが、複数の要素をターゲットにする必要がある複雑なアニメーションがある場合、これは実際には機能しません。 (もちろん、各要素にアニメーションを添付することはできますが、管理するのはかなり恐ろしくなります。)

したがって、これを解決する別の方法があり、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つがこれをサポートすることを難しくしています。)

8
Ian Griffiths

IsBusyプロパティの代わりにRoutedEventを使用することをお勧めします。 OnBusyStartedおよびOnBusyStoppedイベントを発生させ、適切な要素でイベントトリガーを使用するだけです。

1
Jobi Joy

DataObjectクラスのPropertyChangedイベントをサブスクライブして、RoutedEventをUsercontrolレベルから発生させることができます。

RoutedEventが機能するためには、DependancyObjectから派生したクラスが必要です

1
Jobi Joy

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>
0
ligaz