Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2011.01.09;
Скачать: [xml.tar.bz2];

Вниз

ReadDirectoryChangesW   Найти похожие ветки 

 
Dennis I. Komarov ©   (2010-10-11 11:59) [0]

При повторном вызове функции в асинхронном режиме что происходит в системе, если изменений в директории не было? Функция возвращает правду.

Return Values
If the function succeeds, the return value is nonzero. For synchronous calls, this means that the operation succeeded. For asynchronous calls, this indicates that the operation was successfully queued.


 
Сергей М. ©   (2010-10-11 12:02) [1]


> Функция возвращает правду


> что происходит в системе .. ?


Написано же что происходит:

operation was successfully queued


 
Dennis I. Komarov ©   (2010-10-11 12:08) [2]

При повторном вызове "заявка на изменение" будет новая? Т.е. суть такая: корректно ли вызывать повторно функцию, если изменений после первого вызова не произошло?


 
Сергей М. ©   (2010-10-11 12:15) [3]


> корректно ли вызывать повторно функцию, если изменений после
> первого вызова не произошло?


А какой смысл ее повторно вызывать ?
Заявка поставлена - жди результатов ее выполнения ..


 
Dennis I. Komarov ©   (2010-10-11 12:21) [4]


> А какой смысл ее повторно вызывать ?

Ну да, но вопрос был - как на это отреагирует система... :)

> Заявка поставлена - жди результатов ее выполнения ..

Подождали... отвалили по time-out... вызвали еще раз... система обидится?


 
Сергей М. ©   (2010-10-11 12:32) [5]


> отвалили по time-out... вызвали еще раз... система обидится?


Не должна.


 
Сергей М. ©   (2010-10-11 12:36) [6]

но даже несмотря на то что система не обидится, отвал по таймауту нельзя рассматривать как исполнение заявки, посему повторная постановка заявки не нужна


 
Dennis I. Komarov ©   (2010-10-11 12:37) [7]


> посему повторная постановка заявки не нужна

Да с этим я не спорю... Я про систему интересовался ;)


 
Dennis I. Komarov ©   (2010-10-11 12:54) [8]

Код для критики (кроме повторного вызова) :)

unit dirchange;

interface

uses
 Classes, Windows, SysUtils;
const
 BufSize: Integer = 16 * 1024;
type
 PFileNotifyInformation = ^TFileNotifyInformation;
 TFileNotifyInformation = record
   NextEntryOffset: DWORD;
   Action: DWORD;
   FileNameLength:DWORD;
   FileName: array [0..255] of WideChar;
 end;

 TThreadDirChange = class(TThread)
 private
   Directory: string;
   FILE_NOTIFY_CHANGE: Cardinal;
 protected
   procedure Execute; override;
 end;

implementation

procedure TThreadDirChange.Execute;
var
 lpOverlapped: OVERLAPPED;
 dwWaitStatus: Cardinal;
 hDir: THandle;
 lpBuf, Ptr: Pointer;
 cbReturn: Cardinal;
 FileName: PWideChar;
 sTime: _SYSTEMTIME;
begin
 hDir:=CreateFile(PAnsiChar(IncludeTrailingBackSlash(Directory)), GENERIC_READ,
                  FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
                  nil, OPEN_EXISTING,
                  FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);
 try
   if hDir <> INVALID_HANDLE_VALUE then begin
     lpOverlapped.hEvent := CreateEvent(nil, true, false, nil);
     GetMem(lpBuf, BufSize);
     try
       while not Terminated do begin
         ZeroMemory(lpBuf, BufSize);
         if ReadDirectoryChangesW(hDir, lpBuf, BufSize, true, FILE_NOTIFY_CHANGE, @cbReturn, @lpOverlapped, nil) then begin
           dwWaitStatus := WaitForSingleObject(lpOverlapped.hEvent, 1000);
           if dwWaitStatus <> WAIT_TIMEOUT then begin
             Ptr:=lpBuf;
             while PFileNotifyInformation(Ptr).NextEntryOffset <> 0 do begin
               GetLocalTime(sTime);
               GetMem(FileName, PFileNotifyInformation(Ptr).FileNameLength + 2);
               try
                 lstrcpynW(FileName, PFileNotifyInformation(Ptr).FileName, PFileNotifyInformation(Ptr).FileNameLength div 2 + 1);
                 // Обрабатываем что произошло...
                 // Отправляем основному потоку (если надо)
               finally
                 FreeMem(FileName);
               end;
               Inc(Cardinal(Ptr), PFileNotifyInformation(Ptr).NextEntryOffset);
             end;
           end;
         end;
       end;
     finally
       FreeMem(lpBuf);
     end;
   end;
 finally
   CloseHandle(hDir);
 end;
end;

end.


 
Вариант   (2010-10-11 13:26) [9]


> Dennis I. Komarov ©   (11.10.10 12:54) [8]


Я бы установил перед первым использованием поля lpOverlapped в 0.
Возможно этого и не надо, так как используется нами только поле hEvent, устанавливаемое явно. Но я не знаю, что использует функция или система, какие еще поля анализируются, а потому пользуюсь рекомедацией MSDN


> This structure should always be initialized to zero before
> it is used in a function call. If it is not, the function
> may fail and return ERROR_INVALID_PARAMETER.
>



> WaitForSingleObject(lpOverlapped.hEvent, 1000)

 - тайм-аут я так понимаю задан для периодической проверки флага Terminated. О том что необходимо завершить работу потоку можно сигнализировать и другими способами - например другим Event или QueueUserAPC.Соответственно надо заменить WaitForSingleObject на соответствующий вызов другой функции. Но даже и при использовании тайм-аута вполне можно избежать повторного ненужного вызова ReadDirectoryChangesW. Ибо в том варианте, как производится вызов в твоем коде, возможна ситуация, когда будут потеряны  события мониторинга.


 
Dennis I. Komarov ©   (2010-10-11 14:01) [10]


if ReadDirectoryChangesW(hDir, lpBuf, ...
 while not Terminated do begin
   dwWaitStatus := WaitForSingleObject(lpOverlapped.hEvent, 1000);
   if dwWaitStatus <> WAIT_TIMEOUT then begin
     //Копируем lpBuf в lpBuf2 (временный буфер)
   ReadDirectoryChangesW(hDir, lpBuf...
     Ptr:=lpBuf2;
     while PFileNotifyInformation(Ptr).NextEntryOffset <> 0 do begin
...


Но, в процессе lpBuf -> lpBuf2 так же могут быть изменения, которые будут потеряны...

P.S.

> This structure should always be initialized to zero before
> it is used in a function call. If it is not, the function
> may fail and return ERROR_INVALID_PARAMETER.

Принято


 
Вариант   (2010-10-11 14:15) [11]


> Dennis I. Komarov ©   (11.10.10 14:01) [10]

Да, потому что ты так написал. Изменение буфера может произойти после вызова ReadDirectoryChangesW, но до окончания операции. После окончания операции изменения в буфере не произуйдут, пока ты их не изменишь

- а это смотрим твой вариант 1


>  while not Terminated do begin
>         ZeroMemory(lpBuf, BufSize);
(Чистим память, а какое-то мгновение назад туда была положена инфо)

Но если и не чистить память, то после нового вызова ReadDirectoryChangesW так же может произойти перезапись буфера. У тебя может получится примерно такая ситуация, как будто два потока одновременно пишут в один буфер. Даже если и одновременно один пишет, а другой читает - все равно нет согласованности. Для того синхронизацию и делают, что бы разделить доступ к одному ресурсу.
Или другая аналогия - представь критическую секцию - в одном потоке ты вызвал код, для доступа к буфера внутри критической секции, а в другом обращаешься к буферу без критической секции. Смысла в вызове критической секции нет. В данном случае Event играет не только роль сигнализатора - что есть событие, но и роль разделителя доступа к общему ресурсу.


 
Вариант   (2010-10-11 14:19) [12]


> Dennis I. Komarov ©   (11.10.10 14:01) [10]



>  if dwWaitStatus <> WAIT_TIMEOUT then begin
>      //Копируем lpBuf в lpBuf2 (временный буфер)


Кстати в этом варианте уже проблемы с потрей нет.


 
Dennis I. Komarov ©   (2010-10-11 14:28) [13]


> >  while not Terminated do begin
> >         ZeroMemory(lpBuf, BufSize);
> (Чистим память, а какое-то мгновение назад туда была положена
> инфо)
>

Секунду. Это в первом варианте. Там никак не получим такого. В [10] такого нету...


 
Вариант   (2010-10-11 14:32) [14]


> Dennis I. Komarov ©   (11.10.10 14:01) [10]
> Но, в процессе lpBuf -> lpBuf2 так же могут быть изменения,
>  которые будут потеряны...

По той части кода, что во втором варианте, lpBuf  может быть изменен сразу после вызвова ReadDirectoryChangesW, ну и пусть, ты уже нужное мкопировал в другой буфер и начал его обработку. И копировать в этот временный буфер будешь только при слудующей сработке hEvent, что по коду должно происходить внутри твоего

>
>  if dwWaitStatus <> WAIT_TIMEOUT then begin


Так что потерь из-за логики обработки в этом случае не вижу.


 
Вариант   (2010-10-11 14:39) [15]

И да по варианту в [10]


>   //Копируем lpBuf в lpBuf2 (временный буфер)
>    ReadDirectoryChangesW(hDir, lpBuf...


Тоже поставь проверку на результат выполнения  ReadDirectoryChangesW, он не обязательно равен true


 
Dennis I. Komarov ©   (2010-10-11 14:43) [16]


> Тоже поставь проверку на результат выполнения  ReadDirectoryChangesW,
>  он не обязательно равен true

ес-но, это наброски в on-line :)


 
Dennis I. Komarov ©   (2010-10-11 15:10) [17]


> if ReadDirectoryChangesW(hDir, lpBuf, ...
>  while not Terminated do begin
>    dwWaitStatus := WaitForSingleObject(lpOverlapped.hEvent,
>  1000);
>    if dwWaitStatus <> WAIT_TIMEOUT then begin
>      //Копируем lpBuf в lpBuf2 (временный буфер)
>    ReadDirectoryChangesW(hDir, lpBuf...
>      Ptr:=lpBuf2;
>      while PFileNotifyInformation(Ptr).NextEntryOffset <>
> 0 do begin
> ...

Во время выполнения копирования могут произойти изменения в директории. Получается они будут потеряны :(


 
Игорь Шевченко ©   (2010-10-11 15:16) [18]

Я где-то так делаю

   while not Terminated do
   begin
   if not ReadDirectoryChangesW (FDirectoryHandle, FBuffer,
       FOLDER_MONITOR_BUFFER_SIZE, false, FILE_NOTIFY_CHANGE_FILE_NAME,
       @BytesRead, @FOverlapped, nil) then
   begin
     DoOnError(SysErrorMessage(GetLastError));
     Terminate;
     Break; //Невозможно поставить запрос наблюдения каталога в очередь
   end;
   WaitResult := WaitForMultipleObjects(2, @FEventsToWait, false, INFINITE);
   if WaitResult = WAIT_OBJECT_0 then
   begin
     Terminate;
     Break; //Получен внешний запрос на окончание наблюдения
   end
   else if WaitResult <> WAIT_OBJECT_0+1 then
   begin
     DoOnError(SysErrorMessage(GetLastError));
     Terminate;
     Break; //Неизвестная ошибка
   end
   else
   begin
     //Закончилась операция чтения изменений в каталоге
     if not GetOverlappedResult (FDirectoryHandle, FOverlapped, BytesRead,
         false) then
     begin
       DoOnError(SysErrorMessage(GetLastError));
       Terminate;
       Break; //Неизвестная ошибка при попытке получения результата окончания
              //асинхронной операции ввода-вывода
     end;
     // Сейчас в буфере находится BytesRead байт информации об изменениях в
     // каталоге
     Files := TStringList.Create;
     try
       ParseNotificationBuffer (FBuffer, Files);
       for I:=0 to Pred(Files.Count) do
         DoOnNewFile(Files[I]);
     finally
       Files.Free;
     end;
   end;
 end;


 
Вариант   (2010-10-11 15:37) [19]


> Dennis I. Komarov ©   (11.10.10 15:10) [17]


> Во время выполнения копирования могут произойти изменения
> в директории. Получается они будут потеряны :(

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

Из MSDN


>
>
> When you first call ReadDirectoryChangesW, the system allocates
> a buffer to store change information. This buffer is associated
> with the directory handle until it is closed and its size
> does not change during its lifetime. Directory changes that
> occur between calls to this function are added to the buffer
> and then returned with the next call. If the buffer overflows,
>  the changes are discarded and the function fails with ERROR_NOTIFY_ENUM_DIR.
>
>


 
Dennis I. Komarov ©   (2010-10-11 15:38) [20]


> Игорь Шевченко ©   (11.10.10 15:16) [18]

Ну, получим подобное, т.е. если получаем событие "есть изменения" - начинаем обрабатывать полученный буфер и только после выполняется ReadDir..W, т.е. теоретически (как заметил Вариант) во время обработки буфера прошедшие изменения будут проигнорированы...


 
Игорь Шевченко ©   (2010-10-11 15:42) [21]

Dennis I. Komarov ©   (11.10.10 15:38) [20]


> т.е. теоретически (как заметил Вариант) во время обработки
> буфера прошедшие изменения будут проигнорированы...


Не будут


 
Dennis I. Komarov ©   (2010-10-11 15:45) [22]


> Вариант   (11.10.10 15:37) [19]

Тогда код в [8] также не приведет к потери данных (если ZeroMemory вынести из цикла... И пляски со вторым буфером не нужны...


 
Вариант   (2010-10-11 15:45) [23]


> Dennis I. Komarov ©   (11.10.10 15:38) [20]


> т.е. теоретически (как заметил Вариант) во время обработки
> буфера прошедшие изменения будут проигнорированы.

Я вот такого не говорил  прилагательно к
> Игорь Шевченко ©   (11.10.10 15:16) [18]

Там это будет только если будет большая задержка в DoOnNewFile, чего я думаю там нет. И в этом коде нет повторного вызова ReadDirectoryChangesW, когда нет завершения еще певрого вызова...., что было в твоем первом варианте


 
Вариант   (2010-10-11 15:50) [24]


> Dennis I. Komarov ©   (11.10.10 15:45) [22]

Я так понял, что я или плохо объяснил или что-то ты не так понял. Еще раз возвращаясь к твоем у самому первому вопросу


> Dennis I. Komarov ©   (11.10.10 11:59)
> При повторном вызове функции в асинхронном режиме что происходит
> в системе, если изменений в директории не было? Функция
> возвращает правду.


Хотя система и скажет тебе, что все ок (а может и не скажет), но вызывать повторно ReadDirectoryChangesW не дождавшись завершения предыдущего вызова по hEvent в твоем случае нельзя - это может привести к потере изменений. Если же ты получил сработку  hEvent, то  доступ к твоему буферу разрешен. Изменения продолжают писаться в другой "системный" буфер, из которого они передадутся в твой буфер только при следующем вызове ReadDirectoryChangesW...


 
Вариант   (2010-10-11 15:57) [25]


> Dennis I. Komarov ©   (11.10.10 15:45) [22]


> Тогда код в [8] также не приведет к потери данных (если
> ZeroMemory вынести из цикла...
>

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


> И пляски со вторым буфером не нужны..

А вот второй буфер и не обязателен, если действия выполняются быстро, но работа по обработке с буфером должна завершиться до повторного вызова ReadDirectoryChangesW, иначе потери так же возможны.

PS: Если что-то непонятно или есть желание спорить, то завтра я уже в дороге домой:-)


 
Игорь Шевченко ©   (2010-10-11 16:09) [26]


> Там это будет только если будет большая задержка в DoOnNewFile,
>  чего я думаю там нет


Хоть какая задержка

"When you first call ReadDirectoryChangesW, the system allocates a buffer to store change information. This buffer is associated with the directory handle until it is closed
....
Directory changes that occur between calls to this function are added to the buffer and then returned with the next call"


 
Игорь Шевченко ©   (2010-10-11 16:10) [27]

мой пост [26] к Вариант   (11.10.10 15:45) [23]


 
Dennis I. Komarov ©   (2010-10-11 16:10) [28]


> Вариант   (11.10.10 15:50) [24]

да, немного недопоняли :)

Развлекайтесь дальше: :)

unit dirchange;

interface

uses
 Classes, Windows, SysUtils;
const
 BufSize: Integer = 16 * 1024;
type
 PFileNotifyInformation = ^TFileNotifyInformation;
 TFileNotifyInformation = record
   NextEntryOffset: DWORD;
   Action: DWORD;
   FileNameLength:DWORD;
   FileName: array [0..255] of WideChar;
 end;

 TThreadDirChange = class(TThread)
 private
   Directory: string;
   FILE_NOTIFY_CHANGE: Cardinal;
 protected
   procedure Execute; override;
 end;

implementation

procedure TThreadDirChange.Execute;
var
 lpOverlapped: OVERLAPPED;
 dwWaitStatus: Cardinal;
 hDir: THandle;
 lpBuf, Ptr: Pointer;
 cbReturn: Cardinal;
 FileName: PWideChar;
 sTime: _SYSTEMTIME;
begin
 hDir:=CreateFile(PAnsiChar(IncludeTrailingBackSlash(Directory)), GENERIC_READ,
                  FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
                  nil, OPEN_EXISTING,
                  FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);
 try
   if hDir <> INVALID_HANDLE_VALUE then begin
     ZeroMemory(@lpOverlapped, SizeOf(TOverlapped));
     lpOverlapped.hEvent := CreateEvent(nil, true, false, nil);
     GetMem(lpBuf, BufSize);
     try
       ZeroMemory(lpBuf, BufSize);
       if ReadDirectoryChangesW(hDir, lpBuf, BufSize, true, FILE_NOTIFY_CHANGE, @cbReturn, @lpOverlapped, nil) then begin
         while not Terminated do begin
           dwWaitStatus := WaitForSingleObject(lpOverlapped.hEvent, 1000);
           if dwWaitStatus <> WAIT_TIMEOUT then begin
             Ptr:=lpBuf;
             while PFileNotifyInformation(Ptr).NextEntryOffset <> 0 do begin
               GetLocalTime(sTime);
               GetMem(FileName, PFileNotifyInformation(Ptr).FileNameLength + 2);
               try
                 lstrcpynW(FileName, PFileNotifyInformation(Ptr).FileName, PFileNotifyInformation(Ptr).FileNameLength div 2 + 1);
                 // Обрабатываем что произошло...
                 // Отправляем основному потоку (если надо)
               finally
                 FreeMem(FileName);
               end;
               Inc(Cardinal(Ptr), PFileNotifyInformation(Ptr).NextEntryOffset);
             end;
             if not ReadDirectoryChangesW(hDir, lpBuf, BufSize, true, FILE_NOTIFY_CHANGE, @cbReturn, @lpOverlapped, nil) then
               Exit;
           end;
         end;
       end;
     finally
       FreeMem(lpBuf);
     end;
   end;
 finally
   CloseHandle(hDir);
 end;
end;

end.


 
Игорь Шевченко ©   (2010-10-11 16:12) [29]

Dennis I. Komarov ©   (11.10.10 16:10) [28]

Ты видишь, в чем разница между твоим и моим кодом ?


 
Dennis I. Komarov ©   (2010-10-11 16:23) [30]


> Игорь Шевченко ©   (11.10.10 16:12) [29]

dwWaitStatus := WaitForSingleObject(lpOverlapped.hEvent, 1000);
vs
WaitResult := WaitForMultipleObjects(2, @FEventsToWait, false, INFINITE);
+ используется GetOverlappedResult


 
Игорь Шевченко ©   (2010-10-11 17:15) [31]


> используется GetOverlappedResult


а почему ты не используешь ?


 
Dennis I. Komarov ©   (2010-10-11 17:45) [32]


> Игорь Шевченко ©   (11.10.10 17:15) [31]
>


Вычитывал про нее :)


          if dwWaitStatus <> WAIT_TIMEOUT then begin
            if GetOverlappedResult(hDir, lpOverlapped, cbReturn, false) then begin
            Ptr:=lpBuf;
            while PFileNotifyInformation(Ptr).NextEntryOffset <> 0 do begin
              GetLocalTime(sTime);
              GetMem(FileName, PFileNotifyInformation(Ptr).FileNameLength + 2);
              try
                lstrcpynW(FileName, PFileNotifyInformation(Ptr).FileName, PFileNotifyInformation(Ptr).FileNameLength div 2 + 1);
                // Обрабатываем что произошло...
                // Отправляем основному потоку (если надо)
              finally
                FreeMem(FileName);
              end;
              Inc(Cardinal(Ptr), PFileNotifyInformation(Ptr).NextEntryOffset);
            end;
            if not ReadDirectoryChangesW(hDir, lpBuf, BufSize, true, FILE_NOTIFY_CHANGE, @cbReturn, @lpOverlapped, nil) then
              Exit;
          end;
          end;


Можно краткий комментарий по этой функции?


 
Игорь Шевченко ©   (2010-10-11 18:35) [33]


> Можно краткий комментарий по этой функции?


можно.
http://msdn.microsoft.com/en-us/library/ms683209(VS.85).aspx


 
Вариант   (2010-10-12 06:33) [34]


> Игорь Шевченко ©   (11.10.10 16:09) [26]


Спорим дальше;-)


> Хоть какая задержка
> "When you first call ReadDirectoryChangesW, the system allocates
> a buffer to store change information. This buffer is associated
> with the directory handle until it is closed


Добавим продолжение оттуда же


>  If the buffer overflows,
>  the changes are discarded and the function fails with ERROR_NOTIFY_ENUM_DIR.
>


Так как буфер не резиновый и если в директории будут происходить изменения и за  интервал времени X   не будет вызвано следующего ReadDirectoryChangesW, то есть вероятность переполнения буфера и потери событий мониторинга. Функция вернет ошибку.


 
Игорь Шевченко ©   (2010-10-12 10:52) [35]

Вариант   (12.10.10 06:33) [34]


> function fails with ERROR_NOTIFY_ENUM_DIR


Вот по этому признаку и можно разветвить работу - то ли пересканировать каталог вручную, то ли вызвать функцию еще раз. У меня в коде не предусмотрено, а надо бы.
Спасибо за науку :)


 
Dennis I. Komarov ©   (2010-10-12 14:07) [36]


> Игорь Шевченко ©   (11.10.10 18:35) [33]

Это не краткий :)
Меня интересовало использование ее в конкретном Вашем коде, т.к. кроме
if not GetOverlappedResult (FDirectoryHandle, FOverlapped, BytesRead,
        false) then
    begin
      DoOnError(SysErrorMessage(GetLastError));
      Terminate;
      Break; //Неизвестная ошибка при попытке получения результата окончания
             //асинхронной операции ввода-вывода
    end;

нигде не используется, а
ParseNotificationBuffer (FBuffer, Files);
не раскрыта :)


 
Игорь Шевченко ©   (2010-10-12 14:45) [37]

Dennis I. Komarov ©   (12.10.10 14:07) [36]


> ParseNotificationBuffer (FBuffer, Files);
> не раскрыта :)


учитесь именовать функции :)


> нигде не используется


А зачем ей где-то еще использоваться ? Здесь это лишнее подтвержение корректного окончания операции ввода-вывода. При большом желании можно получить размер прочитанных данных, но так как данные в буфере у ReadDirectiryChangesW имеет самоописательную структуру, то размер в общем-то не имеет значения


 
Dennis I. Komarov ©   (2010-10-12 14:57) [38]


> При большом желании можно получить размер прочитанных данных

Ну дык мало ли. Собственно код [32], а все остальное уже после...

Еще можно добавить GetLastError на всех отлупах, но пока не знаю как будет реализована синхронизация с основным потоком, т.ч. это после...



Страницы: 1 вся ветка

Форум: "Начинающим";
Текущий архив: 2011.01.09;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.58 MB
Время: 0.006 c
2-1287351196
Германн
2010-10-18 01:33
2011.01.09
Caption на кнопках ToolBar


2-1287052049
b86
2010-10-14 14:27
2011.01.09
Завести процедуру даблклик


6-1233220277
apic
2009-01-29 12:11
2011.01.09
Сетевое имя компа


2-1286986134
infectioni
2010-10-13 20:08
2011.01.09
параметры куба


6-1233302123
vegarulez
2009-01-30 10:55
2011.01.09
Вопрос про TidHttp. Просмотр отправляемого содержимого.





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский