Windows 7 Aero主题进度条Bug?

11

我遇到了我认为是 Windows 7 上的进度条 bug。为了演示这个 bug,我创建了一个带有按钮和进度条的 WinForm 应用程序。在按钮的“点击”处理程序中,我有以下代码。

private void buttonGo_Click(object sender, EventArgs e)
{
  this.progressBar.Minimum = 0;
  this.progressBar.Maximum = 100;

  this.buttonGo.Text = "Busy";
  this.buttonGo.Update();

  for (int i = 0; i <= 100; ++i)
  {
    this.progressBar.Value = i;
    this.Update();

    System.Threading.Thread.Sleep(10);
  }

  this.buttonGo.Text = "Ready";
}
期望的行为是进度条会前进到100%,然后按钮文本变为“Ready”。但是,在Windows 7上开发此代码时,我注意到进度条会上升到大约75%,然后按钮文本会变为“Ready”。假设代码是同步的,这不应该发生!

经过进一步测试,我发现在运行相同代码的Windows Server 2003上产生了预期结果。此外,在Windows 7上选择非Aero主题也会产生预期结果。

在我看来,这似乎是一个错误。通常,当长时间操作涉及复杂代码时,很难使进度条准确无误,但在我的特定情况下,这非常直观,因此当我发现进度控件不能准确地表示进度时感到有点失望。请问有人注意到这种行为吗?有人找到了解决方法吗?
7个回答

20

涉及进度条的动画效果。如果您的进度条显示为0%,并将其设置为100%,它将不会立即跳转,而是平滑地填充进度条以达到100%。如果这个过程太慢,您已经完成了工作,但是进度条的动画效果还没有结束。所以,即使您已经将它设置为80、90和100%,动画效果仍然会滞后。

我从未找到关闭此功能的方法,但我有一个解决方法。只有在增加进度条时才会进行动画效果。如果您将进度条向后移动,它将立即跳转到该位置。因此,如果我想将进度条设置为x%(x!= 100),那么我会将其移动到x + 1,然后再移动到x。如果我想将其设置为100%,我会将其移动到100、99和100%。(或者您使用其他值,但您已经理解了思路。)这种方式足够快,并且您可以在之前的Windows版本中保留此代码。(尽管我没有这样做)

希望对您有所帮助。


是的,进度条现在表现得相当不错,尽管微软应该有一些标准的方法。 - Samir

5

我遇到了同样的问题。Fozi给了我一个提示。在设置新值之前,我先将值+1。为了让这个方法适用于100%,必须先增加最大值。以下是对我有效的方法。

if (NewValue < progressBar.Maximum)
{
  progressBar.Value = NewValue + 1;
  progressBar.Value--;
}
else
{
  progressBar.Maximum++;
  progressBar.Value = progressBar.Maximum;
  progressBar.Value--;
  progressBar.Maximum--;
}

不错,如果只有几个可见的步骤,这是一个很好的解决方案 :) - Fozi

3

我认为原始问题与时间和Win7(或Aero)的进度条动画机制有关。

这个子程序在包含进度条(pBar)的表单上。

它会改变条的 .Maximum 值,并保持 .Value 值固定为10,以表示1到99的百分比完成情况。该条的 .Minimum 值在设计时设置为0。

这对我来说解决了问题。

Public Sub UpdateStatusPC(ByVal pc As Integer)

    Try

        If pc < 0 Then
            pBar.Maximum = 100
            pBar.Value = 0
        ElseIf pc > 100 Then
            pBar.Maximum = 100
            pBar.Value = 100
        ElseIf pc = 0 Then
            pBar.Maximum = 10
            pBar.Value = 0
        Else
            pBar.Value = 10
            pBar.Maximum = 10 / CDbl(pc / 100.0)
        End If

        pBar.Update()

    Catch ex As Exception

        MsgBox("UpdateStatusPC: " & ex.Message)

    End Try

End Sub

2

对于面临相同问题的Delphi用户:以下是一个名为ProgressBarFix的单元,您可以使用它来自动修复问题,而无需担心更改进度条代码 - 只需在您的表单界面的“uses”子句中包含ComCtrls uses之后的ProgressBarFix,即可自动获取解决方法:

unit ProgressBarFix;
(* The standard progress bar fails under Windows theming -- it fails to animate
   all the way to the right side. C.f.,
   https://dev59.com/2nE95IYBdhLWcg3wp_kg

   To work around the problem, include ProgressBarFix in the interface section's
   "uses" clause *after* ComCtrls (this replaces the TProgressBar definition in
   ConCtrls with the one here, effectively allowing the control defined on the
   form to be replaced with the patch version.

   c.f., http://www.deltics.co.nz/blog/?p=222and http://melander.dk/articles/splitter *)

interface
uses ComCtrls ;

type TProgressBar = class(ComCtrls.TProgressBar)
private
    procedure SetPosition(Value: Integer);
    function GetPosition: Integer;
published
    property Position: Integer read GetPosition write SetPosition default 0;
end ;

implementation

{ TProgressBar }

function TProgressBar.GetPosition: Integer;
begin
    result := inherited Position
end;

procedure TProgressBar.SetPosition(Value: Integer);
begin
    if Value=inherited Position then
        exit ;
    if value<Max then begin
        inherited Position := value+1 ;
        inherited Position := value
    end else begin
        Max := Max+1 ;
        inherited Position := Max ;
        inherited Position := value ;
        Max := Max-1
    end            
end;

end.

1

在“性能选项”中禁用“窗口内的控件和元素动画”可关闭可视效果选项。然后进度条将不再是动画的。


2
你如何告诉软件用户去禁用这个功能?或者你是代表他做出决定并自行禁用它? - Fozi

0

我曾经遇到过在Vista和Windows 7上使用进度条的类似问题。

在我的情况下,关键问题是UI线程的阻塞(就像您在示例中所做的那样)。

Windows不喜欢不响应消息队列中新消息的应用程序。如果您在一个消息上花费太多时间,Windows将标记您的应用程序为“无响应”。在Vista / Win7中,Windows还决定停止更新应用程序窗口。

作为解决方法,您可以将实际工作放在后台工作者上,或者每隔一段时间调用Application.DoEvents()。您确实需要确保您的进度条窗口是模态的,否则DoEvents()可能会使新命令在后台处理过程中开始执行。

如果感觉这种方法有点笨拙,更合适的方法是在BackgroundWorker线程上执行后台工作。它带有支持向UI线程发送事件以更新进度条的功能。


添加Application.DoEvents()并不能“修复”问题。似乎进度条处理消息被排队并变成异步的,因此即使循环已经完成,进度条仍未赶上。 - jmatthias
好的,这样就解决了一个问题。你试过这个吗:https://dev59.com/C0XRa4cB1Zd3GeqPvNkD#315742 - user180326
调用建议中的SetWindowTheme()可以“修复”问题。不幸的是,进度条将被绘制时没有边框,这是进度条的重要部分。 - jmatthias

0
(09/2015)我刚从D6跳到XE8。遇到了一些问题,包括TProgressBar的问题。暂时搁置了一段时间。今晚偶然发现了Erik Knowles的解决方法。太棒了!但是:我运行的第一个场景的最大值为9,770,880。而且(Erik Knowles的“原始”解决方案)真的增加了这个过程所需的时间(因为所有额外的实际更新进度条)。
因此,我扩展了他的类以减少ProgressBar实际重绘的次数。但仅当“原始”最大值大于MIN_TO_REWORK_PCTS(我在这里选择了5000)时才这样做。
如果是这样,ProgressBar只会更新100次(我从这里开始并基本上定居在100,因此有“HUNDO”名称)。
我还考虑了最大值的一些怪癖:
if Abs(FOriginalMax - value) <= 1 then
  pct := HUNDO

我对我的原始9.8m Max进行了测试。并且,使用这个独立的测试应用程序:

:
uses
  :
  ProgressBarFix;

const
  PROGRESS_PTS = 500001;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    PB: TProgressBar;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  x: integer;
begin
PB.Min := 0;
PB.Max := PROGRESS_PTS;
PB.Position := 0;

for x := 1 to PROGRESS_PTS do
  begin
  //let's do something
  //
  Label1.Caption := Format('%d of %d',[x,PROGRESS_PTS]);
  Update;

  PB.Position := x;
  end;

PB.Position := 0;
end;

end.

具有以下PROGRESS_PTS值: 10 100 1,000 10,000 100,000 1,000,000

对于所有这些值,它都是平滑和“准确”的 - 没有真正减慢任何速度。

在测试中,我能够切换我的编译器指令DEF_USE_MY_PROGRESS_BAR以测试两种方式(此TProgressBar替换与原始版本)。

请注意,您可能需要取消注释对Application.ProcessMessages的调用。

这是(我的“增强版”)ProgressBarFix源代码:

unit ProgressBarFix;

interface

uses
  Vcl.ComCtrls;

type
  TProgressBar = class(Vcl.ComCtrls.TProgressBar)
  const
    HUNDO = 100;
    MIN_TO_REWORK_PCTS = 5000;
  private
    function  GetMax: integer;
    procedure SetMax(value: integer);
    function  GetPosition: integer;
    procedure SetPosition(value: integer);
  published
    property Max: integer read GetMax write SetMax default 100;
    property Position: integer read GetPosition write SetPosition default 0;

  private
    FReworkingPcts: boolean;
    FOriginalMax:   integer;
    FLastPct:       integer;
  end;

implementation

function TProgressBar.GetMax: integer;
begin
result := inherited Max;
end;

procedure TProgressBar.SetMax(value: integer);
begin
FOriginalMax := value;
FLastPct := 0;

FReworkingPcts := FOriginalMax > MIN_TO_REWORK_PCTS;

if FReworkingPcts then
  inherited Max := HUNDO
else
  inherited Max := value;
end;

function TProgressBar.GetPosition: integer;
begin
result := inherited Position;
end;

procedure TProgressBar.SetPosition(value: integer);
var
  pct: integer;
begin
//Application.ProcessMessages;

if value = inherited Position then
  exit;

if FReworkingPcts then
  begin
  if Abs(FOriginalMax - value) <= 1 then
    pct := HUNDO
  else
    pct := Trunc((value / FOriginalMax) * HUNDO);

  if pct = FLastPct then
    exit;

  FLastPct := pct;

  value := pct;
  end;

if value < Max then
  begin
  inherited Position := Succ(value);
  inherited Position := value;
  end
else
  begin
  Max := Succ(Max);
  inherited Position := Max;
  inherited Position := value;
  Max := Pred(Max);
  end;
end;

end.

1
所以,你是在说如果最大值非常高,标准的comctl32.dll进度条会明显变慢?我不太明白问题出在哪里... - andlabs

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