WPF Data Trigger и Story Boards - PullRequest
       29

WPF Data Trigger и Story Boards

21 голосов
/ 17 сентября 2008

Я пытаюсь запустить анимацию прогресса, когда модель ViewModel / Presentation занята. У меня есть свойство IsBusy, и ViewModel устанавливается как DataContext из UserControl. Каков наилучший способ вызвать раскадровку "progressAnimation", если свойство IsBusy имеет значение true? Смешивание позволяет только меду добавлять событийные триггеры на уровне 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, используя методы привязки данных, вместо того, чтобы выставлять события и запускать / останавливать анимацию с помощью кода.

Ответы [ 5 ]

35 голосов
/ 15 ноября 2009

То, что вы хотите, возможно, объявив анимацию на самом колесе progress: 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);
        }
    }
}

Надеюсь, это поможет!

8 голосов
/ 16 июля 2014

Хотя ответ, в котором предлагается прикрепить анимацию непосредственно к анимируемому элементу, решает эту проблему в простых случаях, на самом деле это невозможно, если у вас есть сложная анимация, которая должна быть нацелена на несколько элементов. (Конечно, вы можете прикрепить анимацию к каждому элементу, но управлять ею становится довольно ужасно.)

Таким образом, существует альтернативный способ решения этой проблемы, который позволяет использовать DataTrigger для запуска анимации, нацеленной на именованные элементы.

Есть три места, где вы можете прикрепить триггеры в WPF: элементы, стили и шаблоны. Однако первые два варианта здесь не работают. Первое исключено, потому что 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 голос
/ 17 сентября 2008

Вы можете подписаться на событие PropertyChanged класса DataObject и запустить огонь RoutedEvent с уровня Usercontrol.

Для работы RoutedEvent нам нужен класс, производный от DependancyObject

1 голос
/ 17 сентября 2008

Я бы порекомендовал использовать RoutedEvent вместо вашего свойства IsBusy. Просто запустите события OnBusyStarted и OnBusyStopped и используйте триггер события для соответствующих элементов.

0 голосов
/ 17 сентября 2008

Вы можете использовать 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>
...