it-swarm-pt.tech

Disparadores de dados e painéis de histórias WPF

Estou tentando acionar uma animação de progresso sempre que o ViewModel/Presentation Model estiver ocupado. Eu tenho uma propriedade IsBusy e o ViewModel é definido como o DataContext do UserControl. Qual é a melhor maneira de acionar um storyboard "progressAnimation" quando a propriedade IsBusy for verdadeira? Misturar apenas permite que o med adicione Acionadores de Eventos no nível do UserControl, e só posso criar acionadores de propriedades nos meus modelos de dados.

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

Tentei adicionar os DataTriggers como um 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 não é o que eu quero.

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

24
Jonas Follesø

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

    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!

40
Dabblernl

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 direcionar vários elementos. (Você pode anexar uma animação a cada elemento, é claro, mas fica muito horrível de gerenciar.)

Portanto, existe uma maneira alternativa de resolver isso, que permite usar um DataTrigger para executar uma animação que tem como alvo elementos nomeados.

Há três lugares em que você pode anexar gatilhos no WPF: elementos, estilos e modelos. No entanto, as duas primeiras opções não funcionam aqui. O primeiro é descartado 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, tanto quanto eu sei. Tanto quanto me lembro, quando perguntei às pessoas da equipe do WPF sobre isso há muitos anos, eles disseram que gostariam de apoiá-lo, mas não o fizeram. tenha tempo para fazê-lo funcionar.) E os estilos acabaram porque, como diz a mensagem de erro relatada, não é possível segmentar elementos nomeados em uma animação associada a um estilo.

Então isso deixa os modelos. E você pode usar modelos de controle ou 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 essa construção, o WPF pode deixar a animação se referir aos elementos nomeados dentro do modelo. (Deixei a animação e o conteúdo do modelo vazios aqui - obviamente, você o preencheria com o conteúdo e a animação 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 por ele definidos sempre estarão 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 o impeça 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 dificulte a vinculação da animação aos elementos nomeados à direita Sei que há muitas otimizações no WPF para permitir que elementos de um estilo sejam reutilizados com eficiência, portanto, talvez um deles seja o que dificulta o suporte.)

8
Ian Griffiths

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

1
Jobi Joy

Você pode se inscrever no evento PropertyChanged da classe DataObject e disparar um RoutedEvent no nível Usercontrol.

Para o RoutedEvent funcionar, precisamos ter a classe derivada de DependancyObject

1
Jobi Joy

Você pode usar Trigger.EnterAction para iniciar uma animação quando uma propriedade é 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>
0
ligaz