Отправляет email-рассылки с помощью сервиса Sendsay

Советы по Delphi

  Все выпуски  

Советы по Delphi


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

Здравствуйте, уважаемые подписчики! Главной темой сегодняшнего выпуска будет копирование файлов. В Win32 существует функция CopyFile, которая работает быстро, никаких проблем с ней не возникает, что же еще нужно? Если копируется большой файл, то это занимает много времени. И все то время программа будет стоять на строчке с вызовом CopyFile. Это означает, что не сможет даже перерисоваться окно. Конечно, копирование файла можно вынести в отдельный поток. Но даже в этом случае не удастся определить процент выполненной работы. Так что, иногда полезно написать свое копирование файла.

Копирование файла
Для этой цели удобно использовать процедуры BlockRead и BlockWrite. Для указания пользователем, какой файл копировать и куда, здесь используется OpenDialog и SaveDialog. Здесь было бы удобно использовать Gauge для отображения процента выполненной работы. Но Gauge - плохо сделанный компонент (не оптимально). Так что здесь это же реализовано "вручную".

Самой сложной задачей является определение времени, которое потребуется на завершение копирования файла. Казалось бы: определить затраченное время, разделить на процент сделанной работы - и вот готовое полное время копирования. Но кэширование и многозадачность сильно портят картину. Наилучшее решение, которое мне пришло в голову, это искать скорость копирования не на основе всего затраченного времени, а на основе последних секунд. Если даже какой-то другой процесс "притормозит" копирование, реакция на это быстро пройдет. Если кто-нибудь придумает решение лучше - напишите, пожалуйста, об этом на delphi4all@narod.ru.

var
  p, fs: integer;

procedure TForm1.Button1Click(Sender: TObject);
const
  BufSize = 524288;
  LeftS = 'Осталось ';
var
  S, D: File;
  buf: array [0..BufSize] of byte;
  r, w: integer;
  OldP, LastP, LastP1: integer;
  t0, t1: cardinal;
  LeftTime: boolean; { Нужно ли писать об оставшемся времени }
  LeftSec: integer;
begin
  if OpenDialog1.Execute = false then Exit;
  if SaveDialog1.Execute = false then Exit;
  Label1.Caption := 'Копирование: из "' + OpenDialog1.FileName +
    '" в "' + SaveDialog1.FileName + '"';
  AssignFile(S, OpenDialog1.FileName);
  Reset(S, 1);
  AssignFile(D, SaveDialog1.FileName);
  Rewrite(D, 1);
  fs := FileSize(S);
  p := 0; OldP := 0; LastP := 0; LastP1 := 0;
  t0 := GetTickCount; t1 := t0;
  LeftTime := false;
  repeat
    BlockRead(S, buf, BufSize, r);
    BlockWrite(D, buf, r, w);
    inc(p, w);
    if round(p / fs * 100) <> round(OldP / fs * 100) then begin
      Form1.Canvas.FillRect(Bounds(11 + round(OldP / fs * 100), 51, round((p - OldP) / fs * 100), 18));
      OldP := p;
      if not LeftTime then LeftTime := GetTickCount - t0 > 20;
      if LeftTime then begin
        if GetTickCount - t0 > 10000 then begin
          t0 := t1;
          LastP := LastP1;
          t1 := GetTickCount;
          LastP1 := p;
        end;
        LeftSec := round((GetTickCount - t0) /
          (p - LastP) * (fs - p) / 1000);
        case LeftSec of
          0..10: Label1.Caption := LeftS + IntToStr(LeftSec) + ' сек';
          11..25: Label1.Caption := LeftS +
            IntToStr(round(LeftSec / 5) * 5) + ' сек';
          26..54: Label1.Caption := LeftS +
            IntToStr(round(LeftSec / 10) * 10) + ' сек';
          55..180: Label1.Caption := LeftS +
            IntToStr(round(LeftSec / 60)) + ' мин ' +
            IntToStr(round(LeftSec / 20) * 20) + ' сек';
          else Label1.Caption := LeftS +
            IntToStr(round(LeftSec / 60)) + ' мин';
        end;
      end;
    end;
    Application.ProcessMessages;
  until (r < BufSize) or (w < r);
  Label1.Caption := 'Копирование закончилось';
  CloseFile(S);
  CloseFile(D);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SaveDialog1.Options := SaveDialog1.Options +
    [ofOverwritePrompt,ofCreatePrompt];
  fs := 1;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  with Form1.Canvas do begin
    Brush.Color := clWhite;
    Pen.Color := clGray;
    Rectangle(10, 50, 112, 70);
    Brush.Color := clNavy;
    FillRect(Bounds(11, 51, round(p / fs * 100), 18));
  end;
end;

Полезные мелочи
Delphi не позволяет сравнивать записи. Поэтому, чтобы сравнить две переменные типа TPoint, нужно отдельно сравнить поля x и y. Чтобы упростить это сравнение, можно написать comp(p1) = comp(p2). В этом случае будут сравниваться два числа типа comp, занимающие как раз по 8 байт. То есть произойдет сравнение двух областей памяти, в которых лежат p1 и p2. Вот пример использования такого сравнения:
procedure TForm1.Button1Click(Sender: TObject);
var
  p1, p2: TPoint;
begin
  p1 := Point(20, -45);
  p2 := p1;
  if comp(p1) = comp(p2)
    then ShowMessage('p1 = p2')
    else ShowMessage('p1 <> p2');
end;
Вместо comp можно написать любой 8-ми байтовый тип, например, int64.


Все советы и замечания, пожалуйста, присылайте на delphi4all@narod.ru

Всего доброго,
Даниил Карапетян.






http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу
Рейтингуется SpyLog

В избранное