2021-05-28

Is there a workaround for throttled WPF DataTrigger events?

Coming from Why is this WPF animation only triggered once?, I eventually found out that WPF does indeed throttle its' binding events.

The code below demonstrates the behavior: clicking a button starts a task with a loop of 100 iterations that updates a property every 2ms. A control bound to this property counts how many times DataContextChanged was fired.

On my machine, if I click the button 5 times, the output is something like this:

Iterations: 100, Count: 86
Iterations: 100, Count: 87
Iterations: 100, Count: 85
Iterations: 100, Count: 89
Iterations: 100, Count: 90

My original problem (I thought, see link above) was that an animation was only triggered once. I then found out, that it wasn't the animation, it was the DataTrigger that wasn't responding. Then I studied the behavior of DataContextChanged (see code) and now assume that basically WPF throttles all events.

I wanted to define the animation in XAML only, which means I have to depend on DataTrigger firing "correctly" as in "always". It apparently doesn't. Must I - in order to achieve the animation I originally intended - implement my own event (that will not get throttled) and trigger the animation from code?

AnimationTestControl.xaml:

<UserControl x:Class="AnimationTests.AnimationTestControl"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationTests"
             xmlns:helpers="clr-namespace:Iov.Common.Ui.Helpers;assembly=Iov.Common"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid DataContext="{Binding ElementName=self}">
        <StackPanel Orientation="Horizontal">
            <Button Content="Toggle Border" Click="ButtonToggle_Click" />

            <Border x:Name="TestBorder" DataContext="{Binding ElementName=self, Path=TestBorderItem.MyState}"/>
        </StackPanel>
    </Grid>
</UserControl>

AnimationTestControl.xaml.cs:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace AnimationTests
{

    public class TestItem : INotifyPropertyChanged
    {
        private int _myState;
        public int MyState
        {
            get => _myState;
            set { _myState = value; OnPropertyChanged(); }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public partial class AnimationTestControl : UserControl
    {
        public AnimationTestControl()
        {
            InitializeComponent();
        }

        public TestItem TestBorderItem { get; } = new TestItem();

        private void ButtonToggle_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                var cnt = 0;
                var max = 100;
                void handler(object s, DependencyPropertyChangedEventArgs args) => cnt++;
                
                TestBorder.DataContextChanged += handler;

                for (int i = 0; i < max; i++)
                {
                    TestBorderItem.MyState = i + 1;
                    Thread.Sleep(2);
                }

                Console.WriteLine($"{max}: {cnt}");

                TestBorder.DataContextChanged -= handler;
            });
        }
    }
}


from Recent Questions - Stack Overflow https://ift.tt/3vxuVav
https://ift.tt/eA8V8J

No comments:

Post a Comment