Windows 7 Aero Theme Progress Bar Bug?

时间:2010-02-07 18:09:01

标签: windows-7 progress-bar aero

我遇到了我认为是Windows 7上的进度条错误。为了演示该错误,我创建了一个带有按钮和进度条的WinForm应用程序。在按钮的'on-click'句柄中,我有以下代码。

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%,然后按钮文本更改为“就绪”。但是,在Windows 7上开发此代码时,我注意到进度条将上升到大约75%,然后按钮文本将变为“就绪”。假设代码是同步的,这不应该发生!

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

在我看来,这似乎是个错误。通常,当长操作涉及复杂的代码时,很难使进度条准确,但在我的特定情况下,它非常直接,所以当我发现进度控制没有准确地表示进度时,我感到很失望。

还有其他人注意到这种行为吗?有人找到了解决方法吗?

7 个答案:

答案 0 :(得分:18)

它与进度条的动画有关。如果您的进度条为0%并且您将其设置为100%,那么它将不会跳到那里,但会使进度条平滑填充动画。如果这太慢,您将在进度条完成动画之前完成。因此即使你已经将其设置为80%,90%和100%,动画仍然落后。

我从来没有找到办法解决此问题,但我有一个解决方法。只有在增加进度条时才会执行动画。如果向后移动,它会立即跳到该位置。因此,如果我希望进度条位于x%(x!= 100),那么我将其移至x + 1然后移至x。如果我想要它100%,我将它移动到100%,99%和100%。 (或者无论你使用什么值,你都可以得到这个想法。)这个工作得足够快,不会被看到,你也可以将此代码留给以前的Windows版本(尽管我没有)。

答案 1 :(得分:4)

我遇到了同样的问题。 Fozi的tipp帮助了我。在设置新值之前,我已将值设置为1.要使此工作也为100%,必须先增加最大值。以下对我来说很好。

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

答案 2 :(得分:3)

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

此Sub位于包含进度条(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

答案 3 :(得分:2)

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

unit ProgressBarFix;
(* The standard progress bar fails under Windows theming -- it fails to animate
   all the way to the right side. C.f.,
   http://stackoverflow.com/questions/2217688/windows-7-aero-theme-progress-bar-bug

   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.

答案 4 :(得分:1)

在“效果选项”中禁用视觉效果选项“动画窗口内的控件和元素”。然后,进度条将不再动画。

答案 5 :(得分:0)

我在Vista和Windows 7上看到了与进度条类似的问题。

我的案例中的关键问题是阻止了UI线程。 (就像你在样本中所做的那样)。

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

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

如果感觉到kludgy,更恰当的方法是在BackgroundWorker线程上进行后台工作。它支持将事件发送到UI线程以更新进度条。

答案 6 :(得分:0)

(09/2015)我刚刚从D6跳到了XE8。有很多问题。包括这个TProgressBar的东西。把它搁置一段时间。今晚遇到了这个问题(Erik Knowles)。太棒了。除外:我遇到的第一个场景的最大值为9,770,880。并且它(Erik Knowles的“原始”修复)真正添加到此过程所花费的时间(所有额外的实际更新ProgressBar)。

所以我扩展了他的课程以减少ProgressBar实际重绘的次数。但只有“原始”Max值大于MIN_TO_REWORK_PCTS(我在这里定居5000)。

如果是这样的话,ProgressBar只会更新自己的HUNDO次数(这里我开始使用并且几乎已经确定为100,因此是“HUNDO”名称)。

我在Max值上也有一些怪癖:

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 1000 万 100000 百万

所有这些值都很平滑,“准确” - 没有真正放慢任何速度。

在测试中,我能够切换我的编译器指令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.