it-swarm-es.tech

Disparadores de datos WPF y tableros de historias

Estoy tratando de activar una animación de progreso cuando el modelo de modelo de presentación/presentación está ocupado. Tengo una propiedad IsBusy, y ViewModel está configurado como DataContext del UserControl. ¿Cuál es la mejor manera de desencadenar un guión gráfico de "progreso de animación" cuando la propiedad IsBusy es verdadera? Blend solo permite que med agregue activadores de eventos en un nivel de UserControl, y solo puedo crear activadores de propiedades en mis plantillas de datos.

El "progressAnimation" se define como un recurso en el control del usuario.

Intenté agregar DataTriggers como estilo en el UserControl, pero cuando intento iniciar StoryBoard me sale el siguiente error:

'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 es el nombre del objeto que estoy tratando de animar, por lo que eliminar el nombre del objetivo obviamente NO es lo que quiero.

Esperaba resolver esto en XAML usando técnicas de enlace de datos, en lugar de tener que exponer eventos e iniciar/detener la animación a través del código.

24
Jonas Follesø

Lo que quieres es posible declarando la animación en el progreso Rueda en sí: El 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 detrá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);
        }
    }
}

El modelo de vista:

    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 esto ayude!

40
Dabblernl

Aunque la respuesta que propone adjuntar la animación directamente al elemento a ser animado resuelve este problema en casos simples, esto no es realmente viable cuando tienes una animación compleja que necesita apuntar a múltiples elementos. (Puede adjuntar una animación a cada elemento, por supuesto, pero se vuelve bastante horrible de administrar).

Entonces, hay una forma alternativa de resolver esto que le permite usar un DataTrigger para ejecutar una animación que se dirija a elementos con nombre.

Hay tres lugares donde puede adjuntar desencadenantes en WPF: elementos, estilos y plantillas. Sin embargo, las dos primeras opciones no funcionan aquí. El primero se descarta porque WPF no admite el uso de un DataTrigger directamente en un elemento. (No hay una razón particularmente buena para esto, que yo sepa. Hasta donde recuerdo, cuando pregunté a la gente del equipo de WPF sobre esto hace muchos años, dijeron que les hubiera gustado haberlo apoyado pero no lo hicieron). tenga tiempo para que funcione.) Y los estilos están fuera porque, como dice el mensaje de error que ha informado, no puede seleccionar elementos con nombre en una animación asociada con un estilo.

Entonces eso deja plantillas. Y puede usar plantillas de control o de datos para esto.

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

Con esta construcción, WPF se complace en dejar que la animación se refiera a elementos nombrados dentro de la plantilla. (Dejé tanto la animación como el contenido de la plantilla vacíos aquí, obviamente lo llenarías con tu contenido de animación y contenido real).

La razón por la que esto funciona en una plantilla pero no en un estilo es que cuando aplica una plantilla, los elementos con nombre que define siempre estarán presentes, por lo que es seguro que las animaciones definidas dentro del alcance de esa plantilla se refieran a esos elementos. Este no es generalmente el caso con un estilo, porque los estilos se pueden aplicar a múltiples elementos diferentes, cada uno de los cuales puede tener árboles visuales bastante diferentes. (Es un poco frustrante que te impida hacerlo incluso en escenarios en los que puedes estar seguro de que los elementos necesarios estarán allí, pero tal vez haya algo que dificulte que la animación esté vinculada a los elementos nombrados a la derecha sé que hay muchas optimizaciones en WPF para permitir que los elementos de un estilo se reutilicen de manera eficiente, por lo que tal vez uno de ellos sea lo que hace que sea difícil de admitir).

8
Ian Griffiths

Recomendaría usar RoutedEvent en lugar de su propiedad IsBusy. Simplemente active el evento OnBusyStarted y OnBusyStopped y use el desencadenador de evento en los elementos apropiados.

1
Jobi Joy

Puede suscribirse al evento PropertyChanged de la clase DataObject y hacer que se active un evento RoutedEvent desde el nivel de control de usuario.

Para que RoutedEvent funcione, necesitamos tener la clase derivada de DependancyObject

1
Jobi Joy

Puede usar Trigger.EnterAction para iniciar una animación cuando se cambia una propiedad.

<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