0

I'm trying to create a ProgressBar that can animate from 0 to some value. Everything works fine most of time ...

I'm using the code from Owen Johnson. Have some changes like:

public static readonly DependencyProperty SmoothValueProperty =
    DependencyProperty.RegisterAttached("SmoothValue", typeof(double), typeof(ProgressBarSmoother), 
        new FrameworkPropertyMetadata(0d,  FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, changing, coerceValueCallback, false, System.Windows.Data.UpdateSourceTrigger.PropertyChanged));


private static object coerceValueCallback(DependencyObject d, object baseValue)
{
    Console.WriteLine($"{ (double)baseValue } coercevalue");
    return baseValue;
}

public static void changing(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ProgressBarSmoother obj = d as ProgressBarSmoother;
    Console.WriteLine($"Contador: {++obj.Count}. Valor novo: { e.NewValue }");
    if ((double)e.NewValue == 0)
    {
        obj.ResetAnimation();

        Console.WriteLine($"Contador: {++obj.Count}");
        obj.Value = 0;
        return;
    }

    obj.Animation = new DoubleAnimation(0d, (double)e.NewValue, TimeSpan.FromMilliseconds((double)e.NewValue));
    obj.Animation.Completed += (sender, arg) =>
    {
        obj.Animation = null;
        obj.Value = 0;
        obj.BeginAnimation(ValueProperty, null, HandoffBehavior.SnapshotAndReplace);

        Console.WriteLine("Animation completed");
    };

    obj.Foreground = new SolidColorBrush(Colors.Red);
    obj.BeginAnimation(ValueProperty, obj.Animation, HandoffBehavior.SnapshotAndReplace);

    Console.WriteLine($"Contador: {++obj.Count}");
}

// Basically the code that changes the progressbar value is this
private void Controller_OnPlayerChangedState(object sender, MultiPlayerController.OnChangeStateEventArgs e)
{
    if (e.NewState == AudioPlayer_State.Playing)
    {
        Duration = AudioPanel.Controller.GetDuration();

        panel.SetCurrentPositionAsync (Duration);
    }
    else if(e.NewState != AudioPlayer_State.Playing && e.NewState != AudioPlayer_State.Paused)
    {
        panel.SetCurrentPositionAsync ((long)0);
        //System.Threading.Thread.Sleep(100);
    }
}

(have a lot of code just for test and see whats happining)

I've an Audio Player and the progressbar is to see the actual position of audio (cannot be paused, only stopped). The stop button works fine. The problem is when Repeat status it's activated. I receive a event from my dll of Stopped (set value to 0 and my animation goes away) and a few moments later (less then 0ms most of time) a Playing, so I have to update the progressbar animation with the duration.

Sometimes my animation is not called and I put some logs to discover wht. When is not called, CoerceValueCallback happens just one time. And when works, called two times: one with 0, when I destroy the older animation and other with the new duration (same as before).

Just for test, I've put a Thread.Sleep inside the code which changes the value to new duration and works like a charm. My thinking is the delay to change the new value... Basically I need to set to 0 and after to new value.

Can I remove the check of older value needs to be different from newer? Or I can update instantly the value?

Solution until this moment

I created a public function on my control ProgressBarSmoother called ResetAnimation

public void ResetAnimation()
{
    this.Dispatcher.Invoke(() =>
    {
        SmoothValue = 0;
    });
}

And created a getter/setter of SmoothValue

public double SmoothValue
{
    get => (double)GetValue(SmoothValueProperty);
    set => SetValue(SmoothValueProperty, value);
}

On my SetCurrentPositionAsync,

public void SetCurrentPositionAsync (long position)
{
    if (position == 0)
        progressBar.ResetAnimation();

    CurrentPositionAsync = position;
}

And my changing:

public static void changing(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ProgressBarSmoother obj = d as ProgressBarSmoother;
    if ((double)e.NewValue == 0)
    {
        obj.BeginAnimation(ValueProperty, null, HandoffBehavior.SnapshotAndReplace);

        return;
    }

    obj.Animation = new DoubleAnimation(0d, (double)e.NewValue, TimeSpan.FromMilliseconds((double)e.NewValue));
    obj.BeginAnimation(ValueProperty, obj.Animation, HandoffBehavior.SnapshotAndReplace);
}

So.. When I set 0, I instantly call ResetAnimation that can set the Value of property and set on field correctly instead of calling OnPropertyChanged and having some delay.

My Controller_OnPlayerChangedState:

private void Controller_OnPlayerChangedState(object sender, MultiPlayerController.OnChangeStateEventArgs e)
{
    if (e.NewState == AudioPlayer_State.Playing)
    {
        Duration = AudioPanel.Controller.GetDuration();
        panel.SetCurrentPositionAsync((long)Duration);
    }
    else if(e.NewState != AudioPlayer_State.Playing && e.NewState != AudioPlayer_State.Paused)
        panel.SetCurrentPositionAsync(0);
}

Works fine but I don't know if I can have another solution.

Community
  • 1
  • 1
Kevin Kouketsu
  • 786
  • 6
  • 20
  • Can we see the code where `changing` is called or triggered. Basically it seems vulnerable for concurrent operations. You could consider a `lock` but that would be a dirty trick. – Stefan Apr 05 '19 at 12:56
  • I edited the post with the function where changing it's called. Duration it's a property with OnPropertyChagned being called and consequently changing too. – Kevin Kouketsu Apr 05 '19 at 13:00
  • Okay, this can be a though nut to crack.... You have some options, but the most preferable would be to not use `obj.Animation = new ...`. The reason: it might collide during concurrent calls. Is there a way to just reset, and not `new` the animation? – Stefan Apr 05 '19 at 13:17
  • Well, I don't see how to reuse the DoubleAnimation object. The biggest problem I think is if the older value is equal the same. The change it's just ignored (with some reason) but I don't want this behavior. Consequently changing callback it's not called so I can't reuse the DoubleAnimation object – Kevin Kouketsu Apr 05 '19 at 13:25
  • I posted a solution, but I don't know if it's a good. I will let this thread a little longer open so someone can try to help with any ideia. – Kevin Kouketsu Apr 05 '19 at 15:28
  • Ah, nice; let us know if it worked. – Stefan Apr 05 '19 at 15:39
  • 1
    Works ... well... but I prefer to find a more beauty solution if exists (probably yes). – Kevin Kouketsu Apr 05 '19 at 15:43

0 Answers0