10

我遇到了我认为是 Windows 7 上的进度条错误。为了演示该错误,我创建了一个带有按钮和进度条的 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%,然后按钮文本变为“就绪”。但是,在 Windows 7 上开发此代码时,我注意到进度条会上升到大约 75%,然后按钮文本会变为“就绪”。假设代码是同步的,这不应该发生!

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

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

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

4

7 回答 7

19

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

我从来没有找到关闭它的方法,但是我有一个解决方法。只有当您增加进度条时,动画才会完成。如果你向后移动它,它会立即跳到那个位置。因此,如果我希望进度条位于 x% (x != 100),那么我将其移至 x+1,然后移至 x。如果我希望它达到 100%,我将它移动到 100、99 和 100%。(或者你使用的任何值,你都会明白的。)这工作得足够快,以至于不可见,你也可以将此代码保留在以前的 Windows 版本中(尽管我不这样做)。

于 2010-03-28T00:10:20.110 回答
4

我有同样的问题。Fozi 的小费帮助了我。在设置新值之前,我已将值设置为 + 1。为了使这项工作也适用于 100%,之前必须增加最大值。以下对我来说很好。

if (NewValue < progressBar.Maximum)
{
  progressBar.Value = NewValue + 1;
  progressBar.Value--;
}
else
{
  progressBar.Maximum++;
  progressBar.Value = progressBar.Maximum;
  progressBar.Value--;
  progressBar.Maximum--;
}
于 2011-03-07T15:56:44.273 回答
3

I think the original problem is related to timing and Win7's (or Aero's) animation mechanism for the progress bar.

This Sub is on the form that contains the progress bar (pBar).

It varies the bar's .Maximum and keeps .Value fixed at 10 for percent completes of 1 to 99. The bar's .Minimum is set to 0 at design time.

This sorted out the problem for me.

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
于 2010-03-27T23:50:13.873 回答
2

对于面临同样问题的 Delphi 用户:下面是一个名为 ProgressBarFix 的单元,您可以使用它来自动修补问题,而无需担心更改进度条代码——只需在 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.,
   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.
于 2012-12-03T20:44:29.443 回答
1

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

于 2010-08-22T18:06:47.573 回答
0

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

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

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

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

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

于 2010-02-07T19:33:06.950 回答
0

(09/2015) 我刚从 D6 跳到 XE8。有很多问题。包括这个 TProgressBar 的东西。摆了一会儿。今晚遇到了这个(Erik Knowles)修复。极好的。除了:我遇到的第一个场景的最大值为 9,770,880。而且它(Erik Knowles 的“原始”修复)确实增加了这个过程所花费的时间(以及 ProgressBar 的所有额外实际更新)。

因此,我扩展了他的课程以减少 ProgressBar 实际重绘自身的次数。但仅当“原始”最大值大于 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 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.
于 2015-09-15T10:35:45.443 回答