更改值时禁用.NET进度条动画?

28

我知道在SO上有关于动画和进度条的其他问题,但它们似乎围绕着如何消除进度条上方绘制的动画,即沿其行进的高亮部分。

我想做的是消除设置进度条新值时使用的动画。我现在遇到的问题是运行的操作完成后,进度条继续增加到它们的最大位置之后

换句话说,如果我将进度条的Value属性设置为50,则希望它立即移动到中间位置(如果max为100),而不是像现在一样缓慢地建立进度条到该位置。

如果确实有一个关于这个问题的SO问题,只需将其关闭为重复项,我会很乐意删除它,但我找不到任何问题。

这是我找到的一个:禁用WinForms ProgressBar动画,它处理正在进行动画的高亮显示,这不是我所说的。

这里是一个简单的LINQPad演示,展示了这个问题:

void Main()
{
    using (var fm = new Form())
    {
        var bt = new Button
        {
            Text = "Start",
            Location = new Point(8, 8),
            Parent = fm,
        };
        var pb = new ProgressBar
        {
            Top = bt.Top + bt.Height + 8,
            Width = fm.ClientRectangle.Width - 16,
            Left = 8,
            Parent = fm
        };

        bt.Click += (s, e) =>
        {
            bt.Enabled = false;
            Thread t = new Thread(new ThreadStart(() =>
            {
                Thread.Sleep(1000);
                bt.BeginInvoke(new Action(() => { pb.Value = 50; }));
                Thread.Sleep(1000);
                bt.BeginInvoke(new Action(() => { pb.Value = 100; }));
                bt.BeginInvoke(new Action(() => { bt.Enabled = true; }));
            }));
            t.Start();
        };
        fm.ShowDialog();
    }
}

编辑1: 这是Windows 7,玻璃主题,所以我敢肯定这是特定于7或可能也适用于Vista。

这里有一个GIF动画,展示了上面提到的问题。您可以看到,按钮在启用后1秒钟,即在设置了一半的标记后,进度条会动画地达到100%,之后按钮才变为启用状态。

如上所述,将按钮设置回启用状态并将进度条设置为100是“同时进行”的。基本上,我不想要进度条的渐进式增加,我希望它直接跳到50%,然后在按钮启用的同时立即跳到100%。

LINQPad demo


编辑2: 针对David Heffernan的回答,这是我如何更改上面的代码:

bt.BeginInvoke(new Action(() => { pb.Value = 51; pb.Value = 50; }));
Thread.Sleep(1000);
bt.BeginInvoke(new Action(() => { pb.Maximum = 101; pb.Value = 101;
                                  pb.Maximum = 100; pb.Value = 100; }));

很有趣,我遇到了这个问题,认为我的代码有延迟,因为动画实际上需要大约100毫秒才能开始。 - Brett Ryan
5个回答

36

这个动画特性是在带有Aero主题的Vista中引入的。

但是有一个变通方法。如果您将进度条向后移动,动画就不会显示出来。因此,如果您想要立即增加50,可以将Value增加51,然后立即减去1。

当接近100%时会遇到麻烦,因为您无法将Value设置为101(假设Maximum设置为100)。而应该将Maximum设置为1000,例如,将其增加到1000,减少到999,然后移回到1000。

总之,这有点奇怪,但它确实具有使您获得所需效果的好处!


1
好的,我可以接受这个,我猜 :) - Lasse V. Karlsen
1
@Lasse 把它包起来,隐藏起来,假装它不存在! - David Heffernan
1
我在我的问题底部编辑了一个对LINQPad代码的更改。这是你的意思吗?它确实按照描述的方式工作,只是让我感觉有点不舒服,但是没关系,就假装它不存在 :) - Lasse V. Karlsen
1
不错的技巧,VisualStyleRenderer也不太好看。 - Hans Passant

20

这是我的扩展方法,基于David Heffernan建议

将其封装起来,隐藏起来,假装它不存在!

public static class ExtensionMethods
{
    /// <summary>
    /// Sets the progress bar value, without using Windows Aero animation
    /// </summary>
    public static void SetProgressNoAnimation(this ProgressBar pb, int value)
    {
        // Don't redraw if nothing is changing.
        if (value == pb.Value)
            return;

        // To get around this animation, we need to move the progress bar backwards.
        if (value == pb.Maximum) {
            // Special case (can't set value > Maximum).
            pb.Value = value;           // Set the value
            pb.Value = value - 1;       // Move it backwards
        }
        else {
            pb.Value = value + 1;       // Move past
        }
        pb.Value = value;               // Move to correct value
    }
}

2
没有爱情?这是一个很好的建议。 - nathanchere
我喜欢扩展方法的想法,但它无法完全处理我的特殊边界情况。 - Derek W
在您的方法启发下,我想出了这个解决方案并且达到了期望的效果:https://dev59.com/lm025IYBdhLWcg3wVkYR#22941217 - Derek W
我建议将代码包装在这个if( pb.Value != value )中。 - Mauro Sampietro
4
@sam,你应该考虑学习以“快速失败”(fail fast)的方式编写代码。与其堆积多层缩进,你可以用if (value == pb.Value) return;来实现同样的功能。 - Jonathon Reinhart
@JonathonReinhart 请在函数开头添加 "if (value == pb.Value) return;"。即使值没有改变,进度条实际上也会重新绘制!在某些情况下,这会使我的代码加速10倍(进度条被重新绘制了一百万次而不是只有100次)。 - Maxter

2

有另一种方法可以跳过类似视觉样式的进度条的动画:

只需将控件的状态SetState()设置为PBST_PAUSED,然后设置值,最后将其设置回PBST_NORMAL


你能提供一个如何实现的例子吗?ProgressBar似乎没有SetState()方法。 - Daniel
1
这个答案是错误的。SetState()不存在。我猜你说的是SendMessage(PBM_SETSTATE,...),但这并不影响进度条的动画。 - Elmue

0

现在是Jonathan Reinhart的SetProgressNoAnimation方法的VB.Net 2.0及更高版本。我希望这能帮助其他VB开发人员。 setProgressBarValue函数几乎是崩溃证明的。但硬件故障可能会使其崩溃。

''' <summary>
''' In VB.Net, the value of a progress bar can be set without animation.
''' Set the minimum and the maximum value beforehand.
''' This VB version has been written by EAHMK (Evert Kuijpers) in Tilburg in The Netherlands.
''' See SetProgressNoAnimation in
''' https://dev59.com/kW435IYBdhLWcg3wlBGD#5332770
''' by Jonathan Reinhart, based on the suggestion of David Heffernan.
''' </summary>
''' <param name="progressBar">
''' The progress bar that is to present the new value.
''' </param>
''' <param name="newValue">
''' The new value to present in the progress bar.
''' </param>
Public Function setProgressBarValue(progressBar As ProgressBar,
                                    newValue As Integer) As Exception
  Try
    ' Extremes are not supported.
    If newValue < progressBar.Minimum _
     Or newValue > progressBar.Maximum _
     Or progressBar.Maximum = progressBar.Minimum _
     Or progressBar.Maximum = Integer.MaxValue Then
      Return New ArgumentException("The value " & CStr(newValue) & " for" _
                & " the progress bar '" & progressBar.Name & "' is out of bounds.")
    End If

    ' By field maximumReached also progress bar value progressBar.Maximum is supported.
    Dim maximumReached As Boolean = newValue = progressBar.Maximum
    If maximumReached Then
      progressBar.Maximum += 1
    End If
    progressBar.Value = newValue + 1

    progressBar.Value = newValue
    If maximumReached Then
      progressBar.Maximum -= 1
    End If
    ' The value has been presented succesfully in the progress bar progressBar.
    Return Nothing
  Catch ex As Exception
    ' Returns an exception but does not crash on it.
    Return ex
  End Try
End Function

-3

我在VB中对这个问题的绝对解决方案...

Sub FileSaving()

    With barProgress
        .Minimum = 0
        .Maximum = 100000000
        .Value = 100000
    End With

    For
        ...
        saving_codes
        ...
        With barProgress
            .Maximum = .Value * (TotalFilesCount / SavedFilesCount)
        End With
    Next

End Sub

2
这并没有解决所询问的问题。 - Jonathon Reinhart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接