Pergunta

Estou tentando acionar uma animação de progresso sempre que o modelo ViewModel/Presentation estiver ocupado.Eu tenho uma propriedade IsBusy e o ViewModel está definido como DataContext do UserControl.Qual é a melhor maneira de acionar um storyboard "progressAnimation" quando a propriedade IsBusy é verdadeira?O Blend só permite adicionar gatilhos de eventos em um nível de UserControl, e só posso criar gatilhos de propriedade em meus modelos de dados.

O "progressAnimation" é definido como um recurso no controle do usuário.

Tentei adicionar os DataTriggers como estilo no UserControl, mas quando tento iniciar o StoryBoard recebo o seguinte erro:

'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 é o nome do objeto que estou tentando animar, portanto, remover o nome do destino obviamente NÃO é o que eu quero.

Eu esperava resolver isso em XAML usando técnicas de vinculação de dados, em vez de ter que expor eventos e iniciar/parar a animação por meio de código.

Foi útil?

Solução

O que você deseja é possível declarando a animação na própria progressWheel:O 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>

Código por trás:

    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);
        }
    }
}

O modelo de visualização:

    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);
        }
    }
}

Espero que isto ajude!

Outras dicas

Embora a resposta que propõe anexar a animação diretamente ao elemento a ser animado resolva esse problema em casos simples, isso não é realmente viável quando você tem uma animação complexa que precisa atingir vários elementos.(Você pode anexar uma animação a cada elemento, é claro, mas é horrível de gerenciar.)

Portanto, há uma maneira alternativa de resolver isso que permite usar um DataTrigger para executar uma animação direcionada a elementos nomeados.

Existem três lugares onde você pode anexar gatilhos no WPF:elementos, estilos e modelos.No entanto, as duas primeiras opções não funcionam aqui.A primeira está descartada porque o WPF não suporta o uso de um DataTrigger diretamente em um elemento.(Não há nenhuma razão particularmente boa para isso, até onde eu sei.Pelo que me lembro, quando perguntei às pessoas da equipe do WPF sobre isso há muitos anos, elas disseram que gostariam de ter apoiado, mas não tiveram tempo para fazê-lo funcionar.) E os estilos estão fora de questão porque, como a mensagem de erro que você relatou diz que você não pode direcionar elementos nomeados em uma animação associada a um estilo.

Então isso deixa os modelos.E você pode usar modelos de controle ou de dados para isso.

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

Com esta construção, o WPF tem o prazer de permitir que a animação se refira a elementos nomeados dentro do modelo.(Deixei a animação e o conteúdo do modelo vazios aqui - obviamente você preencheria isso com sua animação e conteúdo reais.)

A razão pela qual isso funciona em um modelo, mas não em um estilo, é que quando você aplica um modelo, os elementos nomeados que ele define estarão sempre presentes e, portanto, é seguro que as animações definidas no escopo desse modelo se refiram a esses elementos.Geralmente, esse não é o caso de um estilo, porque os estilos podem ser aplicados a vários elementos diferentes, cada um dos quais pode ter árvores visuais bastante diferentes.(É um pouco frustrante que isso impeça você de fazer isso mesmo em cenários em que você pode ter certeza de que os elementos necessários estarão lá, mas talvez haja algo que torne muito difícil para a animação ser vinculada aos elementos nomeados à direita tempo.Eu sei que há muitas otimizações no WPF para permitir que os elementos de um estilo sejam reutilizados de forma eficiente, então talvez uma delas seja o que torna isso difícil de suportar.)

Eu recomendaria usar RoutedEvent em vez de sua propriedade IsBusy.Basta disparar os eventos OnBusyStarted e OnBusyStopped e usar o gatilho de evento nos elementos apropriados.

Você pode assinar o evento PropertyChanged da classe DataObject e fazer um RoutedEvent disparar no nível Usercontrol.

Para que RoutedEvent funcione, precisamos ter a classe derivada de DependancyObject

Você pode usar Trigger.EnterAction para iniciar uma animação quando uma propriedade for alterada.

<Trigger Property="IsBusy" Value="true">
    <Trigger.EnterActions>
        <BeginStoryboard x:Name="BeginBusy" Storyboard="{StaticResource MyStoryboard}" />
    </Trigger.EnterActions>
    <Trigger.ExitActions>
        <StopStoryboard BeginStoryboardName="BeginBusy" />
    </Trigger.ExitActions>
</Trigger>
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top