Windows 7 Aero Theme Progress Bar Ошибка? - PullRequest
10 голосов
/ 07 февраля 2010

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

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

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

Ответы [ 7 ]

19 голосов
/ 28 марта 2010

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

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

4 голосов
/ 07 марта 2011

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

if (NewValue < progressBar.Maximum)
{
  progressBar.Value = NewValue + 1;
  progressBar.Value--;
}
else
{
  progressBar.Maximum++;
  progressBar.Value = progressBar.Maximum;
  progressBar.Value--;
  progressBar.Maximum--;
}
3 голосов
/ 28 марта 2010

Я думаю, что первоначальная проблема связана с синхронизацией и механизмом анимации Win7 (или Aero) для индикатора выполнения.

Этот Sub находится на форме, которая содержит индикатор выполнения (pBar).

Он изменяет максимальную величину бара и сохраняет фиксированное значение. 10 для процентов от 1 до 99. Минимальное значение бара устанавливается на 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 голосов
/ 04 декабря 2012

Для пользователей 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.,
   /1565277/windows-7-aero-theme-progress-bar-oshibka

   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 голос
/ 22 августа 2010

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

0 голосов
/ 15 сентября 2015

(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 1000 10000 100000 1000000

Он плавный и «точный» для всех этих значений - без какого-либо замедления.

При тестировании я смог переключить директиву компилятора 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.
0 голосов
/ 07 февраля 2010

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

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

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

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

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

...