【问题标题】:Windows 7 Aero Theme Progress Bar Bug?Windows 7 Aero 主题进度条错误?
【发布时间】:2023-03-23 13:35:02
【问题描述】:

我在 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 上选择非航空主题会产生预期的结果。

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

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

【问题讨论】:

    标签: windows-7 progress-bar aero


    【解决方案1】:

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

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

    【讨论】:

    • 是的,进度条现在表现还不错,虽然微软应该有一些标准的方式。
    【解决方案2】:

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

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

    【讨论】:

    • 很好,如果您只有几个步骤并且每个步骤都是可见的,那么很好的解决方案:)
    【解决方案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
    

    【讨论】:

      【解决方案4】:

      致面临同样问题的 Delphi 用户:下面是一个名为 ProgressBarFix 的单元,您可以使用它来自动修补问题而不必担心更改进度条代码——只需在表单界面的“使用”子句中包含 ProgressBarFix 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.
      

      【讨论】:

        【解决方案5】:

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

        【讨论】:

        • 你如何告诉你的软件用户去禁用这个功能?还是您是代表他做出此决定并自己禁用它?
        【解决方案6】:

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

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

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

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

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

        【讨论】:

        • 添加 Application.DoEvents() 并不能“解决”问题。似乎进度条对消息的处理排队并变为异步,因此即使循环完成,进度条也没有赶上。
        • 好的。这是排除了一个问题。你试过这个:stackoverflow.com/questions/313792/…
        • 按照 313792 中的建议调用 SetWindowTheme() 确实“修复”了问题。不幸的是,进度条的绘制没有边框,这是进度条的重要组成部分。
        【解决方案7】:

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

        所以我扩展了他的类以减少 ProgressBar 实际重绘自身的次数。但仅当“原始”最大值大于 MIN_TO_REWORK_PCTS 时(我在这里确定为 5000)。

        如果是这样,ProgressBar 只会自我更新 HUNDO 次(这里我从 100 开始,并且几乎确定为 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.
        

        【讨论】:

        • 所以等等,你是说如果你的最大值非常高,标准的 comctl32.dll 进度条会大大减慢吗?我不太明白问题是什么......
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-08-23
        • 2010-12-30
        • 1970-01-01
        • 2015-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多