Ошибка индикатора выполнения темы Windows 7 Aero?

Я столкнулся с тем, что я считаю ошибкой индикатора выполнения в 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 дает ожидаемые результаты.

На мой взгляд, это похоже на ошибку. Часто бывает очень сложно сделать индикатор выполнения точным, когда длительная операция включает в себя сложный код, но в моем конкретном случае это было очень просто, поэтому я был немного разочарован, когда обнаружил, что элемент управления прогрессом неточно отражает прогресс.

Кто-нибудь еще заметил такое поведение? Кто-нибудь нашел обходной путь?


person jmatthias    schedule 07.02.2010    source источник


Ответы (7)


Это связано с анимацией индикатора выполнения. Если ваш индикатор выполнения находится на 0%, а вы установили его на 100%, он не будет там прыгать, а будет анимировать индикатор выполнения, плавно заполняющийся. Если это слишком медленно, вы закончите до того, как анимация индикатора выполнения завершится. Таким образом, даже если вы уже установили его на 80, 90 и 100%, анимация все равно отстает.

Я так и не нашел способ отключить это, но у меня есть обходной путь. Анимация выполняется только в том случае, если вы увеличиваете индикатор выполнения. Если вы переместите его назад, он немедленно перейдет в это положение. Поэтому, если я хочу, чтобы индикатор выполнения был на уровне x% (x! = 100), я перемещаю его на x + 1, а затем на x. Если мне нужно 100%, я перехожу на 100, 99 и 100%. (Или любые значения, которые вы используете, вы поняли.) Это работает достаточно быстро, чтобы не быть видимым, и вы можете оставить этот код и для предыдущих версий Windows (хотя я этого не делаю).

person Fozi    schedule 28.03.2010
comment
Угу, индикатор выполнения сейчас ведет себя вполне нормально, хотя должен был быть какой-то стандартный способ от Microsoft. - person Samir; 06.04.2010

У меня такая же проблема. Совет Фози помогал мне. Перед установкой нового значения я установил значение +1. Чтобы это работало также на 100%, необходимо предварительно увеличить максимум. Следующее сработало для меня.

if (NewValue < progressBar.Maximum)
{
  progressBar.Value = NewValue + 1;
  progressBar.Value--;
}
else
{
  progressBar.Maximum++;
  progressBar.Value = progressBar.Maximum;
  progressBar.Value--;
  progressBar.Maximum--;
}
person Stokker    schedule 07.03.2011
comment
Хорошее, хорошее решение, если у вас всего несколько шагов и каждый шаг виден :) - person Fozi; 15.02.2013

Я думаю, что исходная проблема связана с синхронизацией и механизмом анимации 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
person John    schedule 27.03.2010

Для пользователей 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.
person Erik Knowles    schedule 03.12.2012

Отключите параметр визуального эффекта «Анимировать элементы управления и элементы внутри окон» в «Параметры производительности». Тогда полосы прогресса больше не будут анимированы.

person Andi    schedule 22.08.2010
comment
Как вы скажете пользователю вашего программного обеспечения отключить эту функцию? Или вы принимаете это решение от его имени и сами отключаете его? - person Fozi; 15.02.2013

Я видел похожие проблемы с индикаторами выполнения в Vista и Windows 7.

Ключевой проблемой в моем случае была блокировка потока пользовательского интерфейса. (Как и в вашем примере).

Windows не любит приложения, которые не отвечают на новые сообщения в очереди сообщений. Если вы потратите слишком много времени на одно сообщение, Windows отметит ваше приложение как «не отвечающее». В Vista / Win7 Windows также решает прекратить обновление окна вашего приложения.

В качестве обходного пути вы можете передать фактическую работу фоновому исполнителю или время от времени вызывать Application.DoEvents(). Вам нужно убедиться, что окно индикатора выполнения является модальным, иначе DoEvents () может разрешить запуск новых команд на полпути в фоновой обработке.

Если вам кажется, что это непонятно, более правильный способ - выполнить фоновую работу в BackgroundWorker потоке. Он поддерживает отправку событий в поток пользовательского интерфейса для обновления индикатора выполнения.

person Community    schedule 07.02.2010
comment
Добавление Application.DoEvents () не «решает» проблему. Кажется, что обработка сообщения индикатором выполнения ставится в очередь и становится асинхронной, поэтому, даже если цикл завершен, индикатор выполнения не догнал. - person jmatthias; 07.02.2010
comment
В ПОРЯДКЕ. Это одна исключенная проблема. Вы пробовали это: stackoverflow.com/questions/313792/ - person ; 07.02.2010
comment
Вызов SetWindowTheme (), как предложено в 313792, «устраняет» проблему. К сожалению, индикатор выполнения отображается без рамки, которая является важной частью индикатора выполнения. - person jmatthias; 08.02.2010

(09/2015) Просто перескочил с D6 на XE8. Имея ряд проблем. Включая эту вещь TProgressBar. Отложил на некоторое время. Сегодня вечером наткнулся на это (Эрик Ноулз) исправление. Фантастика. За исключением: первый сценарий, который я рассмотрел, имел максимальное значение 9,770,880. И это («оригинальное» исправление Эрика Ноулза) ДЕЙСТВИТЕЛЬНО увеличивало время, затрачиваемое на этот процесс (со всем дополнительным актуальным обновлением ProgressBar).

Поэтому я расширил его класс, чтобы уменьшить количество раз, когда ProgressBar фактически перерисовывает себя. Но ТОЛЬКО ЕСЛИ "исходное" максимальное значение больше MIN_TO_REWORK_PCTS (здесь я остановился на 5000).

Если это так, ProgressBar обновляется только HUNDO раз (здесь я начал и почти остановился на 100, отсюда и название "HUNDO").

Я также учел некоторую причуду в значении Max:

if Abs(FOriginalMax - value) <= 1 then
  pct := HUNDO

Я проверил это на моем оригинальном 9,8 м Макс. И с помощью этого автономного тестового приложения:

:
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.
person Free Dorfman    schedule 15.09.2015
comment
Так что подождите, вы говорите, что стандартная полоса прогресса comctl32.dll сильно замедлится, если ваше максимальное значение будет невероятно высоким? Я не совсем понимаю, в чем проблема ... - person andlabs; 05.12.2015