Статьи по Delphi (014)…

Узнать, поддерживает ли принтер PostScript.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  That is really difficult do to if it has to work on all Windows 
  platforms. The best way (no kidding) may be to ask the user which 
  printer to use. What platforms do you need to support? If it is only 
  Win2K (and perhaps XP) one may be able to use this (i have no 
  postscript-enabled printer around to see if it works!): 
}
 uses
   WinSpool, Printers;
 {: Check if the currently selected printer supports postscript. 
  Only applicable on Win2K/XP! }
 function PrinterSupportsPostscript: Boolean;
 const
   POSTSCRIPT_PASSTHROUGH = 4115;
   POSTSCRIPT_IDENTIFY = 4117;
   Escapes: array[0..2] of Cardinal =
   (POSTSCRIPT_DATA, POSTSCRIPT_IDENTIFY, POSTSCRIPT_PASSTHROUGH);
 var
   res: Integer;
   i: Integer;
 begin
   Result := false;
   for i := Low(Escapes) to High(Escapes) do begin
     res := ExtEscape(printer.Handle,
       QUERYESCSUPPORT,
       sizeof(Escapes[0]),
       @Escapes[i], 0, nil);
     if res <> 0 then begin
       Result := true;
       Break;
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 const
   boolstr: array[Boolean] of string = (' not', '');
 var
   i: Integer;
   S: string;
 begin
   for i := 0 to Printer.Printers.Count - 1 do begin
     Printer.PrinterIndex := i;
     memo1.Lines.add(
       Format('Printer %s does%s support Postscript',
       [printer.printers[printer.printerindex],
       boolstr[PrinterSupportsPostscript]]));
   end;
 end;

Сохранить установки принтера в файл.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  Create a unit PrinterSetup and place code in this unit 
  Do the following to save the printer config to file 
}
 procedure TForm1.Button1Click(Sender: TObject);
 var
   PrinterSetup: TPrinterSetup
 begin
   PrinterSetup := TPrinterSetup.Create;
   PrinterSetup.SaveSetup(FileName);
   //where file name is a string to the location of the File ex.'c:\print.cfg' 
  PrinterSetup.Free;
 end
 {***********************************}
 unit PrinterSetup;
 interface
 uses
   printers,
   windows,
   SysUtils,
   Classes,
   WinSpool;
 type
   TPrinterSetup = class
   private
     Device, Driver, Port: array[0..CCHDEVICENAME] of char;
     DeviceMode: THandle;
     procedure Refresh;
   protected
   public
     procedure SaveSetup(FileName: TFilename);
     procedure LoadSetup(FileName: TFilename);
   end;
   TPrinterConfig = record
     ADevice, ADriver, APort: array[0..CCHDEVICENAME] of char;
     SizeOfDeviceMode: DWORD;
   end;
 implementation
 procedure TPrinterSetup.Refresh;
 begin
   Printer.GetPrinter(Device, Driver, Port, DeviceMode);
 end;
 procedure TPrinterSetup.SaveSetup(FileName: TFilename);
 var
   StubDevMode: TDeviceMode;
   SetupPrinter: TPrinterConfig;
   FPrinterHandle: THandle;
   fFileConfig: file of TPrinterConfig;
   fFileDevMode: file of Char;
   pDevMode: PChar;
   Contador: Integer;
 begin
   Refresh;
   with SetupPrinter do
   begin
     StrLCopy(ADevice, Device, SizeOf(ADevice));
     StrLCopy(ADriver, Driver, SizeOf(ADriver));
     StrLCopy(APort, Port, SizeOf(APort));
     OpenPrinter(Device, FPrinterHandle, nil);
     SizeOfDeviceMode := DocumentProperties(0, FPrinterHandle, Device,
       StubDevMode, StubDevMode, 0);
   end;
   AssignFile(fFileConfig, FileName);
   ReWrite(fFileConfig);
   Write(fFileConfig, SetupPrinter);
   CloseFile(fFileConfig);
   AssignFile(fFileDevMode, FileName);
   Reset(fFileDevMode);
   Seek(fFileDevMode, FileSize(fFileDevMode));
   pDevMode := GlobalLock(DeviceMode);
   for Contador := 0 to SetupPrinter.SizeOfDeviceMode - 1 do
   begin
     Write(fFileDevMode, pDevMode[Contador]);
   end;
   CloseFile(fFileDevMode);
   GlobalUnLock(DeviceMode);
 end;
 procedure TPrinterSetup.LoadSetup(FileName: TFilename);
 var
   SetupPrinter: TPrinterConfig;
   fFileConfig: file of TPrinterConfig;
   fFileDevMode: file of Char;
   ADeviceMode: THandle;
   pDevMode: PChar;
   Contador: Integer;
 begin
   if FileExists(FileName) then
   begin
     AssignFile(fFileConfig, FileName);
     Reset(fFileConfig);
     read(fFileConfig, SetupPrinter);
     CloseFile(fFileConfig);
     AssignFile(fFileDevMode, FileName);
     Reset(fFileDevMode);
     Seek(fFileDevMode, SizeOf(SetupPrinter));
     ADeviceMode := GlobalAlloc(GHND, SetupPrinter.SizeOfDeviceMode);
     pDevMode := GlobalLock(ADeviceMode);
     for Contador := 0 to SetupPrinter.SizeOfDeviceMode - 1 do
     begin
       read(fFileDevMode, char(pDevMode[Contador]));
     end;
     CloseFile(fFileDevMode);
     GlobalUnLock(ADeviceMode);
     Printer.SetPrinter(SetupPrinter.ADevice, SetupPrinter.ADriver,
       SetupPrinter.APort, ADeviceMode);
   end;
 end;
 end.

Как печатать растровые и векторные изображения и делать режим предварительного просмотра.

Маленькое предисловие

Т.к. основная моя работа связана с написанием софта для института, обрабатывающего геоданные, то и в отделе, где работаю, так же мучаются проблемами печати (в одном случае - надо печатать карты, с изолиниями, заливкой, подписями и пр.; в другом случае - свои таблицы и сложные отрисовки по внешнему виду). В итоге, моим коллегой был написан кусок, в котором ему удалось добиться качественной печати в двух режимах : MetaFile, Bitmap. Работа с MetaFile у нас сложилась уже исторически - достаточно удобно описать ф-цию, которая что-то отрисовыват (хоть на экране, хоть где), которая принимает TCanvas, и подсовывать ей то канвас дисплея, то канвас метафайла, а потом этот Metafile выбрасывать на печать. Достаточно решить лишь проблемы масштабирования, после чего - вперед. Главная головная боль при таком методе - при отрисовке больших кусков, которые занимают весь лист или его большую часть, надо этот метафайл по размерам делать сразу же в пикселах на этот самый лист. Тогда при изменении размеров (просмотр перед печатью) - искажения при уменьшении не критичны, а вот при увеличении линии и шрифты не "поползут".

Итак:

Набор идей, которые были написаны (с) Андреем Аристовым, программистом отдела матобеспечения СибНИИНП, г. Тюмень. Моего здесь только - приделывание сверху надстроек для личного использования.

Вся работа сводится к следующим шагам :

Получить необходимые коэф-ты. Построить метафайл или bmp для последующего вывода на печать. Напечатать.

Ниже приведенный кусок (прошу меня не пинать, но писал я и писал для достаточно кривой реализации с передачей параметров через глобальные переменные) я использую для того, чтобы получить коэф-ты пересчета. kScale - для пересчета размеров шрифта, а потом уже закладываюсь на его размеры и получаю два новых коэф-та для kW, kH - которые и позволяют мне с учетом высоты шрифта выводить графику и пр. У меня при работе kW <> kH, что приходится учитывать.

Решили пункт 1.

// установить коэф-ты
procedure SetKoeffMeta;
var
  PrevMetafile : TMetafile;
  MetaCanvas : TMetafileCanvas;
begin
  PrevMetafile := nil;
  MetaCanvas := nil;
  try
    PrevMetaFile := TMetaFile.Create;
    try
      MetaCanvas := TMetafileCanvas.Create( PrevMetafile, 0 );
      kScale := GetDeviceCaps( Printer.Handle, LOGPIXELSX ) /
      Screen.PixelsPerInch;
      MetaCanvas.Font.Assign( oGrid.Font);
      MetaCanvas.Font.Size := Round( oGrid.Font.Size * kScale );
      kW := MetaCanvas.TextWidth('W') / oGrid.Canvas.TextWidth('W');
      kH := MetaCanvas.TextHeight('W') / oGrid.Canvas.TextHeight('W');
    finally
      MetaCanvas.Free;
    end;
  finally
    PrevMetafile.Free;
  end;
end;

Решаем 2.

...
var
  PrevMetafile : TMetafile;
  MetaCanvas : TMetafileCanvas;
begin
  PrevMetafile := nil;
  MetaCanvas := nil;
try
  PrevMetaFile := TMetaFile.Create;
  PrevMetafile.Width := oWidth;
  PrevMetafile.Height := oHeight;
  try
    MetaCanvas := TMetafileCanvas.Create( PrevMetafile, 0 );
// здесь должен быть ваш код - с учетом масштабирования.
// я эту вещь вынес в ассигнуемую процедуру, и данный блок
// вызываю лишь для отрисовки целой страницы.

См. PS1.

finally
  MetaCanvas.Free;
end;
...

PS1. Код, который используется для отрисовки. oCanvas - TCanvas метафайла.

...
var
  iHPage: integer; // высота страницы
begin
  with oCanvas do
  begin
    iHPage := 3000;
    // залили область метайфайла белым - для дальнейшей работы
    Pen.Color := clBlack;
    Brush.Color := clWhite;
    FillRect( Rect( 0, 0, 2000, iHPage ) );
    // установили шрифты - с учетом их дальнейшего масштабирования
    oCanvas.Font.Assign( oGrid.Font);
    oCanvas.Font.Size := Round( oGrid.Font.Size * kScale );
    ...
    xEnd := xBegin;
    iH := round( RowHeights[ iRow ] * kH );
    for iCol := 0 to ColCount - 1 do
    begin
      x := xEnd;
      xEnd := x + round( ColWidths[ iCol ] * kW );
      Rectangle( x, yBegin, xEnd, yBegin + iH );
      r := Rect( x + 1, yBegin + 1, xEnd - 1, yBegin + iH - 1 );
      s := Cells[ iCol, iRow ];
      // выписали в полученный квадрат текст
      DrawText( oCanvas.Handle, PChar( s ), Length( s ), r,
      DT_WORDBREAK or DT_CENTER );

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

Последний пункт - надо сформированный метафайл или bmp напечатать.

...
var
  Info: PBitmapInfo;
  InfoSize: Integer;
  Image: Pointer;
  ImageSize: DWORD;
  Bits: HBITMAP;
  DIBWidth, DIBHeight: Longint;
  PrintWidth, PrintHeight: Longint;
begin
  ...
  case ImageType of
    itMetafile:
    begin
      if Picture.Metafile<>nil then
        Printer.Canvas.StretchDraw( Rect(aLeft, aTop, aLeft+fWidth,
        aTop+fHeight), Picture.Metafile);
    end;
    itBitmap:
    begin
      if Picture.Bitmap<>nil then
      begin
        with Printer, Canvas do
        begin
          Bits := Picture.Bitmap.Handle;
          GetDIBSizes(Bits, InfoSize, ImageSize);
          Info := AllocMem(InfoSize);
          try
            Image := AllocMem(ImageSize);
            try
              GetDIB(Bits, 0, Info^, Image^);
              with Info^.bmiHeader do
              begin
                DIBWidth := biWidth;
                DIBHeight := biHeight;
              end;
              PrintWidth := DIBWidth;
              PrintHeight := DIBHeight;
              StretchDIBits(Canvas.Handle, aLeft, aTop, PrintWidth,
              PrintHeight, 0, 0, DIBWidth, DIBHeight, Image, Info^,
              DIB_RGB_COLORS, SRCCOPY);
            finally
              FreeMem(Image, ImageSize);
            end;
          finally
            FreeMem(Info, InfoSize);
          end;
        end;
      end;
    end;
  end;
end;

В чем заключается идея PreView ? Остается имея на руках Metafila, Bmp - отрисовать с пересчетом внешний вид изображения (надо высчитать левый верхний угол и размеры "предварительно просматриваемого" изображения. Для показа изображения достаточно использовать StretchDraw. После того, как удалось вывести объекты на печать, проблему создания PreView решили как "домашнее задание". Кстати, когда мы работаем с Bmp, то для просмотра используем следующий хинт - записываем битовый образ через такую процедуру :

w:=MulDiv(Bmp.Width,GetDeviceCaps(Printer.Handle,LOGPIXELSX),
Screen.PixelsPerInch);
h:=MulDiv(Bmp.Height,GetDeviceCaps(Printer.Handle,LOGPIXELSY),
Screen.PixelsPerInch);
PrevBmp.Width:=w;
PrevBmp.Height:=h;
PrevBmp.Canvas.StretchDraw(Rect(0,0,w,h),Bmp);
aPicture.Assign(PrevBmp);

При этом масштабируется битовый образ с минимальными искажениями, а вот при печати - приходится bmp печатать именно так, как описано выше. Итог - наша bmp при печати чуть меньше, чем печатать ее через WinWord, но при этом - внешне - без каких-либо искажений и пр.

Как узнать края печати в Win16 и Win32.

В Win16, для этого используется API функция Escape() с константой GETPRINTINGOFFSET. В Win32, используется API функция GetDeviceCaps() с константами PHYSICALOFFSETX и PHYSICALOFFSETY. Однако, нет гарантии, что данная последовательность кодов поддерживается принтером, поэтому для проверки желательно всегда вызывать функцию Escape с константой QUERYESCSUPPORT. Следующий пример возвращает отступы печати как в WIN16, так и в WIN32, учитывая тот факт, что не все принтеры поддерживают код GETPRINTINGOFFSET (в WIN16). Если дело обстоит так, то смещение печати вычисляется приближённо, получая размер страницы, вычитая физическую разрешающую способность устройства, и деля пополам.

Пример:

uses Printers;
procedure TForm1.Button1Click(Sender: TObject);
var
  EscapeCode: integer;
  Margin: TPoint;
begin
  if PrintDialog1.Execute then begin
{$IFDEF WIN32}
    Margin.x := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX);
    Margin.y := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY);
{$ELSE}
    EscapeCode := GETPRINTINGOFFSET;
    if Escape(Printer.Handle,
      QUERYESCSUPPORT,
      sizeof(EscapeCode),
      @EscapeCode,
      nil) <> 0 then
      if Escape(Printer.Handle,
        GETPRINTINGOFFSET,
        0,
        nil,
        @Margin) < 1 then begin
        EscapeCode := GETPHYSPAGESIZE;
        if Escape(Printer.Handle,
          QUERYESCSUPPORT,
          sizeof(EscapeCode),
          @EscapeCode,
          nil) <> 0 then
          if Escape(Printer.Handle,
            GETPHYSPAGESIZE,
            0,
            nil,
            @Margin) > 0 then begin
            Margin.x := (Margin.x -
              GetDeviceCaps(Printer.Handle, HorzRes)) div 2;
            Margin.y := (Margin.y -
              GetDeviceCaps(Printer.Handle, VertRes)) div 2;
          end else begin
            Margin.x := 0;
            Margin.y := 0;
          end;
      end;
{$ENDIF}
    Memo1.Lines.Add(IntToStr(Margin.x));
    Memo1.Lines.Add(IntToStr(Margin.y));
  end;
end;

Построчная печать.

В Delphi 1 (а затем и в Delphi 2) я попытался написать программу, печатающую построчно на принтере (любого типа), после выхода из которой принтер НЕ выплевывает лист, а продолжает на нем печатать в случае повторного использования программы, то есть программа позволяет печатать строчку за строчкой, не удаляя при этом лист в случае завершения задачи. Это противоречит неписанным законам Microsoft, поскольку нарушает режим многозадачности, и все же именно для моей задачи мне необходимо именно такое решение. Я создал клон объекта TPrinter, назначение которого вы поймете ниже. Это работает со всеми матричными принтерами.

Вот пример объекта:

var
  Myprinter: TRawPrinter;
  oldprinter: TPrinter;
begin
  MyPrinter := TRawPrinter.Create;
  oldprinter := setprinter(MyPrinter);
  try
    if Printdialog1.execute then
    begin
      myprinter.startraw;
      myprinter.write('Delphi World is the best!');
      myprinter.writeln;
      myprinter.endraw;
    end
  finally
    setprinter(oldprinyter);
    myprinter.free;
  end
end;

Вот пример использования объекта:

unit Rawprinter;
interface
uses printers, windows;
type
  TRawprinter = class(TPrinter)
  public
    dc2: HDC;
    procedure startraw;
    procedure endraw;
    procedure write(const s: string);
    procedure writeln;
  end;
implementation
uses sysutils, forms;
function AbortProc(Prn: HDC; Error: Integer): Bool; stdcall;
begin
  Application.ProcessMessages;
  Result := not Printer.Aborted;
end;
type
  TPrinterDevice = class
    Driver, Device, Port: string;
    constructor Create(ADriver, ADevice, APort: PChar);
    function IsEqual(ADriver, ADevice, APort: PChar): Boolean;
  end;
constructor TPrinterDevice.Create(ADriver, ADevice, APort: PChar);
begin
  inherited Create;
  Driver := ADriver;
  Device := ADevice;
  Port := APort;
end;
function TPrinterDevice.IsEqual(ADriver, ADevice, APort: PChar): Boolean;
begin
  Result := (Device = ADevice) and (Port = APort);
end;
procedure TRawprinter.startraw;
var
  CTitle: array[0..31] of Char;
  CMode: array[0..4] of char;
  DocInfo: TDocInfo;
  r: integer;
begin
  StrPLCopy(CTitle, Title, SizeOf(CTitle) - 1);
  StrPCopy(CMode, 'RAW');
  FillChar(DocInfo, SizeOf(DocInfo), 0);
  with DocInfo do
  begin
    cbSize := SizeOf(DocInfo);
    lpszDocName := CTitle;
    lpszOutput := nil;
    lpszDatatype := CMode;
  end;
  with TPrinterDevice(Printers.Objects[PrinterIndex]) do
  begin
    DC2 := CreateDC(PChar(Driver), PChar(Device), PChar(Port), nil);
  end;
  SetAbortProc(dc2, AbortProc);
  r := StartDoc(dc2, DocInfo);
end;
procedure TRawprinter.endraw;
var
  r: integer;
begin
  r := windows.enddoc(dc2);
end;
type
  passrec = packed record
    l: word;
    s: array[0..255] of char;
  end;
var
  pass: Passrec;
procedure TRawprinter.write(const s: string);
begin
  pass.l := length(s);
  strpcopy(pass.s, s);
  escape(dc2, PASSTHROUGH, 0, @pass, nil);
end;
procedure TRawprinter.writeln;
begin
  pass.l := 2;
  strpcopy(pass.s, #13#10);
  escape(dc2, PASSTHROUGH, 0, @pass, nil);
end;
end.

Распечатать Canvas.

uses 
  Printers; 
procedure PrintText(Text: string); 
begin 
  with Printer do 
  begin 
    BeginDoc; 
    Canvas.TextOut(5, 50, Text); 
    EndDoc; 
  end; 
end;

Печать ячеек.

Автор: Lloyd Linklater (Sysop) (Delphi Technical Support) У кого-нибудь есть пример кода печати в заданной ячейке? Типа PrintAt(row,col,"Text")?

Вот некоторый код, который я нашел после блужданий в группах новостей. Правда сам я его не проверял, но источник утверждает, что он работает. Так что будьте внимательны!

TForm1.PrintTableClick(Sender: TObject);
var
  xcord: integer;
  ycord: integer;
  recordbuffer: string;
begin
  xcord := 10;
  ycord := 10;
  Table1.First;
  Printer.BeginDoc;
  Printer.Canvas.Font.Name := 'Courier New';
  while not Table1.EOF do
  begin
    recordbuffer := concat((Table1.Fields[0].AsString), ' ',
      (Table1.Fields[1].AsString));
    recordbuffer := recordbuffer + concat(' ', (Table1.Fields[2].AsString);
      {пока все поля не будут в recordbuffer}
      Printer.Canvas.TextOut(xcord, ycord, recordbuffer);
      ycord := ycord + 50;
      Table1.next;
  end;
  Printer.Enddoc;
end;

Пример вывода текста на печать, используя COM порт.

Автор: Peter.

Var
  Printer: THandle;
  N      : Cardinal;
  C      : POverlapped;
begin
//Открываем порт принтера для записи
Printer := CreateFile(PChar('LPT1'),
           GENERIC_READ or GENERIC_WRITE,0,nil,
           OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
//Печатаем слово 'Hello World';
WriteFile(Printer,'Hello World',11,N,c);
//Закрываем порт
CloseHandle(Printer);

Печать содержимого DBGrid через QuickReport.

Частенько у пользователя возникает необходимость распечатать отчёт из базы данных. Естественно, что он начинает просить Вас добавить такую возможность в приложение. Как оказалось, при помощи TQuickRep данную задачу можно очень легко решить.

Итак, приступим. Для начала создайте новую форму, назвав её TGridReport, и поместите на неё TQuickRep. Переименуйте QuickRep в GridRep. Затем сделайте следующию процедуру, которая получает в качестве параметра DBGrid:

procedure TGridReport.Preview(Grid: TDBGrid);
var
  i, CurrentLeft, CurrentTop : integer;
  BMark: TBookmark;
begin
  GridRep.Dataset:=Grid.DataSource.DataSet;
  if not GridRep.Bands.HasColumnHeader then
    GridRep.Bands.HasColumnHeader:=true;
  if not GridRep.Bands.HasDetail then
    GridRep.Bands.HasDetail:=true;
  GridRep.Bands.ColumnHeaderBand.Height:=Abs(Grid.TitleFont.Height) + 10;
  GridRep.Bands.DetailBand.Height:=Abs(Grid.Font.Height) + 10;
  CurrentLeft := 12;
  CurrentTop := 6;
  {Запись, на которой пользователь останавливается в DBGrid}
  BMark:=Grid.DataSource.DataSet.GetBookmark;
  {Запретим мерцание грида в процессе работы отчёта}
  Grid.DataSource.DataSet.DisableControls;
  try
    for i:=0 to Grid.FieldCount - 1 do
    begin
      if (CurrentLeft + Canvas.TextWidth(Grid.Columns[i].Title.Caption)) >
      (GridRep.Bands.ColumnHeaderBand.Width) then
      begin
        CurrentLeft := 12;
        CurrentTop := CurrentTop + Canvas.TextHeight('A') + 6;
        GridRep.Bands.ColumnHeaderBand.Height :=
        GridRep.Bands.ColumnHeaderBand.Height + (Canvas.TextHeight('A') + 10);
        GridRep.Bands.DetailBand.Height :=
        GridRep.Bands.DetailBand.Height + (Canvas.TextHeight('A') + 10);
      end;
      {Создадим заголовок отчёта при помощи QRLabels}
      with TQRLabel.Create(GridRep.Bands.ColumnHeaderBand) do
      begin
        Parent := GridRep.Bands.ColumnHeaderBand;
        Color := GridRep.Bands.ColumnHeaderBand.Color;
        Left := CurrentLeft;
        Top := CurrentTop;
        Caption:=Grid.Columns[i].Title.Caption;
      end;
      {Создадим тело отчёта при помощи QRDBText}
      with TQRDbText.Create(GridRep.Bands.DetailBand) do
      begin
        Parent := GridRep.Bands.DetailBand;
        Color := GridRep.Bands.DetailBand.Color;
        Left := CurrentLeft;
        Top := CurrentTop;
        Alignment:=Grid.Columns[i].Alignment;
        AutoSize:=false;
        AutoStretch:=true;
        Width:=Grid.Columns[i].Width;
        Dataset:=GridRep.Dataset;
        DataField:=Grid.Fields[i].FieldName;
        CurrentLeft:=CurrentLeft + (Grid.Columns[i].Width) + 15;
      end;
    end;
    lblPage.Left := bdTitle.Width - lblPage.Width - 10;
    lblDate.Left := bdTitle.Width - lblDate.Width - 10;
    {Далее вызовем метод предварительного просмотра из QuickRep}
    GridRep.PreviewModal; {либо, если желаете, то PreviewModal}
  finally
    with Grid.DataSource.DataSet do
    begin
      GotoBookmark(BMark);
      FreeBookmark(BMark);
      EnableControls;
    end;
  end;
end;

Печать конверта.

Автор: Xavier Pacheco

{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit MainFrm;
interface
uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, printers, StdCtrls, ExtCtrls, Menus, ComCtrls;
type
  TEnvelope = record
    Kind: string; // Stores the envelope type's name
    Width: double; // Holds the width of the envelope
    Height: double; // Holds the height of the envelope
  end;
const
  // This constant array stores envelope types
  EnvArray: array[1..2] of TEnvelope =
  ((Kind: 'Size 10'; Width: 9.5; Height: 4.125), // 9-1/2 x 4-1/8
    (Kind: 'Size 6-3/4'; Width: 6.5; Height: 3.625)); // 6-1/2 x 3-3/4
type
  // This enumerated type represents printing positions.
  TFeedType = (epLHorz, epLVert, epRHorz, epRVert);
  TPrintPrevPanel = class(TPanel)
  public
    property Canvas; // Publicize the Canvas property
  end;
  TMainForm = class(TForm)
    gbEnvelopeSize: TGroupBox;
    rbSize10: TRadioButton;
    rbSize6: TRadioButton;
    mmMain: TMainMenu;
    mmiPrintIt: TMenuItem;
    lblAdressee: TLabel;
    edtName: TEdit;
    edtStreet: TEdit;
    edtCityState: TEdit;
    rgFeedType: TRadioGroup;
    PrintDialog: TPrintDialog;
    procedure FormCreate(Sender: TObject);
    procedure rgFeedTypeClick(Sender: TObject);
    procedure mmiPrintItClick(Sender: TObject);
  private
    PrintPrev: TPrintPrevPanel; // Print  preview panel
    EnvSize: TPoint; // Stores the envelope's size
    EnvPos: TRect; // Stores the envelope's position
    ToAddrPos: TRect; // Stores the address's position
    FeedType: TFeedType; // Stores the feed type from TEnvPosition
    function GetEnvelopeSize: TPoint;
    function GetEnvelopePos: TRect;
    function GetToAddrSize: TPoint;
    function GetToAddrPos: TRect;
    procedure DrawIt;
    procedure RotatePrintFont;
    procedure SetCopies(Copies: Integer);
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
function TMainForm.GetEnvelopeSize: TPoint;
// Gets the envelope's size represented by a TPoint
var
  EnvW, EnvH: integer;
  PixPerInX,
    PixPerInY: integer;
begin
  // Pixels per inch along the horizontal axis
  PixPerInX := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
  // Pixels per inch along the vertical axis
  PixPerInY := GetDeviceCaps(Printer.Handle, LOGPIXELSY);
  // Envelope size differs depending on the user's selection
  if RBSize10.Checked then
  begin
    EnvW := trunc(EnvArray[1].Width * PixPerInX);
    EnvH := trunc(EnvArray[1].Height * PixPerInY);
  end
  else
  begin
    EnvW := trunc(EnvArray[2].Width * PixPerInX);
    EnvH := trunc(EnvArray[2].Height * PixPerInY);
  end;
  // return Result as a TPoint record
  Result := Point(EnvW, EnvH)
end;
function TMainForm.GetEnvelopePos: TRect;
{ Returns the envelope's position relative to its feed type. This
  function requires that the variable EnvSize be initialized }
begin
  // Determine feed type based on user's selection.
  FeedType := TFeedType(rgFeedType.ItemIndex);
  { Return a TRect structure indicating the envelope's
    position as it is ejected from the printer. }
  case FeedType of
    epLHorz:
      Result := Rect(0, 0, EnvSize.X, EnvSize.Y);
    epLVert:
      Result := Rect(0, 0, EnvSize.Y, EnvSize.X);
    epRHorz:
      Result := Rect(Printer.PageWidth - EnvSize.X, 0, Printer.PageWidth,
        EnvSize.Y);
    epRVert:
      Result := Rect(Printer.PageWidth - EnvSize.Y, 0, Printer.PageWidth,
        EnvSize.X);
  end; // Case
end;
function MaxLn(V1, V2: Integer): Integer;
// Returns the larger of the two. If equal, returns the first
begin
  Result := V1; // Default result to V1 }
  if V1 < V2 then
    Result := V2
end;
function TMainForm.GetToAddrSize: TPoint;
var
  TempPoint: TPoint;
begin
  // Calculate the size of the longest line using the MaxLn() function
  TempPoint.x := Printer.Canvas.TextWidth(edtName.Text);
  TempPoint.x := MaxLn(TempPoint.x, Printer.Canvas.TextWidth(edtStreet.Text));
  TempPoint.x := MaxLn(TempPoint.x, Printer.Canvas.TextWidth(edtCityState.Text))
    + 10;
  // Calculate the height of all the address lines
  TempPoint.y := Printer.Canvas.TextHeight(edtName.Text) +
    Printer.Canvas.TextHeight(edtStreet.Text) +
    Printer.Canvas.TextHeight(edtCityState.Text) + 10;
  Result := TempPoint;
end;
function TMainForm.GetToAddrPos: TRect;
// This function requires that EnvSize, and EnvPos be initialized
var
  TempSize: TPoint;
  LT, RB: TPoint;
begin
  // Determine the size of the Address bounding rectangle
  TempSize := GetToAddrSize;
  { Calculate two points, one representing the Left Top (LT) position
    and one representing the Right Bottom (RB) position of the
    address's bounding rectangle. This depends on the FeedType }
  case FeedType of
    epLHorz:
      begin
        LT := Point((EnvSize.x div 2) - (TempSize.x div 2),
          ((EnvSize.y div 2) - (TempSize.y div 2)));
        RB := Point(LT.x + TempSize.x, LT.y + TempSize.Y);
      end;
    epLVert:
      begin
        LT := Point((EnvSize.y div 2) - (TempSize.y div 2),
          ((EnvSize.x div 2) - (TempSize.x div 2)));
        RB := Point(LT.x + TempSize.y, LT.y + TempSize.x);
      end;
    epRHorz:
      begin
        LT := Point((EnvSize.x div 2) - (TempSize.x div 2) + EnvPos.Left,
          ((EnvSize.y div 2) - (TempSize.y div 2)));
        RB := Point(LT.x + TempSize.x, LT.y + TempSize.Y);
      end;
    epRVert:
      begin
        LT := Point((EnvSize.y div 2) - (TempSize.y div 2) + EnvPos.Left,
          ((EnvSize.x div 2) - (TempSize.x div 2)));
        RB := Point(LT.x + TempSize.y, LT.y + TempSize.x);
      end;
  end; // End Case
  Result := Rect(LT.x, LT.y, RB.x, RB.y);
end;
procedure TMainForm.DrawIt;
// This procedure assumes that EnvPos and EnvSize have been initialized
begin
  PrintPrev.Invalidate; // Erase contents of Panel
  PrintPrev.Update;
  // Set the mapping mode for the panel to MM_ISOTROPIC
  SetMapMode(PrintPrev.Canvas.Handle, MM_ISOTROPIC);
  // Set the TPanel's extent to match that of the printer boundaries.
  SetWindowExtEx(PrintPrev.Canvas.Handle,
    Printer.PageWidth, Printer.PageHeight, nil);
  // Set the viewport extent to that of the PrintPrev TPanel size.
  SetViewPortExtEx(PrintPrev.Canvas.Handle,
    PrintPrev.Width, PrintPrev.Height, nil);
  // Set the origin to the position at 0, 0
  SetViewportOrgEx(PrintPrev.Canvas.Handle, 0, 0, nil);
  PrintPrev.Brush.Style := bsSolid;
  with EnvPos do
    // Draw a rectangle to represent the envelope
    PrintPrev.Canvas.Rectangle(Left, Top, Right, Bottom);
  with ToAddrPos, PrintPrev.Canvas do
    case FeedType of
      epLHorz, epRHorz:
        begin
          Rectangle(Left, Top, Right, Top + 2);
          Rectangle(Left, Top + (Bottom - Top) div 2, Right, Top + (Bottom - Top)
            div 2 + 2);
          Rectangle(Left, Bottom, Right, Bottom + 2);
        end;
      epLVert, epRVert:
        begin
          Rectangle(Left, Top, Left + 2, Bottom);
          Rectangle(Left + (Right - Left) div 2, Top, Left + (Right - Left) div 2
            + 2, Bottom);
          Rectangle(Right, Top, Right + 2, Bottom);
        end;
    end; // case
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
  Ratio: double;
begin
  // Calculate a ratio of PageWidth to PageHeight
  Ratio := Printer.PageHeight / Printer.PageWidth;
  // Create a new TPanel instance
  with TPanel.Create(self) do
  begin
    SetBounds(15, 15, 203, trunc(203 * Ratio));
    Color := clBlack;
    BevelInner := bvNone;
    BevelOuter := bvNone;
    Parent := self;
  end;
  // Create a Print preview panel
  PrintPrev := TPrintPrevPanel.Create(self);
  with PrintPrev do
  begin
    SetBounds(10, 10, 200, trunc(200 * Ratio));
    Color := clWhite;
    BevelInner := bvNone;
    BevelOuter := bvNone;
    BorderStyle := bsSingle;
    Parent := self;
  end;
end;
procedure TMainForm.rgFeedTypeClick(Sender: TObject);
begin
  EnvSize := GetEnvelopeSize;
  EnvPos := GetEnvelopePos;
  ToAddrPos := GetToAddrPos;
  DrawIt;
end;
procedure TMainForm.SetCopies(Copies: Integer);
var
  ADevice, ADriver, APort: string;
  ADeviceMode: THandle;
  DevMode: PDeviceMode;
begin
  SetLength(ADevice, 255);
  SetLength(ADriver, 255);
  SetLength(APort, 255);
  { If ADeviceMode is zero, a printer driver is not loaded. Therefore,
    setting PrinterIndex forces the driver to load. }
  if ADeviceMode = 0 then
  begin
    Printer.PrinterIndex := Printer.PrinterIndex;
    Printer.GetPrinter(PChar(ADevice), PChar(ADriver), PChar(APort),
      ADeviceMode);
  end;
  if ADeviceMode <> 0 then
  begin
    DevMode := GlobalLock(ADeviceMode);
    try
      DevMode^.dmFields := DevMode^.dmFields or DM_Copies;
      DevMode^.dmCopies := Copies;
    finally
      GlobalUnlock(ADeviceMode);
    end;
  end
  else
    raise Exception.Create('Could not set printer copies');
end;
procedure TMainForm.mmiPrintItClick(Sender: TObject);
var
  TempHeight: integer;
  SaveFont: TFont;
begin
  if PrintDialog.Execute then
  begin
    // Set the number of copies to print
    SetCopies(PrintDialog.Copies);
    Printer.BeginDoc;
    try
      // Calculate a temporary line height
      TempHeight := Printer.Canvas.TextHeight(edtName.Text);
      with ToAddrPos do
      begin
        { When printing vertically, rotate the font such that it paints
          at a 90 degree angle. }
        if (FeedType = eplVert) or (FeedType = epRVert) then
        begin
          SaveFont := TFont.Create;
          try
            // Save the original font
            SaveFont.Assign(Printer.Canvas.Font);
            RotatePrintFont;
            // Write out the address lines to the printer's Canvas
            Printer.Canvas.TextOut(Left, Bottom, edtName.Text);
            Printer.Canvas.TextOut(Left + TempHeight + 2, Bottom,
              edtStreet.Text);
            Printer.Canvas.TextOut(Left + TempHeight * 2 + 2, Bottom,
              edtCityState.Text);
            // Restore the original font
            Printer.Canvas.Font.Assign(SaveFont);
          finally
            SaveFont.Free;
          end;
        end
        else
        begin
          { If the envelope is not printed vertically, then
            just draw the address lines normally. }
          Printer.Canvas.TextOut(Left, Top, edtName.Text);
          Printer.Canvas.TextOut(Left, Top + TempHeight + 2, edtStreet.Text);
          Printer.Canvas.TextOut(Left, Top + TempHeight * 2 + 2,
            edtCityState.Text);
        end;
      end;
    finally
      Printer.EndDoc;
    end;
  end;
end;
procedure TMainForm.RotatePrintFont;
var
  LogFont: TLogFont;
begin
  with Printer.Canvas do
  begin
    with LogFont do
    begin
      lfHeight := Font.Height; // Set to Printer.Canvas.font.height
      lfWidth := 0; // let font mapper choose width
      lfEscapement := 900; // tenths of degrees so 900 = 90 degrees
      lfOrientation := lfEscapement; // Always set to value of lfEscapement
      lfWeight := FW_NORMAL; // default
      lfItalic := 0; // no italics
      lfUnderline := 0; // no underline
      lfStrikeOut := 0; // no strikeout
      lfCharSet := ANSI_CHARSET; //default
      StrPCopy(lfFaceName, Font.Name); // Printer.Canvas's font's name
      lfQuality := PROOF_QUALITY;
      lfOutPrecision := OUT_TT_ONLY_PRECIS; // force TrueType fonts
      lfClipPrecision := CLIP_DEFAULT_PRECIS; // default
      lfPitchAndFamily := Variable_Pitch; // default
    end;
  end;
  Printer.Canvas.Font.Handle := CreateFontIndirect(LogFont);
end;
end.

Распечатать Excel файл.

uses   ComObj; 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  ExcelApp: OLEVariant; 
begin 
  // Create an Excel instance 
  // Excel Instanz erzeugen 
  ExcelApp := CreateOleObject('Excel.Application'); 
  try 
    ExcelApp.Workbooks.Open('C:\test\xyz.xls'); 
    // you can also modify some settings from PageSetup 
    // Man kann auch noch einige Einstellungen von "Seite Einrichten" anpassen 
    ExcelApp.ActiveSheet.PageSetup.Orientation := xlLandscape; 
    // Print it out 
    // Ausdrucken 
    ExcelApp.Worksheets.PrintOut; 
  finally 
    // Close Excel 
    // Excel wieder schliessen 
    if not VarIsEmpty(ExcelApp) then 
    begin 
      ExcelApp.Quit; 
      ExcelApp := Unassigned; 
    end; 
  end; 
end;

Печать всей формы.

unit PrintF;
{Печатает TLabel, TEdit, TMemo, TStringGrid, TShape и др. DB-компоненты.
Установите Form H & V ScrollBar.Ranges на 768X1008 для страницы 8X10.5.
Примечание: это не компонент. Успехов. Bill}
interface
uses
  SysUtils, WinTypes, WinProcs, Classes, Graphics, Controls,
  Forms, Grids, Printers, StdCtrls, ExtCtrls, Mask;
function PrintForm(AForm: TForm; ATag: Longint): integer;
{используйте:   PrintForm(Form2, 0);
AForm - форма, которую необходимо напечатать. Если вы, к примеру,
печатаете Form2 из обработчика события Form1, то используйте Unit2
в списке используемых модулей в секции implementation молуля Unit1.
ATag - поле Tag компонента, который необходимо печатать или 0 для всех.
Если Tag компонента равен 14 (2+4+8), он буден напечатан в случае,
когда ATag равен 0, 2, 4 или 8.
Функция возвращает количество напечатанных компонентов. }
implementation
var
  ScaleX, ScaleY, I, Count: integer;
  DC: HDC;
  F: TForm;
function ScaleToPrinter(R: TRect): TRect;
begin
  R.Top := (R.Top + F.VertScrollBar.Position) * ScaleY;
  R.Left := (R.Left + F.HorzScrollBar.Position) * ScaleX;
  R.Bottom := (R.Bottom + F.VertScrollBar.Position) * ScaleY;
  R.Right := (R.Right + F.HorzScrollBar.Position) * ScaleY;
  Result := R;
end;
procedure PrintMComponent(MC: TMemo);
var
  C: array[0..255] of char;
  CLen: integer;
  Format: Word;
  R: TRect;
begin
  Printer.Canvas.Font := MC.Font;
  DC := Printer.Canvas.Handle; {так DrawText знает о шрифте}
  R := ScaleToPrinter(MC.BoundsRect);
  if (not (F.Components[I] is TCustomLabel)) and (MC.BorderStyle = bsSingle)
    then
    Printer.Canvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);
  Format := DT_LEFT;
  if (F.Components[I] is TEdit) or (F.Components[I] is TCustomMaskEdit) then
    Format := Format or DT_SINGLELINE or DT_VCENTER
  else
  begin
    if MC.WordWrap then
      Format := DT_WORDBREAK;
    if MC.Alignment = taCenter then
      Format := Format or DT_CENTER;
    if MC.Alignment = taRightJustify then
      Format := Format or DT_RIGHT;
    R.Bottom := R.Bottom + Printer.Canvas.Font.Height;
  end;
  CLen := MC.GetTextBuf(C, 255);
  R.Left := R.Left + ScaleX + ScaleX;
  WinProcs.DrawText(DC, C, CLen, R, Format);
  inc(Count);
end;
procedure PrintShape(SC: TShape);
var
  H, W, S: integer;
  R: TRect;
begin {PrintShape}
  Printer.Canvas.Pen := SC.Pen;
  Printer.Canvas.Pen.Width := Printer.Canvas.Pen.Width * ScaleX;
  Printer.Canvas.Brush := SC.Brush;
  R := ScaleToPrinter(SC.BoundsRect);
  W := R.Right - R.Left;
  H := R.Bottom - R.Top;
  if W < H then
    S := W
  else
    S := H;
  if SC.Shape in [stSquare, stRoundSquare, stCircle] then
  begin
    Inc(R.Left, (W - S) div 2);
    Inc(R.Top, (H - S) div 2);
    W := S;
    H := S;
  end;
  case SC.Shape of
    stRectangle, stSquare:
      Printer.Canvas.Rectangle(R.Left, R.Top, R.Left + W, R.Top + H);
    stRoundRect, stRoundSquare:
      Printer.Canvas.RoundRect(R.Left, R.Top, R.Left + W, R.Top + H, S div 4, S
        div 4);
    stCircle, stEllipse:
      Printer.Canvas.Ellipse(R.Left, R.Top, R.Left + W, R.Top + H);
  end;
  Printer.Canvas.Pen.Width := ScaleX;
  Printer.Canvas.Brush.Style := bsClear;
  inc(Count);
end; {PrintShape}
procedure PrintSGrid(SGC: TStringGrid);
var
  J, K: integer;
  Q, R: TRect;
  Format: Word;
  C: array[0..255] of char;
  CLen: integer;
begin
  Printer.Canvas.Font := SGC.Font;
  DC := Printer.Canvas.Handle; {так DrawText знает о шрифте}
  Format := DT_SINGLELINE or DT_VCENTER;
  Q := SGC.BoundsRect;
  Printer.Canvas.Pen.Width := SGC.GridLineWidth * ScaleX;
  for J := 0 to SGC.ColCount - 1 do
    for K := 0 to SGC.RowCount - 1 do
    begin
      R := SGC.CellRect(J, K);
      if R.Right > R.Left then
      begin
        R.Left := R.Left + Q.Left;
        R.Right := R.Right + Q.Left + SGC.GridLineWidth;
        R.Top := R.Top + Q.Top;
        R.Bottom := R.Bottom + Q.Top + SGC.GridLineWidth;
        R := ScaleToPrinter(R);
        if (J < SGC.FixedCols) or (K < SGC.FixedRows) then
          Printer.Canvas.Brush.Color := SGC.FixedColor
        else
          Printer.Canvas.Brush.Style := bsClear;
        if SGC.GridLineWidth > 0 then
          Printer.Canvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);
        StrPCopy(C, SGC.Cells[J, K]);
        R.Left := R.Left + ScaleX + ScaleX;
        WinProcs.DrawText(DC, C, StrLen(C), R, Format);
      end;
    end;
  Printer.Canvas.Pen.Width := ScaleX;
  inc(Count);
end;
function PrintForm(AForm: TForm; ATag: Longint): integer;
begin {PrintForm}
  Count := 0;
  F := AForm;
  Printer.BeginDoc;
  try
    DC := Printer.Canvas.Handle;
    ScaleX := WinProcs.GetDeviceCaps(DC, LOGPIXELSX) div F.PixelsPerInch;
    ScaleY := WinProcs.GetDeviceCaps(DC, LOGPIXELSY) div F.PixelsPerInch;
    for I := 0 to F.ComponentCount - 1 do
      if TControl(F.Components[I]).Visible then
        if (ATag = 0) or (TControl(F.Components[I]).Tag and ATag = ATag) then
        begin
          if (F.Components[I] is TCustomLabel) or (F.Components[I] is
            TCustomEdit) then
            PrintMComponent(TMemo(F.Components[I]));
          if (F.Components[I] is TShape) then
            PrintShape(TShape(F.Components[I]));
          if (F.Components[I] is TStringGrid) then
            PrintSGrid(TStringGrid(F.Components[I]));
        end;
  finally
    Printer.EndDoc;
    Result := Count;
  end;
end; {PrintForm}
end.
unit Rulers;
{ Добавьте в файл .DCR иконки для двух компонентов.
Успехов, Bill}
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms;
type
  THRuler = class(TGraphicControl)
  private
    { Private declarations }
    fHRulerAlign: TAlign;
    procedure SetHRulerAlign(Value: TAlign);
  protected
    { Protected declarations }
    procedure Paint; override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
  published
    { Published declarations }
    property AlignHRuler: TAlign read fHRulerAlign write SetHRulerAlign default
      alNone;
    property Color default clYellow;
    property Height default 33;
    property Width default 768;
    property Visible;
  end;
type
  TVRuler = class(TGraphicControl)
  private
    { Private declarations }
    fVRulerAlign: TAlign;
    procedure SetVRulerAlign(Value: TAlign);
  protected
    { Protected declarations }
    procedure Paint; override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
  published
    { Published declarations }
    property AlignVRuler: TAlign read fVRulerAlign write SetVRulerAlign default
      alNone;
    property Color default clYellow;
    property Height default 1008;
    property Width default 33;
    property Visible;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Samples', [THRuler, TVRuler]);
end;
procedure THRuler.SetHRulerAlign(Value: TAlign);
begin
  if Value in [alTop, alBottom, alNone] then
  begin
    fHRulerAlign := Value;
    Align := Value;
  end;
end;
constructor THRuler.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  AlignHRuler := alNone;
  Color := clYellow;
  Height := 33;
  Width := 768;
end;
procedure THRuler.Paint;
var
  a12th, N, X: word;
begin
  a12th := Screen.PixelsPerInch div 12;
  N := 0;
  X := 0;
  with Canvas do
  begin
    Brush.Color := Color;
    FillRect(ClientRect);
    with ClientRect do
      Rectangle(Left, Top, Right, Bottom);
    while X < Width do
    begin
      MoveTo(X, 1);
      LineTo(X, 6 * (1 + byte(N mod 3 = 0) +
        byte(N mod 6 = 0) +
        byte(N mod 12 = 0)));
      if (N > 0) and (N mod 12 = 0) then
        TextOut(PenPos.X + 3, 9, IntToStr(N div 12));
      N := N + 1;
      X := X + a12th;
    end;
  end;
end;
{*********************************************}
procedure TVRuler.SetVRulerAlign(Value: TAlign);
begin
  if Value in [alLeft, alRight, alNone] then
  begin
    fVRulerAlign := Value;
    Align := Value;
  end;
end;
constructor TVRuler.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  AlignVRuler := alNone;
  Color := clYellow;
  Height := 1008;
  Width := 33;
end;
procedure TVRuler.Paint;
var
  a6th, N, Y: word;
begin
  a6th := Screen.PixelsPerInch div 6;
  N := 0;
  Y := 0;
  with Canvas do
  begin
    Brush.Color := Color;
    FillRect(ClientRect);
    with ClientRect do
      Rectangle(Left, Top, Right, Bottom);
    while Y < Height do
    begin
      MoveTo(1, Y);
      LineTo(6 * (2 + byte(N mod 3 = 0) +
        byte(N mod 6 = 0)), Y);
      if (N > 0) and (N mod 6 = 0) then
        TextOut(12, PenPos.Y - 16, IntToStr(N div 6));
      N := N + 1;
      Y := Y + a6th;
    end;
  end;
end;
end.

Печать всей формы 2.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
 Dieser Code druckt den Inhalt einer Form aus. 
 Jedoch ohne Rand und Titelleiste. 
 This code prints out form1. Without border and caption bar. 
 Print uses the GetFormImage method to obtain a bitmap of the form and 
 draws that to the printer’s HDC. 
}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   Form1.Print;
 end;
 { 
  The following TI details a better way to print the contents of 
  a form, by getting the device independent bits in 256 colors 
  from the form, and using those bits to print the form to the 
  printer. 
  In addition, a check is made to see if the screen or printer 
  is a palette device, and if so, palette handling for the device 
  is enabled. If the screen device is a palette device, an additional 
  step is taken to fill the bitmap's palette from the system palette, 
  overcoming some buggy video drivers who don't fill the palette in. 
  Note: Since this code does a screen shot of the form, the form must 
  be the topmost window and the whole from must be viewable when the 
  form shot is made. 
  Copyright by SWAG SUPPORT TEAM 
  http://gdsoft.com/swag/swag.html 
}
 procedure PrintForm(AForm: TForm; BorderWidth: Integer);
 var
   dc: HDC;
   isDcPalDevice: BOOL;
   MemDc: hdc;
   MemBitmap: hBitmap;
   OldMemBitmap: hBitmap;
   hDibHeader: THandle;
   pDibHeader: Pointer;
   hBits: THandle;
   pBits: Pointer;
   ScaleX: Double;
   ScaleY: Double;
   ppal: PLOGPALETTE;
   pal: hPalette;
   Oldpal: hPalette;
   i: Integer;
 begin
   {Get the screen dc}
   dc := GetDc(0);
   {Create a compatible dc}
   MemDc := CreateCompatibleDc(dc);
   {create a bitmap}
   MemBitmap := CreateCompatibleBitmap(Dc,
     AForm.Width,
     AForm.Height);
   {select the bitmap into the dc}
   OldMemBitmap := SelectObject(MemDc, MemBitmap);
   {Lets prepare to try a fixup for broken video drivers}
   isDcPalDevice := False;
   if GetDeviceCaps(dc, RASTERCAPS) and
     RC_PALETTE = RC_PALETTE then
   begin
     GetMem(pPal, SizeOf(TLOGPALETTE) +
     (255 * SizeOf(TPALETTEENTRY)));
     FillChar(pPal^, SizeOf(TLOGPALETTE) +
     (255 * SizeOf(TPALETTEENTRY)), #0);
     pPal^.palVersion    := $300;
     pPal^.palNumEntries :=
       GetSystemPaletteEntries(dc,
       0,
       256,
       pPal^.palPalEntry);
     if pPal^.PalNumEntries <> 0 then
     begin
       pal           := CreatePalette(pPal^);
       oldPal        := SelectPalette(MemDc, Pal, False);
       isDcPalDevice := True
     end
     else
       FreeMem(pPal, SizeOf(TLOGPALETTE) +
       (255 * SizeOf(TPALETTEENTRY)));
   end;
   {copy from the screen to the memdc/bitmap}
   BitBlt(MemDc,
     0, 0,
     AForm.Width, AForm.Height,
     Dc,
     AForm.Left, AForm.Top,
     SrcCopy);
   if isDcPalDevice = True then
   begin
     SelectPalette(MemDc, OldPal, False);
     DeleteObject(Pal);
   end;
   {unselect the bitmap}
   SelectObject(MemDc, OldMemBitmap);
   {delete the memory dc}
   DeleteDc(MemDc);
   {Allocate memory for a DIB structure}
   hDibHeader := GlobalAlloc(GHND,
     SizeOf(TBITMAPINFO) +
     (SizeOf(TRGBQUAD) * 256));
   {get a pointer to the alloced memory}
   pDibHeader := GlobalLock(hDibHeader);
   {fill in the dib structure with info on the way we want the DIB}
   FillChar(pDibHeader^,
     SizeOf(TBITMAPINFO) + (SizeOf(TRGBQUAD) * 256),
     #0);
   PBITMAPINFOHEADER(pDibHeader)^.biSize        :=
     SizeOf(TBITMAPINFOHEADER);
   PBITMAPINFOHEADER(pDibHeader)^.biPlanes      := 1;
   PBITMAPINFOHEADER(pDibHeader)^.biBitCount    := 8;
   PBITMAPINFOHEADER(pDibHeader)^.biWidth       := AForm.Width;
   PBITMAPINFOHEADER(pDibHeader)^.biHeight      := AForm.Height;
   PBITMAPINFOHEADER(pDibHeader)^.biCompression := BI_RGB;
   {find out how much memory for the bits}
   GetDIBits(dc,
     MemBitmap,
     0,
     AForm.Height,
     nil,
     TBitmapInfo(pDibHeader^),
     DIB_RGB_COLORS);
   {Alloc memory for the bits}
   hBits := GlobalAlloc(GHND,
     PBitmapInfoHeader(pDibHeader)^.BiSizeImage);
   {Get a pointer to the bits}
   pBits := GlobalLock(hBits);
   {Call fn again, but this time give us the bits!}
   GetDIBits(dc,
     MemBitmap,
     0,
     AForm.Height,
     pBits,
     PBitmapInfo(pDibHeader)^,
     DIB_RGB_COLORS);
   {Lets try a fixup for broken video drivers}
   if isDcPalDevice = True then
   begin
     for i := 0 to (pPal^.PalNumEntries - 1) do
     begin
       PBitmapInfo(pDibHeader)^.bmiColors[i].rgbRed   :=
         pPal^.palPalEntry[i].peRed;
       PBitmapInfo(pDibHeader)^.bmiColors[i].rgbGreen :=
         pPal^.palPalEntry[i].peGreen;
       PBitmapInfo(pDibHeader)^.bmiColors[i].rgbBlue  :=
         pPal^.palPalEntry[i].peBlue;
     end;
     FreeMem(pPal, SizeOf(TLOGPALETTE) +
     (255 * SizeOf(TPALETTEENTRY)));
   end;
   {Release the screen dc}
   ReleaseDc(0, dc);
   {Delete the bitmap}
   DeleteObject(MemBitmap);
   {Start print job}
   Printer.BeginDoc;
   {Scale print size}
   if Printer.PageWidth < Printer.PageHeight then
   begin
     ScaleX := Printer.PageWidth;
     ScaleY := AForm.Height * (Printer.PageWidth / AForm.Width);
   end
   else
   begin
     ScaleX := AForm.Width * (Printer.PageHeight / AForm.Height);
     ScaleY := Printer.PageHeight;
   end;
   {Just incase the printer drver is a palette device}
   isDcPalDevice := False;
   if GetDeviceCaps(Printer.Canvas.Handle, RASTERCAPS) and
     RC_PALETTE = RC_PALETTE then
   begin
     {Create palette from dib}
     GetMem(pPal, SizeOf(TLOGPALETTE) +
     (255 * SizeOf(TPALETTEENTRY)));
     FillChar(pPal^, SizeOf(TLOGPALETTE) +
     (255 * SizeOf(TPALETTEENTRY)), #0);
     pPal^.palVersion    := $300;
     pPal^.palNumEntries := 256;
     for i := 0 to (pPal^.PalNumEntries - 1) do
     begin
       pPal^.palPalEntry[i].peRed   :=
         PBitmapInfo(pDibHeader)^.bmiColors[i].rgbRed;
       pPal^.palPalEntry[i].peGreen :=
         PBitmapInfo(pDibHeader)^.bmiColors[i].rgbGreen;
       pPal^.palPalEntry[i].peBlue  :=
         PBitmapInfo(pDibHeader)^.bmiColors[i].rgbBlue;
     end;
     pal := CreatePalette(pPal^);
     FreeMem(pPal, SizeOf(TLOGPALETTE) +
     (255 * SizeOf(TPALETTEENTRY)));
     oldPal  := SelectPalette(Printer.Canvas.Handle, Pal, False);
     isDcPalDevice := True
   end;
   {send the bits to the printer}
   StretchDiBits(Printer.Canvas.Handle,
     BorderWidth, BorderWidth,
     Round(scaleX)-BorderWidth, Round(scaleY)-BorderWidth,
     0, 0,
     AForm.Width, AForm.Height,
     pBits,
     PBitmapInfo(pDibHeader)^,
     DIB_RGB_COLORS,
     SRCCOPY);
   RotateBitmap(var hDIB: HGlobal; 180; clWhite);
   {Just incase you printer drver is a palette device}
   if isDcPalDevice = True then
   begin
     SelectPalette(Printer.Canvas.Handle, oldPal, False);
     DeleteObject(Pal);
   end;
   {Clean up allocated memory}
   GlobalUnlock(hBits);
   GlobalFree(hBits);
   GlobalUnlock(hDibHeader);
   GlobalFree(hDibHeader);
   {End the print job}
   Printer.EndDoc;
 end;

Печать содержимого TMemo или TListBox.

Идет Иван царевич по лесу и видит: стоит избушка на курьих ножках. Как обычно, к лесу передом к нему (ну сами знаете). Иван царевич ей и говорит: - Избушка, избушка! Повернись ко мне передом, а к лесу задом! А избушка ему в ответ: - Denied of Service

Следующая функция имеет один параметр в виде объекта TStrings и печатает каждую строку на принтер, установленный в системе по умолчанию. Так как эта функция использует TStrings, то она будет работать с различными компонентами, которые содержат свойство типа TStrings, такие как TDBMemo или TOutline:

uses Printers;
procedure PrintStrings(Strings: TStrings);
var
  Prn: TextFile;
  i: word;
begin
  AssignPrn(Prn);
  try
    Rewrite(Prn);
    try
      for i := 0 to Strings.Count - 1 do
        writeln(Prn, Strings.Strings[i]);
    finally
      CloseFile(Prn);
    end;
  except
    on EInOutError do
      MessageDlg('Error Printing text.', mtError, [mbOk], 0);
  end;
end;

Чтобы распечатать содержимое TMemo или TListbox, используйте следующие команды:

PrintStrings(Memo1.Lines);

Или.

PrintStrings(Listbox1.Items);

Как производить Печать из WebBrowsera.

Есть два способа вывода на печать. Первый пример работает в IE 4.x и выше, в то время как второй пример расчитан на IE 3.x:

var
  vaIn, vaOut: OleVariant;
...
WebBrowser.ControlInterface.ExecWB(OLECMDID_PRINT,
OLECMDEXECOPT_DONTPROMPTUSER, vaIn, vaOut);

Либо:

procedure TForm1.PrintIE;
var
  CmdTarget : IOleCommandTarget;
  vaIn, vaOut: OleVariant;
begin
  if WebBrowser1.Document <> nil then
    try
      WebBrowser1.Document.QueryInterface(IOleCommandTarget, CmdTarget);
      if CmdTarget <> nil then
        try
          CmdTarget.Exec( PGuid(nil), OLECMDID_PRINT, 
                         OLECMDEXECOPT_DONTPROMPTUSER, vaIn, vaOut);
        finally
          CmdTarget._Release;
        end;
    except
      // nothing
    end;
end;

Обратите внимание: Если версия Delphi ниже чем 3.02, то необходимо заменить.

PGuid(nil)

На.

PGuid(nil)^

Как напечатать изображение.

Используйте приведенный код. И не забудьте включить Printers в список используемых модулей:

Строки, на которые вы должны обратить внимание, справа содержат комментарий // **. Данный код осуществляет корректное масштабирование изображения, в противном случае при печати вы можете получить небольшую иконку. Разрешение принтера больше, чем разрешение вашего дисплея.

procedure AngleTextOut(CV: TCanvas; const sText: string; x, y, angle: integer);
var
  LogFont: TLogFont;
  SaveFont: TFont;
  procedure TForm1.Button1Click(Sender: TObject);
  var
    ScaleX, ScaleY: Integer;
    R: TRect;
  begin
    Printer.BeginDoc; // **
    with Printer do
      try
        ScaleX := GetDeviceCaps(Handle, logPixelsX) div PixelsPerInch;
        ScaleY := GetDeviceCaps(Handle, logPixelsY) div PixelsPerInch;
        R := Rect(0, 0, Image1.Picture.Width * ScaleX,
          Image1.Picture.Height * ScaleY);
        Canvas.StretchDraw(R, Image1.Picture.Graphic); // **
      finally
        EndDoc; // **
      end;
  end;

Печать текста в обход Windows.

Автор: Steve

ОТкройте файл типа TextFile и пишите в него напрямую:

var
  Lst: TextFile;
begin
  AssignFile(Lst, 'LPT1');
  Rewrite(Lst);
  WriteLn(Lst, 'Здравствуй, мир!');
  Close(Lst);
end.
При этом вы должны помнить, что при данной технологии вы не можете в это же время печатать из другой программы, иначе наступит конец света, а ваша распечатка будет похожа на "запутанный беспорядк".

Если вы планируете посылать на принтер управляющие коды, вызывайте следующую функцию немедленно после перезаписи файла:

procedure SetBinaryMode (var F: Text);  assembler;
asm
  mov ax,$4400
  les di,F
  mov bx,word ptr es:[di]
  int $21
  or dl,$20
  xor dh,dh
  mov ax,$4401
  int $21
end;

Печать на любом принтере.

Автор: Mike Scott Как лучше всего убедиться в том, что текст на странице расположен правильно вне зависимости от принтера, версии windows и т.п.? Есть пара вызовов принтерных Escape-функций API, возвращающих физический размер страницы и отступ от края при печати (верх лево). Используя это, вы можете убедиться в том, печать начинается в одном и том же месте реального листа. Примечание: тем не менее, вы все еще зависите от точности принтера и драйвера, на практике это может вылиться в разброс 1-2 мм.

Для получения размера физической страницы и отступа от верхнего левого угла печатаемой области, используйте следующий код:

var
  PhysSize, PrintingOffset : TPoint ;
begin
  Escape( Printer.Handle, GetPhysPageSize, 0, NIL, @PhysSize ) ;
  Escape( Printer.Handle, GetPrintingOffset, 0, NIL, @PrintingOffset ) ;

PhysSize и PrintingOffset теперь устанавливаются в единицах устройства, т.е. в пикселях принтера. Для преобразования мм в единицы устройства используйте следующий код:

xDevUnits := MulDiv( XXXmm,
GetDeviceCaps( Printer.Handle, HORZRES ),
GetDeviceCaps( Printer.Handle, HORZSIZE ) ) ;
yDevUnits := MulDiv( YYYmm,
GetDeviceCaps( Printer.Handle, VERTRES ),
GetDeviceCaps( Printer.Handle, VERTSIZE ) ) ;

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

Печать структуры таблицы Paradox.

Вот код печати структуры таблицы Paradox, состряпанный на скорую руку. Предполагается, что компонент Table имеет имя Table1.

procedure TForm1.Button1Click(Sender: TObject);
const
  FieldTypes: array[0..16] of string[10] = ('Unknown', 'String', 'Smallint',
    'Integer', 'Word', 'Boolean', 'Float', 'Currency', 'BCD', 'Date', 'Time',
    'DateTime', 'Bytes', 'VarBytes', 'Blob', 'Memo', 'Graphic');
var
  i, nX, nY, nHeight, nWidth: Integer;
  rtxtMetric: TTextMetric;
  s: array[0..3] of string[10];
begin
  with Table1.FieldDefs, Printer do
  begin
    Update;
    PrinterIndex := -1;
    Title := 'Структура ' + Table1.TableName;
    BeginDoc;
    nX := 0;
    nY := 0;
    WinProcs.GetTextMetrics(Canvas.Handle, rtxtMetric);
    nHeight := rtxtMetric.tmHeight;
    nWidth := rtxtMetric.tmAveCharWidth;
    for i := 0 to Count - 1 do
    begin
      s[0] := IntToStr(Items[i].FieldNo) + #9;
      s[1] := Items[i].Name + #9;
      s[2] := FieldTypes[Ord(Items[i].DataType)] + #9;
      s[3] := IntToStr(Items[i].Size);
      Canvas.TextOut(nX, nY, s[0]);
      Inc(nX, Length(s[0]) * nWidth);
      Canvas.TextOut(nX, nY, s[1]);
      Inc(nX, Length(s[1]) * nWidth);
      Canvas.TextOut(nX, nY, s[2]);
      Inc(nX, Length(s[2]) * nWidth);
      Canvas.TextOut(nX, nY, s[3]);
      nX := 0;
      nY := i * nHeight;
    end;
    EndDoc;
  end;
end;

Как распечатать картинку.

Автор: Олег Кулабухов.

uses Printers;
type
  PPalEntriesArray = ^TPalEntriesArray; {for palette re-construction}
  TPalEntriesArray = array[0..0] of TPaletteEntry;
procedure BltTBitmapAsDib(DestDc: hdc; {Handle of where to blt}
  x: word; {Bit at x}
  y: word; {Blt at y}
  Width: word; {Width to stretch}
  Height: word; {Height to stretch}
  bm: TBitmap); {the TBitmap to Blt}
var
  OriginalWidth: LongInt; {width of BM}
  dc: hdc; {screen dc}
  IsPaletteDevice: bool; {if the device uses palettes}
  IsDestPaletteDevice: bool; {if the device uses palettes}
  BitmapInfoSize: integer; {sizeof the bitmapinfoheader}
  lpBitmapInfo: PBitmapInfo; {the bitmap info header}
  hBm: hBitmap; {handle to the bitmap}
  hPal: hPalette; {handle to the palette}
  OldPal: hPalette; {temp palette}
  hBits: THandle; {handle to the DIB bits}
  pBits: pointer; {pointer to the DIB bits}
  lPPalEntriesArray: PPalEntriesArray; {palette entry array}
  NumPalEntries: integer; {number of palette entries}
  i: integer; {looping variable}
begin
  {If range checking is on - lets turn it off for now}
  {we will remember if range checking was on by defining}
  {a define called CKRANGE if range checking is on.}
  {We do this to access array members past the arrays}
  {defined index range without causing a range check}
  {error at runtime. To satisfy the compiler, we must}
  {also access the indexes with a variable. ie: if we}
  {have an array defined as a: array[0..0] of byte,}
  {and an integer i, we can now access a[3] by setting}
  {i := 3; and then accessing a[i] without error}
{$IFOPT R+}
{$DEFINE CKRANGE}
{$R-}
{$ENDIF}
  {Save the original width of the bitmap}
  OriginalWidth := bm.Width;
  {Get the screen's dc to use since memory dc's are not reliable}
  dc := GetDc(0);
  {Are we a palette device?}
  IsPaletteDevice :=
    GetDeviceCaps(dc, RASTERCAPS) and RC_PALETTE = RC_PALETTE;
  {Give back the screen dc}
  dc := ReleaseDc(0, dc);
  {Allocate the BitmapInfo structure}
  if IsPaletteDevice then
    BitmapInfoSize := sizeof(TBitmapInfo) + (sizeof(TRGBQUAD) * 255)
  else
    BitmapInfoSize := sizeof(TBitmapInfo);
  GetMem(lpBitmapInfo, BitmapInfoSize);
  {Zero out the BitmapInfo structure}
  FillChar(lpBitmapInfo^, BitmapInfoSize, #0);
  {Fill in the BitmapInfo structure}
  lpBitmapInfo^.bmiHeader.biSize := sizeof(TBitmapInfoHeader);
  lpBitmapInfo^.bmiHeader.biWidth := OriginalWidth;
  lpBitmapInfo^.bmiHeader.biHeight := bm.Height;
  lpBitmapInfo^.bmiHeader.biPlanes := 1;
  if IsPaletteDevice then
    lpBitmapInfo^.bmiHeader.biBitCount := 8
  else
    lpBitmapInfo^.bmiHeader.biBitCount := 24;
  lpBitmapInfo^.bmiHeader.biCompression := BI_RGB;
  lpBitmapInfo^.bmiHeader.biSizeImage :=
    ((lpBitmapInfo^.bmiHeader.biWidth *
    longint(lpBitmapInfo^.bmiHeader.biBitCount)) div 8) *
    lpBitmapInfo^.bmiHeader.biHeight;
  lpBitmapInfo^.bmiHeader.biXPelsPerMeter := 0;
  lpBitmapInfo^.bmiHeader.biYPelsPerMeter := 0;
  if IsPaletteDevice then
  begin
    lpBitmapInfo^.bmiHeader.biClrUsed := 256;
    lpBitmapInfo^.bmiHeader.biClrImportant := 256;
  end
  else
  begin
    lpBitmapInfo^.bmiHeader.biClrUsed := 0;
    lpBitmapInfo^.bmiHeader.biClrImportant := 0;
  end;
  {Take ownership of the bitmap handle and palette}
  hBm := bm.ReleaseHandle;
  hPal := bm.ReleasePalette;
  {Get the screen's dc to use since memory dc's are not reliable}
  dc := GetDc(0);
  if IsPaletteDevice then
  begin
    {If we are using a palette, it must be}
    {selected into the dc during the conversion}
    OldPal := SelectPalette(dc, hPal, TRUE);
    {Realize the palette}
    RealizePalette(dc);
  end;
  {Tell GetDiBits to fill in the rest of the bitmap info structure}
  GetDiBits(dc,
    hBm,
    0,
    lpBitmapInfo^.bmiHeader.biHeight,
    nil,
    TBitmapInfo(lpBitmapInfo^),
    DIB_RGB_COLORS);
  {Allocate memory for the Bits}
  hBits := GlobalAlloc(GMEM_MOVEABLE,
    lpBitmapInfo^.bmiHeader.biSizeImage);
  pBits := GlobalLock(hBits);
  {Get the bits}
  GetDiBits(dc,
    hBm,
    0,
    lpBitmapInfo^.bmiHeader.biHeight,
    pBits,
    TBitmapInfo(lpBitmapInfo^),
    DIB_RGB_COLORS);
  if IsPaletteDevice then
  begin
    {Lets fix up the color table for buggy video drivers}
    GetMem(lPPalEntriesArray, sizeof(TPaletteEntry) * 256);
{$IFDEF VER100}
    NumPalEntries := GetPaletteEntries(hPal,
      0,
      256,
      lPPalEntriesArray^);
{$ELSE}
    NumPalEntries := GetSystemPaletteEntries(dc,
      0,
      256,
      lPPalEntriesArray^);
{$ENDIF}
    for i := 0 to (NumPalEntries - 1) do
    begin
      lpBitmapInfo^.bmiColors[i].rgbRed :=
        lPPalEntriesArray^[i].peRed;
      lpBitmapInfo^.bmiColors[i].rgbGreen :=
        lPPalEntriesArray^[i].peGreen;
      lpBitmapInfo^.bmiColors[i].rgbBlue :=
        lPPalEntriesArray^[i].peBlue;
    end;
    FreeMem(lPPalEntriesArray, sizeof(TPaletteEntry) * 256);
  end;
  if IsPaletteDevice then
  begin
    {Select the old palette back in}
    SelectPalette(dc, OldPal, TRUE);
    {Realize the old palette}
    RealizePalette(dc);
  end;
  {Give back the screen dc}
  dc := ReleaseDc(0, dc);
  {Is the Dest dc a palette device?}
  IsDestPaletteDevice :=
    GetDeviceCaps(DestDc, RASTERCAPS) and RC_PALETTE = RC_PALETTE;
  if IsPaletteDevice then
  begin
    {If we are using a palette, it must be}
    {selected into the dc during the conversion}
    OldPal := SelectPalette(DestDc, hPal, TRUE);
    {Realize the palette}
    RealizePalette(DestDc);
  end;
  {Do the blt}
  StretchDiBits(DestDc,
    x,
    y,
    Width,
    Height,
    0,
    0,
    OriginalWidth,
    lpBitmapInfo^.bmiHeader.biHeight,
    pBits,
    lpBitmapInfo^,
    DIB_RGB_COLORS,
    SrcCopy);
  if IsDestPaletteDevice then
  begin
    {Select the old palette back in}
    SelectPalette(DestDc, OldPal, TRUE);
    {Realize the old palette}
    RealizePalette(DestDc);
  end;
  {De-Allocate the Dib Bits}
  GlobalUnLock(hBits);
  GlobalFree(hBits);
  {De-Allocate the BitmapInfo}
  FreeMem(lpBitmapInfo, BitmapInfoSize);
  {Set the ownership of the bimap handles back to the bitmap}
  bm.Handle := hBm;
  bm.Palette := hPal;
  {Turn range checking back on if it was on when we started}
{$IFDEF CKRANGE}
{$UNDEF CKRANGE}
{$R+}
{$ENDIF}
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if PrintDialog1.Execute then
  begin
    Printer.BeginDoc;
    BltTBitmapAsDib(Printer.Canvas.Handle,
      0,
      0,
      Image1.Picture.Bitmap.Width,
      Image1.Picture.Bitmap.Height,
      Image1.Picture.Bitmap);
    Printer.EndDoc;
  end;
end;

Распечатать файл через порт принтера.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

 procedure TForm1.Button1Click(Sender: TObject);
 var
   port, prnfile: file;
   buffer: array [1..128] of Char;
   Read: Integer;
 begin
   // Specify a file to print 
  AssignFile(prnfile, 'filetoprint');
   Reset(prnfile, 1);
   // specify printer port 
  AssignFile(port, 'LPT1');
   Rewrite(port, 1);
   repeat
     BlockRead(prnfile, buffer, SizeOf(buffer), Read);
     BlockWrite(port, buffer, Read);
     // Application.ProcessMessages; 
  until EOF(prnfile) or (Read <> SizeOf(buffer));
   CloseFile(prnfile);
   CloseFile(port);
 end;

Распечатать файл через порт принтера 2.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

 { 
  Question: 
  How do I write a raw string of a data to the printer? 
  The following unit "PrtRaw.pas" demonstrates a 16/32 bit 
  raw printing unit for Delphi and Borland C Builder. 
  Following the unit, is an exmaple on using the 
  PrtRaw unit. 
  Notes: 
  1) You are responsible for embedding all necessary 
  control codes that the printer may require (including 
  form feed codes). 
  2) You must specify a valid printer and port name. 
}
 unit PrtRaw;
 {Copyright (c) 1998 by Joe C. Hecht - All rights Reserved}
 {joehecht@gte.net You may use and share this unit}
 {providing my name and the copyright notice stays intact.}
 interface
 uses
   WinTypes;
 {$IFDEF WIN32}
  type SpoolInt = DWORD;
 {$ELSE}
  type SpoolInt = integer;
 {$ENDIF}
 function StartRawPrintJob(PrinterName : pChar;
                           Port : pChar;
                           DocName : pChar) : THandle;
 function StartRawPrintPage(hPrn : THandle) : integer;
 function PrintRawData(hPrn : THandle;
                       Buffer : pointer;
                       NumBytes : SpoolInt) : integer;
 function EndRawPrintPage(hPrn : THandle) : integer;
 function EndRawPrintJob(hPrn : THandle) : integer;
 implementation
  uses
    WinProcs,
 {$IFDEF WIN32}
  WinSpool;
 {$ELSE}
  Print;
 {$ENDIF}
 function StartRawPrintJob(PrinterName : pChar;
                           Port : pChar;
                           DocName : pChar) : THandle;
 {$IFDEF WIN32}
  var
    hPrn : THandle;
    DocInfo1 : TDocInfo1;
 {$ENDIF}
 begin
  {$IFDEF WIN32}
    if (OpenPrinter(PChar(PrinterName),
                    hPrn,
                    nil) = FALSE)  then
    begin
      Result := THandle(-1);
      Exit;
    end;
    DocInfo1.pDocName := DocName;
    DocInfo1.pOutputFile := Port;
    DocInfo1.pDataType := 'RAW';
    if (StartDocPrinter(hPrn,
                        1,
                        @DocInfo1) = 0) then
    begin
      Result := THandle(-1);
      Exit;
    end;
    Result := hPrn;
  {$ELSE}
   result := OpenJob(Port,
                     DocName,
                     0);
  {$ENDIF}
 end;
 function StartRawPrintPage(hPrn : THandle) : integer;
 begin
  {$IFDEF WIN32}
    if (StartPagePrinter(hPrn) = FALSE) then
    begin
      Result := -1;
      Exit;
    end;
    result := 1;
  {$ELSE}
   result := StartSpoolPage(hPrn);
  {$ENDIF}
 end;
 function PrintRawData(hPrn : THandle;
                       Buffer : pointer;
                       NumBytes : SpoolInt) : integer;
 {$IFDEF WIN32}
 var
   BytesWritten : DWORD;
  {$ENDIF}
 begin
   if (NumBytes = 0) then
   begin
     Result := 1;
     Exit;
   end;
  {$IFDEF WIN32}
    if (WritePrinter(hPrn,
                     Buffer,
                     NumBytes,
                     BytesWritten) = FALSE) then
    begin
      Result := -1;
      Exit;
    end;
    if (NumBytes <> BytesWritten) then
    begin
      Result := -1;
      Exit;
    end;
    Result := 1;
  {$ELSE}
   result := WriteSpool(hPrn,
                        Buffer,
                        NumBytes);
  {$ENDIF}
 end;
 function EndRawPrintPage(hPrn : THandle) : integer;
 begin
  {$IFDEF WIN32}
    if (EndPagePrinter(hPrn) = FALSE) then
    begin
      Result := -1;
      Exit;
    end;
    Result := 1;
  {$ELSE}
   result := EndSpoolPage(hPrn);
  {$ENDIF}
 end;
 function EndRawPrintJob(hPrn : THandle) : integer;
 begin
  {$IFDEF WIN32}
    if (EndDocPrinter(hPrn) = FALSE) then
    begin
      Result := -1;
      Exit;
    end;
    if (ClosePrinter(hPrn) = FALSE) then
    begin
      Result := -1;
      Exit;
    end;
    Result := 1;
  {$ELSE}
   result := CloseJob(hPrn);
  {$ENDIF}
 end;
 end.
 // Example of use: 
uses
   PrtRaw;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   hPrn : THandle;
   Data : array[0..31] of char;
 begin
   hPrn := StartRawPrintJob('Generic / Text Only',
                            'LPT1:',
                            'My Document');
   if (integer(hPrn) < 0) then
   begin
     ShowMessage('StartRawPrintJob Failed');
     Exit;
   end;
   if (StartRawPrintPage(hPrn) < 0) then
   begin
     ShowMessage('StartRawPrintPage Failed');
     EndRawPrintJob(hPrn);
     Exit;
   end;
   Data[0] := 'P';
   Data[1] := 'a';
   Data[2] := 'g';
   Data[3] := 'e';
   Data[4] := ' ';
   Data[5] := '1';
   Data[6] := #13;
   Data[7] := #10;
   if (PrintRawData(hPrn,
                    @data,
                    8) < 0) then
   begin
     ShowMessage('PrintRawData Failed');
     EndRawPrintPage(hPrn);
     EndRawPrintJob(hPrn);
     Exit;
   end;
   if (EndRawPrintPage(hPrn) < 0) then
   begin
     ShowMessage('EndRawPrintPage Failed');
     EndRawPrintJob(hPrn);
     Exit;
   end;
   if (StartRawPrintPage(hPrn) < 0) then
   begin
     ShowMessage('StartRawPrintPage Failed');
     EndRawPrintJob(hPrn);
     Exit;
   end;
   Data[0] := 'P';
   Data[1] := 'a';
   Data[2] := 'g';
   Data[3] := 'e';
   Data[4] := ' ';
   Data[5] := '2';
   Data[6] := #13;
   Data[7] := #10;
   if (PrintRawData(hPrn,
                    @data,
                    8) < 0) then
                    begin
     ShowMessage('PrintRawData Failed');
     EndRawPrintPage(hPrn);
     EndRawPrintJob(hPrn);
     Exit;
   end;
   if (EndRawPrintPage(hPrn) < 0) then
   begin
     ShowMessage('EndRawPrintPage Failed');
     EndRawPrintJob(hPrn);
     Exit;
   end;
   if (EndRawPrintJob(hPrn) < 0) then
   begin
     ShowMessage('EndRawPrintJob Failed');
     Exit;
   end;
 end;
 end.

Как изменить размер страницы при печати.

Один из способов, это перед началом печати изменить структуру devicemode у принтера. Другие настройки, которые можно поменять, смотрите в TDEVMODE в хелпе Delphi 1.02 или DEVMODE в Delphi 2.01.

Следующий пример демонстрирует изменение размера печатаемой страницы:

procedure TForm1.Button1Click(Sender: TObject);
var
  Device: array[0..255] of char;
  Driver: array[0..255] of char;
  Port: array[0..255] of char;
  hDMode: THandle;
  PDMode: PDEVMODE;
begin
  Printer.PrinterIndex := Printer.PrinterIndex;
  Printer.GetPrinter(Device, Driver, Port, hDMode);
  if hDMode <> 0 then begin
    pDMode := GlobalLock(hDMode);
    if pDMode <> nil then begin
     {Set to legal}
      pDMode^.dmFields := pDMode^.dmFields or dm_PaperSize;
      pDMode^.dmPaperSize := DMPAPER_LEGAL;
     {Set to custom size}
      pDMode^.dmFields := pDMode^.dmFields or
        DM_PAPERSIZE or
        DM_PAPERWIDTH or
        DM_PAPERLENGTH;
      pDMode^.dmPaperSize := DMPAPER_USER;
      pDMode^.dmPaperWidth := 100 {SomeValueInTenthsOfAMillimeter};
      pDMode^.dmPaperLength := 100 {SomeValueInTenthsOfAMillimeter};
     {Set the bin to use}
      pDMode^.dmFields := pDMode^.dmFields or DMBIN_MANUAL;
      pDMode^.dmDefaultSource := DMBIN_MANUAL;
      GlobalUnlock(hDMode);
    end;
  end;
  Printer.PrinterIndex := Printer.PrinterIndex;
  Printer.BeginDoc;
  Printer.Canvas.TextOut(100, 100, 'Test 1');
  Printer.EndDoc;
end;

Распечатать только выделенный в TRichEdit текст.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   Richedit;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   printarea: TRect;
   richedit_outputarea: TRect;
   printresX, printresY: Integer;
   fmtRange: TFormatRange;
   nextChar: Integer;
   S: string;
 begin
   Printer.BeginDoc;
   try
     with Printer.Canvas do
     begin
       printresX := GetDeviceCaps(Handle, LOGPIXELSX);
       printresY := GetDeviceCaps(Handle, LOGPIXELSY);
       printarea :=
         Rect(printresX,  // 1 inch left margin 
        printresY * 3 div 2,  // 1.5 inch top margin 
        Printer.PageWidth - printresX, // 1 inch right margin 
        Printer.PageHeight - printresY * 3 div 2 // 1.5 inch 
        Bottom Margin);
       // Define a rectangle for the rich edit text. The height is set 
      to the
       // maximum. But we need to convert from device units to twips, 
      // 1 twip = 1/1440 inch or 1/20 point. 
      richedit_outputarea :=
         Rect(printarea.Left * 1440 div printresX,
         printarea.Top * 1440 div printresY,
         printarea.Right * 1440 div printresX,
         printarea.Bottom * 1440 div printresY);
       // Tell rich edit to format its text to the printer. First set 
      // up data record for message: 
      fmtRange.hDC := Handle;            // printer handle 
      fmtRange.hdcTarget := Handle;     // ditto 
      fmtRange.rc := richedit_outputarea;
       fmtRange.rcPage := Rect(0, 0,
         Printer.PageWidth * 1440 div printresX,
         Printer.PageHeight * 1440 div printresY);
       // set range of characters to print to selection 
      fmtRange.chrg.cpMin := richedit1.selstart;
       fmtRange.chrg.cpMax := richedit1.selStart + richedit1.sellength - 1;
       // remove characters that need not be printed from end of 
      selection.
       // failing to do so screws up the repeat loop below. 
      S := Richedit1.SelText;
       while (fmtRange.chrg.cpMax > 0) and
         (S[fmtRange.chrg.cpMax] <= ' ') do Dec(fmtRange.chrg.cpMax);
       repeat
         // Render the text 
        nextChar := richedit1.Perform(EM_FORMATRANGE, 1, Longint(@fmtRange));
         if nextchar < fmtRange.chrg.cpMax then
         begin
           // more text to print 
          printer.newPage;
           fmtRange.chrg.cpMin := nextChar;
         end; { If }
       until nextchar >= fmtRange.chrg.cpMax;
       // Free cached information 
      Richedit1.Perform(EM_FORMATRANGE, 0, 0);
     end;
   finally
     Printer.EndDoc;
   end;
 end;
 { 
  The richedit1.perform( EM_FORMATRANGE call returns the index of the 
  last character that could be fitted into the passed fmtrange.rc, + 1. 
  So if  multiple pages are required one repeats with fmtrange.chrg.cpMin 
  set to this value, until all characters have been printed. 
}

Печать повернутого текста.

procedure AngleTextOut(CV: TCanvas; const sText:
  string; x, y, angle: integer);
var
  LogFont: TLogFont;
  SaveFont: TFont;
begin
  SaveFont := TFont.Create;
  SaveFont.Assign(CV.Font);
  GetObject(SaveFont.Handle, sizeof(TLogFont), @LogFont);
  with LogFont do
  begin
    lfEscapement := angle * 10;
    lfPitchAndFamily := FIXED_PITCH or FF_DONTCARE;
  end; {with}
  CV.Font.Handle := CreateFontIndirect(LogFont);
  SetBkMode(CV.Handle, TRANSPARENT);
  CV.TextOut(x, y, sText);
  CV.Font.Assign(SaveFont);
  SaveFont.Free;
end;

Печать повернутого текста 2.

procedure TextOutVertical(var bitmap: TBitmap; x, y: Integer; s: string);
var
  b1, b2: TBitmap;
  i, j: Integer;
begin
  with bitmap.Canvas do
  begin
    b1 := TBitmap.Create;
    b1.Canvas.Font := lpYhFont;
    b1.Width := TextWidth(s) + 1;
    b1.Height := TextHeight(s) + 1;
    b1.Canvas.TextOut(1, 1, s);
    b2 := TPackedBitmap.Create;
    b2.Width := TextHeight(s);
    b2.Height := TextWidth(s);
    for i := 0 to b1.Width - 1 do
      for j := 0 to b1.Height do
        b2.Canvas.Pixels[j, b2.Height + 1 - i] := b1.Canvas.Pixels[i, j];
    Draw(x, y, b2);
    b1.Free;
    b2.Free;
  end
end;

Печать повернутого текста 3.

Некоторое время я делал так: я создавал шрифт, выбирал его в DC ...

function CreateMyFont(degree: Integer): HFONT;
begin
  CreateMyFont := CreateFont(
  -30, 0, degree, 0, 0,
  0, 0, 0, 1, OUT_TT_PRECIS,
  0, 0, 0, szFontName);
end;

.... и затем использовал любую функцию рисования для вывода текста.

Печать повернутого текста 4.

Приведенное выше решение (1) очень медленно, так как требует рисования текста и содержит, на мой взгляд, неэффективный метод вращения. Попробуйте взамен это:

procedure TForm1.TextUp(aRect:tRect;aTxt:String);
var 
  LFont: TLogFont; 
  hOldFont, hNewFont: HFont;
begin
  GetObject(Canvas.Font.Handle,SizeOf(LFont),Addr(LFont));
  LFont.lfEscapement := 900;
  hNewFont := CreateFontIndirect(LFont);
  hOldFont := SelectObject(Canvas.Handle,hNewFont);
  Canvas.TextOut(aRect.Left+2,aRect.Top,aTxt);
  hNewFont := SelectObject(Canvas.Handle,hOldFont);
  DeleteObject(hNewFont);
end;

Размер полей при печати.

Мне необходимо получить значения (лево, право, верх, низ) "непечатаемой области" принтера. Из статьи Delphi Developer "Take Control of your printer with a custom Delphi Class":

Чтобы получить левое и верхнее поле принтера, используйте Windows Escape функцию с параметром GETPRINTINGOFFSET.

var
  pntMargins: TPoint;
begin
  { @ означает " адрес переменной" }
  Escape(Printer.Handle, GETPRINTINGOFFSET,0,nil,@prntMargins);
end;

Получить размер правого и нижнего поля не так просто. Просто не существует необходимого эквивалента при вызове Escape функции. Но вы можете узнать эти значения, получив физическую ширину (physWidth) и высоту (physHeight) бумаги, печатаемую ширину (PrintWidth) и высоту (PrintHeight) страницы и выполнив несложные арифметические операции:

RightMargin    := physWidth  - PrintWidth  - LeftMargin;
BottomMargin := physHeight - PrintHeight - TopMargin;

Физический размер страницы можно получить с помощью Escape-функции, но на этот раз с использованием параметра GETPHYSPAGESIZE. Указатель pntPageSize содержит ширину страницы в pntPageSize.x и ее высоту в pntPageSize.y.

var
  pntPageSize: TPoint;
begin
  Escape(Printer.Handle, GETPHYSPAGESIZE,o,nil,@pntPageSize);
end; 

Печать через спулер на матричный принтер.

Автор: Оргиш Александр

Печатаю через спулер на матричный принтер текст таким образом :

var
  pcbNeeded: DWORD;
  FDevice: PChar;
  FPort: PChar;
  FDriver: PChar;
  FPrinterHandle: THandle;
  FDeviceMode: THandle;
  FJob: PADDJOBINFO1;
  Stream: TFileStream;
begin
  GetMem(FDevice, 128);
  GetMem(FDriver, 128);
  GetMem(FPort, 128);
  Printer.GetPrinter(FDevice, FDriver, FPort, FDeviceMode);
  if FDeviceMode = 0 then
    Printer.GetPrinter(FDevice, FDriver, FPort, FDeviceMode);
  if OpenPrinter(FDevice, FPrinterHandle, nil) then
  begin
    GetMem(FJob, 1024);
    //Добавляем задание, получаем имя файла в директории windows\spoool\
    AddJob(FPrinterHandle, 1, FJob, 1024, pcbNeeded);
    Stream := TFileStream.Create(FJob.Path, fmCreate);
    // Дальше пишем текст (+ESC команды!!!!) прямо в Stream
    // и не забываем переводить в DOS - кодировку
    .........
    Stream.Free;
    //Постановка задания в очередь - только теперь принтер начинает печатать
    ScheduleJob(FPrinterHandle, FJob.JobID);
    FreeMem(FJob);
    ClosePrinter(FPrinterHandle);
  end;
  FreeMem(FDevice, 128);
  FreeMem(FDriver, 128);
  FreeMem(FPort, 128);
end;

Печать StringGrid.

Прибыли ламеры? Сажайте в камеры!
procedure TForm1.Button1Click(Sender: TObject);
var K: Double;
begin
 Printer.BeginDoc;
 K :=  Printer.Canvas.Font.PixelsPerInch / Canvas.Font.PixelsPerInch*1.2;
 PrintStringGrid(StrGrid,
  K,  // Коэффициент
  200, // отступ от края листа в пихелах по Х
  200, // --"-- по Y
  200  // отступ снизу
  );
 Printer.EndDoc;
end;
{----------------------------------------------------------}
unit GrdPrn3;
interface
uses
 Windows, Classes, Graphics, Grids, Printers, SysUtils;
const
 OrdinaryLineWidth: Integer = 2;
 BoldLineWidth: Integer = 4;
procedure PrintStringGrid(Grid: TStringGrid; Scale: Double; LeftMargin,
TopMargin, BottomMargin:
Integer);
function DrawStringGridEx(Grid: TStringGrid; Scale: Double; FromRow,
LeftMargin, TopMargin, Yfloor: Integer; DestCanvas: TCanvas): Integer;
 // возвращает номер строки, которая не поместилась до Y = Yfloor
 // не проверяет, вылезает ли общая длина таблицы за пределы страницы
 // Слишком длинное слово обрежется
implementation
procedure PrintStringGrid(Grid: TStringGrid; Scale: Double; LeftMargin,
TopMargin, BottomMargin: Integer);
var NextRow: Integer;
begin
 //Printer.BeginDoc;
 if not Printer.Printing then raise Exception.Create('function
 PrintStringGrid must be called between Printer.BeginDoc
   and Printer.EndDoc');
 NextRow := 0;
 repeat
  NextRow := DrawStringGridEx(Grid, Scale, NextRow, LeftMargin, TopMargin,
   Printer.PageHeight - BottomMargin, Printer.Canvas);
  if NextRow <> -1 then Printer.NewPage;
 until NextRow = -1;
 //Printer.EndDoc;
end;
function DrawStringGridEx(Grid: TStringGrid; Scale: Double; FromRow,
LeftMargin, TopMargin, Yfloor: Integer; DestCanvas: TCanvas): Integer;
 // возвращает номер строки, которая не поместилась до Y = Yfloor
var
 i, j, d, TotalPrevH, TotalPrevW, CellH, CellW, LineWidth: Integer;
 R: TRect;
 s: string;
  procedure CorrectCellHeight(ARow: Integer);
  // вычисление правильной высоты ячейки с учетом многострочного текста
  // Текст рабивается только по словам слишком длинное слово обрубается
  var
   i, H: Integer;
   R: TRect;
   s: string;
  begin
   R := Rect(0, 0, CellH*2, CellH);
   s := ':)'; // Одинарная высота строки
   CellH := DrawText(DestCanvas.Handle, PChar(s), Length(s), R,
     DT_LEFT or DT_TOP or DT_WORDBREAK or DT_SINGLELINE or
     DT_NOPREFIX or DT_CALCRECT) + 3*d;
   for i := 0 to Grid.ColCount-1 do
   begin
    CellW := Round(Grid.ColWidths[i]*Scale);
    R := Rect(0, 0, CellW, CellH);
    //InflateRect(R, -d, -d);
    R.Left := R.Left+d;
    R.Top := R.Top + d;
    s := Grid.Cells[i, ARow];
    // Вычисление ширины и высоты
    H := DrawText(DestCanvas.Handle, PChar(s), Length(s), R,
     DT_LEFT or DT_TOP or DT_WORDBREAK or DT_NOPREFIX or DT_CALCRECT);
текста
    if CellH < H + 2*d then CellH := H + 2*d;
    // if CellW < R.Right - R.Left then Слишком длинное слово -
    // не помещается в одну строку; Перенос слов не поддерживается
   end;
  end;
begin
 Result := -1; // все строки уместились между TopMargin и Yfloor
 if (FromRow < 0)or(FromRow >= Grid.RowCount) then Exit;
 DestCanvas.Brush.Style := bsClear;
 DestCanvas.Font := Grid.Font;
//  DestCanvas.Font.Height := Round(Grid.Font.Height*Scale);
 DestCanvas.Font.Size := 10;
 Grid.Canvas.Font := Grid.Font;
 Scale := DestCanvas.TextWidth('test')/Grid.Canvas.TextWidth('test');
 d := Round(2*Scale);
 TotalPrevH := 0;
 for j := 0 to Grid.RowCount-1 do
 begin
  if (j >= Grid.FixedRows) and (j < FromRow) then Continue;
  // Fixed Rows рисуются на каждой странице
  TotalPrevW := 0;
  CellH := Round(Grid.RowHeights[j]*Scale);
  CorrectCellHeight(j);
  if TopMargin + TotalPrevH + CellH > YFloor then
  begin
   Result := j; // j-я строка не помещается в заданный диапазон
   if Result < Grid.FixedRows then Result := -1;
   // если фиксированные строки не влезают на страницу -
   // это тяж¸лый случай...
   Exit;
  end;
  for i := 0 to Grid.ColCount-1 do
  begin
   CellW := Round(Grid.ColWidths[i]*Scale);
   R := Rect(TotalPrevW, TotalPrevH, TotalPrevW + CellW,
     otalPrevH + CellH);
   OffSetRect(R, LeftMargin, TopMargin);
   if (i < Grid.FixedCols)or(j < Grid.FixedRows) then
     LineWidth := BoldLineWidth
   else
     LineWidth := OrdinaryLineWidth;
   DestCanvas.Pen.Width := LineWidth;
   if LineWidth > 0 then
    DestCanvas.Rectangle(R.Left, R.Top, R.Right+1, R.Bottom+1);
   //InflateRect(R, -d, -d);
   R.Left := R.Left+d;
   R.Top := R.Top + d;
   s := Grid.Cells[i, j];
   DrawText(DestCanvas.Handle, PChar(s), Length(s), R,
    DT_LEFT or DT_TOP or DT_WORDBREAK or DT_NOPREFIX);
   TotalPrevW := TotalPrevW + CellW; // Общая ширина всех предыдущих колонок
  end;
  TotalPrevH := TotalPrevH + CellH;  // Общая высота всех предыдущих строк
 end;
end;
end.

Печать TMemo, TStringList или TStrings.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  The following example project 
  shows how to print a memos lines, but you can as well use 
  listbox.items, it will work with every TStrings descendent, even a 
  TStirnglist. 
}
 unit PrintStringsUnit1;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
   Dialogs,
   StdCtrls;
 type
   TForm1 = class(TForm)
     Memo1: TMemo;
     Button1: TButton;
     procedure Button1Click(Sender : TObject);
   private
     { Private declarations }
     procedure PrintHeader(aCanvas : TCanvas; aPageCount : integer;
       aTextrect : TRect; var Continue : boolean);
     procedure PrintFooter(aCanvas : TCanvas; aPageCount : integer;
       aTextrect : TRect; var Continue : boolean);
   public
     { Public declarations }
   end;
 var
   Form1 : TForm1;
 implementation
 uses Printers;
 {$R *.DFM}
 type
   THeaderFooterProc =
     procedure(aCanvas : TCanvas; aPageCount : integer;
     aTextrect : TRect; var Continue : boolean) of object;
    { Prototype for a callback method that PrintString will call 
     when it is time to print a header or footer on a page. The 
     parameters that will be passed to the callback are: 
     aCanvas   : the canvas to output on 
     aPageCount: page number of the current page, counting from 1 
     aTextRect : output rectangle that should be used. This will be 
                 the area available between non-printable margin and 
                 top or bottom margin, in device units (dots). Output 
                 is not restricted to this area, though. 
     continue  : will be passed in as True. If the callback sets it 
                 to false the print job will be aborted. }
 {+------------------------------------------------------------ 
 | Function PrintStrings 
 | 
 | Parameters : 
 |   lines: 
 |     contains the text to print, already formatted into 
 |     lines of suitable length. No additional wordwrapping 
 |     will be done by this routine and also no text clipping 
 |     on the right margin! 
 |   leftmargin, topmargin, rightmargin, bottommargin: 
 |     define the print area. Unit is inches, the margins are 
 |     measured from the edge of the paper, not the printable 
 |     area, and are positive values! The margin will be adjusted 
 |     if it lies outside the printable area. 
 |   linesPerInch: 
 |     used to calculate the line spacing independent of font 
 |     size. 
 |   aFont: 
 |     font to use for printout, must not be Nil. 
 |   measureonly: 
 |     If true the routine will only count pages and not produce any 
 |     output on the printer. Set this parameter to false to actually 
 |     print the text. 
 |   OnPrintheader: 
 |     can be Nil. Callback that will be called after a new page has 
 |     been started but before any text has been output on that page. 
 |     The callback should be used to print a header and/or a watermark 
 |     on the page. 
 |   OnPrintfooter: 
 |     can be Nil. Callback that will be called after all text for one 
 |     page has been printed, before a new page is started. The  callback 
 |     should be used to print a footer on the page. 
 | Returns: 
 |   number of pages printed. If the job has been aborted the return 
 |   value will be 0. 
 | Description: 
 |   Uses the Canvas.TextOut function to perform text output in 
 |   the rectangle defined by the margins. The text can span 
 |   multiple pages. 
 | Nomenclature: 
 |   Paper coordinates are relative to the upper left corner of the 
 |   physical page, canvas coordinates (as used by Delphis  Printer.Canvas) 
 |   are relative to the upper left corner of the printable area. The 
 |   printorigin variable below holds the origin of the canvas  coordinate 
 |   system in paper coordinates. Units for both systems are printer 
 |   dots, the printers device unit, the unit for resolution is dots 
 |   per inch (dpi). 
 | Error Conditions: 
 |   A valid font is required. Margins that are outside the printable 
 |   area will be corrected, invalid margins will raise an EPrinter
  |   exception.
  | Created: 13.05.99 by P. Below
  +------------------------------------------------------------}
 function PrintStrings(Lines : TStrings;
   const leftmargin, rightmargin,
   topmargin, bottommargin: single;
   const linesPerInch: single;
   aFont: TFont;
   measureonly: Boolean;
   OnPrintheader,
   OnPrintfooter: THeaderFooterProc): Integer;
 var
   continuePrint: Boolean;     { continue/abort flag for callbacks }
   pagecount: Integer;     { number of current page }
   textrect: TRect;       { output area, in canvas coordinates }
   headerrect: TRect;       { area for header, in canvas 
coordinates }
   footerrect: TRect;       { area for footes, in canvas 
coordinates }
   lineheight: Integer;     { line spacing in dots }
   charheight: Integer;     { font height in dots  }
   textstart: Integer;     { index of first line to print on 
                                  current page, 0-based. }
   { Calculate text output and header/footer rectangles. }
   procedure CalcPrintRects;
   var
     X_resolution : Integer;  { horizontal printer resolution, in dpi }
     Y_resolution : Integer;  { vertical printer resolution, in dpi }
     pagerect : TRect;    { total page, in paper coordinates }
     printorigin : TPoint;   { origin of canvas coordinate system in 
                                paper coordinates. }
     { Get resolution, paper size and non-printable margin from 
      printer driver. }
     procedure GetPrinterParameters;
     begin
       with Printer.Canvas do
       begin
         X_resolution := GetDeviceCaps(Handle, LOGPIXELSX);
         Y_resolution := GetDeviceCaps(Handle, LOGPIXELSY);
         printorigin.X := GetDeviceCaps(Handle, PHYSICALOFFSETX);
         printorigin.Y := GetDeviceCaps(Handle, PHYSICALOFFSETY);
         pagerect.Left := 0;
         pagerect.Right := GetDeviceCaps(Handle, PHYSICALWIDTH);
         pagerect.Top := 0;
         pagerect.Bottom := GetDeviceCaps(Handle, PHYSICALHEIGHT);
       end; { With }
     end; { GetPrinterParameters }
     { Calculate area between the requested margins, paper-relative. 
      Adjust margins if they fall outside the printable area. 
      Validate the margins, raise EPrinter exception if no text area 
      is left. }
     procedure CalcRects;
     var
       max : integer;
     begin
       with textrect do
       begin
         { Figure textrect in paper coordinates }
         Left := Round(leftmargin * X_resolution);
         if Left < printorigin.x then
           Left := printorigin.x;
         Top := Round(topmargin * Y_resolution);
         if Top < printorigin.y then
           Top := printorigin.y;
           { Printer.PageWidth and PageHeight return the size of the 
            printable area, we need to add the printorigin to get the 
            edge of the printable area in paper coordinates. }
         Right := pagerect.Right - Round(rightmargin * X_resolution);
         max := Printer.PageWidth + printorigin.X;
         if Right > max then
           Right := max;
         Bottom := pagerect.Bottom - Round(bottommargin *
           Y_resolution);
         max := Printer.PageHeight + printorigin.Y;
         if Bottom > max then
           Bottom := max;
         { Validate the margins. }
         if (Left >= Right) or (Top >= Bottom) then
           raise EPrinter.Create('PrintString: the supplied margins are too large, there'
             +
             'is no area to print left on the page.');
       end; { With }
       { Convert textrect to canvas coordinates. }
       OffsetRect(textrect, - printorigin.X, - printorigin.Y);
       { Build header and footer rects. }
       headerrect := Rect(textrect.Left, 0,
         textrect.Right, textrect.Top);
       footerrect := Rect(textrect.Left, textrect.Bottom,
         textrect.Right, Printer.PageHeight);
     end; { CalcRects }
   begin { CalcPrintRects }
     GetPrinterParameters;
     CalcRects;
     lineheight := round(Y_resolution / linesPerInch);
   end; { CalcPrintRects }
   { Print a page with headers and footers. }
   procedure PrintPage;
     procedure FireHeaderFooterEvent(event : THeaderFooterProc; r : TRect);
     begin
       if Assigned(event) then
       begin
         event(Printer.Canvas,
           pagecount,
           r,
           ContinuePrint);
           { Revert to our font, in case event handler changed 
            it. }
         Printer.Canvas.Font := aFont;
       end; { If }
     end; { FireHeaderFooterEvent }
     procedure DoHeader;
     begin
       FireHeaderFooterEvent(OnPrintHeader, headerrect);
     end; { DoHeader }
     procedure DoFooter;
     begin
       FireHeaderFooterEvent(OnPrintFooter, footerrect);
     end; { DoFooter }
     procedure DoPage;
     var
       y : integer;
     begin
       y := textrect.Top;
       while (textStart < Lines.Count) and
         (y <= (textrect.Bottom - charheight)) do
       begin
           { Note: use TextRect instead of TextOut to effect clipping 
            of the line on the right margin. It is a bit slower, 
            though. The clipping rect would be 
            Rect( textrect.left, y, textrect.right, y+charheight). }
         printer.Canvas.TextOut(textrect.Left, y, Lines[textStart]);
         Inc(textStart);
         Inc(y, lineheight);
       end; { While }
     end; { DoPage }
   begin { PrintPage }
     DoHeader;
     if ContinuePrint then
     begin
       DoPage;
       DoFooter;
       if (textStart < Lines.Count) and ContinuePrint then
       begin
         Inc(pagecount);
         Printer.NewPage;
       end; { If }
     end;
   end; { PrintPage }
 begin { PrintStrings }
   Assert(Assigned(afont),
     'PrintString: requires a valid aFont parameter!');
   continuePrint := True;
   pagecount := 1;
   textstart := 0;
   Printer.BeginDoc;
   try
     CalcPrintRects;
     {$IFNDEF WIN32}
     { Fix for Delphi 1 bug. }
     Printer.Canvas.Font.PixelsPerInch := Y_resolution;
     {$ENDIF }
     Printer.Canvas.Font := aFont;
     charheight := printer.Canvas.TextHeight('Ду');
     while (textstart < Lines.Count) and ContinuePrint do
       PrintPage;
   finally
     if continuePrint and not measureonly then
       Printer.EndDoc
     else
     begin
       Printer.Abort;
     end;
   end;
   if continuePrint then
     Result := pagecount
   else
     Result := 0;
 end; { PrintStrings }
 procedure TForm1.Button1Click(Sender : TObject);
 begin
   ShowMessage(Format('%d pages printed',
     [PrintStrings(memo1.Lines,
     0.75, 0.5, 0.75, 1,
     6,
     memo1.Font,
     False,
     PrintHeader, PrintFooter)
     ]));
 end;
 procedure TForm1.PrintFooter(aCanvas : TCanvas; aPageCount : integer;
   aTextrect : TRect; var Continue : boolean);
 var
   S: string;
   res: integer;
 begin
   with aCanvas do
   begin
     { Draw a gray line one point wide below the text }
     res := GetDeviceCaps(Handle, LOGPIXELSY);
     pen.Style := psSolid;
     pen.Color := clGray;
     pen.Width := Round(res / 72);
     MoveTo(aTextRect.Left, aTextRect.Top);
     LineTo(aTextRect.Right, aTextRect.Top);
     { Print the page number in Arial 8pt, gray, on right side of 
      footer rect. }
     S := Format('Page %d', [aPageCount]);
     Font.Name := 'Arial';
     Font.Size := 8;
     Font.Color := clGray;
     TextOut(aTextRect.Right - TextWidth(S), aTextRect.Top + res div
       18,
       S);
   end;
 end;
 procedure TForm1.PrintHeader(aCanvas : TCanvas; aPageCount : integer;
   aTextrect : TRect; var Continue : boolean);
 var
   res: Integer;
 begin
   with aCanvas do
   begin
     { Draw a gray line one point wide 4 points above the text }
     res := GetDeviceCaps(Handle, LOGPIXELSY);
     pen.Style := psSolid;
     pen.Color := clGray;
     pen.Width := Round(res / 72);
     MoveTo(aTextRect.Left, aTextRect.Bottom - res div 18);
     LineTo(aTextRect.Right, aTextRect.Bottom - res div 18);
     { Print the company name in Arial 8pt, gray, on left side of 
      footer rect. }
     Font.Name := 'Arial';
     Font.Size := 8;
     Font.Color := clGray;
     TextOut(aTextRect.Left, aTextRect.Bottom - res div 10 -
       TextHeight('W'),
       'W. W. Shyster & Cie.');
   end;
 end;
 end.

Как определить количество заданий на печать.

Автор: Олег Кулабухов

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

type
  TForm1 = class(TForm)
    Label1: TLabel;
  private
    { Private declarations }
    procedure WM_SpoolerStatus(var Msg: TWMSPOOLERSTATUS);
      message WM_SPOOLERSTATUS;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.WM_SpoolerStatus(var Msg: TWMSPOOLERSTATUS);
begin
  Lable1.Caption := IntToStr(msg.JobsLeft) +
    ' Jobs currenly in spooler';
  msg.Result := 0;
end;

Распечатать текст.

var
  F: TextFile; 
begin 
  AssignFile(F, 'LPT1'); // LPT2, COM1, COM2... 
  Rewrite(F); 
  Writeln(F, 'Hello'); 
  Writeln(F, 'There!'); 
  Writeln(F, #12); 
  CloseFile(F); 
end;

Сброс на печать текстового файла.

Используйте CreateFile для получения дескриптора LPT1.

LPTHandle := CreateFile( 'LPT1',GENERIC_WRITE,
0, PSecurityAttributes(nil),
OPEN_EXISTING, FILE_FLAG_OVERLAPPED,
0);

Затем используйте WriteFile для посылки строки символов или сделайте так:

While not TransmitCommChar( LPTHandle, CharToSend ) do
  Application.ProcessMessages;

Этим вы перешлете в параллельный порт строчку символов. Для обработки код ждет последний символ, после чего немедленно посылает новый. Метод на поверку оказался весьма быстр.

Вывод текста на печать, используя com порт.

Сели в машину, компьютерщик, новый русский и слесарь. Заводят - не заводится. Слесарь говорит: - Сейчас я гляну, что случилось, все сделаю - и поедем. Новый русский говорит: - Сиди, я сейчас по сотовому позвоню, приедет бригада мастеров и все быстро сделает. А компьютерщик говорит: - Может, все выйдем из машины и снова зайдём?
var
  Printer: THandle;
  N : Cardinal;
  C : POverlapped;
begin
  //Открываем порт принтера для записи
  Printer := CreateFile(PChar('LPT1'),
  GENERIC_READ or GENERIC_WRITE, 0, nil,
  OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
  //Печатаем слово 'Hello World';
  WriteFile(Printer, 'Hello World', 11, N, c);
  //Закрываем порт
  CloseHandle(Printer);

Распечатать TImage.

{1.}
uses 
  printers; 
procedure TForm1.Button1Click(Sender: TObject); 
var  
  ScaleX, ScaleY: Integer; 
  RR: TRect; 
begin 
  with Printer do 
  begin 
    BeginDoc; 
    // Mit BeginDoc wird ein Druckauftrag initiiert. 
    // The StartDoc function starts a print job. 
    try 
      ScaleX := GetDeviceCaps(Handle, logPixelsX) div PixelsPerInch; 
      ScaleY := GetDeviceCaps(Handle, logPixelsY) div PixelsPerInch; 
      // Informationen uber die Auflosung 
      // Retrieves information about the Pixels per Inch of the Printer. 
      RR := Rect(0, 0, Image1.picture.Width * scaleX, Image1.Picture.Height * ScaleY); 
      Canvas.StretchDraw(RR, Image1.Picture.Graphic); 
      // An die Auflosung anpassen 
      // Stretch to fit 
    finally 
      EndDoc;   //Methode EndDoc beendet den aktuellen Druckauftrag und schlie?t die 
      // Textdatei-Variable. 
      // Steht in finally - um auch bei Abbruch des Druckauftrages Papierausgabe 
      // sicherzustellen 
    end; 
  end; 
end;
{2.} 
// Based on posting to borland.public.delphi.winapi by Rodney E Geraghty, 8/8/97. 
procedure PrintBitmap(Canvas: TCanvas; DestRect: TRect; Bitmap: TBitmap); 
var 
  BitmapHeader: pBitmapInfo; 
  BitmapImage: Pointer; 
  HeaderSize: DWORD; 
  ImageSize: DWORD; 
begin 
  GetDIBSizes(Bitmap.Handle, HeaderSize, ImageSize); 
  GetMem(BitmapHeader, HeaderSize); 
  GetMem(BitmapImage, ImageSize); 
  try 
    GetDIB(Bitmap.Handle, Bitmap.Palette, BitmapHeader^, BitmapImage^); 
    StretchDIBits(Canvas.Handle, 
      DestRect.Left, DestRect.Top,    // Destination Origin 
      DestRect.Right - DestRect.Left, // Destination Width 
      DestRect.Bottom - DestRect.Top, // Destination Height 
      0, 0,                           // Source Origin 
      Bitmap.Width, Bitmap.Height,    // Source Width & Height 
      BitmapImage, 
      TBitmapInfo(BitmapHeader^), 
      DIB_RGB_COLORS, 
      SRCCOPY) 
  finally 
    FreeMem(BitmapHeader); 
    FreeMem(BitmapImage) 
  end 
end {PrintBitmap};

Распечатать TImage 2.

Автор: http://www.experts-exchange.com

uses
  printers;
procedure DrawImage(Canvas: TCanvas; DestRect: TRect; ABitmap: TBitmap);
var
  Header, Bits: Pointer;
  HeaderSize: DWORD;
  BitsSize: DWORD;
begin
  GetDIBSizes(ABitmap.Handle, HeaderSize, BitsSize);
  Header := AllocMem(HeaderSize);
  Bits := AllocMem(BitsSize);
  try
    GetDIB(ABitmap.Handle, ABitmap.Palette, Header^, Bits^);
    StretchDIBits(Canvas.Handle, DestRect.Left, DestRect.Top,
      DestRect.Right, DestRect.Bottom,
      0, 0, ABitmap.Width, ABitmap.Height, Bits, TBitmapInfo(Header^),
      DIB_RGB_COLORS, SRCCOPY);
  finally
    FreeMem(Header, HeaderSize);
    FreeMem(Bits, BitsSize);
  end;
end;
procedure PrintImage(Image: TImage; ZoomPercent: Integer);
// if ZoomPercent=100, Image will be printed across the whole page
var
  relHeight, relWidth: integer;
begin
  Screen.Cursor := crHourglass;
  Printer.BeginDoc;
  with Image.Picture.Bitmap do
  begin
    if ((Width / Height) > (Printer.PageWidth / Printer.PageHeight)) then
    begin
      // Stretch Bitmap to width of PrinterPage
      relWidth := Printer.PageWidth;
      relHeight := MulDiv(Height, Printer.PageWidth, Width);
    end
    else
    begin
      // Stretch Bitmap to height of PrinterPage
      relWidth := MulDiv(Width, Printer.PageHeight, Height);
      relHeight := Printer.PageHeight;
    end;
    relWidth := Round(relWidth * ZoomPercent / 100);
    relHeight := Round(relHeight * ZoomPercent / 100);
    DrawImage(Printer.Canvas, Rect(0, 0, relWidth, relHeight),
      Image.Picture.Bitmap);
  end;
  Printer.EndDoc;
  Screen.cursor := crDefault;
end;
// Example Call:
procedure TForm1.Button1Click(Sender: TObject);
begin
  // Print image at 40% zoom:
  PrintImage(Image1, 40);
end;

Реализация команды PrintTo.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   Printers, Shellapi;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   Device: array[0..255] of Char;
   Driver: array[0..255] of Char;
   Port: array[0..255] of Char;
   S: string;
   hDeviceMode: THandle;
   documentname: string;
 begin
   Printer.PrinterIndex := -1;  // select a printer, in this case default 
  Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
   S := Format('"%s" "%s" "%s"', [Device, Driver, Port]);
   documentname := 'c:\anydocument.doc';
   ShellExecute(Handle, 'printto', PChar(documentname), PChar(S), nil, SW_HIDE);
 end;

Печать Dos-файла в порт напрямую.

Автор: http://blackman.wp-club.net При печати Dos-файла в порт напрямую можно это сделать.

Например, напечатать за 2 прохода: ESC @ - инициализация принтера ESC G - включение режима печати за 2 прохода ESC H - выключение режима печати за 2 прохода.

var
  FileOut: TextFile;
  filename: string[128];
  ....
    Filename := 'PRN';
  AssignFile(Fileout, Filename);
  ...
    Write(FileOut, Chr(27) + '@');
  Str1 := AnToAs(chr(27) + 'G' + 'Double' + chr(27) + 'H');
  Writeln(FileOut, Str1);
  ...
    {преобразование Ansi to Ascii}
function AnToAs(s: string): string;
var
  i, kod: Integer;
begin
  Result := s;
  for i := 1 to length(s) do
  begin
    kod := Ord(s[i]);
    if kod 13 then
      Result[i] := ' ';
    if (kod >= 192) and (kod = 239) then
      Result[i] := Chr(kod - 64);
    if (kod >= 240) and (kod = 255) then
      Result[i] := Chr(kod - 16);
    if kod = 168 then
      Result[i] := Chr(240);
    if kod = 184 then
      Result[i] := Chr(241);
  end;
end;

Распечатать TStringList.

uses 
  Printers; 
procedure PrintGrid(sGrid: TStringGrid; sTitle: string); 
var 
  X1, X2: Integer; 
  Y1, Y2: Integer; 
  TmpI: Integer; 
  F: Integer; 
  TR: TRect; 
begin 
  Printer.Title := sTitle; 
  Printer.BeginDoc; 
  Printer.Canvas.Pen.Color  := 0; 
  Printer.Canvas.Font.Name  := 'Times New Roman'; 
  Printer.Canvas.Font.Size  := 12; 
  Printer.Canvas.Font.Style := [fsBold, fsUnderline];
  Printer.Canvas.TextOut(0, 100, Printer.Title); 
  for F := 1 to sGrid.ColCount - 1 do  
  begin
    X1 := 0; 
    for TmpI := 1 to (F - 1) do 
      X1 := X1 + 5 * (sGrid.ColWidths[TmpI]); 
    Y1 := 300; 
    X2 := 0; 
    for TmpI := 1 to F do 
      X2 := X2 + 5 * (sGrid.ColWidths[TmpI]); 
    Y2 := 450; 
    TR := Rect(X1, Y1, X2 - 30, Y2); 
    Printer.Canvas.Font.Style := [fsBold]; 
    Printer.Canvas.Font.Size := 7; 
    Printer.Canvas.TextRect(TR, X1 + 50, 350, sGrid.Cells[F, 0]); 
    Printer.Canvas.Font.Style := []; 
    for TmpI := 1 to sGrid.RowCount - 1 do  
    begin 
      Y1 := 150 * TmpI + 300; 
      Y2 := 150 * (TmpI + 1) + 300; 
      TR := Rect(X1, Y1, X2 - 30, Y2); 
      Printer.Canvas.TextRect(TR, X1 + 50, Y1 + 50, sGrid.Cells[F, TmpI]); 
    end; 
  end; 
  Printer.EndDoc; 
end; 
//Examplem, Beispiel: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  PrintGrid(StringGrid1, 'Print Stringgrid'); 
end;

Распечатать TStrings на принтере по умолчанию.

procedure PrintStrings(S: TStrings; Font: TFont; Title: string);
var
 LeftMargin, TopMargin, LineCoord, LineOnPage, LinesOnDoc,
 CurrentLine, TextHeight, LinesPerPage, LineInterval: integer;
 procedure StartDoc;
 begin
   LinesOnDoc := S.Count;
   Printer.Canvas.Font.Assign(Font);
   Printer.Canvas.TextOut(0, 0, ' ');
   LeftMargin := (Printer.Canvas.Font.PixelsPerInch) div 2;
   TopMargin  := (Printer.Canvas.Font.PixelsPerInch) div 2;
   TextHeight := Abs(Printer.Canvas.Font.Height);
   LineInterval := TextHeight + (TextHeight div 2);
   LinesPerPage := (Printer.PageHeight - TopMargin) div LineInterval;
   CurrentLine := 0;
 end;
 function MorePages:boolean;
 begin
   Result := (CurrentLine <  LinesOnDoc) and
             not Printer.Aborted;
 end;
 procedure StartPage;
 begin
   LineOnPage := 0;
   LineCoord := TopMargin;
 end;
 procedure NextPage;
 begin
   if MorePages then Printer.NewPage;
 end;
 function MoreLines:boolean;
 begin
   Result := (LineOnPage <  LinesPerPage) and
             (LineOnPage <  LinesOnDoc) and
             not Printer.Aborted;
 end;
 procedure NextLine;
 begin
   Inc(LineOnPage);
   Inc(LineCoord, LineInterval);
   Inc(CurrentLine);
 end;
 procedure PrintLine;
 begin
   Printer.Canvas.TextOut(LeftMargin, LineCoord, S.Strings[CurrentLine]);
 end;
begin
 Printer.Title := Title;
 Printer.BeginDoc;
 StartDoc;
 while MorePages do
 begin
   StartPage;
   while MoreLines do
   begin
     PrintLine;
     NextLine;
     Application.ProcessMessages;
   end;
   NextPage;
 end;
 Printer.EndDoc;
end;

Показать диалог печати и распечатать текстовый файл.

unit Unit1; 
interface 
uses 
  {...,}ComCtrls; 
type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    PrintDialog1: TPrintDialog; 
    RichEdit1: TRichEdit; 
    procedure Button1Click(Sender: TObject); 
   {...} 
  end; 
var 
  Form1: TForm1; 
implementation 
{$R *.DFM} 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  if PrintDialog1.Execute then 
    Richedit1.Print('Ltp1'); {Ltp1, Lpt2 are printers} 
end; 
end.

Как распечатать WEB страничку при помощи HTML контрола.

Ну и почерк у тебя, Xerox!

Можно использовать два метода HTML контрола: AutoPrint или PrintPage.

Пример использования AutoPrint:

uses
  Printers;
procedure TForm1.Button1Click(Sender: TObject);
var
  OldCur: TCursor;
begin
  OldCur := Screen.Cursor;
  with Printer do
  begin
    BeginDoc;
    HTML1.AutoPrint(handle);
    Title := HTML1.URL;
    EndDoc;
  end;
  Screen.Cursor := OldCur;
end;

Как печатать без TPrinter.

Автор: Олег Кулабухов

Пример показывает, как использовать PrintDlg() для того, чтобы дать возможность пользователю выбрать принтер, и напечатать две страницы.

uses CommDlg;
{$IFNDEF WIN32}
const
  MAX_PATH = 144;
{$ENDIF}
procedure TForm1.Button1Click(Sender: TObject);
var
  Pd: TPrintDlg;
  DocInfo: TDocInfo;
begin
  FillChar(Pd, sizeof(Pd), #0);
  Pd.lStructSize := sizeof(Pd);
  Pd.hWndOwner := Form1.Handle;
  Pd.Flags := PD_RETURNDC;
  if PrintDlg(pd) then
  begin
    FillChar(DocInfo, sizeof(DocInfo), #0);
    DocInfo.cbSize := SizeOf(DocInfo);
    GetMem(DocInfo.lpszDocName, 32);
    GetMem(DocInfo.lpszOutput, MAX_PATH);
    lStrCpy(DocInfo.lpszDocName, 'My Document');
    {Add this line to print to a file }
    lStrCpy(DocInfo.lpszOutput, 'C:\Download\Test.doc');
    StartDoc(Pd.hDc, DocInfo);
    StartPage(Pd.hDc);
    TextOut(Pd.hDc, 100, 100, 'Page 1', 6);
    EndPage(Pd.hDc);
    StartPage(Pd.hDc);
    TextOut(Pd.hDc, 100, 100, 'Page 2', 6);
    EndPage(Pd.hDc);
    EndDoc(Pd.hDc);
    FreeMem(DocInfo.lpszDocName, 32);
    FreeMem(DocInfo.lpszOutput, MAX_PATH);
  end;
end;

Как печатать в цвете.

Более мощный компьютер глючит быстрее и точнее.

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

uses Printers;
procedure TForm1.Button1Click(Sender: TObject);
var
  Device: array [0..255] of char;
  Driver: array [0..255] of char;
  Port: array [0..255] of char;
  hDMode: THandle;
  PDMode: PDEVMODE;
begin
  with Printer do
  begin
    PrinterIndex := PrinterIndex;
    GetPrinter(Device, Driver, Port, hDMode);
    if hDMode <> 0 then
    begin
      pDMode := GlobalLock(hDMode);
      if pDMode <> nil then
      begin
        pDMode.dmFields := pDMode.dmFields or dm_Color;
        pDMode.dmColor := DMCOLOR_COLOR;
        GlobalUnlock(hDMode);
      end;
    end;
    PrinterIndex := PrinterIndex;
    BeginDoc;
    Canvas.Font.Color := clRed;
    Canvas.TextOut(100,100, 'Delphi World is COOL :)');
    EndDoc;
  end;
end;

Как распечатать PRN файл.

Автор: http://www.swissdelphicenter.ch

uses
  Printers, Winspool;
function SpoolFile(const FileName, PrinterName: string): Integer;
var
  Buffer: record
    JobInfo: record // ADDJOB_INFO_1
      Path: PChar;
      JobID: DWORD;
    end;
    PathBuffer: array[0..255] of Char;
  end;
  SizeNeeded: DWORD;
  Handle: THandle;
  PrtName: string;
  ok: Boolean;
begin
  // Flush job to printer
  PrtName := PrinterName;
  if PrtName = '' then
    PrtName := Printer.Printers[Printer.PrinterIndex]; // Default printer name
  ok := False;
  if OpenPrinter(PChar(PrtName), Handle, nil) then
    if AddJob(Handle, 1, @Buffer, SizeOf(Buffer), SizeNeeded) then
      if CopyFile(PChar(FileName), Buffer.JobInfo.Path, True) then
        if ScheduleJob(Handle, Buffer.JobInfo.JobID) then
          ok := True;
  if not ok then
    Result := GetLastError
  else
    Result := 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if SpoolFile('c:\test.prn', Printer.Printers[0]) = 0 then
    ShowMessage('No error...');
end;

Процедурные типы.

Основное назначение процедурных типов - дать программисту гибкие средства передачи функций и процедур в качестве фактических параметров обращения к другим процедурам и функциям.

Для объявления процедурного типа используется заголовок процедуры (функции), в котором опускается ее имя, например:

type
  Proc1 = procedure(a, b, с: Real; var d: Real);
  Proc2 = procedure(var a, b);
  РгосЗ = procedure;
  Func1 = function: string;
  Func2 = function(var s: string): Real;
Как видно из приведенных примеров, существует два процедурных типа: тип-процедура и тип-функция.

В следующий программе иллюстрируется механизм передачи процедур в качестве фактических параметров вызова. Программа выводит на экран таблицу двух функций: sin1 (х) = (sin(x) + 1) * Ехр(-х) и cosi(x) = (Cos(x) + 1) * Ехр(-х) . Вычисление и печать значений этих функций реализуются в процедуре printFunc, которой в качестве параметров передается количество np вычислений функции в диапазоне х от 0 до 2*3.141592 и имя нужной функции.

function Sinl(X: Real): Real;
begin
  Result := (Sin(X) + 1) * Exp(-X)
end; // Sin 1
function Cosl(X: Real): Real;
begin
  Result := (Cos(X) + 1) * Exp(-X)
end; // Cosi
procedure TfmExample.bbRunClick(Sender: TObject);
type
  // Процедурный тип Procedure PrintFunc(NP: Integer; F; Func);
  Func = function(X: Real): Real;
var
  k: Integer;
  X: Real;
begin
  for k := 0 to NP do
  begin
    X := k * 2 * pi / NP;
    mmOutput.Lines.Add(FloatToStrF(X, ffExponent, 10, 2) + #9#9+
     FloatToStrF(F(X), ffExponent, 10, 2));
  end;
end; // PrintFunc
begin // bbRunClick
  nmiOutput.Lines.Add(#9'Функция SINI:');
  PrintFunc(10, Sini);
  mmOutput.Lines.Add(#9'Функция COSI:');
  PrintFunc(10, Cosi);
end;
Обратите внимание: передаваемые подпрограммы не могут быть локальными, т. е. процедурами или функциями, объявленными внутри другой подпрограммы. Вот почему описание подпрогра^.' sini и cosi размещаются вне обработчика bbRunciick, но выше не." по тексту модуля. Замечу, что символ #9 - это символ табуляции. который вставляется в формируемые строки для разделения колонок с цифрами.

В программе могут быть объявлены переменные процедурных типов, например,так:

var
  p1: Proc1;
  fl, f2: Func2;
  ар: array[1..N] of Proc1;

Переменным процедурных типов допускается присваивать в качестве значений имена соответствующих подпрограмм. После такого присваивания имя переменной становится синонимом имени подпрограммы.

Unit с полезными функциями для работы с процессами.

Автор: Alex Kantchev WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Unit с полезными функциями для работы с процессами
Этот Unit содержит полезные функции для работы с процессами.
Взять информацию о данном процессе, обо всех процессах, убить процесс, и т.д.
Полезна при создании системных приложений под Win32.
Надо хорошо оттестировать этот Unit.
Зависимости: windows, PSAPI, TlHelp32, SysUtils;
Автор:       Alex Kantchev, stoma@bitex.bg
Copyright:   Моя разработка, некоторые функции базируются
             на примере в MSDN jan 2000 Collection
Дата:        5 июня 2002 г.
***************************************************** }
unit ProcUtilz;
interface
uses windows, PSAPI, TlHelp32, SysUtils;
type
  TLpModuleInfo = packed record
    ModuleInfo: LPMODULEINFO;
    ModulePID: Cardinal;
    ModuleName: string;
  end;
type
  TLpModuleInfoArray = array of TLpModuleInfo;
function RegisterServiceProcess(dwProcessID, dwType: Integer): Integer; stdcall;
external 'KERNEL32.DLL';
function DisplayProcessInThreeFingerSalute(PID: Integer; Disp: Boolean):
  Boolean;
function TakeProcessID(WindowTitle: string): Integer;
function GetCurrAppPID: Integer;
function GetAllProcessesInfo(ExtractFullPath: Boolean = false):
  TLpModuleInfoArray;
function ExtractExeFromModName(ModuleName: string): string;
function TerminateTask(PID: integer): integer;
implementation
//Wziat PID na danoi process ot nego window title
function TakeProcessID(WindowTitle: string): Integer;
var
  WH: THandle;
begin
  result := 0;
  WH := FindWindow(nil, pchar(WindowTitle));
  if WH <> 0 then
    GetWindowThreadProcessID(WH, @Result);
end;
//Wziat PID na tekuchii process
function GetCurrAppPID: Integer;
begin
  GetCurrAppPID := GetCurrentProcessID;
end;
//Pokzat process s PID v task menagera Windows 9X
//WNIMANIE: Rabotaet tolko pod Win9x !!!!
function DisplayProcessInThreeFingerSalute(PID: Integer; Disp: Boolean):
  Boolean;
begin
  result := false;
  if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
  begin
    try
      if Disp = True then
        RegisterServiceProcess(PID, 0)
      else
        RegisterServiceProcess(PID, 1);
    except
      result := false;
    end;
  end;
  DisplayProcessInThreeFingerSalute := result;
end;
//Ostanavlivaet rabotu procesa. Ne rabotaet so WinNT
//serviznae processi.
function TerminateTask(PID: integer): integer;
var
  process_handle: integer;
  lpExitCode: Cardinal;
begin
  process_handle := openprocess(PROCESS_ALL_ACCESS, true, pid);
  GetExitCodeProcess(process_handle, lpExitCode);
  if (process_handle = 0) then
    TerminateTask := GetLastError
  else if terminateprocess(process_handle, lpExitCode) then
  begin
    TerminateTask := 0;
    CloseHandle(process_handle);
  end
  else
  begin
    TerminateTask := GetLastError;
    CloseHandle(process_handle);
  end;
end;
//Wziat informacia ob processse po ego PID
//Testirano pod WinNT.
function GetProcessInfo(PID: WORD): LPMODULEINFO;
var
  RetVal: LPMODULEINFO;
  hProc: DWORD;
  hMod: HMODULE;
  cm: cardinal;
begin
  hProc := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false,
    PID);
  GetMem(RetVal, sizeOf(LPMODULEINFO));
  if not (hProc = 0) then
  begin
    EnumProcessModules(hProc, @hMod, 4, cm);
    GetModuleInformation(hProc, hMod, RetVal, SizeOf(RetVal));
  end;
  GetProcessInfo := RetVal;
end;
//Wziat executable processa ot ego polnai put
function ExtractExeFromModName(ModuleName: string): string;
begin
  ExtractExeFromModName := Copy(ModuleName, LastDelimiter('\', ModuleName) + 1,
    Length(ModuleName));
  ;
end;
//Wziat informacia ob wse processi rabotaushtie w tekuchii
//moment. Testirano pod WinNT
function GetAllProcessesInfo(ExtractFullPath: Boolean = false):
  TLpModuleInfoArray;
var
  ProcList: array[0..$FFF] of DWORD;
  RetVal: TLpModuleInfoArray;
  ProcCnt: Cardinal;
  I, MaxCnt: WORD;
  ModName: array[0..max_path] of char;
  ph, mh: THandle;
  cm: Cardinal;
  SnapShot: THandle;
  ProcEntry: TProcessEntry32;
  RetValLength, CVal: WORD;
  ModInfo: LPMODULEINFO;
begin
  //case the platform is Win9X
  if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
  begin
    GetMem(ModInfo, SizeOf(LPMODULEINFO));
    SnapShot := CreateToolhelp32Snapshot(th32cs_snapprocess, 0);
    RetValLength := 0;
    CVal := 0;
    if not integer(SnapShot) = -1 then
    begin
      ProcEntry.dwSize := sizeof(TProcessEntry32);
      if Process32First(SnapShot, ProcEntry) then
        repeat
          //get the size of out array
          Inc(RetValLength);
        until not Process32Next(SnapShot, ProcEntry);
      //set the size of the output array
      SetLength(RetVal, RetValLength);
      //iterate through processes and get their info
      if Process32First(SnapShot, ProcEntry) then
        repeat
          begin
            Inc(CVal);
            ModInfo.lpBaseOfDll := nil;
            ModInfo.SizeOfImage := ProcEntry.dwSize;
            ModInfo.EntryPoint := nil;
            RetVal[CVal].ModuleInfo := ModInfo;
            RetVal[CVal].ModulePID := ProcEntry.th32ProcessID;
            if (ExtractFullPath) then
              RetVal[CVal].ModuleName := string(ProcEntry.szExeFile)
            else
              RetVal[CVal].ModuleName :=
                ExtractExeFromModName(string(ProcEntry.szExeFile));
            ModInfo := nil;
          end;
        until not Process32Next(SnapShot, ProcEntry);
    end;
  end
    //case the platform is WinNT/2K/XP
  else
  begin
    EnumProcesses(@ProcList, sizeof(ProcList), ProcCnt);
    MaxCnt := ProcCnt div 4;
    SetLength(RetVal, MaxCnt);
    //iterate through processes and get their info
    for i := Low(RetVal) to High(RetVal) do
    begin
      //Check for reserved PIDs
      if ProcList[i] = 0 then
      begin
        RetVal[i].ModuleName := 'System Idle Process';
        RetVal[i].ModulePID := 0;
        RetVal[i].ModuleInfo := ProcUtilz.GetProcessInfo(i);
      end
      else if ProcList[i] = 8 then
      begin
        RetVal[i].ModuleName := 'System';
        RetVal[i].ModulePID := 8;
        RetVal[i].ModuleInfo := ProcUtilz.GetProcessInfo(i);
      end
        //Gather info about all processes
      else
      begin
        RetVal[i].ModulePID := ProcList[i];
        RetVal[i].ModuleInfo := GetProcessInfo(ProcList[i]);
        //get module name
        ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false,
          ProcList[i]);
        if ph > 0 then
        begin
          EnumProcessModules(ph, @mh, 4, cm);
          GetModuleFileNameEx(ph, mh, ModName, sizeof(ModName));
          if (ExtractFullPath) then
            RetVal[i].ModuleName := string(ModName)
          else
            RetVal[i].ModuleName := ExtractExeFromModName(string(ModName));
        end
        else
          RetVal[i].ModuleName := 'UNKNOWN';
        CloseHandle(ph);
      end;
    end;
  end;
  //return the array of LPMODULEINFO structz
  GetAllProcessesInfo := RetVal;
end;
end.

Пример использования:

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  PC: WORD;
begin
  ListBox1.Clear;
  ProcArr := TLpModuleInfoArray(ProcUtilz.GetAllProcessesInfo);
  PC := 0;
  for i := Low(ProcArr) to High(ProcArr) do
  begin
    ListBox1.Items.Add('Process Name: ' + ProcArr[i].ModuleName +
      ' : Proccess ID ' + IntToStr(ProcArr[i].ModulePID) + ' : Image Size: ' +
      IntToStr(ProcArr[i].ModuleInfo.SizeOfImage));
    Inc(PC);
  end;
  ListBox1.Items.Add('Total process count: ' + IntToStr(PC));
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  EC: Integer;
begin
  EC := ProcUtilz.TerminateTask(ProcArr[ListBox1.ItemIndex].ModulePID);
  if EC = 0 then
    MessageDlg('Task terminated successfully!', mtInformation, [mbOK], 0)
  else
    MessageDlg('Unable to terminate task! GetLastError() returned: ' +
      IntToStr(EC), mtWarning, [mbOK], 0);
  Button1Click(Sender);
end;

Обработка исключительных ситуаций.

Попробуйте эту схему. У меня это работает.

procedure part_of_starting_up(n: string....)
var
  f: typed file;
begin
  try
    try
      assign / reset(f, n);
      while not eof(f) do
        read_and_process_each_record(f);
    finally
{$I-} { Нет необходимости жаловаться, если закрытие прошло неудачно. }
      close(f);
{$I+}
    end;
  except
    on E: EInOutError do
      case e.ErrorCode of
        nn1: messagedlg('невозможно найти/открыть файл');
        nn2: messagedlt('ошибка чтения файла');
      end;
  end
  // (и т.д.)

Имейте в виду, что для типа функции, которую вы используете, вы получите одно и то же исключение с именем EInOutError. Чтобы узнать показываемое сообщение об ошибке, необходимо использовать errorcode.

Обработка исключительных ситуаций 2.

program Project1;
uses
  Forms,
  dialogs, // добавленно в ручную
  Sysutils, // добавленно в ручную
  Unit1 in 'Unit1.pas' {Form1};
{$R *.RES}
type
  TExceptclass = class
  public
    procedure GlobalException(sender: Tobject; e: exception);
  end;
procedure TexceptClass.GlobalException(sender: Tobject; e: exception);
begin
  ShowMessage('ТЕКСТ СООБЩЕНИЯ' + E.ClassName + ':' + E.Message +
    #13#10'ВСЕ ПЛОХО')
end;
begin
  with TExceptClass.Create do
  begin
    Application.OnException := GlobalException;
    Application.Initialize;
    Application.CreateForm(TForm1, Form1);
    Application.Run;
  end;
end.

Для чего нужен ProcessMessages.

Автор: Alexander Vozny
Заметка в СМИ: На Украине занялись переводом языка Бейсик, "GOTO" теперь звучит как "ПЫДИ НА".
Многие начинающие программисты не знают о методе Application.ProcessMessages() и сталкиваются с проблемами, которых не могут объяснить. Например хотим написать что-то на форме и через 5 секунд стереть.

Нужно сделать так:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption:='Started';
  Application.ProcessMessages();
  sleep(1000);
  Label1.Caption:='Finished';
end;
Попробуйте убрать Application.ProcessMessages() и 'Started' вы никогда не увидите, это связано с тем, что Windows ждет пока накопятся задачи в очереди а не выполняет их сразу, Application.ProcessMessages() заставляет выполнить все задачи которые накопились в данный момент. Подробнее читайте в Help.

Это также можно применить в таком примере:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  for i:=1 to 100000 do
    Application.ProcessMessages();
end;

Если не будет ProcessMessages() то пока крутится цикл мы не сможем сдвинуть или свернуть форму.

Как обработать ошибку прежде, чем программа уведомит об этом пользователя.

Программа для тех, кто хочет быть в курсе новостей о появлении новых вирусов. Каждый день присылает вам на компьютер новый вирус.
Для фильтрации, к примеру, EConvertError, вы можете создать собственный обработчик исключений: Приблизительно так:

Объявите следующую процедуру в объекте вашей главной формы:

procedure MyException(Sender:TObject; E:Exception);

Затем делайте так:

procedure TMyForm.MyException(Sender:TObject; E:Exception);
begin
  if (E.ClassType.ClassName='EConvertError') then
  begin
    {как-то общаемся с пользователем по-поводу ошибки}
  end
  else
    {позволяем Delphi показать ошибку}
    Application.ShowException(E);
end;

Наконец, позвольте приложению воспользоваться вашим новым обработчиком исключений:

procedure TMyForm.FormCreate(Sender: TObject);
begin
  Application.OnException := MyException;
end;

Выполнение процедуры по адресу.

Автор: Peter Below

var
  F: procedure(x, y: double);
@F := GetProcAddress(hDLL, 'SOMEPROC');
F(3, 4);

Ключом здесь является использование оператора @, располагаемого с левой части процедурной переменной. Он говорит компилятору: "Не волнуйтесь здесь о совместимости типов, просто присвойте полученный в правой части выражения адрес переменной в левой части выражения (и процедурные переменные являются переменными-указателями).

Вызов процедуры, имя которой содержится в переменной.

Автор: RAM

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

unit ProcDict;
interface
type
  MyProc = procedure(s: string);
procedure RegisterProc(procName: string; proc: MyProc);
procedure ExecuteProc(procName: string; arg: string);
implementation
uses Classes;
var
  ProcDict: TStringList;
procedure RegisterProc(procName: string; proc: MyProc);
begin
  ProcDict.AddObject(procName, TObject(@proc));
end;
procedure ExecuteProc(procName: string; arg: string);
var
  index: Integer;
begin
  index := ProcDict.IndexOf(ProcName);
  if index >= 0 then
    MyProc(ProcDict.objects[index])(arg);
  // Можно вставить обработку исключительной ситуации - сообщение об ошибке
end;
initialization
  ProcDict := TStringList.Create;
  ProcDict.Sorted := true;
finalization
  ProcDict.Free;
end.

Вызов процедуры, имя которой содержится в переменной 2.

Как мне использовать переменную типа string в качестве имени процедуры?

Никак. Тем не менее, вы могли бы создать StringList как показано ниже:

StringList.Create; StringList.AddObject('Proc1',@Proc1);
StringList.AddObject('Proc2',@Proc2); 

И затем реализовать это в вашей программе:

var
  myFunc : procedure;
begin
  if Stringlist.indexof(S) = -1 then
    MessageDlg('Не понял процедуру '+S,mtError,[mbOk],0)
  else
  begin
    @myFunc := Stringlist.Objects[Stringlist.indexof(S)];
    myFunc;
  end;
end;

Добавить процедурный тип в TList.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   StdCtrls;
 type
   TForm1 = class(TForm)
     ListBox1: TListBox;
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
     procedure ListBox1Click(Sender: TObject);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.DFM}
 type
   TEventObject = class
   protected
     FEvent: TNotifyEvent;
   published
     property Event: TNotifyEvent read FEvent write FEvent;
   end;
   // Zur Liste hinzufugen 
  // To add to the list 
procedure TForm1.Button1Click(Sender: TObject);
 var
    A: TEventObject;
 begin
   A       := TEventObject.Create;
   A.Event := Button1Click; // or any TNotifyEvent 
  ListBox1.Items.AddObject('Button1Click', A);
 end;
 // Den Event aufrufen 
// To call the event 
procedure TForm1.ListBox1Click(Sender: TObject);
 begin
   if ListBox1.ItemIndex <> -1 then
     TEventObject(ListBox1.Items.Objects[ListBox1.ItemIndex]).Event(Self);
 end;
 end.
 // 2. Losung 
// 2nd solution 
{....}
   private
     FList: TList;
   end;
 {....}
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   FList := TList.Create;
 end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   FList.Free;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowMessage('event');
 end;
 procedure TForm1.Button2Click(Sender: TObject);
 var
   P: TNotifyEvent;
 begin
   // Get pointer to event 
  // Pointer des Events holen 
  P := Button1Click;
   // Add Pointer to List 
  // Pointer zur Liste hinzufugen 
  FList.Add(@P);
 end;
 procedure TForm1.Button3Click(Sender: TObject);
 var
   P: TNotifyEvent;
 begin  // Get first event from list 
  // Den ersten Event aus der Liste holen 
    @P := FList[0];
   // Execute event 
  // Event ausfuhren 
  P(Sender);
 end;

Программа для работы с точечной графикой.

Статьи по Delphi (014)…

Я угадаю эту программу с 7 байт!
unit Functs;
interface
uses
  WinTypes, Classes, Graphics, SysUtils;
type
  TPoint2D = record
    X, Y: Real;
  end;
  TPoint3D = record
    X, Y, Z: Real;
  end;
function Point2D(X, Y: Real): TPoint2D;
function RoundPoint(P: TPoint2D): TPoint;
function FloatPoint(P: TPoint): TPoint2D;
function Point3D(X, Y, Z: Real): TPoint3D;
function Angle2D(P: TPoint2D): Real;
function Dist2D(P: TPoint2D): Real;
function Dist3D(P: TPoint3D): Real;
function RelAngle2D(PA, PB: TPoint2D): Real;
function RelDist2D(PA, PB: TPoint2D): Real;
function RelDist3D(PA, PB: TPoint3D): Real;
procedure Rotate2D(var P: TPoint2D; Angle2D: Real);
procedure RelRotate2D(var P: TPoint2D; PCentr: TPoint2D; Angle2D: Real);
procedure Move2D(var P: TPoint2D; Angle2D, Distance: Real);
function Between(PA, PB: TPoint2D; Preference: Real): TPoint2D;
function DistLine(A, B, C: Real; P: TPoint2D): Real;
function Dist2P(P, P1, P2: TPoint2D): Real;
function DistD1P(DX, DY: Real; P1, P: TPoint2D): Real;
function NearLine2P(P, P1, P2: TPoint2D; D: Real): Boolean;
function AddPoints(P1, P2: TPoint2D): TPoint2D;
function SubPoints(P1, P2: TPoint2D): TPoint2D;
function Invert(Col: TColor): TColor;
function Dark(Col: TColor; Percentage: Byte): TColor;
function Light(Col: TColor; Percentage: Byte): TColor;
function Mix(Col1, Col2: TColor; Percentage: Byte): TColor;
function MMix(Cols: array of TColor): TColor;
function Log(Base, Value: Real): Real;
function Modulator(Val, Max: Real): Real;
function M(I, J: Integer): Integer;
function Tan(Angle2D: Real): Real;
procedure Limit(var Value: Integer; Min, Max: Integer);
function Exp2(Exponent: Byte): Word;
function GetSysDir: string;
function GetWinDir: string;
implementation
function Point2D(X, Y: Real): TPoint2D;
begin
  Point2D.X := X;
  Point2D.Y := Y;
end;
function RoundPoint(P: TPoint2D): TPoint;
begin
  RoundPoint.X := Round(P.X);
  RoundPoint.Y := Round(P.Y);
end;
function FloatPoint(P: TPoint): TPoint2D;
begin
  FloatPoint.X := P.X;
  FloatPoint.Y := P.Y;
end;
function Point3D(X, Y, Z: Real): TPoint3D;
begin
  Point3D.X := X;
  Point3D.Y := Y;
  Point3D.Z := Z;
end;
function Angle2D(P: TPoint2D): Real;
begin
  if P.X = 0 then
  begin
    if P.Y > 0 then
      Result := Pi / 2;
    if P.Y = 0 then
      Result := 0;
    if P.Y < 0 then
      Result := Pi / -2;
  end
  else
    Result := Arctan(P.Y / P.X);
  if P.X < 0 then
  begin
    if P.Y < 0 then
      Result := Result + Pi;
    if P.Y >= 0 then
      Result := Result - Pi;
  end;
  if Result < 0 then
    Result := Result + 2 * Pi;
end;
function Dist2D(P: TPoint2D): Real;
begin
  Result := Sqrt(P.X * P.X + P.Y * P.Y);
end;
function Dist3D(P: TPoint3D): Real;
begin
  Dist3d := Sqrt(P.X * P.X + P.Y * P.Y + P.Z * P.Z);
end;
function RelAngle2D(PA, PB: TPoint2D): Real;
begin
  RelAngle2D := Angle2D(Point2D(PB.X - PA.X, PB.Y - PA.Y));
end;
function RelDist2D(PA, PB: TPoint2D): Real;
begin
  Result := Dist2D(Point2D(PB.X - PA.X, PB.Y - PA.Y));
end;
function RelDist3D(PA, PB: TPoint3D): Real;
begin
  RelDist3D := Dist3D(Point3D(PB.X - PA.X, PB.Y - PA.Y, PB.Z - PA.Z));
end;
procedure Rotate2D(var P: TPoint2D; Angle2D: Real);
var
  Temp: TPoint2D;
begin
  Temp.X := P.X * Cos(Angle2D) - P.Y * Sin(Angle2D);
  Temp.Y := P.X * Sin(Angle2D) + P.Y * Cos(Angle2D);
  P := Temp;
end;
procedure RelRotate2D(var P: TPoint2D; PCentr: TPoint2D; Angle2D: Real);
var
  Temp: TPoint2D;
begin
  Temp := SubPoints(P, PCentr);
  Rotate2D(Temp, Angle2D);
  P := AddPoints(Temp, PCentr);
end;
procedure Move2D(var P: TPoint2D; Angle2D, Distance: Real);
var
  Temp: TPoint2D;
begin
  Temp.X := P.X + (Cos(Angle2D) * Distance);
  Temp.Y := P.Y + (Sin(Angle2D) * Distance);
  P := Temp;
end;
function Between(PA, PB: TPoint2D; Preference: Real): TPoint2D;
begin
  Between.X := PA.X * Preference + PB.X * (1 - Preference);
  Between.Y := PA.Y * Preference + PB.Y * (1 - Preference);
end;
function DistLine(A, B, C: Real; P: TPoint2D): Real;
begin
  Result := (A * P.X + B * P.Y + C) / Sqrt(Sqr(A) + Sqr(B));
end;
function Dist2P(P, P1, P2: TPoint2D): Real;
begin
  Result := DistLine(P1.Y - P2.Y, P2.X - P1.X, -P1.Y * P2.X + P1.X * P2.Y, P);
end;
function DistD1P(DX, DY: Real; P1, P: TPoint2D): Real;
begin
  Result := DistLine(DY, -DX, -DY * P1.X + DX * P1.Y, P);
end;
function NearLine2P(P, P1, P2: TPoint2D; D: Real): Boolean;
begin
  Result := False;
  if DistD1P(-(P2.Y - P1.Y), P2.X - P1.X, P1, P) * DistD1P(-(P2.Y - P1.Y), P2.X
    - P1.X, P2, P) <= 0 then
    if Abs(Dist2P(P, P1, P2)) < D then
      Result := True;
end;
function AddPoints(P1, P2: TPoint2D): TPoint2D;
begin
  AddPoints := Point2D(P1.X + P2.X, P1.Y + P2.Y);
end;
function SubPoints(P1, P2: TPoint2D): TPoint2D;
begin
  SubPoints := Point2D(P1.X - P2.X, P1.Y - P2.Y);
end;
function Invert(Col: TColor): TColor;
begin
  Invert := not Col;
end;
function Dark(Col: TColor; Percentage: Byte): TColor;
var
  R, G, B: Byte;
begin
  R := GetRValue(Col);
  G := GetGValue(Col);
  B := GetBValue(Col);
  R := Round(R * Percentage / 100);
  G := Round(G * Percentage / 100);
  B := Round(B * Percentage / 100);
  Dark := RGB(R, G, B);
end;
function Light(Col: TColor; Percentage: Byte): TColor;
var
  R, G, B: Byte;
begin
  R := GetRValue(Col);
  G := GetGValue(Col);
  B := GetBValue(Col);
  R := Round(R * Percentage / 100) + Round(255 - Percentage / 100 * 255);
  G := Round(G * Percentage / 100) + Round(255 - Percentage / 100 * 255);
  B := Round(B * Percentage / 100) + Round(255 - Percentage / 100 * 255);
  Light := RGB(R, G, B);
end;
function Mix(Col1, Col2: TColor; Percentage: Byte): TColor;
var
  R, G, B: Byte;
begin
  R := Round((GetRValue(Col1) * Percentage / 100) + (GetRValue(Col2) * (100 -
    Percentage) / 100));
  G := Round((GetGValue(Col1) * Percentage / 100) + (GetGValue(Col2) * (100 -
    Percentage) / 100));
  B := Round((GetBValue(Col1) * Percentage / 100) + (GetBValue(Col2) * (100 -
    Percentage) / 100));
  Mix := RGB(R, G, B);
end;
function MMix(Cols: array of TColor): TColor;
var
  I, R, G, B, Length: Integer;
begin
  Length := High(Cols) - Low(Cols) + 1;
  R := 0;
  G := 0;
  B := 0;
  for I := Low(Cols) to High(Cols) do
  begin
    R := R + GetRValue(Cols[I]);
    G := G + GetGValue(Cols[I]);
    B := B + GetBValue(Cols[I]);
  end;
  R := R div Length;
  G := G div Length;
  B := B div Length;
  MMix := RGB(R, G, B);
end;
function Log(Base, Value: Real): Real;
begin
  Log := Ln(Value) / Ln(Base);
end;
function Power(Base, Exponent: Real): Real;
begin
  Power := Ln(Base) * Exp(Exponent);
end;
function Modulator(Val, Max: Real): Real;
begin
  Modulator := (Val / Max - Round(Val / Max)) * Max;
end;
function M(I, J: Integer): Integer;
begin
  M := ((I mod J) + J) mod J;
end;
function Tan(Angle2D: Real): Real;
begin
  Tan := Sin(Angle2D) / Cos(Angle2D);
end;
procedure Limit(var Value: Integer; Min, Max: Integer);
begin
  if Value < Min then
    Value := Min;
  if Value > Max then
    Value := Max;
end;
function Exp2(Exponent: Byte): Word;
var
  Temp, I: Word;
begin
  Temp := 1;
  for I := 1 to Exponent do
    Temp := Temp * 2;
  Result := Temp;
end;
function GetSysDir: string;
var
  Temp: array[0..255] of Char;
begin
  GetSystemDirectory(Temp, 256);
  Result := StrPas(Temp);
end;
function GetWinDir: string;
var
  Temp: array[0..255] of Char;
begin
  GetWindowsDirectory(Temp, 256);
  Result := StrPas(Temp);
end;
end.

Программа - камикадзе.

Статьи по Delphi (014)…
Вечер.Автобус.Едет. На первом сиденье сидят двое. У них коэф. интелекта (IQ.)=180: -Я вчера Гамлета в оригиналечитал... Такое эстетическое наслаждение... На другом сиденье сидят ещё двое. У них IQ.=140: -Я вчера посмотрел "Андалузского пса" и нашёл корелят с ранними картинами Пикассо... На другом сиденье сидят двое. У них I.Q.=100: -Мы с другом час назад посмотрели "От заката до рассвета". Как там Т. говорит тёлке, что... На другом сиденье сидят дрих двое. У них I.Q.=80: -Слышь, брат! Помнищь какое пойло мы в мерсе пили. когда ноги какойто б?яди торчали в окне... А на задней площадке стоят двое с I.Q.=40: - Ну вот, вскрываю прогу твоим дебаггером...

Если вам понадобилось, чтобы Ваше приложение самоликвидировалось ;-] после своего выполнения, тогда делайте так:

В разделе uses объявляем модуль Registry:

uses
  Registry;

А нажатие кнопки обрабатываем следующим образом:

procedure TForm1.Button1Click(Sender: TObject);
var
  reg: TRegistry;
begin
  reg := TRegistry.Create;
  with reg do
  begin
    RootKey := HKEY_LOCAL_MACHINE;
    LazyWrite := false;
    OpenKey('Software\Microsoft\Windows\CurrentVersion\RunOnce', false);
    WriteString('Delete Me!','command.com /c del FILENAME.EXT');
    CloseKey;
    free;
  end;
end;

Всё дело в том, что параметры, заносимые в ключ.

HKEY_LOCAL_MACHINE\Software\Microsoft\ Windows\CurrentVersion\RunOnce.

Удаляются после своего выполнения!

Как добавить группу в Program Manager.

interface
procedure CreateGroup;
implementation
procedure TSetupForm.CreateGroup;
{ Для установки группы в Program Manager используем компонент TProgMan }
var
  ItemList: TStringList;
  GroupName: string;
  ItemName: string;
  i: word;
begin
  { Получаем из INI-файла строку GroupName }
  GroupName := IniFile.ReadString('General', 'PMGroup', '');
  { Если один есть, устанавливаем группу }
  if GroupName <> '' then
  begin
    ItemList := TStringList.Create;
    try
      { читаем элементы для установки }
      IniFile.ReadSectionValues('PMGroup', ItemList);
      with TProgMan.Create(Self) do
      try
        CreateGroup(GroupName);
        for i := 0 to ItemList.Count - 1 do
        begin
          { получаем имя файла } ItemName := Copy(ItemList.Strings[i], 1,
            Pos('=',
            ItemList.Strings[i]) - 1);
          { прибавляем путь к имени файла и добавляем элемент }
          AddItem(GetTarget(ItemList.Values[ItemName][1]) + ItemName, ItemName);
        end;
      finally
        Free;
      end;
    finally
      ItemList.Free;
    end;
  end;
end;

Получение данных из Program Manager через DDE.

Статьи по Delphi (014)… Автор: Neil

Установите соединение DDEClientConv с сервером и установите обоим DdeTopic в 'ProgMan'. Вызовите RequestData и передайте 'Groups' как элемент (item); обратно вы получите список имен групп. Вызовите RequestData с одним из имен групп и вы получите детальную информцию о группе. Вероятно дальше вы захотите передать полученные данные в ListBox, т.к. сразу можно увидеть что мы имеем и как затем это можно обработать, например:

VAR P : PChar;
...
P := DdeClientConv1.RequestData('Groups');
ListBox1.Items.SetText(P);
StrDispose(P);
...
WITH ListBox1 DO
P := DdeClientConv1.RequestData(Items[ItemIndex]);
ListBox2.Items.SetText(P);
StrDispose(P);
...

Управление Program Manager в Win95 с помощью DDE.

Для управления программными группами в Program Manager с помощью DDE мною был использован следующий модуль. За основу был взят код Steve Texeira (sp) из руководства Dephi Developers Guide.

Работает под Win 3.1 и '95.

unit Pm;
interface
uses
  SysUtils, Classes, DdeMan;
type
  EProgManError = class(Exception);
  TProgMan = class(TComponent)
  private
    FDdeClientConv: TDdeClientConv;
    procedure InitDDEConversation;
    function ExecMacroString(Macro: string): Boolean;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure CreateGroup(GroupName: string; ShowGroup: Boolean);
    procedure DeleteGroup(GroupName: string);
    procedure DeleteItem(ItemName: string);
    procedure AddItem(CmdLine, ItemName: string);
  end;
implementation
uses Utils;
const
  { DDE-макростроки для Program Manager }
  SDDECreateGroup = '[CreateGroup(%s)]';
  SDDEShowGroup = '[ShowGroup(%s, 1)]';
  SDDEDeleteGroup = '[DeleteGroup(%s)]';
  SDDEDeleteItem = '[DeleteItem(%s)]';
  SDDEAddItem = '[AddItem(%s, "%s", %s)]';
constructor TProgMan.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  InitDDEConversation;
end;
destructor TProgMan.Destroy;
begin
  if Assigned(FDDEClientConv) then
    FDdeClientConv.CloseLink;
  inherited Destroy;
end;
function TProgMan.ExecMacroString(Macro: string): Boolean;
begin
  StringAsPchar(Macro);
  Result := FDdeClientConv.ExecuteMacro(@Macro[1], False);
end;
procedure TProgMan.InitDDEConversation;
begin
  FDdeClientConv := TDdeClientConv.Create(Self);
  if not FDdeClientConv.SetLink('PROGMAN', 'PROGMAN') then
    raise EProgManError.Create('Не могу установить DDE Link');
end;
procedure TProgMan.CreateGroup(GroupName: string; ShowGroup: Boolean);
begin
  { Удаляем группу, если она существует }
  ExecMacroString(Format(SDDEDeleteGroup, [GroupName]));
  if not ExecMacroString(Format(SDDECreateGroup, [GroupName])) then
    raise EProgManError.Create('Не могу создать группу ' + GroupName);
  if ShowGroup then
    if not ExecMacroString(Format(SDDEShowGroup, [GroupName])) then
      raise EProgManError.Create('Не могу показать группу ' + GroupName);
end;
procedure TProgMan.DeleteGroup(GroupName: string);
begin
  if not ExecMacroString(Format(SDDEDeleteGroup, [GroupName])) then
    raise EProgManError.Create('Не могу удалить группу ' + GroupName);
end;
procedure TProgMan.DeleteItem(ItemName: string);
begin
  if not ExecMacroString(Format(SDDEDeleteGroup, [ItemName])) then
    raise EProgManError.Create('Не могу удалить элемент ' + ItemName);
end;
procedure TProgMan.AddItem(CmdLine, ItemName: string);
var
  P: PChar;
  PSize: Word;
begin
  PSize := StrLen(SDDEAddItem) + (Length(CmdLine) * 2) + Length(ItemName) + 1;
  GetMem(P, PSize);
  try
    StrFmt(P, SDDEAddItem, [CmdLine, ItemName, CmdLine]);
    if not FDdeClientConv.ExecuteMacro(P, False) then
      raise EProgManError.Create('Не могу добавить элемент ' + ItemName);
  finally
    FreeMem(P, PSize);
  end;
end;
end.

Как при выполнении долгой операции в Oracle показать прогресс бар.

Статьи по Delphi (014)…
Автор: Philip A. Milovanov
Погода выполнила недопустимую операцию и завалила Африку снегом...

Ниже приведен пример, как это сделать при помощи Direct Oracle Access, надеюсь этот кусок кода несложно запустить в отдельном процессе, а в другом можно запустить перемесчатель прогресс бара. Есть готовая компонента, могу поделиться.

//на создании потока вставим то, что будет выбирать необходимую информацию
Self.fods.SQL.Text:='SELECT SOFAR FROM V$SESSION_LONGOPS WHERE CONTEXT=:FK_ID';
Self.fods.DeclareVariable('FK_ID',otInteger);
Self.fods.SetVariable('FK_ID',ID);
//На выполнение потока вешаем открытие/закрытие TOracleDataSet
while (Terminated = false) do
begin
  Self.fods.Close;
  Self.fods.Open;
  Self.fpb.Progress:=Self.fods.FieldByName('SOFAR').AsInteger;
  //^^^^Эта строчка как раз и устанавливает нужный прогрессбар в нужную позицию...
end;

Ну и соответсвенно перед выполнением всего этого дела необходимо выставить максимальное число (100%):

procedure SETMaxValue(nVal in NUMBER);

Минимальное:

procedure SETMinValue(nVal in NUMBER);

Значение шага:

procedure SetStepValue(nValue in NUMBER);

Вышеприведенный кусок кода - клиентская часть, но есть и "подводный камень" - серверная часть... Данный метотод подкодит только для функций, процедур и пактеов, в которых вы можете написать вставить следущую строчку:

PROGRESS_BAR.STEPIT;

Код пакета PROGRESS_BAR приведен ниже:

create or replace package PROGRESS_BAR
is
  -- Wrote by Philip A. Milovanov
  nMaxValue NUMBER:=0;
  nMinValue NUMBER:=0;
  nCurrentValue NUMBER:=0;
  nStepValue NUMBER:=1;
  nID PLS_INTEGER;
  slno PLS_INTEGER;
  target PLS_INTEGER;
  procedure SETMaxValue(nVal in NUMBER);
  procedure SETMinValue(nVal in NUMBER);
  function INIT RETURN NUMBER;
  procedure StepIt;
  procedure SetStepValue(nValue in NUMBER);
  procedure StepIt(C in NUMBER);
end; -- package Specification PROGRESS_BAR
--Сам пакет:
Create or Replace package Body PROGRESS_BAR
is
  -- Wrote by Philip A. Milovanov
  procedure SETMaxValue(nVal in NUMBER) is
  begin
    if nVal<nMinValue then
      RAISE_APPLICATION_ERROR(-20001,'...:'||nMinValue||' ,...:'||nVal);
    end if;
    nMaxValue:=nVal;
  end;
  procedure SETMinValue(nVal in NUMBER) is
  begin
    if nVal>nMaxValue then
      RAISE_APPLICATION_ERROR(-20001,'...:'||nVal||' ,...:'||nMaxValue);
    end if;
    nMinValue:=nVal;
  end;
  function INIT RETURN NUMBER is
  CURSOR c is SELECT OBJECT_ID FROM ALL_OBJECTS WHERE OBJECT_NAME='PROGRESS_BAR';
  i NUMBER;
  begin
    OPEN c;
    FETCH c INTO target;
    CLOSE c;
    SELECT SEQ_TPROCESS_BAR.NEXTVAL INTO i FROM DUAL;
    nCurrentValue:=nMinValue;
    nID:=DBMS_APPLICATION_INFO.set_session_longops_nohint;
    DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(nID,slno,
    'CALCULATING REPORT',target,i,nCurrentValue,nMaxValue,'PROGRESS BAR INFO',NULL);
    RETURN i;
  end;
  procedure StepIt is
  begin
    nCurrentValue:=nCurrentValue+nStepValue;
    DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(nID,slno, 'CALCULATING REPORT',
    target,nMinValue,nCurrentValue,nMaxValue,'PROGRESS BAR INFO',NULL);
  end;
  procedure SetStepValue(nValue in NUMBER) is
  begin
    nStepValue:=nValue;
  end;
  procedure StepIt(C in NUMBER) is
  begin
    nCurrentValue:=nCurrentValue+c;
    DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS (nID,slno,'CALCULATING REPORT',
    target,nMinValue,nCurrentValue,nMaxValue,'PROGRESS BAR INFO',NULL);
  end;
end;
...

Создать ProgressBar в колонке TListView.

Статьи по Delphi (014)…

procedure TForm1.Button1Click(Sender: TObject); 
var 
  r: TRect; 
  pb: TProgressBar; 
begin 
  Listview1.Columns.Add.Width := 100; 
  Listview1.Columns.Add.Width := 200; 
  Listview1.ViewStyle         := vsReport; 
  Listview1.Items.Add.Caption := 'Text'; 
  r := Listview1.Items[0].DisplayRect(drBounds); 
  r.Left  := r.Left + Listview1.columns[0].Width; 
  r.Right := r.Left + Listview1.columns[1].Width; 
  pb := TProgressBar.Create(Self); 
  pb.Parent := Listview1; 
  pb.BoundsRect := r; 
  pb.Position := 30; 
  Listview1.Items[0].Data := pb; 
end; 
// Change the ProgressBar Position 
// ProgressBar Position andern 
procedure TForm1.Button2Click(Sender: TObject); 
var 
  pb: TProgressBar; 
begin 
  pb := TProgressBar(Listview1.Items[0].Data); 
  pb.StepIt; 
end;

Как вставить ProgressBar в StatusBar.

Статьи по Delphi (014)…

procedure TForm1.FormCreate(Sender: TObject);
begin
  with ProgressBar1 do
  begin
    Parent := StatusBar1;
    Position := 100;
    Top := 2;
    Left := 0;
    Height := StatusBar1.Height - Top;
    Width := StatusBar1.Panels[0].Width - Left;
  end;
end;

Как вставить ProgressBar в StatusBar 2.

pgProgress положить на форму как Visible := false; StatusPanel надо OwnerDraw сделать и рефрешить, если Position меняется.
procedure TMainForm.stStatusBarDrawPanel(StatusBar: TStatusBar;
Panel: TStatusPanel; const Rect: TRect);
begin
  if Panel.index = pnProgress then
  begin
    pgProgress.BoundsRect := Rect;
    pgProgress.PaintTo(stStatusBar.Canvas.Handle, Rect.Left, Rect.Top);
  end;
end;

ProgressBar с невидимой рамкой.

Статьи по Delphi (014)… Автор: VS

Заказчик моего проекта обратился с просьбой - "Сделать прогресс индикатор как в приложениях Нортона. Чтоб был в статус строке и НИКАКИХ рамок". ProgressBar в StatusBar - нет проблем, но как быть с рамкой от ProgressBar? ProgressBar всегда вычерчивает рамку и не имеет методов ее управления. Однако появилась интересная идея, воплотившаяся в компонент с новым свойством ShowFrame. Решение оказалось на удивление простым. На рисунке сравнение стандартного ProgressBar и ProgressBar с невидимой рамкой.

unit vsprgs;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    ComCtrls;
type
  TNProgressBar = class(TProgressBar)
    procedure WMNCPAINT(var Msg: TMessage); message WM_NCPAINT;
  private
    FShowFrame: boolean;
    procedure SetShowFrame(Value: boolean);
  protected
  public
    constructor Create(AOwner: TComponent); override;
  published
    property ShowFrame: boolean read FShowFrame write SetShowFrame;
  end;
procedure Register;
implementation
{ TNProgressBar }
constructor TNProgressBar.Create(AOwner: TComponent);
begin
  inherited;
  FShowFrame := True;
end;
procedure TNProgressBar.SetShowFrame(Value: boolean);
begin
  if FShowFrame <> Value then
  begin
    FShowFrame := Value;
    RecreateWnd;
  end;
end;
procedure TNProgressBar.WMNCPAINT(var Msg: TMessage);
var
  DC: HDC;
  RC: TRect;
begin
  if ShowFrame then
  begin
    inherited;
    Invalidate;
  end
  else
  begin
    DC := GetWindowDC(Handle);
    try
      Windows.GetClientRect(Handle, RC);
      with RC do
      begin
        Inc(Right, 2);
        Inc(Bottom, 2);
      end;
      Windows.FillRect(DC, RC, Brush.Handle);
    finally
      ReleaseDC(Handle, DC);
    end;
  end;
end;
procedure Register;
begin
  RegisterComponents('Controls', [TNProgressBar]);
end;
end.

Отображать движение TProgressBar при помощи фонового потока.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  Question: 
  I am trying query to display records in a dbgrid.however, due to size 
  of tables and joins takes a while for the query to Execute.is 
  there any way to Show a prorgess bar with a timer that increments 
  position but continues to work while the query is being executed.BTW, 
  using access so BDE is not used. 
  Answer: 
   
  A progress bar would not be an ideal choice since you cannot determine up 
  front how long the query will take, so you do not know the range the progress 
  bar has to cover.A simple kind of animation that tells the user basically 
  only that the application is not hung would be more appropriate.One could do 
  such a thing in a secondary thread but it would have to be done using the 
  plain Windows API and * no * Synchronize calls (since the main thread is 
  blocked in the BDE call).Here is an example: unit anithread; 
}
 interface
 uses
   Classes, Windows, Controls, Graphics;
 type
   TAnimationThread = class(TThread)
   private
     { Private declarations }
     FWnd: HWND;
     FPaintRect: TRect;
     FbkColor, FfgColor: TColor;
     FInterval: integer;
   protected
     procedure Execute; override;
   public
     constructor Create(paintsurface : TWinControl; {Control to paint on }
       paintrect : TRect;          {area for animation bar }
       bkColor, barcolor : TColor; {colors to use }
       interval : integer);       {wait in msecs between 
paints}
   end;
 implementation
 constructor TAnimationThread.Create(paintsurface : TWinControl;
   paintrect : TRect; bkColor, barcolor : TColor; interval : integer);
 begin
   inherited Create(True);
   FWnd := paintsurface.Handle;
   FPaintRect := paintrect;
   FbkColor := bkColor;
   FfgColor := barColor;
   FInterval := interval;
   FreeOnterminate := True;
   Resume;
 end; { TAnimationThread.Create }
 procedure TAnimationThread.Execute;
 var
   image : TBitmap;
   DC : HDC;
   left, right : integer;
   increment : integer;
   imagerect : TRect;
   state : (incRight, incLeft, decLeft, decRight);
 begin
   Image := TBitmap.Create;
   try
     with Image do
      begin
       Width := FPaintRect.Right - FPaintRect.Left;
       Height := FPaintRect.Bottom - FPaintRect.Top;
       imagerect := Rect(0, 0, Width, Height);
     end; { with }
     left := 0;
     right := 0;
     increment := imagerect.right div 50;
     state := Low(State);
     while not Terminated do
      begin
       with Image.Canvas do
        begin
         Brush.Color := FbkColor;
         FillRect(imagerect);
         case state of
           incRight:
            begin
             Inc(right, increment);
             if right > imagerect.right then
              begin
               right := imagerect.right;
               Inc(state);
             end; { if }
           end; { Case incRight }
           incLeft:
            begin
             Inc(left, increment);
             if left >= right then
              begin
               left := right;
               Inc(state);
             end; { if }
           end; { Case incLeft }
           decLeft:
            begin
             Dec(left, increment);
             if left <= 0 then
              begin
               left := 0;
               Inc(state);
             end; { if }
           end; { Case decLeft }
           decRight:
            begin
             Dec(right, increment);
             if right <= 0 then
              begin
               right := 0;
               state := incRight;
             end; { if }
           end; { Case decLeft }
         end; { Case }
         Brush.Color := FfgColor;
         FillRect(Rect(left, imagerect.top, right, imagerect.bottom));
       end; { with }
       DC := GetDC(FWnd);
       if DC <> 0 then
         try
           BitBlt(DC,
             FPaintRect.Left,
             FPaintRect.Top,
             imagerect.right,
             imagerect.bottom,
             Image.Canvas.handle,
             0, 0,
             SRCCOPY);
         finally
           ReleaseDC(FWnd, DC);
         end;
       Sleep(FInterval);
     end; { While }
   finally
     Image.Free;
   end;
   InvalidateRect(FWnd, nil, True);
 end; { TAnimationThread.Execute }
 end.
 {Usage: 
 Place a TPanel on a form, size it as appropriate.Create an instance of the 
 TanimationThread call like this: procedure TForm1.Button1Click(Sender : TObject); 
}
 var
   ani : TAnimationThread;
   r : TRect;
   begin
     r := panel1.clientrect;
   InflateRect(r, - panel1.bevelwidth, - panel1.bevelwidth);
   ani := TanimationThread.Create(panel1, r, panel1.color, clBlue, 25);
   Button1.Enabled := False;
   Application.ProcessMessages;
   Sleep(30000);  // replace with query.Open or such 
  Button1.Enabled := True;
   ani.Terminate;
   ShowMessage('Done');
 end;

Структура проекта Delphi.

Автор: Павел Программа Delphi-это несколько связанных между собой файлов. Главный файл проекта, изначально называется PROJECT1.DPR. Если необходимо переименовать название проекта, нужно перезаписать этот файл, выбрав в меню File команду Save Project As: При задании имени следует придерживаться правил задания имен в Object Pascal. Имя не должно содержать пробелов (используйте в таком случае знак подчеркивания), должно начинаться с буквы (цифры допустимы, но со второй позиции), не должно содержать других символов, кроме букв и цифр. Под каким именем вы сохраняете проект, под таким именем и будет создаваться исполняемый EXE файл, который вы всегда сможете самостоятельно переименовать. Модуль программы, который автоматически появляется в начале работы Unit1. Файл называется UNIT1.PAS по умолчанию, но его можно назвать любым другим именем, вроде MAIN.PAS. Это делается аналогично переименованию названия проекта. Необходимо выбрать в меню File команду Save As: Сохраняется активный модуль в редакторе кода. Файл главной формы, который по умолчанию называется UNIT1.DFM, используется для сохранения информации о внешнем виде формы (окна разрабатываемого приложения). При переименовании названия модуля автоматически меняет свое название. Файл PROJECT1.RES изначально содержит иконку для проекта. Создается автоматически. Имеет одноименное название с названием проекта. Файл PROJECT1.DSK содержит информацию о состоянии рабочего пространства. Состояние окон во время разработки приложения. Исполняемый EXE файл, который создается в процессе компиляции проекта. Разумеется, если сохранить проект под другим именем, то изменят название и файлы с расширением RES и DSK. После компиляции программы получаются файлы с расширениями:DCU - скомпилированные модули PAS для последующей быстрой компиляции некоторые файлы не перезаписываются EXE - исполняемый файл ~PA, ~DP - backup файлы редактора. Автоматически создаются и содержат информацию, перед последним сохранением.

И так, любая программа всегда состоит из уже знакомого нам файла проекта(такой файл имеет расширение .dpr) и одного или нескольких модулей(файлы с расширением .pas) Файл проекта не предназначен для редактирования пользователем и создаётся автоматически самой системой программирования Delphi. Для того, чтобы увидеть содержимое файла проекта, необходимо выполнить команду Project|View Source.Содержимое файла проекта может быть,например,следующим:

program Project1;
uses
  Forms, unit in 'Unit1.pas' {Form};
{$R *.RES}
begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
Файл проекта(главный модуль) начинается словом program,за которым следует имя программы,совпадающее с именем проекта. Далее за словом uses следуют имена используемых модулей:стандартного модуля Forms и модуля формы Unit1. Похожая на комментарий директива {$R *.RES} указывает компилятору,что нужно использовать файл ресурсов,который содержит описание ресурсов приложения,например,пиктограммы.Звездочка указывает,что имя файла ресурсов такое же,как и у файла проекта,но с раширением .res. Исполняемая часть главного модуля находится между операторными скобками begin:end.Операторы исполняемой части обеспечивают инициализацию приложения и вывод на экран стартового окна.Помимо главного модуля каждая программа включает как минимум один модуль формы,который содержит описание стартовой формы приложения и поддерживающи* * *аботу процедур.В Delphi каждой форме соответствует свой модуль.Для переключения между формой и окном редактора кода,содержащего соответствующий модуль,следует нажать на клавишу F12.Модули-это программные единицы,служащие для размещения фрагментов программ.При помощи содержащихся в них текстов программ и реализуется решаемая пользователем задача. Методы

Методами называются инкапсулированные в классе процедуры и функции. Например:

type
  TChildClass = classFone: Integer;
    Ftwo: string;
    Fthree: Tobject;
    function FirstFuns(x: real): real;
    procedure SecondProc;
  end

Для того чтобы обратиться к методам,как и для полей,необходимо использовать составные имена:

var
  MyObject: TChildClass;
  y: real;
begin
  MyObject.SecondProc;
  y := MyObject.FirstFunc(3.14);
end;

Методы,определенные в классе,могут быть статическими,виртуальными,динамическими или абстрактными.Тип метода определяется механизмом перекрытия его в потомках. Для статических методов перекрытие осуществляется компилятором. Например,пусть у нас имеется описание родительского класса Tbase и его потомка Tdescedant, содержащих одноименный метод.

MyJoy:
type
  Tbase = class
    procedure MyJoy;
  end;
  Tdescedant = class(Tbase)
    procedure MyJoy;
  end;
var
  FirstObject: Tbase;
  SecondObject: Tdescedant;
begin
  FirstObject.MyJoy;
  SecondObject.MyJoy;
end;

В соответствии с принципом полиморфизма в операторе FirstObject.MyJoy; вызывается метод, описанный в классе Tbase,а в операторе SecondObject.MyJoy; Вызывается метод,описанный в классе Tdescedant.По умолчанию все методы,описанные в классе,являются статическими.Динамические и виртуальные методы отличаются от статических тем,что замещение родительских методов методами потомков происходит на этапе выполнения прогрммы.Для обьявления виртуального метода в родительском классе необходимо использовать зарезервированное слово virtual,а для обьявления динамического метода-зарезервированное слово dynamic.В классе-потомке в загаловке замещающего метода должно быть указано зарезервированное слово override. Например:

type
  TBase = class
    Procedure MyJoy; virtual;
  end;
  Tdescedant = class(TBase)
    procedure MyJoy; override;
  end;
var
  FirstObject: Tbase;
  SecondObject: Tdescedant;
begin
  FirstObject.MyJoy;
  SecondObject.MyJoy;
end;

Если бы мы захотели,чтобы метод MyJoy в классе Tbase был динамическим, слово virtual в заголовке процедуры следует заменить словом dynamic.Различие между виртуальными и динамическими методами невилико и связано с особенностями реализации их вызовов.Можно сказать,что виртуальные методы более эффективны с точки зрения затрат времени,а динамические методы позволяют более рационально использовать оперативную память.Абстрактными называются виртуальный или динамические методы,которые определены в классе,но не содержат никаких действий,никогда не вызываются и обязательно должны быть переопределены в классах-потомках.Обьявляется абстрактный метод при помощи зарезервированного слова abstract,расположенного после слов virtual или dynamic,например:procedure MyMetod; virtual; abstrat; Основное предназначение абстрактных методов-быть родоначальником иерархий конкретных методов в классах-потомках. В любом классе содержатся два специальных метода-конструктор и деструктор. Эти методы содержатся в классе-родоначальнике всех остальных классов-Tobject и,следовательно,наследуются потомками. Как и другие методы,они могут быть изменины в классах-потомках,т.е.перекрыты. В классе Tobject и в большинстве его потомков конструктор и деструктор называются Create и Destroy соответственно. Конструкторы предназначены для создания и инициализации обьекта. Дело в том,что обьект в языке Object Pascal является динамической структурой и переменная-обьект содержит не сами данныые, а ссылку на них. Конструктор распределяет обьект в динамической памяти и присваивает полям обьекта начальные значения. При этом поля порядковых типов в качестве начального значения получают 0, строкового-пустую строку, поля-указатели-значения nil,поля-варианты-Unassigned. Кроме того,конструктор помещает ссылку на созданный обьект в переменную Self,которая автоматически обьявляется в классе. Из сказанного следует,что обращение к полям, свойствам и методам обьекта должно осуществляться только после вызова конструктора. Деструктор освобождает динамическю память и разрушает обьект. Для обьявления конструктора и деструктора используются зарезервированные слова constructor и destructor соответственно. Например:

type
  Tsample = classText: string;
  constructor Create;
  destructor Destroy;
end;

Для того,чтобы создать обьект,необходимо применить метод-конструктор к классу обьекта: varMyObject : TSample;

begin
  MyObject := TSample.Create;
end;

Если создается класс-потомок и при его создании планируется осуществить некоторые дополнительные действия,отсутствующие у класса-родителя, то в конструкторе класса-потомка следует сначала вызвать конструктор своего родителя, а уже потом осуществлять дополнительные действия. Вызвать любой перекрытый метод родительского класса можно при помощи зарезервированного слова inherited(унаследованный). Например, если в классе TDescedant имеется свой собственный конструктор typeTDescedant = class(TBase)FMark : Boolean;

constructor Create(Mark: Boolean);
end;

То его реализация могла бы быть такой: constructor TDescedant.Create(Mark:Boolean);

begin
  inherited Create;
  Emark := Mark;
end;

Где вызов родительского конструктора осуществляется оператором inherited Create; а оператор Emark := Mark; Осуществляет дополнительные действия. Кроме деструктора Destroy,в базовом классе Tobject определен метод Free, который прежде проверяет, был ли обьект на самом деле реализован и только потом вызывает метод Destroy. Если обьект не был создан конструктором, то обращение к деструктору,приведет к генерации исключительной ситуации. Следовательно,для уничтожения ненужного обьекта удобнее использовать метод Free, например: MyObject.Free; В классе могут быть определены методы, которые могут вызываться без создания и инициализации обьекта. Эти методы называются методами класса,и для их обьявления используется зарезервированное слово class. Например:

type
  TChildClass = class(Tobject)
    class function ChildClassInfo: string;
  end;
  vary: string;
begin
  y := ChildClassInfo;
end;
Как правило, методы класса предназначены для справочной информации о классе-имени класса, предке класса, размере класса и т.д. События Событие-это то,что происходит в процессе работы программы. В Delphi определенно несколько десятков типовых событий. Рассмотрим,что представляют собой события с точки зрения языка Object Pascal. Событие-это свойство процедурного типа,и его значением является указатель на некоторый метод. Присвоить такому свойству значение означает указать адрес метода, который будет выполняться в момент наступления события.Такие методы называются обработчиками событий. В качестве примера рассмотрим,как описаны стандартные события OnDblClick (возникает при двойном щелчке левой кнопкой мыши), OnMouseDown(возникает при нажатии кнопки мыши) и OnMouseMove (возникает при перемещении мыши) в классе TControl:Tcontrol = class(Tcomponent) Private FonDblClick: TnotifyEvent;FonMouseDown:TmouseEvent; FonMouseMove:TmouseMoveEvent protected property OnDblClick: Tnotify read FonDblClick writeFonDblClick; Property OnMouseDown: TmouseEvent read FonMouseDown writeProperty OnMouseMove: TmouseMoveEvent read FonMouseMove writeFonMouseMove; end; Назначение зарезервированных слов private и protected мы рассотрим позже, а пока отметим следующее. Поля FonDblClick,FonMouseDown и FonMouseMove, используемые при описании свойств-событии, предназначенны для хранения указателя на метод, являющийся обработчиком соответствующего события. Используемые при описаний полей процедурные типы определяются следующим образом: TypeTnotifyEvent = procedure (Sender: Tobject) of object; TmouseEvent = procedure(Sender:Tobject;Button: TmouseButton; Shift: TshiftState; x, y: Integer) of object; TmouseMoveEvent = procedure(Sender:Tobject; Shift: TshiftState;X, y:Integer) of object; Общим для всех процедурных типов является параметр Sender, содержащий указатель на обьект-источник события. Параметр Button содержит информацию,какая клавиша мыши была нажата. Параметр Shift содержит код клавиши,нажатой на клавиатуре.x и y -текущие координаты мыши в пределах компонента. Поля

Полями называются инкапсулированные в классе данные. Поля класса подобны полям записи, но в отличие от них могут быть любого типа, в том числе классами, например: type TchildClass = class Fone : Integer; Ftwo : String; Fthree : TObject; end; Исходя из принципа инкапсуляции,обращение к полям должно осуществляется при помощи методов и свойств класса. Вместе с тем, в Object Pascal допускается обращаться к полям и непосредственно .Для того чтобы обратиться к полю, необходимо записать составные имя, состоящее из имени класса и имени поля,разделенных точкой,например: var MyObject : TChildClass; begin MyObject.Fone := 16; MyObject.Ftwo := 'Некоторое строковое значение'; end; Обычно имя поля такое же, как и имя соответствующего свойства, но к имени поля в качестве первой буквы добавляют букву F.

Публикация во FrontPage.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses comobj;
 {....}
 procedure TForm1.PublishTheWeb(dir, dest, un, pw: string);
 { dir: 
    // location of the local web to publish 
    // Ort des lokalen Webs, das veroffentlicht werden soll }
 { dest 
    // destination URL, where the web to publish 
    // URL, wo das Web veroffentlicht werden soll}
 { un 
    // username 
    // Benutzername }
 { pw 
    // password 
    // Kennwort }
 const
    FpPublishNone = 0;
   FpPublishIncremental = 1;
   FpPublishAddToExistingWeb = 2;
   FpPublishCopySubwebs = 4;
 var
    fp: OLEVariant;
   web: OLEVariant;
 begin
   try
     // create an instance of frontpage 
    // eine Instanz von Frontpage erzeugen 
    fp := CreateOleObject('Frontpage.Application');
     // open the web to publish 
    // das zu veroffentlichende Web offnen 
    web := fp.Webs.Open(dir);
     // before the web can be published, you should open an Internet connection 
    // using rasdial.exe 
    // see Delphi-Tip "...establish a connection to the internet ?" 
    // or you set your Internetoptions to connect automatically to the Internet 
    // Bevor das Web veroffentlicht werden kann, sollte eine Internetverbindung 
    // aufgebaut werden 
    // siehe Delphi-Tip "...eine Internetverbindung aufbauen ?" 
    // oder die Internetoptionen sind so gesetzt, dass eine Verbindung automatisch 
    // aufgebaut wird 
    { open the Internet-connection}
     // publish it 
    // veroffentlichen 
    web.Publish(dest, FpPublishAddToExistingWeb, un, pw)
   except
     ShowMessage('Can''t load FrontPage.')
   end;
 end;

Свойства в Delphi.

- Ты видел, козлина, чтобы я наш apple гадостью всякой поливал? - Урод ты, дед. У нас четвертый пень уже год стоит вместо MAC`a. - Да, внучек, я этот пентиум на мешок с маком и выменял.

Свойства в Delphi могут быть простыми, перечислимыми, типа множество, объектными и массивами.

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

unit OurComponent;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TOurComponent = class(TComponent)
  private
    { Private declarations }
    FMyInteger: Integer;
    FMyChar: Char;
    FMyString: string;
    procedure SetMyInteger(const Value: Integer);
    procedure SetMyChar(const Value: Char);
    procedure SetMyString(const Value: string);
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    { Published declarations }
    property MyInteger: Integer read FMyInteger write SetMyInteger;
    property MyChar: Char read FMyChar write SetMyChar;
    property MyString: string read FMyString write SetMyString;
end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('Samples', [TOurComponent]);
end;
{ TOurComponent }
procedure TOurComponent.SetMyChar(const Value: Char);
begin
  FMyChar := Value;
end;
procedure TOurComponent.SetMyInteger(const Value: Integer);
begin
  FMyInteger := Value;
end;
procedure TOurComponent.SetMyString(const Value: string);
begin
  FMyString := Value;
end;
end.

Перечислимые свойства - это стандартные перечислимые, логический и определенные пользователем перечислимые типы данных. В инспекторе объектов для них редакторы свойств отображаются в виде списка выбора (combobox).

unit OurComponent;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs;
type
  TMyEnumerate = (meFirst, meSecond, meThird);
  TOurComponent = class(TComponent)
  private
    { Private declarations }
    FMyBoolean: Boolean;
    FMyCursor: TCursor;
    FMyEnumerate: TMyEnumerate;
    procedure SetMyCursor(const Value: TCursor);
    procedure SetMyBoolean(const Value: Boolean);
    procedure SetMyEnumerate(const Value: TMyEnumerate);
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    { Published declarations }
    property MyCursor: TCursor read FMyCursor write SetMyCursor;
    property MyBoolean: Boolean read FMyBoolean write SetMyBoolean;
    property MyEnumerate: TMyEnumerate read FMyEnumerate write
    SetMyEnumerate;
end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('Samples', [TOurComponent]);
end;
{ TOurComponent }
procedure TOurComponent.SetMyCursor(const Value: TCursor);
begin
  FMyCursor := Value;
end;
procedure TOurComponent.SetMyEnumerate(const Value: TMyEnumerate);
begin
  FMyEnumerate := Value;
end;
procedure TOurComponent.SetMyBoolean(const Value: Boolean);
begin
  FMyBoolean := Value;
end;
end.

Свойства типа множество - это стандандартные, а также определенные пользователем, множества. В инспекторе объектов для них редакторы свойств имеют знак [+] перед названием свойства.

Размер публикуемых (published) свойств ограничен 32 элементами. Если вам необходимо свойство с более чем 32 элементами - объявите свое свойство публичным (public).

unit OurComponent;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs;
type
  TMySet = (msOne, msTwo, msThee, msFour, msFive);
  TMySets = set of TMySet;
  TOurComponent = class(TComponent)
  private
    { Private declarations }
    FMySet: TMySets;
    procedure SetMySet(const Value: TMySets);
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    { Published declarations }
    property MySet: TMySets read FMySet write SetMySet;
end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('Samples', [TOurComponent]);
end;
{ TOurComponent }
procedure TOurComponent.SetMySet(const Value: TMySets);
begin
  FMySet := Value;
end;
end.

Объектные свойства - это стандартные (TFont, TCanvas и т.п.) или определенные пользователем объекты, как правило наследники TPersistent. В инспекторе объектов для них редакторы свойств имеют знак [+] перед названием свойства и кнопку с тремя точками [...](пользовательский редактор свойства).

unit OurComponent;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs;
type
  TOurComponent = class(TComponent)
  private
    { Private declarations }
    FMyFont: TFont;
    procedure SetMyFont(const Value: TFont);
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property MyFont: TFont read FMyFont write SetMyFont;
end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('Samples', [TOurComponent]);
end;
{ TOurComponent }
constructor TOurComponent.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  {В отличие от предыдущих свойств, объекты обязательно
  необходимо создавать в конструкторе компонента, как
  правило после вызова унаследованного конструктора}
  FMyFont.Create;
end;
destructor TOurComponent.Destroy;
begin
  FMyFont.Free;
  {В отличие от предыдущих свойств, объекты обязательно
  необходимо разрушать в деструкторе компонента, как
  правило перед вызовом унаследованного деструктора}
  inherited Destroy;
end;
procedure TOurComponent.SetMyFont(const Value: TFont);
begin
  {Объектному свойству значение присваивается с
  помощью вызова метода Assign}
  FMyFont.Assign(Value);
end;
end.

На заметку: Перемещение между объявлением метода и реализацией происходит по нажатию клавиш Ctrl + Shift + стрелки вверх/вниз.

Свойства типа масив - обычные массива Object Pascal, но в отличии от последних могут индексироваться не только числовыми значениями но и строковыми. К сожалению этот тип свойства требует пользовательского редактора свойств (в инспекторе объектов редактор свойства имеет кнопку с тремя точками [...]), по-этому в указанном ниже примере свойство ArrayProp объявлено в секции public.

type
  TOurComponent = class(TComponent)
  private
    { Private declarations }
    FArrayProp: array[0..9] of integer;
    function GetArrayProp(aIndex: integer): integer;
    procedure SetArrayProp(aIndex: integer; const Value: integer);
  protected
    { Protected declarations }
  public
    { Public declarations }
    property ArrayProp[aIndex: integer]: integer read GetArrayProp
    write SetArrayProp;
  published
    { Published declarations }
end;

Спецификаторы свойств

Спецификатор default указывает сохранять значение свойства в файле формы или нет. Если значение свойства совпадает со значением default - значение в файле формы не сохраняется, если значения не равны - сохраняется. Это можно проверить, положив компонент на форму и выбрать правой кнопкой мыши пункт меню "View as Text". Default не устанавливает первоначальное значение свойства к указанному. Это необходимо сделать в конструкторе компонента.

unit OurComponent;
interface
uses
  Windows, SysUtils, Classes, Graphics, Forms, Controls;
type
  TOurComponent = class(TComponent)
  private
    { Private declarations }
    FMyInteger: Integer;
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
  published
    { Published declarations }
    property MyInteger: Integer read FMyInteger write FMyInteger default 10;
end;
implementation
constructor TOurComponent.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FInteger := 10;
end;
end.

Спецификатор nodefault отменяет заданное по умолчанию значение свойства. Этот спецификатор, как правило, используется для отмены заданого по умолчанию значения унаследованного свойства. Например: property AutoSize nodefault; Спецификатор stored указывает когда сохранять в файле формы значение свойства. После stored может стоять true (всегда сохранять), false (никогда не сохранять) или название функции, которая возвращает логический результат.

property OneProp: integer read FOneProp write SetOneProp stored False;
property TwoProp: integer read FTwoProp write SetTwoProp stored True;
property ThreeProp: integer read FThreeProp write SetThreeProp stored Fuct;

Спецификатор index мы разберем в следующем выпуске, когда будем говорить о методах доступа к значению свойства. На заметку: Выделять прямоугольный фрагмент текста в редакторе исходного кода можно с помощью комбинации клавиш Alt + Shift + стрелки вверх/вниз.

Создание свойства-массива компонентов.

Автор: Mike Scott У меня есть форма, на которой имеется около 20 компонентов CheckBox, и я хотел бы поочередно ссылаться на них как на элементы массива... Вот способ использования свойства-массива. Здесь использована стандартная техника создания списка компонентов CheckBox в обработчике события OnCreate, позволяющая затем обращаться к ним как именно к массиву компонентов CheckBox.

Здесь я приведу два способа "собирания" CheckBox в одном массиве. Один просто собирает все CheckBox на форме. Это простой список, но вы должны убедиться в том, что порядок размещения в нем компонентов такой, каким вы хотите его видеть в массиве. Это нормально в данном случае. Другой способ перечисляет все компоненты согласно их нумерации, заложенной в их имени, но чтобы им воспользоваться, его необходимо раскоментарить. Существует множество других способов. Пусть данный совет послужит вам отправной точкой в изучении интересного вопроса оптимизации кода.

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FCheckboxes: TList;
    function GetCheckbox(Index: integer): TCheckbox;
  public
    property Checkboxes[Index: integer]: TCheckbox read GetCheckbox;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
  i: integer;
  AComponent: TComponent;
begin
  FCheckboxes := TList.Create;
  { ТАКЖЕ: это собирает все CheckBox в порядке очередности }
  for i := 0 to ComponentCount - 1 do
    if Components[i] is TCheckbox then
      FCheckboxes.Add(Components[i]);
  { ИЛИ: если они имеют имя CheckboxNN и вы хотите
  разместить их в массиве в том же порядке... }
  {for i := 1 to MaxInt do begin
  AComponent := FindComponent( 'Checkbox' + IntToStr( i ) ) ;
  if AComponent = NIL then break ;
  FCheckboxes.Add( AComponent ) ;
  end ;}
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FCheckboxes.Free;
end;
function TForm1.GetCheckbox(Index: integer): TCheckbox;
begin
  Result := TCheckbox(FCheckboxes[Index]);
end;

Пропорциональное отображение в TScrollBar или TStringGrid.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// Getting a TScrollbar control to Show a proportional thumb: 
procedure TForm1.Button1Click(Sender: TObject);
 var
   info: TScrollInfo;
 begin
   FillChar(info, SizeOf(info), 0);
   with info do
   begin
     cbsize := SizeOf(info);
     fmask  := SIF_PAGE;
     nPage  := ScrollBar1.LargeChange;
   end;
   SetScrollInfo(ScrollBar1.Handle, SB_CTL, info, True);
 end;
 // Same for a TStringGrid 
procedure TForm1.Button1Click(Sender: TObject);
 var
   info: TScrollInfo;
 begin
   FillChar(info, SizeOf(info), 0);
   with info do
   begin
     cbsize := SizeOf(info);
     fmask  := SIF_ALL;
     GetScrollInfo(StringGrid1.Handle, SB_VERT, info);
     fmask := fmask or SIF_PAGE;
     nPage := 5 * (nmax - nmin) div StringGrid1.RowCount;
     // whatever number of cells you consider a "page" 
  end;
   SetScrollInfo(StringGrid1.Handle, SB_VERT, info, True);
 end;

Как получить контекст свойства по его целочисленному значению.

Автор: Ralph Friedman WEB-сайт: http://www.lmc-mediaagentur.de

unit PropertyList;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    StdCtrls, Buttons;
type
  TMyStream = class(TFileStream)
  private
    FFred: integer;
  published
    property Fred: integer read FFred write FFred;
  end;
type
  TFrmPropertyList = class(TForm)
    SpeedButton1: TSpeedButton;
    ListBox1: TListBox;
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  FrmPropertyList: TFrmPropertyList;
implementation
{$R *.DFM}
uses
  TypInfo;
procedure ListProperties(AInstance: TPersistent; AList: TStrings);
var
  i: integer;
  pInfo: PTypeInfo;
  pType: PTypeData;
  propList: PPropList;
  propCnt: integer;
  tmpStr: string;
begin
  pInfo := AInstance.ClassInfo;
  if (pInfo = nil) or (pInfo^.Kind <> tkClass) then
    raise Exception.Create('Invalid type information');
  pType := GetTypeData(pInfo); {Pointer to TTypeData}
  AList.Add('Class name: ' + pInfo^.Name);
  {If any properties, add them to the list}
  propCnt := pType^.PropCount;
  if propCnt > 0 then
  begin
    AList.Add(EmptyStr);
    tmpStr := IntToStr(propCnt) + ' Propert';
    if propCnt > 1 then
      tmpStr := tmpStr + 'ies'
    else
      tmpStr := tmpStr + 'y';
    AList.Add(tmpStr);
    FillChar(tmpStr[1], Length(tmpStr), '-');
    AList.Add(tmpStr);
    {Get memory for the property list}
    GetMem(propList, sizeOf(PPropInfo) * propCnt);
    try
      {Fill in the property list}
      GetPropInfos(pInfo, propList);
      {Fill in info for each property}
      for i := 0 to propCnt - 1 do
        AList.Add(propList[i].Name + ': ' + propList[i].PropType^.Name);
    finally
      FreeMem(propList, sizeOf(PPropInfo) * propCnt);
    end;
  end;
end;
function GetPropertyList(AControl: TPersistent; AProperty: string): PPropInfo;
var
  i: integer;
  props: PPropList;
  typeData: PTypeData;
begin
  Result := nil;
  if (AControl = nil) or (AControl.ClassInfo = nil) then
    Exit;
  typeData := GetTypeData(AControl.ClassInfo);
  if (typeData = nil) or (typeData^.PropCount = 0) then
    Exit;
  GetMem(props, typeData^.PropCount * SizeOf(Pointer));
  try
    GetPropInfos(AControl.ClassInfo, props);
    for i := 0 to typeData^.PropCount - 1 do
    begin
      with Props^[i]^ do
        if (Name = AProperty) then
          result := Props^[i];
    end;
  finally
    FreeMem(props);
  end;
end;
procedure TFrmPropertyList.SpeedButton1Click(Sender: TObject);
var
  c: integer;
begin
  ListProperties(self, ListBox1.Items);
  for c := 0 to ComponentCount - 1 do
  begin
    ListBox1.Items.Add(EmptyStr);
    ListProperties(Components[c], ListBox1.Items);
  end;
end;
end.

Как получить значение свойства в виде варианта по тексту имени свойства.

Автор: Ralph Friedman WEB-сайт: http://www.lmc-mediaagentur.de

unit MorePropInfo;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    StdCtrls;
type
  TFrmMorePropInfo = class(TForm)
    Button1: TButton;
    Button2: TButton;
    ListBox1: TListBox;
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  FrmMorePropInfo: TFrmMorePropInfo;
implementation
{$R *.DFM}
uses
  TypInfo;
procedure GetPropertyValues(AObj: TObject; AValues: TStrings);
var
  count: integer;
  data: PTypeData;
  default: string;
  i: integer;
  info: PTypeInfo;
  propList: PPropList;
  propInfo: PPropInfo;
  propName: string;
  value: variant;
begin
  info := AObj.ClassInfo;
  data := GetTypeData(info);
  GetMem(propList, data^.PropCount * SizeOf(PPropInfo));
  try
    count := GetPropList(info, tkAny, propList);
    for i := 0 to count - 1 do
    begin
      propName := propList^[i]^.Name;
      propInfo := GetPropInfo(info, propName);
      if propInfo <> nil then
      begin
        case propInfo^.PropType^.Kind of
          tkClass, tkMethod:
            value := '$' + IntToHex(GetOrdProp(AObj, propInfo), 8);
          tkFloat:
            value := GetFloatProp(AObj, propInfo);
          tkInteger:
            value := GetOrdProp(AObj, propInfo);
          tkString, tkLString, tkWString:
            value := GetStrProp(AObj, propInfo);
          tkEnumeration:
            value := GetEnumProp(AObj, propInfo);
        else
          value := '???';
        end;
        if propInfo.default = longint($80000000) then
          default := 'none'
        else
          default := IntToStr(propInfo.default);
        AValues.Add(Format('%s: %s [default: %s]', [propName, value, default]));
{$80000000 apparently indicates "no default"}
      end;
    end;
  finally
    FreeMem(propList, data^.PropCount * SizeOf(PPropInfo));
  end;
end;
procedure TFrmMorePropInfo.Button2Click(Sender: TObject);
var
  count: integer;
  data: PTypeData;
  i: integer;
  info: PTypeInfo;
  propList: PPropList;
  propInfo: PPropInfo;
  propName: string;
  propVal: variant;
  tmpS: string;
begin
  info := Button2.ClassInfo;
  data := GetTypeData(info);
  GetMem(propList, data^.PropCount * SizeOf(PPropInfo));
  try
    count := GetPropList(info, tkAny, propList);
    ListBox1.Clear;
    for i := 0 to count - 1 do
    begin
      propName := propList^[i]^.Name;
      propInfo := GetPropInfo(info, propName);
      if propInfo <> nil then
      begin
        case propInfo^.PropType^.Kind of
          tkClass, tkMethod:
            propVal := '$' + IntToHex(GetOrdProp(Button2, propInfo), 8);
          tkFloat:
            propVal := GetFloatProp(Button2, propInfo);
          tkInteger:
            propVal := GetOrdProp(Button2, propInfo);
          tkString, tkLString, tkWString:
            propVal := GetStrProp(Button2, propInfo);
          tkEnumeration:
            propVal := GetEnumProp(Button2, propInfo);
        else
          propVal := '...';
        end;
        tmpS := propVal;
        ListBox1.Items.Add(Format('%s: %s [default: %s]', [propName, tmpS, '$'
          + IntToHex(propInfo.default, 8)]));
{$80000000 apparently indicates "no default"}
      end;
    end;
  finally
    FreeMem(propList, data^.PropCount * SizeOf(PPropInfo));
  end;
end;
end.

Редактор свойства Color с заданными ограничениями.

Автор: Ed Jordan

Редактор свойства, пример которого приведен ниже, имеет ограничение на устанавливаемые цвета: только clRed, clWhite или clBlue.

unit ClrComps;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes,
  Graphics, Controls, Forms, Dialogs, DsgnIntf;
type
  TColorComponent = class(TComponent)
  private
    FColor: TColor;
  protected
    procedure SetColor(Value: TColor);
  public
    constructor Create(AnOwner: TComponent); override;
  published
    property Color: TColor read FColor write SetColor;
  end;
  { Это специальный редактор свойства выбора цветов... }
  TMyColorProperty = class(TIntegerProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    function GetValue: string; override;
    procedure GetValues(Proc: TGetStrProc); override;
    procedure SetValue(const Value: string); override;
  end;
procedure Register;
implementation
{ TMyColorProperty }
function TMyColorProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paMultiSelect, paValueList];
end;
function TMyColorProperty.GetValue: string;
begin
  Result := ColorToString(TColor(GetOrdValue));
end;
procedure TMyColorProperty.GetValues(Proc: TGetStrProc);
begin
  Proc('clRed');
  Proc('clWhite');
  Proc('clBlue');
end;
procedure TMyColorProperty.SetValue(const Value: string);
var
  NewValue: Longint;
begin
  if IdentToColor(Value, NewValue) and
    ((NewValue = clRed) or
    (NewValue = clWhite) or
    (NewValue = clBlue)) then
    SetOrdValue(NewValue);
end;
{ Образец компонента... }
constructor TColorComponent.Create(AnOwner: TComponent);
begin
  inherited Create(AnOwner);
  FColor := clRed;
end;
procedure TColorComponent.SetColor(Value: TColor);
begin
  if (Value = clRed) or
    (Value = clWhite) or
    (Value = clBlue) then
  begin
    FColor := Value;
  end;
end;
procedure Register;
begin
  RegisterComponents('Samples', [TColorComponent]);
  RegisterPropertyEditor(TypeInfo(TColor), TColorComponent,
    'Color', TMyColorProperty);
end;
end.

Способ защиты программы от несанкционированного копирования.

Автор: Павел Озерский WEB-сайт: http://delphibase.endimus.com

Дробим прогу на exe и несколько DLL-ей, функциям в DLL-ях даем одинаковые рubliс'и, а названия DLL-ям даем длинные, притом начальные части названий должны совпадать, так, чтобы короткие имена отличались только числом после тильды. Вот по коротким-то именам мы их вызывать и будем. А число после тильды, как известно, зависит от порядка копирования файлов на диск, при стирании файлов с "промежуточными" номерами не меняется и при копировании не воспроизводится. Так что, копируя в "неалфавитном порядке", перемежая рабочие DLL-и с "заглушками" (потом их можно стереть), легко можно добиться невоспроизводимости сочетаний длинное-короткое имя, и после несанкционированного копирования EXE будет вызывать не те функции из не тех библиотек. Единственная тонкость: неплохо поставить проверку на длинность имени в самих dll-ях, причем с использованием FindFirstFile, так как часть win32-платформ передает dll-и, вызываемой по короткому имени, как имя ее модуля именно короткое имя. А иначе прога будет легко копироваться из DOS (т.е. только короткие имена) без потери функциональности.

Как защитить запись в DBGrid от удаления.

- Убирать за собой надо... - раздраженно сказал программист, удаляя папку `mssetup.t
procedure TForm1.DBGrid1KeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
  if (ssctrl in shift) and (key=vk_delete) then
    key:=0;
end;

Доступ к защищенным свойствам.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{*** It is not always necessary to write a new class when you miss the properties you need ***}
 { 
  Let's say, you writing a program with some standard controls on it's form. 
  One of the components is a TProgressBar. 
  All components, except the progressbar, are yellow (brrrr!!!) 
  but it is not possible to change the color of the progressbar the normal way. 
  There are three things that you can do: 
  1. Leave it this way 
  2. Write a new component 
  3. Try to use WinAPI 
  OR try this: 
}
 // Put this on top of your unit 
type
  TAccessControl = class(TWinControl)
    public
     property Color;
  end;
 // and put into TForm1.FormCreate() the following code: 
  TAccessControl(MyProgressBar).Color:= clYellow;
 { 
  This is possible because TwinControl is the immediate ancestor. 
  There is also another trick to give a existing class more functionality 
  without the need of creating a new component, for example: 
}
 type
  // Just a few lines of code and your TLabel 
 // components on the form has more 
 // functionality without writing a new class 
 TLabel = class(stdctrls.TLabel)
  public
    procedure SayHello();
  end;
  // ... 
procedure TLabel.SayHello();
 begin
   ShowMessage( 'I just want to say hello (and not hello world!)');
 end;
 // As you know the Custom version of a class does not 
// publish all it's properties, so you can do it the same way as with TLabel. 
type
  TTheBaseClass = class(UnitName.TTheBaseClass)
  public
    property Align;
    property Color;
    property YourProp;
    {etc etc}
  end;
 { 
  NOTES: 
  - You can never add new published properties when controls properties are stored 
    in the DFM file (on a form) 
  - You can put modified classes into another unit file so that you can add it 
    AS THE LAST UNIT to the uses clause. 
  I hope you find it usable. 
}

Защита программ от взлома.

Статьи по Delphi (014)…
Ползет по улице в дымину пьяный хакер. Еле руки и ноги передвигает. А навстречу ему другой, трезвый: - Слушай, Вась, ты чего? Ведь ты же неделю назад закодировался? - Ага! - Ик! - А я - Ик! - в-вчера к-к-код подобрал...

Эта статья посвящена достаточно актуальной в настоящее время тематике - защите программ от взлома и нелегального копирования. Этой теме посвящено много статей, одна из наиболее интересных (из тех, которые попались мне) - статья "Защита shareware-программ" Владимира Каталова в Компьютерре Online#240. Он привел ряд советов по написанию shareware программ и я не хочу повторяться - сходите, почитайте.

Рассмотрим некоторые тонкости организации защиты на достаточно популярном примере - предполагаем, что программа защищена некоторым кодом (серийным номером, паролем), который сообщается пользователю после соблюдения им определенных условий. До регистрации в этой программе заблокирован ряд каких либо полезных функций, используется надоедливая реклама или ограничен строк работы. После ввода этого кода производится его проверка и при положительном исходе проверки программа начинает нормально работать. По неизвестной мне причине в большинстве современных программ данная защита сделана однообразно и для ее снятия необходимо 10-15 минут. В этой статье я постараюсь поделиться опытом в построении систем защиты. Могу сразу предупредить - хорошему хакеру противостоять практически бесполезно, да и не нужно - при желании любая защита может быть взломана, это вопрос времени.

Инструментарий хакера. Современный хакер имеет в своем арсенале набор разнообразных утилит для взлома. Их можно подразделить на несколько категорий:

Отладчики. Позволяют прерывать выполнение программы при достижении заранее заданных условий, производить пошаговое выполнение программы, изменять содержимое памяти и регистров и т.п. . Наиболее популярным, удобным и мощным является отладчик SoftICE, который при достаточно примитивном интерфейсе обладает приличными возможностями и весьма стабильно работает. Дизассемблеры. Производят дизассемблирование программы для дальнейшего изучения полученного кода. Один из наиболее популярных - IDA. От дизассемблера достаточно легко защититься - зашифровать или заархивировать программу. Тогда дизассемблируется только архиватор или кодировщик. Средства мониторинга. Это набор утилит, отслеживающих операции с файлами, реестром, портами и сетью. Средства пассивного анализа программы. Показывают разную информацию о программе - извлекают ресурсы, показывают связи, используемые библиотеки. Классический пример - утилита DEPENDS.EXE из комплекта Visual Studio. Она показывает, какие библиотеки используются программой и какие функции импортируются. Прочие утилиты. Их великое множество (можно найти на диске типа "Все для хакера", причем в изобилии). Это разнообразные редакторы, анализаторы ...

Наиболее популярны следующие программы мониторинга :

FileMon - утилита, позволяющая вести мониторинг всех операций с файлами. Имеет удобный фильтр, может сохранять отчет в файле. Поэтому нет смысла делать "секретные" файлы где-нибудь в Windows/System - их элементарно найти. RegMon - аналог FileMon, только ведется мониторинг всех операций с реестром. Аналогично файлам, бессмысленно создавать в реестре "секретные" ключи - они сразу бросаются в глаза. PortMon - мониторинг работы с портами ввода/вывода TCP_VIEW - монитор соединений по TCP-IP RegUtils - набор утилит для контроля за реестром - делает копии реестра, позволяет сравнивать копии и просматривать изменения.

Утилиты типа FileMon могут резко упростить взлом программы - легко определить место, в котором программа обращается к указанному файлу или ключу реестра.

Основы построения защиты - шаг за шагом

Как ввести регистрационный код. Ввод пароля или регистрационного номера является ответственным делом - хакер постарается отловить адрес памяти, в который будет записан пароль. Затем на обращение по этому адресу ставится точка останова (команда BPM в SoftICE), что позволяет поймать начало процедуры проверки регистрационного кода. Если для ввода используются стандартные элементы ввода Windows, то алгоритм действий хакера можно формализовать и выглядит он примерно так:

Устанавливает точку останова на считывание текста из стандартного элемента ввода (функции GetWindowText, GetGlgItemText модуля KERNEL32) При вызове этой функции анализируем ее параметры и таким образом определяем, по какому адресу будет размещено считываемое значение и ставим обращение к этой области памяти точку останова. А достоверности определенного адреса легко убедиться - после выполнения функции там появится введенная строка При срабатывании этой точки останова мы попадаем в анализатор введенного значения и либо делаем генератор регистрационных ключей, либо ломаем процедуру проверки. И то, и другое весьма просто сделать - достаточно только изучить ассемблер и API

Набор этих действий стандартен и мне не раз попадались подробные руководства типа "Взлом Windows программ - шаг за шагом", ориентированные на продвинутого пользователя.

Рассмотри несколько решений, которые могут затруднить взлом на этом этапе.

Старайтесь как можно меньше применять стандартные функции (особенно API-шные) и компоненты VCL. Так что Assembler, Assembler и еще раз Assembler ... Сущность этого совета надеюсь очевидна - современные дизассемблеры умеют распознавать стандартные процедуры высокоуровневых языков, а API - вообще отдельный разговор - SoftICE обладает изумительной возможностью - загружать символьные имена для любых указанных библиотек (особенно для KERNEL32.DLL) - отладка резко упрощается, т.к. мы видим имена вызываемых функций и можем ставить точки останова на вызов функций по их имени. Применяйте нестандартный способ ввода пароля. Наипростейший путь - написать свой визуальный компонент для ввода регистрационного кода. Он конечно должен будет обрабатывать события от клавиатуры, но момент считывания кода нельзя поймать избитыми методами. Это уже что-то, но есть второй способ взлома, основанный на поиске введенного кода в памяти. Для этого в SoftICE есть удобная команда "S стартовый адрес L длина 'образец'" , которая позволяет найти введенное значение в памяти. Не храните введенный код в одном месте ! Не храните введенный код открытым текстом ! Итак, что же следует сделать. Для начала необходимо завести в программе 5-10 переменных типа STRING и после ввода кода переписать введенное значение в них. Делать это лучше всего не в одном месте, а распределить по программе. Таким образом поиск даст кучу адресов, по которым будет находиться введенный код. Я в таком случае поступаю так - по таймеру создаю в динамической памяти новую строковую переменную, пишу в нее код. Затем на следующем срабатывании таймера создаю новую переменную, переписываю в нее код, а старую уничтожаю. При определенном навыке можно заполонить память значениями введенного кода и сделать поиск почти бесполезным. Причем такое копирование можно совместить с проверкой кода или эмуляцией этой проверки. Затем с эти строками неплохо поделать какие-либо операции - сравнить с чем-нибудь ... Советы 3 и 1 можно объединить - создать свой компонент, который позволит вводить код нестандартным способом с его одновременной шифровкой. Анализ регистрационного кода. Итак, код введен и приняты меры для того, чтобы его было непросто найти (хотя найти то его можно, но это время, навык ...). Теперь следующий шаг - анализ. Поэтому сразу совет: Ни в коем случае не анализируйте код сразу после его ввода. Чем дальше ввод кода от его анализа, тем лучше. Самое разумное - после ввода кода поблагодарить пользователя за сотрудничество и сообщить, что со временем будет выполнена регистрация программы. А анализ кода произвести, например, через 1-2 минуты в совершенно другом месте программы. Не проверяйте код только в одном месте и не пишите для проверки функцию. Достаточно найти и отключить эту проверку, и защита взломана. Если проверок несколько, они разные и распределены по программе, то взлом затрудняется. Не проверяйте пароль одним алгоритмом. Рекомендуется разработать 2-3 алгоритма проверки, например 1-2 цифры должны делиться на 3, а 3-7 наложенные по какому-либо алгоритму на имя пользователя должны дать в сумме 4. Эти две проверки осуществляем в различных местах с достаточно большим временным разносом - взломав первый метод хакер не будет догадываться о существовании еще нескольких, которые проявятся со временем. Ни в коем случае не предпринимайте никаких действий после проверки. По неизвестной причине большинство программ выглядят примерно так
if not(SuperRegCodeCheck) then 
begin 
  ShowMessage('Неверный код, дальнейшая работа невозможна');
  halt;
end;

В примере некая процедура проверяет код и при несовпадении предпринимает активные действия, которые буквально кричат "вот она где защита !!". Наилучший шаг - выждать день -два (или хотя бы минут 15). Причем все действия по проверке следует как можно дальше отнести от выдачи сообщений и прочих действий, предпринимаемых при обнаружении неправильного кода.

Отвлекающие маневры. Кроме реальных функций проверки кода очень неплохо сделать пару бутафорских - они будут вызываться после ввода кода, проводить активные манипуляции с введенным значением, выдавать сообщения о некорректности введенного кода ... - т.е. отвлекать внимание от реальной проверки. Не храните результатов проверки в переменной и не используйте ее для явного ограничения функций незарегистрированной программы. Классический пример нарушения этого правила
if not(LegalCopy) then
  ShowMessage('Сохранение работает только в зарегистрированной версии')
else
  SaveFile;

Таким образом элементарный анализ показывает, что переменная LegalCopy хранит результат проверки и поставив на нее точку останова можно выловить саму проверку. Отредактировав это значения в памяти можно временно сделать копию "зарегистрированной",а установка точки останова на изменение этой переменной выведет на место ее проверки. Да и взлом сводится к тому, что функция проверки кода урезается до двух команд ассемблера:

MOV [адрес LegalCopy], 1
RET
(вытекает из 9) Не храните результатов проверки на диске или в реестре.

Типичная ошибка - выяснили, что копия зарегистрирована и сделали где-нибудь метку. Отловить это достаточно просто (см. описание REGMON и FILEMON). Наилучший способ - сохранить пароль и имя пользователя в том виде, в котором он их ввел. Затем при каждом запуске программы проверять корректность этого кода, но не забывая Совет _11. Ничего не проверяйте сразу при запуске приложения или сразу после считывания сохраненного имени или кода. Помните, что считывание кода и его ввод в окне регистрации идентичны по мерам защиты - дублирование в разных областях памяти, шифрование ...

Выводы: мы устроим проверку кода в нескольких местах программы, при этом применим несколько алгоритмов проверки, не будем использовать API.Кроме того, стоит проделать несколько отвлекающих маневров.

Общие советы по защите программ.

CRC - контрольные суммы. Любой файл, строку или блок данных можно защитить контрольной суммой, которую затем можно рассчитать и сравнить с эталоном. При сравнении с эталоном конечно следует весть осторожно - см. первые 11 советов. Итак, совет 12. Защищайте программы и данные контрольными суммами. Это поможет не только от взлома, но и защитит программы от вируса или внедрения троянца. Применяйте шифровку программ и данных. Очень неплохо сжать программу и данные. Я, например, разработал свой собственный архиватор - RAR-у и ZIP-у он конкуренции не составит, но сжатые им данные разжать очень непросто, придется изрядно повозиться. Да и изменить их проблематично - придется разжать, изменить и сжать. Отлов пошаговой отладки программы. Существует много способов, я в свое время провел целое исследование этого вопроса под DOS, насобирал и придумал не менее 20 методов, но они мало приемлемы под Windows. Самый простой и надежный способ - таймер. При работе программы периодически фиксируем системное время и рассчитываем время работы фрагментов кода между ними. И если 200-400 команд процессора работают 2-3 минуты, то тут есть над чем задуматься. Не определяйте дату и время стандартными способом !! Придумайте что-нибудь оригинальное. Не стоит хранить что-либо секретное в файлах или реестре. Работа с файлами или реестром может быть детально запротоколирована и проанализирована, и все тайное станет явным. Не храните ничего важного открытым текстом, особенно сообщения типа "Это незарегистрированная версия ...", "Введенный пароль не верен ...".

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

Советы по созданию меток для организации ограничения по времени

Защита "ограничение времени работы" состоит в том, что программа каким образом фиксирует момент своего первого запуска и работает установленное время (обычно 20-30 дней). После истечения этого срока программа отказывается запускаться. Как проверить текущую дату я уже где-то тут писал - нестандартным способом, например по дате на файлах реестра или свежесозданном своем файле. Весь фокус в другом - как зафиксировать на компьютере дату первого запуска (естественно так, чтобы изничтожение программы и ее повторная установка не давали эффекта). Использование "секретных" файлов в системных папках или изменения в существующих файлах легко отловить при помощи FILEMON. Реестр то же отпадает из-за REGMON. Прочие методы (типа записи в ВООТ сектор ...) тоже неприемлемы - не те времена, по Windows все это не пройдет. Наиболее оригинально (на мой взгляд) прошить дату в саму программу и постоянно обновлять ее на своем сайте (естественно, автоматически). Таким образом отсчет неявно идет от момента скачивания программы с сайта. Есть тут правда и минус - после завершения срока можно повторно скачать эту программу и получить еще 15-20 дней ... . С другой стороны это оригинально - пользователю рано или поздно надоест скачивать эту программу и он или откажется от нее, или купит. Но при этом стоит помнить, что программу можно скачать несколько раз и сравнить варианты, выявив, где лежит дата. Поэтому стоит позаботиться о том, чтобы изменился почти весь файл (например, изменить пару опций компилятора).

Советы по формированию регистрационных кодов

Формирование кодов может вестись по следующим основным направлениям:

Жестко фиксированные коды, прошитые в программу. Их обычно немного и их огласка сводит защиту к нулю. Некий алгоритм проверки кода. Немного лучше первого, но лишь немного. Возьмите за пример код Windows - его знает любой пользователь Алгоритм проверки кода, использующий имя пользователя. Очевидно, что для каждого имени будет уникальный номер (или номера - их может быть несколько, в зависимости от алгоритма). Это уже лучше, но нелегальное распространение держится на эгоизме зарегистрированных пользователей - ничто не мешает им предать имя/пароль огласке, но тогда хотя бы можно вычислить виновника и заблокировать его код Алгоритм проверки кода, использующий имя пользователя и некоторые уникальные или динамически изменяющиеся параметры, например информацию о компьютере. Это надежно, дает привязку к компьютеру, но в наш век постоянных апгрейдов очень неудобен. On-Line регистрация. Состоит в том, что программа в On-Line связывается с сайтом разработчиков (или компании, осуществляющей продужу софта) и передает туда ревизиты пользователя. В ответ программе передается регистрационная информация. Этот метод может и хорош для ряда программ, но на мой взгляд не выдерживает никакой критики по двум соображениям: Никто не может гарантировать, что конкретно передаст программа в Инет. А передать она может все, что угодно - параметры компьютера, пароли, любые данные и т.п. Конкретный пользователь ножет не иметь доступа к Инет. Это особенно важно для программ, работа которых не связана напрямую с Сетью. И зарегистрировать такую программу его практически никто к себе на компьютер не пустит (из соображений п.п. 1)

Рекомендовать тут что-либо бесполезно, но я например использую разновидности метода 3.

Программа A4 Proxy для проверки анонимности Proxy.

Оформил: DeeCo

Если надо бысто переключать прокси серверы, проверить их на анонимность, скорость и т.д. воспользуйтесь программой A4Proxy. Ниже написано как настроить свой броузер на работу с этой прогой. Настройка Internet: Explorer: надо зайти Сервис\совойства обозревателя\подключение\настройка сети нужно поставить галочку использовать прокси сервер, ввести адрес и порт на котором работает прокси. Настройка Netscape Navigator: правка/настройки/дополнительные настройки/прокси сервер выбрать "Подключение через прокси сервер", нажать "настройка" и вписать прокси (для HTTP): 127.0.0.1 и порт например: 3128. Настройка Opera: Файл/Настройки/Подключения/Пркси-серверы/ выбрать HTTP и вписать 127.0.0.1 и порт например: 3128. Если надо бысто переключать прокси серверы, проверить их на анонимность, скорость и т.д. воспользуйтесь программой A4Proxy Фишки: Еще при помощи этой проги можно поменять http_referrer. Для этого сначала надо посетить несколько web-страниц, потом открыть закладку "Browser options" и внизу посмотреть поле "Outgoing requestst" т.е. исходящие запросы, это та инфа которую послал бровзер посещенным сайтам, там же есть и "Referer" надо по нему щелкнуть чтобы два поля вверху заполнились, там вписать свое значение например: http://antichat.by.ru/ :), затем нажать кнопку "add" Также можно поменять "User-Agent" но если там что-то типа: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98), то лучше так и оставить и не выделяться, и не забудьте поставить галочку внизу рядом с "Enable HTTP requestst modification" чтобы данные при отправке изменились. Tray menu Favorites - Сдесь находятся, прокси-серверы помеченные как "Дабавлен в список" и одним щелчком мыши можно выбрать активный New on each req - Если выбрать эту функцию то a4proxy будет использовать разные пркси-серверы для каждой web-страницы, это можно сделать и на вкладке "proxy options" Modify Requests - Сдесь можно активировать "Modification List" (список изменений) заголовков запросом Block All Cookies - Выберите, что-бы блокировать все исходящие и всодящие "Куки". Direct Connection - Если вы хотите что бы браузер на прямую обратился к web-странице то используйте Direct Connection Proxy activity Proxy Activity Показывает текущий статус и статистику подключений a4proxy, если все поля пустые это значит что ваш браузер не может соедениться с a4proxy. Proxy Clients - IP адреса в сети, которые используют A4Proxy как прокси-сервер. Этот список можно использовать, что бы добавлять некоторые IP-адреса в stop-лист. Requested Hosts - Имена запрошеных хостов, Names of requested hosts- Этот список можно использовать что бы ассоциировать запросы к удаленному хосту с определенных прокси-серверов, так же, что бы добавить хост в "HOST Stop List". Sockets used - Этот индикатор показывает наибольшее количество каналов данных, используемых одновременно. Sockets Activity - Показывает статистику текущих операций. Proxy check Используется для проверки анонимности и скорости прокси-сервера, список прокси-серверов содержит много полезной информации, проктутите его что бы увидеть все колонки. Кнопка "Check Proxy"- Проверка proxy-сервера, если проверка затянулась то можно нажать "Stop&Save" Reset Data если эта функция активирована, то все данные о прокси-сервере будут сбрасываться при каждой проверке, если не активна(по умолчанию), то данные будут собиратья по принципу "Плохой результат важнее хорошего" Repeats количество повторяющихся проверок Active Requests - количество активных запросов, если в течении длительного времени количество не уменьшается, то рекомендуется нажать кнопку "Stop&Save" Если слева появилась иконка в форме зеленого круга перечеркнутого красным крестиком, это значит что прокси анонимный (скрывает IP) но не соответствует критериям выбранным в закладке "Proxy options". Proxy options Здесь при помощи настроек можно установить фильтры для прокси-сервров, и контролировать их поведение, так же можно изменить порт соединения программы A4Proxy с вашим браузером. прокси-серверы, которые не удовлетворяют требованиям списка, помечаются красным крестиком Первая группа настроек позволяет вам препятствовать использованию прокси серверы, которые посылают следующие заголовки запросов web-сайтам: HTTP_VIA- Это основной фильтр. Параметр HTTP_VIA "говорит" веб-серверу что вы используете прокси сервер. Когда вы вибираете этот фильтр, вы "вычеркиваете" все такие прокси-серверы Присутствие этой переменной в запросе к веб-сайту обнаруживает факт, использования вами proxy-сервера, так же передается информация о proxy- сервере, но не о вашей машине. HTTP_FORWARDED - Аналогичен to HTTP_VIA. Показывает адрес и порт прокси сервера, а так же адрес и порт через который был подан запрос. HTTP_USER_AGENT_VIA - Снова, аналогичный предыдущему. В некоторый случаях показывает через какой прокси был подан запрос. HTTP_CACHE_CONTROL and HTTP_CACHE_INFO - Присутствие любого из этих параметров передает веб-сайту что вы используете прокси, эти параметры содержат информацию о кэше прокси -сервера. HTTP_[PROXY]_CONNECTION- Выключайте в большинстве случаев. Тип соединения "close" показывает, что используется прокси-сервер (Броузет использует соединение типа "Keep-Alive") Use new proxy on each request - Включите, для того что-бы использовать разные прокси при каждом запросе. Таким образом для сервера это будет выглядеть, будто много разных людей получают доступ в одно и то же время. Но это может замедлить соединение. Use n proxies from position... - Вы можете сортировать прокси по разным параметрам, щелкая мышкой в верху колонки Connection: Keep-Alive only - Это два основных вида подключения "Close" и "Keep-Alive". Первый типичен для прокси серверов, второй использыется броузерами для прямого подключения. Некоторые прокси серверы поддерживают "Keep-Alive", это смотрится как прямое соединение броузера. Эта опция ограничиват прокси теми, которые поддерживают такой тип. Наличие информации Proxy_(Connection), в запросе показывает, что он сделан через прокси сервер, если вы хотите убрать эту информацию, выберите эту функцию. Но некоторые веб-сайты, не устанавливают соединения без это1 информации. Кроме того некоторые прокси сами изменяют данные в соответствии со своими настройками Simulate HTTP_X_FORWARDED_FOR_IP (HTTP_CLIENT_IP) - Этот параметр заменяет реальный IP-адрес случайным, Anonymity Check Options: Check for anonymity on each request - Если выбран этот пункт, то A4Proxy проверяет на анонимность текущий прокси сервер, перед отправлением запроса. Check for FTP/HTTPS support on Reset Data - Выберите эту функцию если вы хотите вести полную проверку функций прокси серверов, когда вы проверяете их со сбросом данных(вкладка Proxy Options) Additional Options: Default Proxy - Прокси сервер используемый по умолчанию(если не используется функция Use new proxy on each request, т.е. использование нового прокси при каждом запросе) Proxies in use - Этот индикатор показывает те прокси, которые соответствуют требованиям пользователя. Anonymity rank - Этот индикатор показывает относительный уровень анонимности соединения, в соответствии с выбранными фильтрами и дрю наастройками Browser Port - Порт для входящих HTTP запросов с вашего броузера (и сетевых компьютеров, использующих A4Proxy для соединения с интернет). Вы можете использовать числа от 1024 до 65535 или 80 (рекомендуется оставить по умолчанию) Но если другая программа использует этот же порт, вам следует поменять его и в A4Proxy и в вашем броузере. Check Port - Порт используемый для проверки прокси-сервера на анонимность. Вы можете использовать, то же число что и броузер, или другой. Browser options Во время посещения веб-сайтов, загружая файлы, ваш браузер посылает им, так называемые HTTP request (запросы), информируя о том какая страничка вам нужна, в каждом таком запросе содержится различная информация, необходимая для соединения браузера и веб-сайт: Тип вашей операционной системы (Windows 95/98/Me/2000/XP, и т.д.) и тип используемого браузера, эти данные могут быть использованы для обнаружения "дыр" Accept-Language (Язык/Кодировка) Эта информация может быть использована для ограничения доступа к ресурсам или для др. целей. The Referrer information- позволяет выявить ваши интересы, т.е. откуда вы пришли Cookies- Могут содержать огромное количество информации, как ннапример e-mail, адрес, пароли... Вся эта информация находится в заголовках HTTP запросов. При использовании A4Proxy, который "стоит" между вашим браузером и сайтом, становится возможным изменять или удалять эти данные, для этого надо выбрать функцию "HTTP request modifications" и ввести описание в список изменений, если вы только хотите блокировать "Cooke" то выберите функцию "Block Cookies". Обе эти фукции доступны из меню в трее. Modification list - Содержит список данных HTTP, которые вы хотите изменить или удалить. Variable field- Имена тех переменных, которые вы хотите удалить или изменить (без двоеточия, они будут добавлены автоматически) Replace with... field- Введите здесь новые данные или оставьте поле пустым, если вы хотите удалить эти данные из запросов- конпка "Revove" "Set as defoult"-Нажмите что-бы ипользовать данные по умолчанию, такие как: Accept-Language:en (английский) - язык вашего браузера, Идентификация вас как; User-Agent:Mozilla/4.0 - скрывет тип и версию используемого броузера. Очень просто вносить изменения, используя список исходящих запросов: Список исходящих запросов содержит последние HTTP запросы, разделенные пустыми линиями. Самые последние помещены вверх. Этот список очень удобный, он позволяет немедленно увидеть, какие данные в настоящий момент присутствуют в запросе, и легко удалить или поменять их. Когда вы выберите данные в списке исходящих запросов поля: "Variable" и "Replace with..." автоматически заполняются, затем вы можете изменить содержание и добавить в список исходящих. Некоторые наиболее часто встречающиеся данные: GET, POST, CONNECT - эти данные не отноятся к переменным HTTP, это названия методов запроса, т.о. не изменяйте это поле. Двоеточие разделяет переменную. "Accept:" - Показывает типы файлов которые могут быть приняты. Информирует удаленный компьютер о типе информации к которой вы получаете доступ. "Accept-Language:" - Информирует удаленный компьютер о языке, используемом вашим броузером. "Accept-Encoding:" - Информирует удаленный компьютер кодировке, принимаемой вашим броузером. "If-Modified-Since:" - Загружвет файл, если только он был изменен с [дата/время] "User-Agent:" - Содержит информацию о вашей ОС и типе броузера. "Host:" - Запрошенный хост. Не удаляйте и не изменяйте этот параметр, запрос пропадет "Proxy-Connection:" - Тип соединения. В большинстве случаев не надо удалять, т.к. эти данные изменяются прокси сервером. "Pragma:"- Метод использования прокси сервером кэша. В большинсте случаев присутствие этих данных говорит о подключении через прокси сервер. Security&Redirection CLIENT-PROXY Server Associations - Список клиентов в сети, proxy-серверов сопоставленных с ними. Если IP-адрес клиента(машины сделавшей запрос) находится в этом списке, то HTTP запрос перенаправляется proxy-серверу присвоенному этому IP. Этот режим позволяет например, использовать особый proxy-сервер для каждого компьютера в локальной сети. Чтобы сопоставить машину с определенным прокси сервером выберите клиента(компьютер в LAN-Local area network т.е. локальная сеть) из списка активных proxy-серверов и нажмите "add" (добавить) HOST-PROXY Server Associations - Список удаленных компьютеров и прокси-серверов сопоставленных с ними. Если запрсивший хост содержится в списке, то запрос перенаправляется соответствующему proxy- серверу. Этот режим позволяет например, использовать особый proxy-сервер для определенного хоста. Для то что бы сопоставить хост и proxy-сервер, выберите хост из списка хостов, которые были запрошены (Закладка Proxy Activity : Requested Hosts) и прокси сервер из списка proxy-серверов (Proxy Check tab), нажмите "add" Client Stop List- Все запросы с этих IP-адресов блокируются. HOST Stop List- Все запросы сделанные к этим IP-адресам блокируются программой A4Proxy. Полезно для блокировки баннеров CLIENT Allowed List- Этот параметр полезен для администраторов, которые открывают доступ в интернет всем иашинам в локальной сети, используя A4Proxy. Все адреса которых нет в этом списке блокируются Notify in Browser Response- Когда эта функция включена , машины присутствующие "CLIENT Stop List" получают соответствующее предупреждение в их браузерах, когда пытаются получить доступ в интернет. Аналогичное уведомление получает пользователь, удаленного хоста, при попытке получить доступ к ресурсам машины, находящейся в этом списке. Enable Stop and Associations Lists- Включите эту функцию, что бы активировать вышеперечисленные настройки. Если программа не работает Возможные причины: -Все Proxies повреждены (нерабочие) -Неправилная настройка Если вы проверили все прокси сервера в базе данных, и все из них показаны с Красным Крестом ,то что может быть неправильно: 1. Firewall на вашей машине(компьютере), блокирует входящий трафик к A4Proxy - это функционирует, как будто этот прокси посылает запросы к себе через proxies, чтобы проверить их надежно. Если firewall блокирует входящий трафик на порт 80 (или порт на который настроена программа в разделе Proxy Options-Listen port 8080 или 3128 - но 80 предпочтительней), проверка не будет работать. 2. Ваша машина(компьютер) не имеет публичный IP адрес, который является видимым в Интернете: Пожалуйста запустите Тест Совместимости(Compatibility Test) в меню Помощи(Help). 3. Присутствие программного обеспечения, которое использует порт 80 (или порт который установлен в A4Proxy для прослушивания).Пожалуйста запустите Тест Совместимости(Compatibility Test) в меню Помощи (Help). 4. Ваш поставщик Интернета блокирует доступ общественным прокси серверам. Введите в строку адрес и номер порта одного из прокси серверов непосредственно в вашем browser вместо 127.0.0.1, и см., можете ли Вы соединяться с любым вэбсервером в этой конфигурации. Повторите это с различным proxies. Если Вы можете соединиться с websites, ваш поставщик не блокирует proxies. 5. Ваш поставщик может блокировать входящий трафик на некоторых портах (своего рода firewall, используемый поставщиком). 6. Возможно включена функция Direct Conneckion, дотупная в меню "Options" и в tray menu Вы не можете соедениться с любым сайтом. Если, после ввода в настройках браузера 127.0.0.1 порта 80 Вы не можете подсоедениться к любому сайту, возможная причина, что порт 80 используется другой программой(приложением). Пожалуйста запустите Тест Совместимости(Compatibility Test) в меню Помощи(Help). Фактически, это - адрес вашей собственной машины(компьтера). Ваш browser теперь посылает запросы о страницах и файлах не к website непосредственно, а к A4Proxy, который, в свою очередь, переадресовывает все эти запросы анонимным прокси серверам. Связь между A4Proxy и вашим browser требует, чтобы они оба использовали один и тот же порт: в этом случае, 80. Однако, если порт уже занят в соответствии с другой программой, это предотвратит связь между browser и A4Proxy, и Вы не будете способны подключиться к любому websites. Удостовертесь, что ваша машина(компьютер) не управляет другим местным(локальным) прокси сервером, или Web сервером (MS Front Page Personal Web Server extension), или WinGate и т.д. - любое программное обеспечение, которое использует порт 80, предотвратит ваш browser от "наблюдения" A4Proxy.

Проверка попадания точки в прямоугольник (TPoint в TRect).

Автор: Lipskiy WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Проверка попадания точки в прямоугольник (TPoint в TRect)
Проверка - входят ли координаты точки TPoint в область,
ограниченную координатами прямоугольника TRect.
P.S. Вероятно, есть стандартная, но я не нашел.
Зависимости: Windows
Автор:       lipskiy, lipskiy@mail.ru, ICQ:51219290, Санкт-Петербург
Copyright:   Собственное написание (lipskiy)
Дата:        26 июня 2002 г.
***************************************************** }
function PointIntoRect(Point: TPoint; Rect: TRect): boolean;
begin
  Result :=
    (Point.x >= Rect.Left) and
    (Point.x <= Rect.Right) and
    (Point.y >= Rect.Top) and
    (Point.y <= Rect.Bottom);
end;

Находится ли точка внутри многоугольника.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function PtInRgn(TestPolygon : array of TPoint; const P : TPoint): boolean;
 var
   ToTheLeftofPoint, ToTheRightofPoint : byte;
   np : integer;
   OpenPolygon : boolean;
   XIntersection : real;
 begin
   ToTheLeftofPoint := 0;
   ToTheRightofPoint := 0;
   OpenPolygon := False;
   {Prufen ob das Polygon geschlossen ist}
   {tests if the polygon is closed}
   if not ((TestPolygon[0].X = TestPolygon[High(TestPolygon)].X) and
     (TestPolygon[0].Y = TestPolygon[High(TestPolygon)].Y)) then
     OpenPolygon := True;
   {Tests fur jedes Paar der Punkte, um zu sehen wenn die Seite zwischen 
   ihnen, die horizontale Linie schneidet, die TestPoint durchlauft}
   {tests for each couple of points to see if the side between them 
   crosses the horizontal line going through TestPoint}
   for np := 1 to High(TestPolygon) do
     if ((TestPolygon[np - 1].Y <= P.Y) and
       (TestPolygon[np].Y > P.Y)) or
       ((TestPolygon[np - 1].Y > P.Y) and
       (TestPolygon[np].Y <= P.Y))
       {Wenn es so ist}    {if it does}
       then
     begin
       {berechnet die x Koordinate des Schnitts}
       {computes the x coordinate of the intersection}
       XIntersection := TestPolygon[np - 1].X +
         ((TestPolygon[np].X - TestPolygon[np - 1].X) /
         (TestPolygon[np].Y - TestPolygon[np - 1].Y)) * (P.Y - TestPolygon[np - 1].Y);
       {Zahler entsprechend verringern}
       {increments appropriate counter}
       if XIntersection < P.X then Inc(ToTheLeftofPoint);
       if XIntersection > P.X then Inc(ToTheRightofPoint);
     end;
   {Falls das Polygon offen ist, die letzte Seite testen}
   {if the polygon is open, test for the last side}
   if OpenPolygon then
   begin
     np := High(TestPolygon);  {Thanks to William Boyd - 03/06/2001}
     if ((TestPolygon[np].Y <= P.Y) and
       (TestPolygon[0].Y > P.Y)) or
       ((TestPolygon[np].Y > P.Y) and
       (TestPolygon[0].Y <= P.Y)) then
     begin
       XIntersection := TestPolygon[np].X +
         ((TestPolygon[0].X - TestPolygon[np].X) /
         (TestPolygon[0].Y - TestPolygon[np].Y)) * (P.Y - TestPolygon[np].Y);
       if XIntersection < P.X then Inc(ToTheLeftofPoint);
       if XIntersection > P.X then Inc(ToTheRightofPoint);
     end;
   end;
   if (ToTheLeftofPoint mod 2 = 1) and (ToTheRightofPoint mod 2 = 1) then Result := True
   else
     Result := False;
 end; {PtInRgn}

Запись буфера BDE на диск.

Can't write: disk full. (R)etry, (F)ormat, (C)all #911?
Общее: Сделанные в таблице изменения непосредственно на диск не записываются до тех пор, пока таблица не будет закрыта. Потеря питания или сбой в системе может привести к потере данных и прочим неприятностям. Чтобы избежать этого, существует два прямых вызова Database Engine, дающих один и тот же результат. Эти функции - DbiUseIdleTime и DbiSaveChanges. DbiSaveChanges(hDBICur):

DbiSaveChanges сохраняет на диске все обновления, находящиеся в буфере таблицы, связанной с курсором (hDBICur). Может быть вызвана из любого места программы. Например, можно при каждом обновлении записи сохранять на диске все изменения (добавьте dbiProcs в список используемых модулей):

procedure TForm1.Table1AfterPost(DataSet: TDataSet);
begin
  DbiSaveChanges(Table1.handle);
end;
При таком способе можно не беспокоиться насчет потерь данных в случае потери питания или сбоя системы, которое может произойти после обновления записи. DbiSaveChanges также можно использовать для того, чтобы временную таблицу (созданную с помощью DbiCreateTempTable) сделать постоянной. Эта функция не применима к таблицам SQL. DbiUseIdleTime: DbiUseIdleTime может быть вызвана, если "Windows Message Queue" (очередь запросов Windows) пуста. Это позволяет Database Engine сохранить на диске "грязные буферы". Другими словами, выполняется операция DbiSaveChanges, но применительно ко ВСЕМ измененным таблицам. Тем не менее, данная операция не обязательно должна выполняться после каждого обновления записи, ее нужно приберечь для "холостого" периода (период простоя, idle).

В Delphi это может быть использовано таким образом (добавьте dbiProcs в список используемых модулей):

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.onIdle := UseIdle;
end;
procedure Tform1.UseIdle(Sender: TObject; var Done: Boolean);
begin
  DbiUseIdleTime;
end;
Некоторые замечания: Использование обоих вызовов DbiUseIdleTime и DbiSaveChanges (после каждого обновления записи) излишне и сопровождается необязательными вызовами функций. Если приложение выполняет множественный ввод новых записей или их редактирование в течение небольшого периода времени, рекомендуем осуществлять вызов функции DbiUseIdleTime во время простоя клинта, а вызов DbiSaveChanges после осуществления "пакета" обновлений.

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

Как поместить картинку в заголовок TListView.

Программисты (он и она) лежат в постели и отдыхают после занятий любовью. Она: - Не хочешь повторить процедуру? Он: - Функция еще не вернула значения.

Иногда бывает полезно в заголовке колонки показывать стрелочку, чтобы информировать пользователя, по какой колонке идёт сортировка. Добавьте следующий код в форму:

procedure TForm1.SetColumnImage( List: TListView; Column,
Image: Integer; ShowImage: Boolean);
var
  Align, hHeader: integer;
  HD: HD_ITEM;
begin
  hHeader := SendMessage(List.Handle, LVM_GETHEADER, 0, 0);
  with HD do
  begin
    case List.Columns[Column].Alignment of
      taLeftJustify:
        Align := HDF_LEFT;
      taCenter:
        Align := HDF_CENTER;
      taRightJustify:
        Align := HDF_RIGHT;
      else
        Align := HDF_LEFT;
    end;
    mask := HDI_IMAGE or HDI_FORMAT;
    pszText := PChar(List.Columns[Column].Caption);
    if ShowImage then
      fmt := HDF_STRING or HDF_IMAGE or HDF_BITMAP_ON_RIGHT
    else
      fmt := HDF_STRING or Align;
    iImage := Image;
  end;
  SendMessage(hHeader, HDM_SETITEM, Column, Integer(@HD));
end;

Картинки берутся из списка SmallImages. Вам надо будет вызвать эту функцию для каждой колонки и установить ShowImage в TRUE для той колонки, которую Вы будете сортировать. Сделать это можно в функции OnColumnClick():

procedure TForm1.ListView1ColumnClick(Sender: TObject;
Column: TListColumn);
var
  i: integer;
begin
  // Это Ваша собственная функция сортировки
  CustomSort( @CustomSortProc, Column.index );
  // Этот цикл отображает иконку в выбранной колонке.
  for i := 0 to ListView1.Columns.Count-1 do
    SetColumnImage( ListView1, i, 0, i = Column.index );
end;

Проблема: Изменение размера колонки генерирует сообщение WM_PAINT, которое стирает картинку, поэтому Вам прийдётся переопределить WM_PAINT и вызвать SetColumnImage снова.

Использовался компонент TApplicationEvents в delphi 5.

В DBGrid напротив некоторых строк можно поставить галочку.

Идут курсы Майкрософт по Win'98. Инструктор (И) читает лекции чайникам-слушателям, среди которых случайно затесался программер. (П). (И) Windows - это абсолютно надежная система, но если все же что-то случится, то для этих целей существует утилита Scandisk, которая работает абсолютно безупречно. Главное: не вздумайте отключать автоматический запуск этой утилиты после сбоев в работе. Вот недавно был такой случай: один программист удалил Scandisk, и когда Windows'98 случайно повисла, то после перезагрузки жесткий диск был поврежден, а неисправность не была устранена. А на другом компьютере, где была установлена эта утилита, после сбоя в работе Windows'98 проверила диск, поставила новые параметры и для подстраховки перезагрузила компьютер. (П) Ага, знаю я этот комп, до сих пор Sсаndisк'ом проверяется, ставит новые параметры и для подстраховки перезагружается.

На сервере - таблица Advertis.DB, первичный ключ ID - autoincrement. Ha локальном диске - таблица Founds.DB, с полем Advertis: integer, по которому есть индекс, и.

tblFounds.IndexFieldNames = 'Advertis'

Ha гриде:

procedure TMainForm.dbgWorkDblClick(Sender: TObject);
begin
  TriggerRowSelection;
end;
procedure TMainForm.TriggerRowSelection;
begin
  if dmFile.AdvertisCount <> 0 then
  begin
    with dmFile do
      if not tblFounds.FindKey([tblAdvertisID.Value]) then
        tblFounds.AppendRecord([tblAdvertisID.Value])
      else
        tblFounds.Delete;
    dbgWork.Refresh;
  end;
end;
procedure TMainForm.dbgWorkDrawColumnCell(Sender: TObject;
          const Rect: TRect; DataCol: Integer; Column: TColumn;
          State: TGridDrawState);
begin
  if DataCol = 0 then
    with dmFile, dbgWork.Canvas do
    begin
      FillRect(Rect); {clear the cell}
      if tblFounds.FindKey([tblAdvertisID.Value]) then
        TextOut(Rect.Left, Rect.Top, 'ь');
    end;
end;

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

А колонка для галки в гриде определялась так:

with dmFile, dbgWork.Columns do
begin
  BeginUpdate;
  Clear;
  {check mark}
  nc := Add;
  nc.Width := 14;
  nc.Font.name := 'Wingdings';
  nc.Font.Size := 11;
  nc.Alignment := taRightJustify;
  nc.Title.Caption := 'ю';
  nc.Title.Font.name := 'Wingdings';
  nc.Title.Font.Size := 10;
  nc.Title.Alignment := taCenter;
  {skip определения остальных колонок}
  EndUpdate;
end;

Вроде всё. Ну, как напечатать/обработать только помеченное, сам разберёшься. У меня там накручено чего-то с фильтрами, думаю, можно проще. Что касается других способов - можно вместо временной таблицы попользовать список, массив или in-memory table.

Имплантация таймера в компонент.

Автор: Mike Scott Я не могу разобраться как мне вставить TTimer в мой компонент и получить доступ к его методам и свойствам. Я нигде не могу найти пример такого использования таймера. Кто-нибудь может мне помочь?

Я просто создал это для вас:

type
  TMyControl = class(TCustomControl)
  private
    FTimer: TTimer;
    procedure TimerEvent(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
  end;
constructor TMyControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FTimer := TTimer.Create(Self);
  FTimer.Interval := 100; { 100 ms }
  FTimer.OnTimer := TimerEvent;
end;
procedure TMyControl.TimerEvent(Sender: TObject);
begin
  { вот ваш обработчик события, который вызывается при каждом
  срабатывании таймера - делайте здесь все что вам необходимо }
end;

Легче не бывает! Правда, есть за что любить Delphi?

Квадратное уравнение.

unit SqRoot_;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, OleCtrls;
type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Button1: TButton;
    Label5: TLabel;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
// решает квадратное уравнение
procedure SqRoot(a, b, c: real; var x1, x2: real; var ok: boolean);
    { a,b,c - коэффициенты уравнения
      x1,x2 - корни уравнения
      ok = True  - решение есть
      ok = False - решения нет }
var
  d: real; // дискриминант
begin
  d := Sqr(b) - 4 * a * c;
  if d < 0
    then
    ok := False // уравнение не имеет решения
  else
  begin
    ok := True;
    x1 := (-b + Sqrt(d)) / (2 * a);
    x2 := (b + Sqrt(d)) / (2 * a);
  end;
end;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
  k1, k2: real; // корни уравнения
  rez: boolean; // True - решение есть, False - решения нет
  mes: string; // сообщение
begin
  SqRoot(StrToFloat(Edit1.Text), StrToFloat(Edit2.Text),
    StrToFloat(Edit3.Text), k1, k2, rez);
  if rez then
    mes := 'Корни уравнения' + #13 +
      'x1=' + FloatToStrF(k1, ffGeneral, 4, 2) + #13 +
      'x2=' + FloatToStrF(k2, ffGeneral, 4, 2) + #13
  else
    mes := 'Уравнение не имеет решения';
  label5.Caption := mes;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  winhelp(Form1.Handle, 'sqroot.hlp', HELP_CONTEXT, 1);
end;
end.

Качественно уменьшить изображение.

В Delphi изменять размеры изображения очень просто, используя CopyRect:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1.Canvas.Font.Size := 24;
  Form1.Canvas.TextOut(0, 0, 'Text');
  Form1.Canvas.CopyRect(Bounds(0, 50, 25, 10), Form1.Canvas,
  Bounds(0, 0, 100, 40));
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  x, y: integer;
  i, j: integer;
  r, g, b: integer;
begin
  Form1.Canvas.Font.Size := 24;
  Form1.Canvas.TextOut(0, 0, 'Text');
  for y := 0 to 10 do
  begin
    for x := 0 to 25 do
    begin
      r := 0;
      for i := 0 to 3 do
        for j := 0 to 3 do
          r := r + GetRValue(Form1.Canvas.Pixels[4*x+i, 4*y+j]);
      r := round(r / 16);
      g := 0;
      for i := 0 to 3 do
        for j := 0 to 3 do
          g := g + GetGValue(Form1.Canvas.Pixels[4*x+i, 4*y+j]);
      g := round(g / 16);
      b := 0;
      for i := 0 to 3 do
        for j := 0 to 3 do
          b := b + GetBValue(Form1.Canvas.Pixels[4*x+i, 4*y+j]);
      b := round(b / 16);
      Form1.Canvas.Pixels[x,y+50] := RGB(r, g, b)
    end;
    Application.ProcessMessages;
  end;
end;

Модуль, позволяющий в Delphi осуществить форму запроса для компонентов DBGrid.

Автор: Rick Rutt
Визит министра здравоохранения (МЗ) в одну из психиатрических лечебниц. Осмотрев палаты, процедурные залы, столовую МЗ изьявляет желание проверить, как содержатся буйнопомешанные больные, опасные для окружающих. Спускаются на 1-й этаж, Главный врач (ГВ) отпирает стальную кованую дверь. За дверью длинный коридор. МЗ переходит от камеры, к камере, читает таблички, иногда заглядывает внутрь. Около одной из камер. МЗ: - Так-так. Острая паранойя, OS/2 Warp, мания величия... Заглядывает в окошко. Угрюмый тип, увидев МЗ, начинает метаться по камере, крича: "Ла-а-меры!!! Ла-а-а-меры!!! Суксь, Суксь МастДайная!!!" МЗ: (Обращается к ГВ) Часто он так? ГВ: Как новое лицо увидит, сразу приступ... Идут дальше. МЗ: Так-так. Острая паранойя. UNIX, мания величия... Заглядывает в окошко. Угрюмый тип, оторвавшись от созерцания собственных рук, поворачивается, и расплывшись в улыбке, изрекает: "Истинная многозадачность, полный контроль, Рулез Форева!" МЗ: (Обращается к ГВ) Тихий какой! Выздоравливает? ГВ: Нет, временное улучшение. (Повернувшись к окошку) - NT! Больной, вскочив с кровати, начинает метаться по камере, крича: "А-а-а!!! Вытесняющая многозадачность!!! Исходные тексты!!! Суксь!!! Ла-а-а-меры!!!" Идут дальше. МЗ: Так-так. Острая паранойя. Windows'95, мания величия. Заглядывает в окошко... Камера пуста. МЗ: (Недоумённо поворачивается к ГВ) - А где же больной? На процедурах? ГВ: Видите ли... Только поймите нас правильно... Бухгалтерия у нас на Excel, личные дела больных на Access...
Предлагаю Вашему вниманию модуль Delphi для модального диалога, поддерживающий форму запроса (Query By Form - QBF) для компонентов DbGrid с возможностью получения данных от Table-компонентов (не используя Query-компонентов). Встроенные характеристики обмена данными в Delphi делают эту задачу намного труднее, чем, например, в таких ресурсоемких инструментальных средствах, как Oracle Forms (Оракловые формы). Данный модуль не такой мощный как встроенные QBF-возможности Оракловых форм, но он заполняет значительную брешь в функциональности Delphi.

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

unit Db_QBF; { Форма запроса базы данных }
{ Все права защищены. Автор Rick Rutt.
Данный модуль может без какой-либо оплаты быть использован в программе,
скопирован или распространен любым человеком и для любой цели, если все
копии данного модуля сохраняют это авторское уведомление.
Автор предоставляет разрешение каждому для создания производного кода, если
каждая производная работа содержит авторское уведомление и строку
"Части данной работы основываются на Db_QBF.PAS, созданным Rick Rutt."
}
{ Данный модуль обеспечивает простую, но эффективную форму запроса
для доступа приложений к базам данных, используя Borland Delphi.
Данный модуль также располагает сервисом Sort By Form (форма сортировки).
Форма запроса отображает модальное диалоговое окно с компонентом StringGrid,
содержащим искомые поля, полученные при вызове DbGrid. Пользователь может
ввести точную величину поиска для любого количества полей и использовать
функцию drag and drop (перетащи и брось) для изменения порядка сортировки полей.
(Только тех полей, которые содержат искомые величины, влияющие на сортировку.)
Когда пользователь щелкает в диалоговом окне на кнопку OK, данный модуль
модифицирует значение свойства IndexFieldNames компонента DbGrid, применяет
диапазон поиска (точные величины), и обновляет данные.
В случае, если пользователь не указывает ни одной из величин поиска,
данный модуль очищает значение свойства IndexFieldNames компонента DbGrid,
очищает диапазон поиска и обновляет данные.
Сервис Sort By Form работает аналогично, за исключением того,
что не принимает в расчет величину поиска, введенную пользователем. Пользователь
пользуется функцией drag and drop (перетащи и брось) для установления порядка
сортировки и затем нажимает на кнопку OK. Данный модуль модифицирует
значение свойства IndexFieldNames компонента DbGrid, очищает диапазон поиска
и обновляет данные.
}
{ Создайте соответствуюшую форму диалога, используя меню "File/New.../Dialogs"
и выбрав пункт "Standard Dialog Box". Разместите на форме компонент StringGrid
(Вы найдете его в палитре компонентов на странице "Additional").
Установите следующие размеры StringGrid: высота 161 и ширина 305.
И, наконец, замените исходный код новой формы (PAS-файл) данным модулем.
}
interface
uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Buttons,
  StdCtrls, ExtCtrls, Grids, DBGrids;
{ Следующие две процедуры обеспечивают механизм доступа
сервисов данного модуля.
Кнопка (или пункт меню) вызывают процедуру,
передавая ей в качестве аргумента DbGrid. (Не забудьте добавить строку
"uses Db_QBF;" в секцию реализации модуля вызова форм.)
Ограничение: компонент DbGrid должен ссылаться на DataSource,
который, в свою очередь, ссылается на DataSet, работающий с
таблицой. Данный модуль не поддерживает запрос напрямую к
DataSet ввиду отсутствия свойства IndexFieldNames.
}
procedure QueryByForm(grid: TDbGrid);
procedure SortByForm(grid: TDbGrid);
{ Следующая секция управляется средой Delphi. }
type
  TdlgQBF = class(TForm)
    OKBtn: TBitBtn;
    CancelBtn: TBitBtn;
    HelpBtn: TBitBtn;
    gridQBF: TStringGrid;
    procedure OKBtnClick(Sender: TObject);
    procedure CancelBtnClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  dlgQBF: TdlgQBF;
implementation
{ Следующая секция пишется программистом
с помощью среды Delphi. }
uses Dialogs, Db, DbTables;
{$R *.DFM}
const
  qbfRowHeight = 16;
  qbfColWidth = 150;
  qbfFieldLabel = '<<Поле>>';
  qbfValueLabel = '<<Значение>>';
  qbfQueryCaption = 'Запрос для таблицы ';
  qbfSortCaption = 'Порядок сортировки для таблицы ';
var
  { Объявим некоторые элементы управления, участвующие
  в QBF-диалоге при нажатии кнопки OK. }
  CallingGrid: TDbGrid;
  CallingMode: (modeQuery, modeSort);
procedure SetupAndShowForm;
  { Инициализация формы, обеспечивающей визуализацию
  работы двух объявленных выше процедур }
var
  i, j, n: integer;
  tbl: TTable;
  f: TField;
begin
  n := CallingGrid.FieldCount;
  if n <= 0 then
  begin { Вместо вывода сообщений могут генерится исключительные ситуации }
    MessageDlg(
      'При обращении к DbGrid, модуль Db_QBF не обнаружил полей',
      mtWarning, [mbOK], 0);
  end
  else if CallingGrid.DataSource = nil then
  begin
    MessageDlg(
      'При обращении к DbGrid, модуль Db_QBF не обнаружил ссылки на DataSource',
      mtWarning, [mbOK], 0);
  end
  else if CallingGrid.DataSource.DataSet = nil then
  begin
    MessageDlg(
      'При обращении к DbGrid, модуль Db_QBF обнаружил подключенный
      DataSource без ссылки на DataSet',
      mtWarning, [mbOK], 0);
  end
  else if not (CallingGrid.DataSource.DataSet is TTable) then
  begin
    MessageDlg(
      'При обращении к DbGrid, модуль Db_QBF обнаружил подключенный
      DataSource с сылкой на DataSet, не являющийся таблицей.',
      mtWarning, [mbOK], 0);
  end
  else
    with dlgQBF.gridQBF do
    begin
      { Данные свойства могут быть изменены и в режиме проектирования }
      DefaultRowHeight := qbfRowHeight;
      Scrollbars := ssVertical;
      ColCount := 2; { Для режима сортировки необходимы две пустые колонки }
      { Данные свойства должны быть установлены во время выполнения программы }
      RowCount := Succ(n);
      Cells[0, 0] := qbfFieldLabel;
      Options := Options + [goRowMoving];
      tbl := TTable(CallingGrid.DataSource.DataSet);
      if CallingMode = modeQuery then
      begin
        dlgQBF.Caption := qbfQueryCaption + tbl.TableName;
        Cells[1, 0] := qbfValueLabel;
        Options := Options + [goEditing];
          { Позволяем пользователю ввести значение }
        DefaultColWidth := qbfColWidth;
      end
      else
      begin
        dlgQBF.Caption := qbfSortCaption + tbl.TableName;
        Cells[1, 0] := '';
          { Ввод "пустышки" для первой, нефункциональной колонки }
        Options := Options - [goEditing]; { Убираем возможность редактирования }
        DefaultColWidth := (2 * qbfColWidth);
          { Этим трюком мы помещаем две пустых секции над одной колонкой }
      end;
      j := 0; { Фактическое число полей, показываемое пользователю }
      for i := 1 to n do
      begin
        f := CallingGrid.Fields[Pred(i)];
        if f.DataType in [ftBlob, ftBytes, ftGraphic, ftMemo, ftUnknown,
          ftVarBytes] then
          RowCount := Pred(RowCount) { Игнорируем неиндексируемые поля }
        else
        begin
          Inc(j);
          Cells[0, j] := f.FieldName;
          Cells[1, j] := ''; { Сбрасываем искомую величину }
        end;
      end;
      dlgQBF.HelpBtn.Visible := False; { Помощь, понятно, отсутствует... }
      dlgQBF.ShowModal;
    end; { with dlgQBF.gridQBF }
end;
procedure QueryByForm(Grid: TDbGrid);
begin
  CallingGrid := Grid; { Сохраняем для использования при нажатии на кнопку OK }
  CallingMode := modeQuery;
  SetupAndShowForm;
end;
procedure SortByForm(Grid: TDbGrid);
begin
  CallingGrid := Grid; { Сохраняем для использования при нажатии на кнопку ОК }
  CallingMode := modeSort;
  SetupAndShowForm;
end;
procedure TdlgQBF.CancelBtnClick(Sender: TObject);
begin
  { Просто прячем диалог, не делая никаких изменений в вызывающем Grid'е. }
  dlgQBF.Hide;
end;
procedure TdlgQBF.OKBtnClick(Sender: TObject);
var
  flds, sep, val: string;
  i, n, nfld: integer;
begin
  flds := ''; { Список полей, разделенных ';'. }
  sep := ''; { Разделитель ';' ставится после добавления первого поля. }
  nfld := 0; { Количество полей в списке. }
  with dlgQBF.gridQBF do
  begin
    n := Pred(RowCount);
    if n > 0 then
      for i := 1 to n do
      begin
        val := Cells[1, i];
          { Значение поиска, введенное пользователем (если имеется) }
        if (CallingMode = modeSort)
          or (val <> '') then
        begin
          flds := flds + sep + Cells[0, i];
          sep := ';';
          nfld := Succ(nfld);
        end;
      end;
    with CallingGrid.DataSource.DataSet as TTable do
    begin
      IndexFieldNames := flds;
      if (CallingMode = modeSort)
        or (flds = '') then
      begin
        CancelRange;
      end
      else
      begin
        SetRangeStart;
        for i := 1 to n do
        begin
          val := Cells[1, i];
          if val <> '' then
          begin
            FieldByName(Cells[0, i]).AsString := val;
          end;
        end;
        {Устанавливаем конец диапазона так, чтобы он соответствовал его началу}
        SetRangeEnd;
        for i := 1 to n do
        begin
          val := Cells[1, i];
          if val <> '' then
          begin
            FieldByName(Cells[0, i]).AsString := val;
          end;
        end;
        ApplyRange;
      end;
      Refresh;
    end; { with CallingGrid.DataSource.DataSet }
  end; { with dlgQBF.gridQBF }
  dlgQBF.Hide;
end;
end.

Параметризованные запросы.

Как мне передать переменную в запрос? Сначала вы должны создать запрос, использующий переменную.
  Select Test."FName", Test."Salary Of Employee"
  From Test
  Where Test."Salary of Employee" > :val
Примечание: Если вы просто пишете имя поля как "Salary of Employee", вы получите ошибку "Capability Not Supported". Это должно быть просто Test."Salary of Employee". В нашем случае имя переменной "val", но это может быть любое другое (естественно). Затем вы переходите к свойству TQuery's params и устанавливаете параметр "val" в зависимости от требуемого типа. В нашем примере мы используем тип integer.

Затем вы должны создать код, устанавливающий значение параметра. Для задания значения мы будем использовать компонент TEdit.

procedure TForm1.Button1Click(Sender: TObject);
begin
  with Query1 do
  begin
    Close;
    ParamByName('val').AsInteger := StrToInt(Edit1.Text);
    Open;
  end;
end;
Примечание: рекомендуем в качестве меры предосторожности разместить приведенный выше код в блоке try..except. Если в своем запросе вы хотите использовать ключевое слово LIKE, то вы можете сделать это так: Примечание: Следующий код использует таблицу пользователя, расположенную в каталоге \delphi\demos\data. При этом также возможно использование псевдонима DBDEMOS. Код SQL для свойства TQuery.SQL:
  SELECT * FROM CUSTOMER
  WHERE Company LIKE :CompanyName

Код Delphi:

procedure TForm1.Button1Click(Sender: TObject);
begin
  with Query1 do
  begin
    Close;
    ParamByName('CompanyName').AsString := Edit1.Text + '%';
    Open;
  end;
end;
Альтернативный способ передачи параметра (с последующим использованием ParamByName) - params[TheParameterNumber].

Вот демонстрация такого способа:

ParamByName('CompanyName').AsString := Edit1.Text + '%';

Или, в качестве альтернативы:

Params[0].AsString := Edit1.Text + '%';

Хитрость шаблона - в конкатенирующем знаке процента в конце параметра.

Проблемы со шрифтами у QuickReport.

Я использую Delphi 2.0 и QuickReports 1.1. Я создал ОЧЕНЬ сложный (графически, во всяком случае) отчет и только что обнаружил, что это не выводится как положено на экране и на принтере во время выполнения программы (хотя это и корректно отображается в окне предварительного просмотра в режиме проектирования). Ошибка возникает только в том случае, если в Windows 95 установлены большие (LARGE) системные шрифты! Ну и что мне теперь прикажете делать? У меня тоже была такая же серьезная проблема, но только при печати под Win 95 на офисный HP 4M и при просмотре отчета под NT 4.0. Но то же самое приложение у меня прекрасно работало под NT 3.51 SP5.

Я видел дискуссию по поводу этой ошибки, возникающей в GDI коде под NT 4.0, поэтому я не удивился, обнаружив ее у себя. Мне пришлось засучить рукава и очень тесно познакомиться с кодом QuickReports, особенно с процедурой первичного вывода текста, когда программа изменяет шрифт в каждой выводимой области. Вот этот злосчастный сегмент кода в методе TQRCustomControl.Print:

QRPrinter.Canvas.Font:=Font;
QRPrinter.Canvas.Font.size:=trunc(abs(parentreport.xpos(font.size)));
QRPrinter.Canvas.brush.Color:=Color;
Теперь те из нас, которые истратили слишком много времени на изучение исходного кода VCL узнали, что VCL поддерживает кэш ресурса через менеджера дескрипторов в Graphics.pas и что попытки уменьшения количества используемых ресурсов Windows сводятся к методу подсчета ссылок на дескрипторы многократно испольуемых ресурсов. Коду, приведенному выше, отлично удалось это обойти! Каждый раз холст принтера устанавливает шрифты полей, таким образом уменьшая счетчик используемых ресурсов в ноль с дальнейшим его освобождением и затем масштабирует размер шрифта для соответствия его метрике принтера, таким образом требуя размещения нового шрифтового ресурса. Всдедствие этого _каждое_ поле отчета использует для вывода собственный шрифт, тратя для этого каждый раз системные ресурсы и вгоняя в штопор систему (регистратор метафайлов), пытающуюся уследить за бешенным ритмом смены шрифтов и массовым съеданием ресурсов. В качестве эксперимента я создал непечатаемую этикетку с тем же самым размером шрифта, что и у масштабируемого шрифта принтера, тем самым заставляя ресурс кэшироваться - ошибка исчезла как страшный сон! Это доказывает идентичность проблемы в NT 4.0 - те же действующие лица: регистратор расширенных метафайлов и огромное количество изменений шрифтов.

Лучшее решение я увидел в следующем:

// При установке нового шрифта сохраняем шрифт принтера
SaveFont := TFont.Create;
SaveFont.Assign(QRPrinter.Canvas.Font);
QRPrinter.Canvas.Font:=Font;
QRPrinter.Canvas.Font.size:=trunc(abs(parentreport.xpos(font.size)));
QRPrinter.Canvas.brush.Color:=Color;
// Освобождаем сохраненный шрифт принтера. Теперь работа сделана.
SaveFont.Free;
SaveFont := nil;
Дополнительными строками нам удается проверить тот факт, что шрифт принтера уже используется или же для холста принтера выбирается тот же самый шрифт. Это работает уже вполне корректно и позволяет печатать правильные отчеты под NT 4.0. Некоторым странным совпадением можно считать устранение зашитых жирных шрифтов, изменившее шрифты, печатаемые под Win95.

Так, если у вас есть зарегистрированная версия QuickReports, вы можете сделать коррекцию исходных файлов, и вы увидите, что ваши отчеты будут генериться быстрее, выглядеть "мягче" и не будет ошибок и издержек шрифтовых ресурсов из-за ограничения кода расширенных метафайлов и GDI.

Заголовок окна предварительного просмотра QuickReport.

Попробуйте сделать следующее:

QRPrinter.PreviewCaption := 'Мой заголовок'; 

QRPrinter - глобальная переменная, определенная в модуле Quickrep.pas.

Как сохранить QuickReport в stream.

Автор: http://www.swissdelphicenter.ch

uses QRPrntr;
procedure SaveQuickReportToStream(AQuickReport: TQuickRep; AStream: TStream);
var
  PL: TQRPageList;
  I: Integer;
begin
  PL := nil;
  try
    PL := TQRPageList.Create;
    PL.Stream := TQRStream.Create(100000);
    AQuickReport.Prepare;
    PL.LockList;
    try
      for I := 1 to AQuickReport.QRPrinter.PageCount do
        PL.AddPage(AQuickReport.QRPrinter.GetPage(I));
      PL.Finish;
    finally
      PL.UnlockList;
    end;
    PL.Stream.SaveToStream(AStream);
  finally
    FreeAndNil(PL);
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  stream: TFileStream;
begin
  stream := TFileStream.Create('c:\quickreport.dat', fmCreate);
  QuickReportToStream(QuickRep1, stream);
  stream.Free;
end;

Функция для быстрого копирования таблиц вместе со всеми дополнительными файлами.

// Только для не SQL-ых, т.е не промышленных БД (dBase, Paradox ..)
// Путь нужно задавать только АНГЛИЙСКИМИ буквами
procedure QuickCopyTable(T: TTable; DestTblName: string; Overwrite: boolean);
var
  DBType: DBIName;
  WasOpen: boolean;
  NumCopied: word;
begin
  WasOpen := T.Active;
  if not WasOpen then
    T.Open;
  Check(DbiGetProp(hDBIObj(T.Handle),drvDRIVERTYPE, @DBType,SizeOf(DBINAME), NumCopied));
  Check(DbiCopyTable(T.DBHandle, Overwrite, PChar(T.TableName),DBType, PChar(DestTblName)));
  T.Active := WasOpen;
end;

Быстрый поиск в базах данных.

Статьи по Delphi (014)…

Кинул как-то уркаган маляву по Яндексу...

Я представляю на Ваш суд утилиту быстрого поиска по базе данных. Данная технология производит поиск по полям, преобразуя их значения в строки (все значения преобразуются в верхний регистр, включая действительные числа). Данное решение может быть не самым быстрым, однако на поверку оно оказывается быстрее остальных, обнаруженных мною в Интернете (может вам повезет больше). Более того, представьте, что действительное значение какого-либо поля равно 4.509375354, а значение поиска равно 7, в этом случае утилита засчитает "попадание". Утилита удобна также тем, что она за один проход производит поиск более, чем в одном поле. Это удобно, если у Вас имеются, к примеру, два поля с адресами. Это моя первая "серьезная" разработка, так как первое, с чем я столкнулся, изучая Delphi, стала необходимость включения процедуры поиска в любое приложение, работающее с базой данных. А так как поиск - вещь тоже сугубо специфическая, как и любое приложение, то мне пришлось побороть свой страх перед "крутым программированием" и попробовать написать свой поисковый механизм, удовлетворивший меня (и, надеюсь, других) своей скоростью и возможностью "мульти"-поиска по нескольким полям. Я надеюсь, что он поможет тем программистам, кто часто сталкивается с подобными задачами. Технология довольно легка для понимания, но если у Вас возникли какие-либо вопросы, пошлите мне письмо электронной почтой, я буду рад Вам помочь. Посмотрев код, можно легко узнать поддерживаемые типы полей (добавить новые не составит проблем). Если кто-либо обнаружит ошибочный код или расширит функциональность утилиты, пожалуйста, пошлите это мне, я буду весьма благодарен. Спасибо.

unit Finder;
interface
uses DB, DBTables, SysUtils;
function GrabMemoFieldAsPChar(TheField: TMemoField): PChar;
function DoFindIn(TheField: TField; SFor: string): Boolean;
function FindIt(TheTable: TDataSet; TheFields: array of integer;
  SearchBackward: Boolean; FromBeginning: Boolean; SFor: string): Boolean;
{применение функции FindIt -
if FindIt(NotesSearchT,
[NotesSearchT.FieldByName('Leadman').Index],
False, True, SearchText.Text) then DoSomething; }
implementation
function GrabMemoFieldAsPChar(TheField: TMemoField): PChar;
begin
  with TBlobStream.Create(TheField, bmRead) do
  begin
    GetMem(Result, Size + 1);
    FillChar(Result^, Size + 1, #0);
    Read(Result^, Size);
    Free;
  end;
end;
function DoFindIn(TheField: TField; SFor: string): Boolean;
var
  PChForMemo: PChar;
begin
  Result := False;
  case TheField.DataType of
    ftString:
      begin
        if (Pos(SFor, UpperCase(TheField.AsString)) > 0) then
          Result := True;
      end;
    ftInteger:
      begin
        if (Pos(SFor, TheField.AsString) > 0) then
          Result := True;
      end;
    ftBoolean:
      begin
        if SFor = UpperCase(TheField.AsString) then
          Result := True;
      end;
    ftFloat:
      begin
        if (Pos(SFor, TheField.AsString) > 0) then
          Result := True;
      end;
    ftCurrency:
      begin
        if (Pos(SFor, TheField.AsString) > 0) then
          Result := True;
      end;
    ftDate..ftDateTime:
      begin
        if (Pos(SFor, TheField.AsString) > 0) then
          Result := True;
      end;
    ftMemo:
      begin
        SFor[Ord(SFor[0]) + 1] := #0;
        PChForMemo := GrabMemoFieldAsPChar(TMemoField(TheField));
        StrUpper(PChForMemo);
        if not (StrPos(PChForMemo, @SFor[1]) = nil) then
          Result :=
            True;
        FreeMem(PChForMemo, StrLen(PChForMemo + 1));
      end;
  end;
end;
function FindIt(TheTable: TDataSet; TheFields: array of integer;
  SearchBackward: Boolean; FromBeginning: Boolean; SFor: string): Boolean;
var
  i, HighTheFields, LowTheFields: integer;
  BM: TBookmark;
begin
  TheTable.DisableControls;
  BM := TheTable.GetBookmark;
  try
    LowTheFields := Low(TheFields);
    HighTheFields := High(TheFields);
    SFor := UpperCase(SFor);
    Result := False;
    if FromBeginning then
      TheTable.First;
    if SearchBackward then
    begin
      TheTable.Prior;
      while not TheTable.BOF do
      begin
        for i := LowTheFields to HighTheFields do
        begin
          if DoFindIn(TheTable.Fields[TheFields[i]], SFor) then
          begin
            Result := True;
            Break;
          end;
        end;
        if Result then
          Break
        else
          TheTable.Prior;
      end;
    end
    else
    begin
      TheTable.Next;
      while not TheTable.EOF do
      begin
        for i := LowTheFields to HighTheFields do
        begin
          if DoFindIn(TheTable.Fields[TheFields[i]], SFor) then
          begin
            Result := True;
            Break;
          end;
        end;
        if Result then
          Break
        else
          TheTable.Next;
      end;
    end;
  finally
    TheTable.EnableControls;
    if not Result then
      TheTable.GotoBookmark(BM);
    TheTable.FreeBookmark(BM);
  end;
end;
end.

Пример быстрой работы с графикой.

Автор: Koster

Пример быстрой работы с графикой в среде Windows без использования средств DirectX Совместимость: Windows 95, 98, NT, 2000, Me, TrE, XP, Whistler, Tristler :)).

// © Koster < mtaurus@rambler.ru >
// Greetz to: Vano aka RIS, Uras aka Assargadon
// Special thanx to: Leon the Trillennium
 
unit VisualForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, Buttons, ComCtrls;
type
  TfmMain = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    pbDraw: TPaintBox;
    Timer1: TTimer;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
    procedure CreateBitmap(aSX, aSY: Integer);
    procedure RecreateBitmap(aSX, aSY: Integer);
    procedure DeleteBitmap;
    procedure RestrictSize(var msg: TMessage); message WM_GETMINMAXINFO;
    procedure pbDrawPaint(Sender: TObject);
  private
    ScrBitmap: TBitmap;
    Scr: Pointer;
    SX, SY: Integer;
  public
    { Public declarations }
  end;
var
  fmMain: TfmMain;
implementation
{$R *.DFM}
type
  TBig = array[0..0] of Integer;
procedure TfmMain.CreateBitmap(aSX, aSY: Integer);
var
  BInfo: tagBITMAPINFO;
begin
  // Создание DIB
  SX := aSX; SY := aSY;
  BInfo.bmiHeader.biSize := sizeof(tagBITMAPINFOHEADER);
  BInfo.bmiHeader.biWidth := SX;
  BInfo.bmiHeader.biHeight := -SY;
  BInfo.bmiHeader.biPlanes := 1;
  BInfo.bmiHeader.biBitCount := 32;
  BInfo.bmiHeader.biCompression := BI_RGB;
  ScrBitmap := TBitmap.Create();
  ScrBitmap.Handle := CreateDIBSection(Canvas.Handle, BInfo, DIB_RGB_COLORS, Scr, 0, 0);
  ZeroMemory(Scr, SX * SY * 4);
end;
procedure TfmMain.DeleteBitmap;
begin
  // Удаление DIB
  ScrBitmap.FreeImage();
  ScrBitmap.Destroy;
end;
procedure TfmMain.RecreateBitmap(aSX, aSY: Integer);
var
  BInfo: tagBITMAPINFO;
begin
  // Пересоздание DIB при изменении размеров "экрана"
  ScrBitmap.FreeImage();
  SX := aSX; SY := aSY;
  BInfo.bmiHeader.biSize := sizeof(tagBITMAPINFOHEADER);
  BInfo.bmiHeader.biWidth := SX;
  BInfo.bmiHeader.biHeight := -SY;
  BInfo.bmiHeader.biPlanes := 1;
  BInfo.bmiHeader.biBitCount := 32;
  BInfo.bmiHeader.biCompression := BI_RGB;
  ScrBitmap.Handle := CreateDIBSection(Canvas.Handle, BInfo, DIB_RGB_COLORS, Scr, 0, 0);
  ZeroMemory(Scr, SX * SY * 4);
end;
procedure TfmMain.FormCreate(Sender: TObject);
begin
  CreateBitmap(pbDraw.ClientWidth, pbDraw.ClientHeight);
  pbDraw.Canvas.Draw(0, 0, ScrBitmap);
  Caption := 'Визуализатор'; Application.Title := Caption;
end;
procedure TfmMain.FormDestroy(Sender: TObject);
begin
  DeleteBitmap();
end;
procedure TfmMain.FormResize(Sender: TObject);
begin
  ReCreateBitmap(pbDraw.ClientWidth, pbDraw.ClientHeight);
  pbDraw.Canvas.Draw(0, 0, ScrBitmap);
end;
procedure TfmMain.RestrictSize(var msg: TMessage);
var
  p: PMinMaxInfo;
begin
  // Ограничитель размеров окна (обработка сообщений Windows).
  // Удобная вещь кстати (важно: см. объявление процедуры в классе TFmMain)
  // The lParam contains a pointer on a structure of type TMinMaxInfo
  p := PMinMaxInfo(Msg.lParam);
  // This represents the size of the Window when Maximized
//  p.ptMaxSize.x := 320;
//  p.ptMaxSize.y := 240;
  // This represents the position of the Window when Maximized
//  p.ptMaxPosition.x := 10;
//  p.ptMaxPosition.y := 10;
  // This represents the minimum size of the Window
  p.ptMinTrackSize.x := 520;
  p.ptMinTrackSize.y := 240;
  // This represents the maximum size of the Window
//  p.ptMaxTrackSize.x := 400;
//  p.ptMaxTrackSize.y := 320;
end;
procedure TfmMain.pbDrawPaint(Sender: TObject);
begin
  pbDraw.Canvas.Draw(0, 0, ScrBitmap);
end;
Пример работы с данной конструкцией: SX - текущий размер нашего "экрана" по горизонтали SY - по вертикали TBig(Scr^). Scr - это указатель на массив пикселей битмапа, который в нашем случае имеет разрядность 32 (32 бита, или 4 байта на пиксел, что эквивалентно типу Integer. См. объявление типа TBig). Конструкция TBig(Scr^) позволяет адресовать эту память как массив пиксел. Чтобы получить доступ к пикселу нужно использовать индекс массива [x + y * SX]. Функция RGB. Это стандартная делфяцкая функция, не приспособленная для того что мы тут творим, а только для своего "родного" класс TCanvas и его цветовых кодов. В Windows при использовании 32-разрядных битмапов формат пиксела такой (начиная с первого байта):
            BBBBBBBB GGGGGGGG RRRRRRRR ********
В Delphi (то что ВСЕГДА возвращает функция RGB, при любой разрядности картинки):
            RRRRRRRR GGGGGGGG BBBBBBBB ********
Усматривается аналогия :) Все что нужно это просто перечислить аргументы функции в обратном порядке :))
            TBig(Scr^)[x + y * SX] := RGB(B, G, R);
B, G, R - соответственно значения интенсивности синего, зеленого, и красного цветов размером байт, т.е. [0..255]. Палитра 32-разрядным режимом не поддерживается, за нас думает Windows (вернее, понятия палитры в таком режиме вообще нет). Ну а нам остается это все юзать как надо +)))

Чтобы почистить виртуальный экран, нужно сделать так: ZeroMemory(Scr, SX * SY * 4);

procedure TfmMain.Timer1Timer(Sender: TObject);
var
  x, y: Integer;
begin
  // В цикле рисуется полная левота. Рисуйте тут свою левоту :)
  for x := 0 to SX - 1 do for y := 0 to SY - 1 do
    TBig(Scr^)[x + y * SX] := RGB(Random(256),Random(256),Random(256));
  // При желании, используем средства Delphi на объекте ScrBitmap типа TBitmap
  // в т.ч. можно нарисовать на нем другой Bitmap с помощью функции
  // ScrBitmap.Canvas.Draw(x,y,AnotherBitmap);
  // Чтобы текст выглядел красивее (без фона), раскомментируйте строки
  // SetBkMode(ScrBitmap.Canvas.Handle, TRANSPARENT);
  ScrBitmap.Canvas.Font.Size := 24;
  ScrBitmap.Canvas.TextOut(10, 10, 'Demo');
  // SetBkMode(ScrBitmap.Canvas.Handle, OPAQUE);
  // Нарисуемся
  pbDrawPaint(Self);
end;
end.

Быстрый поиск в списке.

ListBox1.Perform(LB_SELECTSTRING, -1, longint(Pchar(Edit1.text)));

Быстрая сортировка.

Автор: www.structur.h1.ru Цель: изучение алгоритма быстрой сортировки и ее модификаций. На этом занятии мы изучим алгоритм быстрой сортировки, который, пожалуй, используется более часто, чем любой другой. Основа алгоритма была разработана в 1960 году (C.A.R.Hoare) и с тех пор внимательно изучалась многими людьми. Быстрая сортировка особенно популярна ввиду легкости ее реализации; это довольно хороший алгоритм общего назначения, который хорошо работает во многих ситуациях, и использует при этом меньше ресурсов, чем другие алгоритмы. Основные достоинства этого алгоритма состоят в том, что он точечный (использует лишь небольшой дополнительный стек), в среднем требует только около N log N операций для того, чтобы отсортировать N элементов, и имеет экстремально короткий внутренний цикл. Недостатки алгоритма состоят в том, что он рекурсивен (реализация очень затруднена когда рекурсия недоступна), в худшем случае он требует N2 операций, кроме того он очень "хрупок": небольшая ошибка в реализации, которая легко может пройти незамеченной, мо жет привести к тому, что алгоритм будет работать очень плохо на некоторых файлах. Производительность быстрой сортировки хорошо изучена. Алгоритм подвергался математическому анализу, поэтому существуют точные математические формулы касающиеся вопросов его производительности. Результаты анализа были неоднократно проверены эмпирическим пу тем, и алгоритм был отработан до такого состояния, что стал наиболее предпочтительным для широкого спектра задач сортировки. Все это делает алгоритм стоящим более детального изучения наиболее эффективных путей его реализации. Похожие способы реализации по дходят также и для других алгоритмов, но в алгоритме быстрой сортировки мы можем использовать их с уверенностью, поскольку его производительность хорошо изучена. Улучшить алгоритм быстрой сортировки является большим искушением: более быстрый алгоритм сортировки - это своеобразная "мышеловка" для программистов. Почти с того момента, как Oia?a впервые опубликовал свой алгоритм, в литературе стали появляться "улучшен ные" версии этого алгоритма. Было опробовано и проанализировано множество идей, но все равно очень просто обмануться, поскольку алгоритм настолько хорошо сбалансирован, что результатом улучшения в одной его части может стать более сильное ухудшение в друг ой его части. Мы изучим в некоторых деталях три модификации этого алгоритма, которые дают ему существенное улучшение. Хорошо же отлаженная версия быстрой сортировки скорее всего будет работать гораздо быстрее, чем любой другой алгоритм. Однако стоит еще раз напомнить, что алгоритм очень хрупок и любое его изменение может привести к нежелательным и неожиданным эффектам дл я некоторых входных данных.

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

program Quitsort;
 uses
   crt;
 Const
  N=10;
 Type
  Mas=array[1..n] of integer;
var
  a: mas;
  k: integer;
function Part(l, r: integer):integer;
var
  v, i, j, b: integer;
begin
  V:=a[r];
  I:=l-1;
  j:=r;
  repeat
    repeat
      dec(j)
    until (a[j]<=v) or (j=i+1);
    repeat
      inc(i)
    until (a[i]>=v) or (i=j-1);
    b:=a[i];
    a[i]:=a[j];
    a[j]:=b;
  until i>=j;
  a[j]:=a[i];
  a[i]:= a[r];
  a[r]:=b;
  part:=i;
end;
procedure QuickSort(l, t: integer);
var i: integer;
begin
  if l<t then
    begin
      i:=part(l, t);
      QuickSort(l,i-1);
      QuickSort(i+1,t);
    end;
end;
begin
  clrscr;
  randomize;
  for k:=1 to 10 do
    begin
      a[k]:=random(100);
      write(a[k]:3);
    end;
  QuickSort(1,n);
  writeln;
  for k:=1 to n do
    write(a[k]:3);
  readln;
end.
Пример: 
60,79, 82, 58, 39, 9, 54, 92, 44, 32
60,79, 82, 58, 39, 9, 54, 92, 44, 32
9,79, 82, 58, 39, 60, 54, 92, 44, 32
9,79, 82, 58, 39, 60, 54, 92, 44, 32
9, 32, 82, 58, 39, 60, 54, 92, 44, 79 
9, 32, 44, 58, 39, 60, 54, 92, 82, 79
9, 32, 44, 58, 39, 54, 60, 92, 82, 79
9, 32, 44, 58, 39, 92, 60, 54, 82, 79
9, 32, 44, 58, 39, 54, 60, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 44, 58, 60, 39, 54, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 39, 58, 54, 44, 60, 79, 82, 92
9, 32, 39, 58, 54, 44, 60, 79, 82, 92
9, 32, 39, 44, 54, 58, 60, 79, 82, 92
9, 32, 39, 44, 58, 54, 60, 79, 82, 92
9, 32, 39, 44, 54, 58, 60, 79, 82, 92
9, 32, 39, 44, 54, 58, 60, 79, 92, 82
9, 32, 39, 44, 54, 58, 60, 79, 82, 92
"Внутренний цикл" быстрой сортировки состоит только из увеличения указателя и сравнения элементов массива с фиксированным числом. Это как раз и делает быструю сортировку быстрой. Сложно придумать более простой внутренний цикл. Положительные эффекты сторож евых ключей также оказывают здесь свое влияние, поскольку добавление еще одной проверки к внутреннему циклу оказало бы отрицательное влияние на производительность алгоритма. Самая сомнительная черта вышеприведенной программы состоит в том, что она очень мало эффективна на простых подфайлах. Например, если файл уже сортирован, то разделы будут вырожденными, и программа просто вызовет сама себя N раз, каждый раз с меньшим на од ин элемент подфайлом. Это означает, что не только производительность программы упадет примерно до N2/2, но и пространство необходимое для ее работы будет около N (смотри ниже), что неприемлемо. К счастью, есть довольно простые способы сделать так, чтобы т акой "худший" случай не произошел при практическом использовании программы. Когда в файле присутствуют одинаковые ключи, то возникает еще два сомнительных вопроса. Первое, должны ли оба указателя останавливаться на ключах равных делящему элементу или останавливать только один из них, а второй будет проходить их все, или оба указа теля должны проходить над ними. На самом деле, этот вопрос детально изучался, и результаты показали, что самое лучшее - это останавливать оба указателя. Это позволяет удерживать более или менее сбалансированные разделы в присутствии многих одинаковых ключ ей. На самом деле, эта программа может быть слегка улучшена терминированием сканирования j<i, и использованием после этого quicksort(l, j) для первого рекурсивного вызова. Характеристики Производительности Быстрой Сортировки Самое лучшее, что могло бы произойти для алгоритма - это если бы каждый из подфайлов делился бы на два равных по величине подфайла. В результате количество сравнений делаемых быстрой сортировкой было бы равно значению рекурсивного выражения CN = 2CN/2+N - наилучший случай. (2CN/2 покрывает расходы по сортировке двух полученных подфайлов; N - это стоимость обработки каждого элемента, используя один или другой указатель.) Нам известно также, что примерное значение этого выражения равно CN = N lg N. Хотя мы встречаемся с подобной ситуацией не так часто, но в среднем время работы программы будет соответствовать этой формуле. Если принять во внимание вероятные позиции каждого раздела, то это сделает вычисления более сложными, но конечный результат буде т аналогичным. Свойство 1 Быстрая сортировка в среднем использует 2N ln N сравнений. Методы улучшения быстрой сортировки. 1. Небольшие Подфайлы. Первое улучшение в алгоритме быстрой сортировки возникает из наблюдения, что программа гарантировано вызывает себя для огромного количества небольших подфайлов, поэтому следует использовать самый лучший метод сортировки когда мы встречаем небольшой подфай л. Очевидный способ добиться этого, это изменить проверку в начале рекурсивной функции из "if r>l then" на вызов сортировки вставкой (соответственно измененной для восприятия границ сортируемого подфайла): "if r-l<=M then insertion(l, r)." Значение для M не обязано быть "самым-самым" лучшим: алгоритм работает примерно одинаково для M от 5 до 25. Время работы программы при этом снижается примерно на 20% для большинства программ. <p> При небольших подфайлах (5- 25 элементов) быстрая сортировка очень много раз вызывает сама себя (в наше примере для 10 элементов она вызвала сама себя 15 раз), поэтому следует применять не быструю сортировку, а сортировку вставкой. </p> <table><tr><td><br> <PRE> <B>procedure</B> QuickSort (l,t:integer); <B>var</B> i:integer; <B>begin</B> <B>if</B> t-l>m <B>then</B> <B>begin</B> i:=part(l,t); QuickSort (l,i-1); QuickSort (i+1,t); <B>end</B> <B>Else</B> Insert(l,t); <B>end</B>; </PRE> </td></tr></table><br> <p> <b>2. Деление по Медиане из Трех</b> <p> Второе улучшение в алгоритме быстрой сортировки состоит в попытке использования лучшего делящего элемента. У нас есть несколько возможностей. Наиболее безопасная из них будет попытка избежать худшего случая посредством выбора произвольного элемента массив а в качестве делящего элемента. Тогда вероятность худшего случая становится пренебрежимо мала. Это простой пример "вероятностного" алгоритма, который почти всегда работает вне зависимости от входных данных. Произвольность может быть хорошим инструментом п ри разработке алгоритмов, особенно если возможны подозрительные входные данные. <p> Более полезное улучшение состоит в том, чтобы взять из файла три элемента, и затем использовать среднее из них в качестве делящего элемента. Если элементы взяты из начала, середины, и конца файла, то можно избежать использования сторожевых элементов: сорт ируем взятые три элемента, затем обмениваем центральный элемент с a[r-1], и затем используем алгоритм деления на массиве a[l+1..r-2]. Это улучшение называется делением по медиане из трех. <p> Метод деления по медиане из трех полезен по трем причинам. Во-первых, он делает вероятность худшего случая гораздо более низкой. Чтобы этот алгоритм использовал время пропорциональной N2, два из трех взятых элементов должны быть либо самыми меньшими, либо самыми большими, и это должно повторяться из раздела в раздел. Во-вторых, этот метод уничтожает необходимость в сторожевых элементах, поскольку эту роль играет один из трех взятых нами перед делением элементов. В третьих, он на самом деле снижает время р аботы алгоритма приблизительно на 5%. </p> <table><tr><td><br> <PRE> <B>procedure</B> exchange(i,j:integer); <B>var</B> k:integer; <B>begin</B> k:=a[i]; a[i]:=a[j]; a[j]:=k; <B>end</B>; <B>procedure</B> Mediana; <B>var</B> i:integer; <B>begin</B> i:=n <B>div</B> 4;<FONT COLOR="#00007F"><I>{Рис.}</FONT><FONT COLOR="#000000"></I> <B>if</B> a[i]>a[i*2] <B>then</B> <B>if</B> a[i]>a[i*3] <B>then</B> exchange(i,n) <B>else</B> exchange(i*3,n) <B>else</B> <B>if</B> a[i*2]>a[i*3] <B>then</B> exchange(i*2,n); quicksort(1,n); <B>end</B>; </PRE> </font> </td></tr></table><br> <p> <b>3. Нерекурсивная реализация.</b> <p> Можно переписать данный алгоритм без использования рекурсии используя стек, но здесь мы это не будем делать. <p> Комбинация нерекурсивной реализации деления по медиане из трех с отсечением на небольшие файлы может улучшить время работы алгоритма от 25% до 30%. <p> Итак, на сегодняшнем занятии мы рассмотрели алгоритм быстрой сортировки. <p> <b>Слияние</b> <p> На сегодняшнем занятии мы начнем рассмотрении темы внешняя сортировка. <p> Внешняя сортировка сортирует файлы, которые не помещаются целиком в оперативную память. <p> Внешняя сортировка сильно отличается от внутренней. Дело в том, что доступ к файлу является последовательным, а не параллельным как это было в массиве. И поэтому считывать файл можно только блоками и этот блок отсортировать в памяти и снова записать в фай л. <p> Принципиальную возможность эффективно отсортировать файл, работая с его частями и не выходя за пределы части обеспечивает алгоритм слияния. <p> Под слиянием понимается объединение двух (или более) упорядоченных последовательностей в одну упорядоченную последовательность при помощи циклического выбора элементов, доступных в данный момент. <p> Слияние - намного более простая операция, чем сортировка. <p> Мы рассмотрим 2 алгоритма слияния: <p> Прямое слияние. Алгоритм Боуза - Нельсона Естественное (Неймановское) слияние. <br> Прямое слияние. Алгоритм Боуза - Нельсона Последовательность а разбивается на две половины b и с. <p> Последовательности b и с сливаются при помощи объединения отдельных элементов в упорядоченные пары. <p> Полученной последовательности присваивается имя а, после чего повторяются шаги 1 и 2; при этом упорядоченные пары сливаются в упорядоченные четверки. <p> Предыдущие шаги повторяются, при этом четверки сливаются в восьмерки и т.д., пока не будет упорядочена вся последовательность, т.к. длины последовательностей каждый раз удваиваются. <p> Пример </p> <pre> Исходная последовательность А = 44 55 12 42 94 18 06 67 1 b = 44 55 12 42 с = 94 18 06 67 а = 44 94' 18 55' 06 12' 42 67 2 b = 44 94' 18 55' с =06 12' 42 67 а = 06 12 44 94' 18 42 55 67' 3 b = 06 12 44 94' с = 18 42 55 67' а = 06 12 18 42 44 55 67 94 </pre> <p> Операция, которая однократно обрабатывает всё множество данных, называется фазой. <p> Наименьший подпроцесс, который, повторяясь, образует процесс сортировки, называется проходом или этапом. <p> В нашем примере сортировка производится за три прохода. Каждый проход состоит из фазы разбиения и фазы слияния. <p> Главным минусом сортировки слиянием является удвоение размера памяти, первоначально занятой сортируемыми данными. Рассмотрим алгоритм с рекурсивным актом слияния, предложенный Боузом и Нельсоном и не требующий резерва памяти. <p> Он основан на очевидной идее: слить две равные упорядоченные части можно слиянием их начальных половин, слиянием конечных и слиянием 2-й половины 1-го результата с 1-й половиной 2-го результата, например: <p> Если части не равны или не делятся точно пополам, процедуру уточняют надлежащим образом. Аналогично слияние "половинок" можно свести к слиянию "четвертушек", "восьмушек" и т. д.; имеет место рекурсия. </p> <table><tr><td><br> <PRE> <B>Const</B> n=200; <B>Type</B> tipkl=word; tip = <B>Record</B> kl: tipkl; z:<B>Array</B>[1..50] <B>of</B> real <B>End</B>; <B>Var</B> A: <B>Array</B>[1..n] <B>of</B> tip; j:word; <B>Procedure</B> Bose (<B>Var</B> AA; voz:Boolean); <B>Var</B> m,j:word; x:tip; <FONT COLOR="#00007F"><I>{tip - тип сортируемых записей}</FONT><FONT COLOR="#000000"></I> A: <B>Array</B> [1..65520 <B>div</B> Sizeof(tip)] <B>of</B> tip <B>Absolute</B> AA; <B>Procedure</B> Sli(j,r,m: word); </FONT><FONT COLOR="#00007F"><I>{ r - расстояние между началами сливаемых частей, а m - их размер, j - наименьший номер записи}</FONT><FONT COLOR="#000000"></I> <B>Begin</B> <B>if</B> j+r<=n <B>Then</B> <B>If</B> m=1 <B>Then</B> <B>Begin</B> <B>If</B> voz <B>Xor</B> (A[j].kl < A[j+r].kl) <B>Then</B> <B>Begin</B> x:=A[j]; A[j]:= A[j+r]; A[j+r]:=x <B>End</B> <B>End</B> <B>Else</B> <B>Begin</B> m:=m <B>div</B> 2; Sli(j,r,m); </FONT><FONT COLOR="#00007F"><I>{Слияние "начал"}</FONT><FONT COLOR="#000000"></I> <B>If</B> j+r+m<=n <B>Then</B> Sli(j+m,r,m); </FONT><FONT COLOR="#00007F"><I>{Слияние "концов"}</FONT><FONT COLOR="#000000"></I> Sli(j+m,r-m,m) <B>End</B> </FONT><FONT COLOR="#00007F"><I>{Слияние в центральной части}</FONT><FONT COLOR="#000000"></I> <B>End</FONT><FONT COLOR="#00007F"></B><I>{блока Sli}</FONT><FONT COLOR="#000000"></I>; <B>Begin</B> m:=1; <B>Repeat</B> j:=1; </FONT><FONT COLOR="#00007F"><I>{Цикл слияния списков равного размера: }</FONT><FONT COLOR="#000000"></I> <B>While</B> j+m<=n <B>do</B> <B>Begin</B> Sli(j,m,m); j:=j+m+m <B>End</B>; m:=m+m </FONT><FONT COLOR="#00007F"><I>{Удвоение размера списка перед началом нового прохода}</FONT><FONT COLOR="#000000"></I> <B>Until</B> m >= n </FONT><FONT COLOR="#00007F"><I>{Конец цикла, реализующего все дерево слияний}</FONT><FONT COLOR="#000000"></I> <B>End</FONT><FONT COLOR="#00007F"></B><I>{блока Bose}</FONT><FONT COLOR="#000000"></I>; <B>BEGIN</B> Randomize; <B>For</B> j:=1 <B>to</B> n <B>do</B> <B>begin</B> A[j].kl:= Random(65535); Write(A[j].kl:8); <B>end</B>; Readln; Bose(A,<B>true</B>); <B>For</B> j:=1 <B>to</B> n <B>do</B> Write(A[j].kl:8); Readln <B>END</B>. </PRE> </font> </td></tr></table><br> <p> <b>Естественное (Неймановское) слияние.</b> <p> Она объединяются упорядоченные части, спонтанно возникшие в исходном массиве; они могут быть также следствием предыдущей обработки данных. Рассчитывать на одинаковый размер сливаемых частей не приходится. <p> Записи, идущие в порядке неубывания ключей, сцепляются, образуя подсписок. Минимальный подсписок одна запись. <p> Пример: </p> <pre> Пусть даны ключи записей 5 7 8 3 9 4 1 7 6 </pre> <p> Ищем подсписки <p> В один общий список соединяются 1-й, 3-й, 5-й и т. д. подсписки, в другой - 2-й, 4-й и т. д. подсписки. <p> Произведем слияние 1 подсписка 1 списка и 1 подсписка 2 списка, 2 подсписка 1 списка и 2 подсписка 2 списка и т.д. </p> <pre> Будут получены следующие цепи 3 --> 5 --> 7 --> 8 --> 9 и 1 --> 4 --> 7 </pre> <p> Подсписок, состоящий из записи "6", пары не имеет и "принудительно" объединяется с последней цепью, принимающей вид 1 --> 4--> 6 --> 7. <p> При нашем небольшом числе записей 2-й этап, на котором сливаются две цепи, окажется последним. <p> В общем случае на каждом этапе подсписок - результат слияния начальных подсписков 1 и 2 списка становится началом нового 1-го списка, а результат слияния следующих двух подсписков - началом 2-го списка. Следующие образуемые подсписки поочередно включаются в 1-й и во 2-й список. <p> Для программной реализации заводят массив sp: элемент sp[i] - это номер записи, которая следует за i-й. <p> Последняя запись одного подсписка ссылается на первую запись другого, а для различения концов подсписков эта ссылка снабжается знаком минус. </p> <table><tr><td><br> <PRE> <B>Repeat</B> <FONT COLOR="#00007F"><I>{Повторение актов слияний подсписков}</FONT><FONT COLOR="#000000"></I> <B>If</B> A[j].kl < A[i].kl <B>Then</B> </FONT><FONT COLOR="#00007F"><I>{Выбирается меньшая запись}</FONT><FONT COLOR="#000000"></I> <B>Begin</B> sp[k]:=j; k:=j; j:=sp[j]; <B>If</B> j<=0 <B>Then</B> </FONT><FONT COLOR="#00007F"><I>{Сцепление с остатком "i"-подсписка}</FONT><FONT COLOR="#000000"></I> <B>Begin</B> sp[k]:=i; <B>Repeat</B> m:=i; i:=sp[i] <B>Until</B> i<=0 <B>End</B> <B>End</B> <B>Else</B> <B>Begin</B> sp[k]:=i; k:=i; i:=sp[i]; <B>If</B> i<=0 <B>Then</B> </FONT><FONT COLOR="#00007F"><I>{Сцепление с остатком "j"-подсписка}</FONT><FONT COLOR="#000000"></I> <B>Begin</B> sp[k]:=j; <B>Repeat</B> m:=j; j:=sp[j] <B>Until</B> j<=0 <B>End</B> <B>End</B>; <B>If</B> j<=0 <B>Then</B> <B>Begin</B> sp[m]:= 0; sp[p]:=-sp[p]; i:=-i; j:=-j; <B>If</B> j<>0 <B>Then</B> p:=r; k:=r; r:=m <B>End</B> <B>Until</B> j=0; </PRE> </font> </td></tr></table><br> <p> {В конец сформированного подсписка всегда заносится нулевая ссылка (sp[m]:= 0), ибо он может оказаться последним. <p> Действие sp[p]:= -sp[p] обозначает минусом конец ранее построенного подсписка. <p> В переменных i,j ссылки на начала новых сливаемых подсписков - со знаком минус; его снимаем. Переход к новым подспискам требует обновления переменных p, k, r} <p> Итак, на сегодняшнем занятии мы рассмотрели алгоритмы слияния. </p> <!-- end -->

Группа радиокнопок и ActiveControl.

Автор: Ralph Friedman На форме я имею группу радиокнопок. Я хотел бы вызывать контекстно-зависимую подсказку, если пользователь нажал F1. Для данной конкретной группы радиокнопок я установил HelpContext равным 22, но при любом вызове ActiveControl.HelpContext это возвращает (0). Все другие элементы управления работают как положено. Что я делаю неправильно?

Нет. Проблема в том, что ActiveControl - RadioButton, а не RadioButtonGroup. Поместите следующий код в обработчик события формы OnShow, он должен решить вашу проблему:

procedure TForm1.FormShow(Sender: TObject);
var
  c: integer;
begin
  with RadioGroup1 do
  begin
    for c := 0 to ControlCount - 1 do
      TRadioButton(Controls[c]).HelpContext := HelpContext;
  end;
end;

Инициализация исключительной ситуации.

Автор: Xavier Pacheco

{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit MainFrm;
interface
uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, AppEvnts, Buttons;
type
  ENotSoBadError = class(Exception);
  EBadError = class(Exception);
  ERealBadError = class(Exception);
  TMainForm = class(TForm)
    btnNotSoBad: TButton;
    btnBad: TButton;
    btnRealBad: TButton;
    appevnMain: TApplicationEvents;
    procedure btnNotSoBadClick(Sender: TObject);
    procedure btnBadClick(Sender: TObject);
    procedure btnRealBadClick(Sender: TObject);
    procedure appevnMainException(Sender: TObject; E: Exception);
  public
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnNotSoBadClick(Sender: TObject);
begin
  raise ENotSoBadError.Create('This isn''t so bad!');
end;
procedure TMainForm.btnBadClick(Sender: TObject);
begin
  raise EBadError.Create('This is bad!');
end;
procedure TMainForm.btnRealBadClick(Sender: TObject);
begin
  raise ERealBadError.Create('This is real bad!');
end;
procedure TMainForm.appevnMainException(Sender: TObject; E: Exception);
var
  rslt: Boolean;
begin
  if E is EBadError then
  begin
    { Show a custom message box and prompt for application termination. }
    rslt := MessageDlg(Format('%s %s %s %s %s', ['An', E.ClassName,
      'exception has occurred.', E.Message, 'Quit App?']),
        mtError, [mbYes, mbNo], 0) = mrYes;
    if rslt then
      Application.Terminate;
  end
  else if E is ERealBadError then
  begin // Show a custom message
    // and terminate the application.
    MessageDlg(Format('%s %s %s %s %s', ['An', E.ClassName,
      'exception has occured.', E.Message, 'Quitting Application']),
        mtError, [mbOK], 0);
    Application.Terminate;
  end
  else // Perform default exception handling
    Application.ShowException(E);
end;
end.

Как получить неповторяющиеся случайные числа.

Автор: http://www.swissdelphicenter.ch

procedure Shuffle(var aArray; aItemCount: Integer; aItemSize: Integer);
{ after Julian M Bucknall }
var
  Inx: Integer;
  RandInx: Integer;
  SwapItem: PByteArray;
  A: TByteArray absolute aArray;
begin
  if (aItemCount > 1) then
  begin
    GetMem(SwapItem, aItemSize);
    try
      for Inx := 0 to (aItemCount - 2) do
      begin
        RandInx := Random(aItemCount - Inx);
        Move(A[Inx * aItemSize], SwapItem^, aItemSize);
        Move(A[RandInx * aItemSize], A[Inx * aItemSize], aItemSize);
        Move(SwapItem^, A[RandInx * aItemSize], aItemSize);
      end;
    finally
      FreeMem(SwapItem, aItemSize);
    end;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  a: array[1..10] of Integer;
  i: Shortint;
begin
  Randomize;
  for i := Low(a) to High(a) do
    a[i] := i;
  Shuffle(a, High(a), SizeOf(Integer));
  for i := 1 to High(a) - 1 do
    ListBox1.Items.Add(IntToStr(a[i]));
end;

Как выбрать случайную запись.

Автор: http://www.swissdelphicenter.ch

procedure TForm1.FormCreate(Sender: TObject);
begin
  Randomize;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  Table1.First;
  Table1.MoveBy(Random(Table1.RecordCount));
end;

Нарисовать случайным образом кривую Безье.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure TForm1.Button1Click(Sender: TObject);
 var
   pusat: array of TPoint;
   i: Integer;
 begin
   SetLength(pusat, 4);
   for i := Low(pusat) to High(pusat) do
   begin
     pusat[i].x := Random(Width);
     pusat[i].y := Random(Height);
   end;
   Canvas.PolyBezier(pusat);
 end;

Случайные пароли.

Автор: Archer Тут поговорим случайных паролях с точки зрения математики. Конкретнее - как количество возможных вариантов пароля зависит от его длины и размерности используемого при генерации "алфавита". Тема не новая, но ни в одной статье на эту тему (во всяком случае из тех, что я читал) вопрос не рассматривается достаточно подробно. Частенько просто отделываются утверждением, что возможных вариантов паролей "огромное количество". Но это понятие растяжимое - современный ПК тоже выполняет "огромное количество" операций в секунду. Так сколько ему понадобится времени, чтобы при переборе вариантов добраться до нашего пароля? Особо продвинутые авторы пугают читателя огромными числами. Не будем верить на слово - если мы пишем программу, в которой предусмотрены пароли, надо самим уметь оценить их надежность. Не вдаваясь в строгое математическое доказательство, посмотрим, откуда берутся эти зверские числа и как их рассчитать для каждого конкретного случая. Давайте договоримся, что мы имеем алфавит из n возможных символов, а наш пароль k символов в длину. Надо узнать, сколько вариантов пароля может быть в принципе, и дополнительно посмотрим, сколько будет вариантов паролей из неповторяющихся символов - иногда встречаются утверждения, что такой пароль лучше. Не ручаюсь за точность терминологии, лень было искать:), просто для себя назовем такие пароли соответственно "обычными" и "уникальными". Обычные пароли Как мы только что договорились, обычными называем пароли, которые могут содержать повторяющиеся символы. То есть это полное количество комбинаций длиной k, которые мы можем составить из n элементов. Слишком сильно в математику вдаваться не будем, а рассмотрим несколько простых примеров. Начнем как положено, с самого начала. Пример 1: У нас есть элементы "А" и "В". Сколько комбинаций длиной 1 можно из них сотворить? Очевидно, две. Попутно отмечаем на будущее, что это равно nk. Пример 2: У нас есть элементы "А", "В" и "С". Сколько имеем комбинаций длиной в 2 символа? Давайте считать. Перебираем по принципу "каждый с каждым". Получаем "АА", "АВ", "АС", "ВА", "ВВ", "ВС", "СА", "СВ", "СС", всего 9. Занятное совпадение - снова nk :) Дальше предлагаю просто принять на веру, что их nk, поскольку так оно и есть, строго доказывать будет длинновато. Интересующиеся могут обратиться к разделу алгебры "Комбинаторика". Это называется размещения с повторениями. Рассмотрим более практический пример. Число букв английского алфавита 26. Составляем всевозможные пароли длиной в 9 символов. Их у нас 269=5 429 503 678 976 штук. Для начала неплохо:) Уникальные пароли Снова вернемся к примерам. Уникальные пароли не содержат повторяющихся символов, потому первый пример оставляет нам оба варианта. Из второго примера мы должны вычеркнуть "АА", "ВВ" и "СС", то есть имеем шесть случаев. В математике это называется размещениями. Количество размещений (без повторения) рассчитываем по формуле n*(n-1)*(n-2)*...*(n-k+1) = n! / (n-k)! (что такое факториал, надеюсь, все помнят). Сразу видим, что для наших примеров получаем соответственно 2 и 6, то есть все правильно. Снова тот же более практический пример - на этот раз имеем 26! / (26 - 9)! = 26! / 17! = 1 133 836 704 000 вариантов. Даже без "куркулятора" понятно, насколько мы сузили диапазон возможных паролей наложением условия неповторяемости символов. Это к вопросу об использовании "уникальных" паролей:) Реальный пример Давайте перейдем к практическим вещам. Например, рассмотрим пароли MS Office. Они могут быть до 15 знаков в длину, включать буквы (не только английские, но и языков локализации), цифры, а также специальные символы ("{", "#", "!" и т.п.). Кроме того, учитывается регистр символов, то есть английских букв реально можем использовать 52. Надо отметить, что MS Office ничего не имеет против пароля, состоящего из букв разных языков, но я бы не стал такого делать - легко самому запутаться. Потому прикидываем для английского - 52 буквы, 10 цифр, еще пару десятков надо накинуть на спецсимволы... Для круглого счета определим наш алфавит в 80 символов. Максимальное количество паролей максимальной длины при таком допущении будет примерно 3,5 * 1028. А если не допускать повторения символов? 8,6 * 1027. Снова вариантов в несколько раз меньше. Зависимости

Попробуем ответить на самый интересный вопрос - как количество вариантов зависит от размерности алфавита и длины пароля? И что вообще важнее - увеличивать размерность алфавита или использовать пароль максимальной длины? Формулы этих зависимостей мы уже получили, для наглядности теперь нарисуем пару картинок. Снова берем 80 символов алфавита и 15 символов пароля.

Статьи по Delphi (014)…
Рисунок 1: Зависимость количества "обычных" вариантов от размерности алфавита и длины пароля. Рисунок 2: Зависимость количества "уникальных" вариантов от размерности алфавита и длины пароля.
К сожалению, "рисовалка" немного не дотягивает до максимальных значений, но все закономерности налицо. На мой взгляд, рисунки показывают, что минимально приемлимый пароль имеет от 10 символов в длину и от 25 символов использованного алфавита, и что количество вариантов сильнее зависит от размерности алфавита.

Так сколько же компьютеру понадобится времени для взлома нашего документа? Давайте оценим. Специально заходил на сайты нескольких утилит для "восстановления забытых паролей", но сведений о скорости перебора там благоразумно не приводилось:) Единственное, что удалось откопать в сети - упоминание о том, что одна из таких программ, написанная несколько лет назад, перебирает 85000 вариантов в секунду на Pentium III. В алгоритмах с тех пор вряд ли что-то существенно изменилось. Давайте введем поправку на возросшие компьютерные мощности, например, помножим на пять. Для 9-ти значного пароля и для алфавита в 26 символов получится около 145 суток. Это для обычных паролей. Для паролей из неповторяющихся символов получается ровно месяц. Конечно, эти расуждения относятся к взлому пароля прямым перебором вариантов - относительно иных способов не берусь подсчитать.

Формат архивного файла RAR.

Оформил: DeeCo

(ОПИСЫВАЕМЫЙ ФОРМАТ АРХИВА ДЕЙСТВИТЕЛЕН ДЛЯ RAR ВЕРСИИ 1.50 И СТАРШЕ) Файл архива состоит из блоков разной длины. Порядок следования этих блоков может меняться, но первым блоком всегда должен быть блок-маркер, за которым следует блок заголовка архива. Каждый блок начинается со следующих полей: HEAD_CRC 2 байта CRC всего блока или его части HEAD_TYPE 1 байт Тип блока HEAD_FLAGS 2 байта Флаги блока HEAD_SIZE 2 байта Размер блока ADD_SIZE 4 байта Необязательное поле - добавление к размеру блока Поле ADD_SIZE присутствует, только если (HEAD_FLAGS & 0x8000) != 0 Общий размер блока указан в поле HEAD_SIZE, если (HEAD_FLAGS & 0x8000) == 0 или HEAD_SIZE+ADD_SIZE, если есть поле ADD_SIZE, при этом (HEAD_FLAGS & 0x8000) != 0. Во всех блоках следующие биты в HEAD_FLAGS имеют одинаковое значение: 0x4000 - если установлен, то старые версии RAR будут игнорировать этот блок и удалять его при изменении архива; если не установлен, то блок копируется в новый архивный файл при изменении архива; 0x8000 - если установлен, то поле ADD_SIZE присутствует и размер полного блока составляет HEAD_SIZE+ADD_SIZE. Заявленные типы блоков: HEAD_TYPE=0x72 блок-маркер HEAD_TYPE=0x73 заголовок архива HEAD_TYPE=0x74 заголовок файла HEAD_TYPE=0x75 заголовок комментария HEAD_TYPE=0x76 дополнительная информация HEAD_TYPE=0x77 субблок HEAD_TYPE=0x78 информация для восстановления Блок комментария используется только внутри других блоков. Обработка архива происходит следующим образом: 1. Читается и проверяется блок-маркер 2. Читается заголовок архива 3. Читаются или пропускаются HEAD_SIZE-размер(MAIN_HEAD) байт 4. Если обнаружен конец архива, то обработка архива прекращается, иначе читаются 7 байт в полях HEAD_CRC, HEAD_TYPE, HEAD_FLAGS, HEAD_SIZE. 5. Проверяется HEAD_TYPE. Если надо прочитать блок: если HEAD_TYPE==0x74 прочитать заголовок файла (первые 7 байт уже прочитаны) прочитать или пропустить HEAD_SIZE-размер(FILE_HEAD) байт прочитать или пропустить FILE_SIZE байт иначе прочитать соответствующий блок HEAD_TYPE: прочитать HEAD_SIZE-7 байт если (HEAD_FLAGS & 0x8000) прочитать ADD_SIZE байт Если надо пропустить блок: пропустить HEAD_SIZE-7 байт если (HEAD_FLAGS & 0x8000) пропустить ADD_SIZE байт 6. Перейти к шагу 4. ========================================================================== Форматы блоков ========================================================================== Блок-маркер (MARK_HEAD) ~~~~~~~~~~~~~~~~~~~~~~~ HEAD_CRC Всегда 0x6152 2 байта HEAD_TYPE Тип заголовка: 0x72 1 байт HEAD_FLAGS Всегда 0x1a21 2 байта HEAD_SIZE Размер блока = 0x0007 2 байта Блок-маркер в действительности считается фиксированной последовательностью байт: 0x52 0x61 0x72 0x21 0x1a 0x07 0x00 Заголовок архива (MAIN_HEAD) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HEAD_CRC CRC полей от HEAD_TYPE до RESERVED2 2 байта HEAD_TYPE Тип заголовка: 0x73 1 байт HEAD_FLAGS Битовые флаги: 2 байта 0x01 - Атрибут тома (том многотомного архива) 0x02 - Присутствует архивный комментарий 0x04 - Атрибут блокировки архива 0x08 - Атрибут непрерывного (solid) архива 0x10 - Не используется 0x20 - Присутствует авторская подпись (AV) остальные биты в HEAD_FLAGS зарезервированы для внутреннего использования HEAD_SIZE Общий размер архивного заголовка, включая архивные 2 байта комментарии RESERVED1 Зарезервировано 2 байта RESERVED2 Зарезервировано 4 байта Блок комментария присутствует, если (HEAD_FLAGS & 0x02) != 0 Заголовок файла (файл в архиве) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HEAD_CRC CRC полей от HEAD_TYPE до FILEATTR и имени файла 2 байта HEAD_TYPE Тип заголовка: 0x74 1 байт HEAD_FLAGS Битовые флаги: 2 байта 0x01 - файл продолжается из предыдущего тома 0x02 - файл продолжается в следующем томе 0x04 - файл зашифрован паролем 0x08 - присутствует комментарий файла 0x10 - используется информация из предыдущих файлов (флаг непрерывности) (для RAR 2.0 и выше) биты 7 6 5 (для RAR 2.0 и выше) 0 0 0 - размер словаря 64 Кбайт 0 0 1 - размер словаря 128 Кбайт 0 1 0 - размер словаря 256 Кбайт 0 1 1 - размер словаря 512 Кбайт 1 0 0 - размер словаря 1024 Кбайт 1 0 1 - зарезервировано 1 1 0 - зарезервировано 1 1 1 - файл в каталоге (HEAD_FLAGS & 0x8000) == 1, так как полный размер блока составляет HEAD_SIZE + PACK_SIZE HEAD_SIZE Полный размер заголовка файла, включая имя файла и комментарии 2 байта PACK_SIZE Размер файла в архиве (сжатый) 4 байта UNP_SIZE Размер исходного файла (несжатый) 4 байта HOST_OS Использованная при архивировании операционная система 1 байт 0 - MS-DOS 1 - OS/2 2 - Win32 3 - Unix 4 - Mac OS FILE_CRC CRC файла 4 байта FTIME Дата и время в стандартном формате MS-DOS 4 байта UNP_VER Версия RAR, необходимая для извлечения файла 1 байт METHOD Метод сжатия 1 байт NAME_SIZE Размер имени файла 2 байта ATTR Атрибуты файла 4 байта FILE_NAME Имя файла - строка размером NAME_SIZE байт Блок комментария присутствует, если (HEAD_FLAGS & 0x08) != 0 Блок комментария ~~~~~~~~~~~~~~~~ HEAD_CRC CRC полей от HEAD_TYPE до COMM_CRC 2 байта HEAD_TYPE Тип заголовка: 0x75 1 байт HEAD_FLAGS Битовые флаги 2 байта HEAD_SIZE Размер заголовка комментария + размер комментария 2 байта UNP_SIZE Размер несжатого комментария 2 байта UNP_VER Версия RAR, необходимая для извлечения комментария 1 байт METHOD Метод сжатия 1 байт COMM_CRC CRC комментария 2 байта COMMENT Текст комментария Блок дополнительной информации ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HEAD_CRC CRC блока 2 байта HEAD_TYPE Тип заголовка: 0x76 1 байт HEAD_FLAGS Битовые флаги 2 байта HEAD_SIZE Общий размер блока 2 байта INFO Прочие данные Субблок ~~~~~~~ Объект в архиве (блок или заголовок) может сопровождаться субблоком. Субблок зависит от основного объекта. Субблок может быть удален или перемещен в новой версии архива при его обновлении. Субблок содержит следующие поля: HEAD_CRC CRC блока 2 байта HEAD_TYPE Тип заголовка: 0x77 1 байт HEAD_FLAGS Битовые флаги 2 байта (HEAD_FLAGS & 0x8000) == 1, так как полный размер блока составляет HEAD_SIZE + DATA_SIZE HEAD_SIZE Общий размер блока 2 байта DATA_SIZE Общий размер данных 4 байта SUB_TYPE Тип субблока 2 байта RESERVED Должно быть 0 1 байт Другие поля Другие поля в зависимости от типа субблока Субблок расширенных атрибутов OS/2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HEAD_CRC CRC блока 2 байта HEAD_TYPE Тип заголовка: 0x77 1 байт HEAD_FLAGS Битовые флаги 2 байта (HEAD_FLAGS & 0x8000) == 1, так как полный размер блока составляет HEAD_SIZE + DATA_SIZE HEAD_SIZE Общий размер блока 2 байта DATA_SIZE Общий размер данных (сжатый размер расширенных атрибутов) 4 байта SUB_TYPE 0x100 2 байта RESERVED Должно быть 0 1 байт UNP_SIZE Размер несжатых расширенных атрибутов 4 байта UNP_VER Версия RAR, необходимая для извлечения расширенных атрибутов 1 байт METHOD Метод сжатия 1 байт EA_CRC CRC расширенных атрибутов 4 байта ========================================================================== Примечания ========================================================================== 1. Для обработки SFX-архива нужно пропустить модуль SFX, для чего в архиве осуществляется поиск блока-маркера. В модуле SFX нет последовательности байтов блока-маркера (0x52 0x61 0x72 0x21 0x1a 0x07 0x00). 2. CRC вычисляется с помощью стандартного многочлена 0xEDB88320. В случае, если размер CRC меньше 4 байт, то используются только младшие байты. 3. Кодирование метода сжатия: 0x30 - сохранение (без сжатия) 0x31 - скоростное сжатие 0x32 - быстрое сжатие 0x33 - нормальное сжатие 0x34 - хорошее сжатие 0x35 - лучшее сжатие 4. Номер версии RAR, необходимой для извлечения, кодируется как 10 * старший номер версии + младший номер версии.

RAS API для непродвинутых.

Автор: Александр Терехов
Особые благодарности Королеве дельфийского королевства Елене Филипповой, которая подвигла меня на ваяние сего опуса.
Вместо предисловия Я не являюсь профессиональным программистом и никогда не писал статьей по программированию. Поэтому "продвинутых" специалистов прошу не утруждать себя чтением ниже изложенного дабы избежать обструкции по поводу допущенных мною в настоящей статье ошибок и вольностей. Итак. Многие из "непродвинутых" дельфийцев, пытаясь использовать интернет-компоненты, сталкиваются с проблемой "удаленного соединения" (*) . Собственно подключаться не трудно - чаще всего такие компоненты с помощью системной библиотеки wsock32.dll сами инициализируют подключение к Интернет по "удаленному соединению". Гораздо труднее бывает после интернет-сессии отключить "удаленное соединение". Поэтому на Круглом столе "Королевства Дельфи" с регулярностью наступления "критических дней" появляются вопросы (в том числе и мои): "как после .... завершить соединение по удаленному доступу?" А "продвинутые гуру" от программирования с завидным постоянством на них отвечают: "смотри Ras API..." Давайте и посмотрим, что такое Ras API и с чем его едят :) Ras API - Remote Access Service Application Programming Interface. По-русски: "интерфейс программирования приложений, использующих службу удаленного доступа". Этот интерфейс как раз и служит для соединения по "удаленному доступу", а также отслеживания его состояния, отсоединения от телефонной линии, создания новых "удаленных соединений", изменения свойств уже созданных "соединений" и многого, многого другого. В библиотеку rasapi32.dll включено 97 экспортируемых функций. Даже в Help для Дельфи Microsoft любезно включило описание работы с функциями этой библиотеки. Напечатайте где-нибудь в проекте, например, RasDial и нажмите F1 - убедитесь сами. Казалось бы в чем проблемы? Ан нет! В поставке Дельфи нет интерфейсного модуля для работы с этой библиотекой. Что такое интерфейсный модуль? Это Pascal Unit - *.pas или *.dcu, в котором описаны используемые в DLL константы, типы переменных, функции и процедуры, порядок обращения к ним и др. Например, Windows.pas (обратите внимание на самую первую запись, включаемую в клаузу Uses) - это интерфейсный модуль для системных библиотек kernel32.dll, user32.dll и gdi32.dll и др., который позволяет Pascal-программам обращаться к процедурам и функциям этих библиотек. После некоторого скитания по Интернет мне удалось обнаружить столь нужный нам всем интерфейсный модуль для rasapi.dll - называется он RasUnit.pas. Весь модуль описывать здесь я не буду. Опишу только пять функций, позволяющих соединиться с Интернет по телефонной линии с помощью модема, отследить состояние "удаленного соединения" и отключиться от телефонной линии по завершению интернет-сеанса. 1. Получение сведений о всех зарегистрированных в системе "удаленных соединениях". Для получения сведений о всех зарегистрированных в системе "удаленных соединений" используется библиотечная функция (**) RasEnumEntries

Рассмотрим работу этой функции. Прежде всего определим переменные:

BuffSize: Integer; размер массива из AnsiChar, в который будут помещаться сведения об "удаленных соединениях" Entries: Integer; количество зарегистрированных "удаленных соединений" Entry : Array[1..MaxEntries] of TRasEntryName; массив состоящий из переменных, в которые будут помещены сведения об "удаленных соединениях", где константа MaxEntries - количество возможных соединений, TRasEntryName - определение (type) записи состоящей из двух полей dwSize и szEntryName (определены в RasUnit.pas) X, Result_ : Integer; необходимые процедурные переменные AllEntries: TStrings; сюда мы поместим названия "удаленных соединений" для дальнейшей работы с ними Перейдем к описанию работы функции RasEnumEntries.

1. Определим размер переменной типа TRasEntryName и инициализируем переменную Entry, поместив в поле dwSize полученный размер.

Entry[1].dwSize := SizeOf(TRasEntryName);

2. Определим размер AnsiChar-массива, в который поместим сведения обо всех "удаленных соединениях".

BuffSize := SizeOf(TRasEntryName) * MaxEntries;
3. Вызовем функцию RasEnumEntries в результате чего получим искомые результаты: Result_:=RasEnumEntries(nil, nil, @Entry[1], BuffSize, Entries), где Result_- в случае успешного выполнения возвращает 0, в противном случае получим ERROR_BUFFER_TOO_SMALL (буфер слишком маленький) или ERROR_NOT_ENOUGH_MEMORY(не хватает памяти). BuffSize - указанный нами размер AnsiChar-массива. @Entry[1]- получим указатель на первый элемент массива, в который поместились необходимые нам сведения. Entries - получим количество зарегистрированных в системе "удаленных доступов". Дальше уже просто.

В случае успешного выполнения функции и существования хотя бы одного зарегистрированного "удаленного соединения" заполним нашу переменную.

if (Result_ = 0) and (Entries > 0) then
begin
  AllEntries := TStringList.Create;
  for X := 1 to Entries do
  begin
    AllEntries.Add(Entry[x].szEntryName);
  end;
  {.....здесь мы работает со своей переменной, например,
    помещаем сведения об "удаленных соединениях" в ListBox......}
  AllEntries.Free;
end;
2. Соединение с интернет-сервером через выбранный "удаленный доступ" и получение статуса соединения Для соединения с интернет-сервером используются две библиотечные функции RasGetEntryDialParams и RasDial. Для обработки ошибок, возникших в процессе соединения, используется еще одна библиотечная функция RasGetErrorString. Определим необходимые переменные.

Глобальные:

MyDialParam : TMyDialParam переменная состояния соединения, где
TMyDialParam = Record
AMsg : Integer; - код сообщения
AState : TRasConnState; - статус соединения (тип переменной определен в  RasUnit.pas)
AError : Integer; - код ошибки
hRas: ThRASConn в эту переменную будет помещен handle (так сказать "ИНН") "удаленного соединения", к этой переменной будет обращаться функция RasHangUp для завершения соединения, тип переменной описан в RasUnit.pas

Локальные:

Fp:LongBool если в "удаленном доступе" не указан пароль пользователя, то эта переменная устанавливается в False и появляется приглашение ввести пароль, если пароль указан, то переменная устанавливается в True и приглашение не появляется. DialParams: TRasDialParams переменная, в которую будут переданы параметры "удаленного соединения", описывать тип этой переменной я не буду - он хорошо описан в Win32 Programmer's Reference (кто не знает - это один из разделов Неlр'а, поставляемого вместе с Delphi) и определен в RasUnit.pas AEntryDial:String переменная, в которую поместим название "удаленного соединения" R: Integer результат выполнения библиотечных функций C : Array[0..100] of Char переменная, в которую записывается текст сообщения об ошибке

Кроме переменных необходимо также определить CallBack-процедуру, которая будет использована в функции RasDial (***).

procedure RasCallBack(msg: Integer;
  state: TRasConnState;
  error: Integer); stdcall
{****}
{где
  msg: Integer - код сообщения
  state: TRasConnState - состояние соединения
  error: Integer - код ошибки}

В этой процедуре передадим глобальной переменной MyDialParam значения указанных переменных.

MyDialParam.AMsg := msg;
MyDialParam.AState := state;
MyDialParam.AError := error;

А также вызовем функцию GetStatusString (будет описана ниже), которая сообщит нам в Label1.Caption о состоянии соединения.

Form1.Label1.Caption := GetStatusString(MyDialParam.AState, MyDialParam.AError);
Form1.Label1.Update; {на всякий случай}
Все, на этом с CallBack процедурой закончено. Переходим к описанию процесса подключения к "удаленному соединения". Получим название выбранного нами "удаленного соединения"
AEntryDial:=ListBox1.Items.Strings[ListBox1.ItemIndex];
Заполним все поля переменной DialParams нолями.
FillChar(DialParams, SizeOf(TRasDialParams), 0);
Инициализируем переменную DialParams и поместим в поле szEntryName этой переменной название выбранного "удаленного соединения"
With DialParams Do
Begin
    dwSize:=Sizeof(TRasDialParams);
    StrPCopy(szEntryName, FEntry2Dial);
End;
Вызовем библиотечную функцию RasGetEntryDialParams, которая заполнит оставшиеся незаполненными поля переменной DialParams
R:=RasGetEntryDialParams(nil, DialParams, Fp);
Далее, если все удачно (см. значение переменной R), вызываем библиотечную функцию RasDial, поместив в нее переменную DialParams и указав на описанную выше CallBack-процедуру RasCallBack. В случае ошибок в работе функции RasDial вызовем библиотечную функцию обработки ошибок RasGetErrorString

, которая даст нам текст сообщения об ошибке, и выйдем из процедуры.

if R = 0 then
begin
  Application.ProcessMessages; {*****}
  R := RasDial(nil, nil, DialParams, 0, @RasCallback, hRAS);
  if R <> 0 then
  begin
    RasGetErrorString(R, C, 100);
    MessageBox(0, C, 'Ошибка!', MB_OK);
    Exit;
  end;
end;
6. На этом все! Напоследок опишем Function GetStatusString(State: TRasConnState; Error: Integer): String

Которая даст нам состояние соединения. Думаю, что переменные State и Error уже не требуют объяснения.

if Error <> 0 then
begin
  RasGetErrorString(Error, C, 100);
  Result := C;
end
else
begin
  S := '';
  case State of
    RASCS_OpenPort:
      S := 'Opening port';
      // .................................
      // вырезано ....
      // .................................
      RASCS_Disconnected:
      S := 'Disconnected';
  end;
  Result := S;
end;
Теперь уже совсем все с подключением "удаленного соединения" и получением его статуса! 3. Завершение удаленного соединения. Самый распространенный вопрос по рассматриваемой нами теме это - "как после .... завершить соединение по удаленному доступу?"

После того, что мы уже рассмотрели, ответ на этот вопрос покажется очень простым - надо вызвать библиотечную функцию RasHangUp с одной единственной уже описанной нами переменной hRas: ThRASConn.

RasHangUp(hRas)
Удачи! Интерфейсный модуль RasUnit.pas и проект, использующий рассмотренные нами функции библиотеки rasapi32.dll, прилагается: Файлы проекта + RasUnit : RasAPI.zip (23 K) Примечания: * - В настоящей статье под "удаленным соединением" подразумевается подключение к интернет-провайдеру по телефонной сети с помощью модема. ** - Далее под "библиотечной функцией" будем понимать функцию библиотеки rasapi32.dll *** - Для не посвященных в дебри программирования от Microsoft. CallBack-функция - это функция "обратного вызова". Служит для обработки некоторых функций и процедур созданных компанией Microsoft. В Раsсаl'е не применяется. С CallBack-функциями приходится часто сталкиваться при попытках программирования внутри Delphi на API (Application Programming Interface - интерфейс программирования приложений). К чему такие сложности? Не знаю. Возможно это такой стиль программирования от Microsoft. J Т.к. в нашем примере возвращать никаких "CallBack-данных" не требуется, поэтому вместо CallBack-функции определим CallBack-процедуру, да простит меня за это Билл Гейтс (кто не знает - это отец-основатель Microsoft). Библиотечная функция RasDial, вызывая эту процедуру помещает в переменную state код состояния соединения. **** - Stdcall - это способ передачи данных через стек CPU (справа - налево). Зарезервированное слово stdcall необходимо применять при обращении к находящимся в DLL (Dynamic Link Library - динамически подключаемая библиотека) процедурам и функциям, написанных на другом языке программирования - это из Неlр'а. Я однажды забыл указать этот параметр при обращении к DLL написанной на Pascal и в результате "подвесил" компьютер. Поэтому, всегда при обращении к библиотекам указывайте - stdcall.

***** - Это для того, чтобы в процессе соединения с сервером наше приложение могло реагировать на сообщения Windows, например, на нажатие кнопок.

RAS API программирование.

Автор: Антонянц Вадим Валерьевич
Приходит программист в библиотеку и спрашивает библиотекаря: - Книги по программированию где? Ему объясняют, что заканчивать предложение на вопросительном слове стилистически неправильно. Программист: - Понял! Книги по программированию где, дурья твоя башка?

В последнее время, я получаю много писем с просьбой объяснить на конкретном примере, принцип построения программ заменяющих собой стандартное средство дозвона Windows. Это довольно сложно сделать просто и понятно, в связи со спецификой работы разных модификаций этой популярной операционной системы. Для каждого случая необходим индивидуальный подход, но основополагающие алгоритмы функционирования неизменны. В начале 1999 года, я опубликовал в Usenet~е, некоторые соображения по этому поводу в месте с простым примером написания программы осуществляющей дозвон и соединение с провайдером. Не берусь утверждать, что такой подход единственно правильный. Но как мне кажется данный пример, позволяет ответить на массу вопросов по программированию RASAPI.

Некоторое время назад у меня появилась проблема с учетом времени проведенного в сети и дополнительного сервиса в плане автоматической конфигурации модема в зависимости от времени суток. Хочу пояснить, что у нас используется АТС старого типа с механическими контактами и поэтому качество соединения с провайдером изменяется по определенному закону. С 10-12 часов дня до 22-24 часов вероятность рас соединения в процессе работы примерно возрастает, а в остальное время таких проблем обычно не возникает. Выходом из ситуации являлось дополнительное программирование модема при помощи АТ команд. То есть создается несколько соединений в телефонной книге Windows и в зависимости от времени суток дозванивание до провайдера осуществляется по определенной закономерности. Появилось желание все это автоматизировать, но подходящей программы "звонилки" обнаружить не удалось. Тогда было принято решение написать программу позволяющую решить все эти и некоторые другие поставленные задачи по телефонному соединению с провайдером.

Первоначальный вариант эскиза программы был написан мной на Microsoft С, благодаря достаточному обилию информации по Remote Access Service (Удаленное Обслуживание Доступа) и вполне приличной справочной документации в стандартной поставке Visual C++ V5.0. Однако, такие факторы как "раздутость" кода бесконечные мытарства при выражении оптимизированного алгоритма функционирования и наконец большие затраты времени на составление красивого интерфейса пользователя привели меня к мысли о написании программы в среде разработки Delphi 4. Вначале это казалось довольно сложной задачей, так как в Delphi отсутствует модуль прототипов RASAPI функций, но небольшое их количество и логическая простота составления подобных прототипов в объектном Паскале Delphi позволило в короткий срок сгруппировать необходимые данные.

Предполагаемый алгоритм работы программы "звонилки" достаточно прост. Есть два пути проведения соединения с удаленным сервером. Первый путь - это когда программа не интегрирована в операционную систему и работает автономно. То есть она при помощи RAS API функций получает информацию о списке текущих соединений и после выбора необходимого, начинает процесс телефонного соединения вплоть до конечного результата. Системная программа "звонилка" в этом случаи может быть активизирована независимо от вашей программы и при наличии встроенной функции учета времени проведенного в сети может сучиться, что при соединении с ее помощью учет времени будет нарушен. Правда на сей счет, есть оговорка, что при безостановочном цикле проверки существующих соединений все-таки можно проводить достаточно точный учет времени проведенного в Интернете. Но при этом значительно расходуются ресурсы системы. Второй путь - несколько сложнее, но он наиболее интересен с точки зрения алгоритма реализации. При помощи функций RAS API, программа интегрируется в систему и в некоторых случаях позволяет заменять собой системную "звонилку". При необходимости проведения соединения, система проецирует в адресное пространство вызывающего процесса вашу DLL, вызывая функцию повторного вызова, прототип которой заранее известен и стандартизирован. В момент вызова данной функции из DLL, в ней может быть создан новый поток или потоки в текущем пространстве вызывающего процесса для создания интерфейса программы "звонилки". Так же, из этой DLL может быть создан новый объект ядра типа процесс со своими 4 Гб виртуального адресного пространства, в котором порождается главный поток отдельной программы "звонилки".

В конечном итоге я остановился на втором варианте, где из моей DLL вызывается отдельная программа "звонилка", запускаемая в отдельном процессе и управляемая из функции повторного вызова DLL разнообразными способами. Осталось дело за малым - выбором способа обмена данными между приложениями. Автоматизация OLE и создание и внедрение COM объектов были отброшены сразу из за больших и безосновательных затрат системных ресурсов и памяти. Оставалось использовать несколько устаревший способ динамический обмена данными между приложениями DDE. Вот тут и начались казусы. При применении библиотеки DDEML при выполнении транзакции XTYP_REQUEST (запрос клиентом на возвращение данных из сервера) необходимо создать в DLL специальную функцию повторного вызова, в которую при запросе клиента, сервер должен возвращать данные. Хочу сразу отметить, что здесь речь идет только об асинхронных транзакциях, так как функция повторного вызова DLL должна возвращать приложению ее запросившему, результат работы вашей программы "звонилки" и в случае неудачи вызывать системную "звонилку". Так вот, если производилась вышеописанная транзакция и не было выхода из функции повторного вызова, отправка данных сервера клиенту не производилась (в свою функцию повторного вызова). Причем это происходило и в библиотеке написанной в Microsoft С. Не помогало да же открытие нового потока и создание обработчика транзакции в нем. Причиной тому, по моему глубокому убеждению, являлось специфика работы Windows 98 (в Windows NT все работало прекрасно). Решением проблемы оставалось использование DDE без применения библиотеки Ddeml.dll и оконные общения на уровне системы. Первый путь из-за своей неэффективности (из пушки по воробьям), был отброшен сразу и обмен данными между библиотекой и программой "звонилки" производился при помощи оконных сообщений (Window Messages).

Теперь я хочу кратко описать алгоритм функционирования моей программы. В случае интеграции программы в систему, при надобности соединения с удаленным сервером браузер, например Internet Explorer, проецирует DLL в сое адресное пространство (динамическая загрузка) и вызывает функцию повторного вызова. Информацию об имени функции и полном пути к DLL он получает в процессе интеграции программы. В начальном этапе вызова функции в DLL создается новый поток, в котором регистрируется новый класс окна и создается невидимое пользователю скрытое окно. Функция обработчик оконных сообщений регистрирует только два типа оконных сообщений - это WM_DESTROY и WM_USER. Первое необходимо для деструктуризации окна, а второе, непосредственно для получения данных из программы сервера. В дальнейшем, производится поиск окна программы "звонилки" в системе и в случаи его отсутствия порождается новый отдельный процесс, в главном потоке которого идет выполнение Вашей программы. Дальнейший обмен данными между приложениями происходит на уровне оконных сообщений. При удачном или неудачном результате дозвона функция повторного вызова возвращает в программу заказчик соответствующее значение и в зависимости от этого, вызывается или не вызывается системная программа "звонилка". При выгрузке DLL из памяти скрытое окно получает сообщение WM_DESTROY, тем самым, корректно завершая поток. Хочу особо отметить, что для упрощения кода программы в программе-сервере (написанной в Delphi) не создается дополнительное окно, а все оконные сообщения обрабатываются в главном окне программы. Для этого была переопределена процедура базового класса VCL WndProc. Данный способ не является достаточно корректным, однако при правильном понимании специфики работы VCL (Visual components library) значительно упрощает программирование.

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

PS: Внимание!!! Если в среде разработки Delphi4 будет открыта страница с Maim.pas, работа отлаживаемой программы будет нарушена т. к. это окно будет ошибочно найдена функцией API FindWindow. Поэтому пред отладкой программы закройте эту страницу.

Загрузить пример (17 Кб).

Работа с отчетами Rave Report в режиме RunTime.

Автор: Турушев Виталий Введение С выходом Delphi 7 мы стали свидетелями выхода нового генератора отчетов Rave Report Borland Edition от разработчиков фирмы Nevrona. Среда разработки Rave имеет довольно много новшеств, и в тоже время ряд ошибок и недочетов. Среди положительных качеств можно отметить: сохранение проекта отчета в файл и чтение его из файла, что позволяет удобно загрузить или сохранить необходимый проект отчета, и в дальнейшем работать с ним. Также есть набор компонентов для конвертирования отчета Rave в другие форматы (PDF, HTML, RTF, TEXT). Rave Report значительно облегчает разработку отчетов где используются базы данных, ведь выбор компонентов доступа к источникам данных весьма неплох. Среди недочетов отмечу: недоработку среды разработки отчетов Rave, где присутствует ряд серьезных ошибок. Также Nevrona предоставила довольно таки очень скудную справку для разработчика и не порадовала достаточным количеством примеров работы с отчетами Rave. Причиной написания данной статьи стало отсутствие примеров и описания работы с отчетами Rave в режиме RunTime, что не позволяет пользователю более гибко работать с отчетом (например: изменение данных/оформления в отчете при наступлении определенного события в программе, а возможности Event Editor в Rave Report увы ограничены), что отталкивает пользователей от использования генератора отчетов Rave. Возможно, по этой причине или по причине недоработки генератора отчета часть пользователей возвращается к генератору отчетов Quick Report или к генераторам отчетов других разработчиков. Исследование классов в проекте отчета Rave Report

Для работы с отчетами в RunTime потребуется, знание имен подключаемых модулей в раздел uses, создаваемого проекта. Ниже представлена таблица описания основных модулей, которые могут понадобятся для работы с отчетом Rave в RunTime. Примечание: Для разработчиков CLX приложений название модулей почти идентично, только в имени модуля предшествует символ «Q». Например: QRvCsBars. Все модули находятся в каталоге «\Rave5\Lib» куда установлена среда программирования Delphi 7.

Имя модуля Описание модуля
RvClass    Этот модуль содержит реализацию базовых классов
RvProj     В этом модуле набор классов реализующие собственно сам проект 
             отчета и отвечающих за работу с ним
RvCsDraw   Классы в этом модуле реализуют графические примитивы
RvCsBars   В этом модуле реализованы штриховые коды
RvCsStd    В этом модуле реализуются основные элементы оформления отчета
RvCsRpt,   Набор классов в данном модуле служит для вывода данных из 
RvCsData     баз данных или других источников данных
Примечание: В качестве файла отчета взят уже готовый демонстрационный пример отчета RaveDemo.rav, поставляемый с генератором отчета Rave Report, который находится в каталоге «\Rave5\Demos». Поэтому следует указать полный путь к файлу отчета или скопировать данный файл в каталог с текущим проектом. И так, осталось выяснить, что далее необходимо для работы с отчетом в RunTime. Всю работу с проектом отчета обеспечивает класс TRaveProjectManager – менеджер отчетов. Следовательно, потребуются некоторые знания о свойствах и методах этого класса. Примечание: Следует разделять понятие проект отчета и отчет как таковой в отдельности. Проект отчета – может содержать в себе целую коллекцию отдельных отчетов. Отчет – это набор страниц, элементов оформления, элементов доступа к различным источникам данных и т.п., то, что подготовлено или подготавливается для печати отчета. Также могут, пригодиться знания по работе с технологией RTTI для извлечения наименований свойств, событий (методов) и другой информации из объектов (компонентов) отчета Rave Report. Можно и не изучать принципы работы с технологией RTTI, ведь наименование доступных свойств интересующего объекта можно увидеть в среде разработки Rave Report инспектора объектов.

И так, двигаемся далее. Для работы с отчетом в RunTime, конечно, необходимо знать из набора каких объектов состоит данный отчет и, от каких классов произошли эти объекты, ведь в справочной системе Rave Report о них нет ни слова. Для этого необходимо написать процедуру, которая поможет узнать, из каких объектов состоит отчет, а также поможет выяснить имена классов и объектов. Код данной процедуры представлен ниже:

procedure GetListObjects(ListClass: TStrings; ClassX: TComponent; TabStr:
  string; AddObjects: Boolean = False);
var
  I: Integer;
begin
  if (ClassX = nil) or (ListClass = nil) then
    EXIT;
  for I := 0 to ClassX.ComponentCount - 1 do
  begin
    if AddObjects then
      ListClass.AddObject(TabStr + ClassX.Components[I].Name +
        ' - ' + ClassX.Components[I].ClassName, ClassX.Components[I])
    else
      ListClass.Add(TabStr + ClassX.Components[I].Name +
        ' - ' + ClassX.Components[I].ClassName);
    if ClassX.Components[I].ComponentCount > 0 then
      GetListObjects(ListClass, ClassX.Components[I], TabStr + '..',
        AddObjects);
  end;
end;

Как видите, процедура достаточно проста. В цикле процедуры осуществляется проход по всем дочерним компонентам в компоненте «ClassX» и если дочерний компонент содержит в себе еще вложенные компоненты, то используется рекурсивный вызов процедуры GetListObjects. В результате чего будет получен список всех компонентов. Вполне вероятно, что захочется узнать всех предков исследуемого объекта, тогда можно воспользоваться приведенной ниже процедурой.

procedure GetListParentClassName(ListClass: TStrings;
  ClassX: TClass; ClearList: Boolean = True);
begin
  if (ClassX = nil) or (ListClass = nil) then
    EXIT;
  if ClearList then
    ListClass.Clear;
  while ClassX <> nil do
  begin
    ListClass.Add(ClassX.ClassName);
    ClassX := ClassX.ClassParent;
  end;
end;
Пример вызова процедуры GetListParentClassName: GetListParentClassName(MemoInfo.Lines, RvProjectRTR.ClassType); или GetListParentClassName(MemoInfo.Lines, TRvProject); Как было сказано выше, для доступа к проекту отчета необходим класс TRaveProjectManager. Чтобы получить к нему доступ, необходимо обратиться к свойству «ProjMan» класса TRvProject. Как известно в проекте отчета Rave Report может содержаться несколько отчетов. Работа с отчетом осуществляется через класс TRaveReport. Для доступа к текущему активному отчету нужно обратиться к свойству «ActiveReport» класса TRaveProjectManager.

Вот для свойства «ActiveReport» класса TRaveProjectManager и следует применить выше описанную процедуру GetListObjects для получения списка объектов из отчета. Для использования процедуры можно использовать, к примеру, такие строки кода:

...
ListObjects.Clear;
GetListObjects(ListObjects.Items, RvProjectRTR.ProjMan.ActiveReport, '', True);
...
Посмотрите на результат работы процедуры GetListObjects. Вы наверняка сразу заметили одну особенность, что все имена классов представленных объектов начинаются с приставки «TRave…». Ну вот, зная имена классов, работать будет уже намного легче. Совет: Если известен некий класс, но неизвестно в каком модуле данный класс описан, то это можно выяснить с помощью поисковика файлов с поддержкой поиска текста внутри файлов. В поисковике задать маску для поиска «*.dcu» в папке «\Rave5\Lib», а в строке для поиска текста указать наименование класса, например: TRaveControl. Исследование объектов средствами технологии RTTI Получив список объектов, не мешало бы поподробнее получить информацию об интересующем объекте. В этом, безусловно, поможет технология RTTI. Если вы имеете опыт работы с RTTI, то можете пропустить данный раздел статьи. Примечание: В данной статье не будет достаточно подробно рассматриваться принцип работы с RTTI так как эта тема довольно-таки велика по объему. Будет рассмотрено только то, что поможет в дальнейшей работе. Более подробно с принципами работы технологии RTTI вы можете познакомиться в книге Стива Тейксейра и Ксавье Пачеко «DELPHI 5 Руководство разработчика. Том 2. Разработка компонентов и работа с базами данных». Процедуры, реализованные в этой статье, основаны на примерах из этой книги, и были несколько доработаны для работы с проектом, описываемым в данной статье.

Для работы с RTTI в раздел проекта uses необходимо подключить модуль TypInfo. Для извлечения информации RTTI обычно требуется две структуры: PTypeInfo и PTypeData. Ниже приведена процедура, которая выводит базовую информацию об интересующем объекте.

// Извлечение базовой информации об объекте
procedure GetListClassInfo(ListInfo: TStrings; ClassX: TObject; ClearList:
  Boolean = True);
var
  // В данные структуры записывается информация об RTTI объекта
  Class_PTI: PTypeInfo;
  Class_PTD: PTypeData;
begin
  if (ClassX = nil) or (ListInfo = nil) then
    EXIT;
  if ClearList then
    ListInfo.Clear;
  // Получение информации об RTTI объекта
  Class_PTI := ClassX.ClassInfo;
  Class_PTD := GetTypeData(Class_PTI);
  // Вывод базовой информации об объекте
  with ListInfo do
  begin
    Add('Базовая информация:');
    Add(Format('Имя класса:                 '#9'  %s',[Class_PTI.Name]));
    Add(Format('Тип класса:                 '#9'  %s',
     [GetEnumName(TypeInfo(TTypeKind),
        Integer(Class_PTI.Kind))]));
    Add(Format('Размер объекта:             '#9'  %d',[ClassX.InstanceSize]));
    Add(Format('Описан в модуле:            '#9'  %s',[Class_PTD.UnitName]));
    Add(Format('Всего доступно свойств:     %d', [Class_PTD.PropCount]));
    if ClassX is TRaveControl then
    begin
      Add('Родительский компонент:');
      Add(Format('Тип класса:     %s',
        [TRaveControl(ClassX).Parent.ClassName]));
      Add(Format('Имя компонента: %s', [TRaveControl(ClassX).Parent.Name]));
    end;
    Add('Генеалогическое дерево класса:');
    // Вывод информации о предках объекта
    GetListParentClassName(ListInfo, ClassX.ClassType, False);
  end;
end;
Примечание: Следует помнить, что в RTTI доступны только те свойства и методы, которые определены в секции published исследуемого объекта, т.е. те которые видны в инспекторе объ-ектов среды разработки Delphi. Свойства и методы необъявленные в секции published через технологию RTTI будут недоступны.

Следующая процедура выводит список наименований свойств, свойств-событий (методы) и тип свойств исследуемого объекта. Также данная процедура выводит текущие значения, присвоенные свойствам объекта. Для свойств типа tkClass (в этих свойствах храниться ссылка на некий объект) выводиться имя объекта, на который ссылается данное свойство. Если же это свойство не ссылается на объект, то будет выведено значение «NIL».

// Извлечение информации о наименовании свойств и событий объекта
procedure GetListProperty(ListPropertys: TStrings; ClassX: TObject; AddObjects:
  Boolean = False; ClearList: Boolean = True);
var
  PropList: PPropList;
  Class_PTI: PTypeInfo;
  Class_PTD: PTypeData;
  I, PropertyCount: Integer;
  S, StrVal: string;
  TmpObj: TObject;
begin
  if (ClassX = nil) or (ListPropertys = nil) then
    EXIT;
  if ClearList then
    ListPropertys.Clear;
  Class_PTI := ClassX.ClassInfo;
  Class_PTD := GetTypeData(Class_PTI);
  if Class_PTD.PropCount <> 0 then
  begin
    // Выделение памяти под структуры TPropInfo, в зависимости от количества свойств объекта
    GetMem(PropList, SizeOf(PPropInfo) * Class_PTD.PropCount);
    try
      // Заполнение PropList указателями на структуры TPropInfo
      GetPropInfos(ClassX.ClassInfo, PropList);
      for I := 0 to Class_PTD.PropCount - 1 do
        // Добавляются свойства не являющиеся событиями
        if not (PropList[I]^.PropType^.Kind = tkMethod) then
        begin
          // Извлечение текущего значения свойства
          if PropList[I]^.PropType^.Kind = tkClass then
          begin
            TmpObj := GetObjectProp(ClassX, PropList[I]^.Name);
            if TmpObj = nil then
              StrVal := 'NIL'
            else
              // Если у объекта есть предок TComponent,
              // то извлекается имя объекта иначе имя класса
              if TmpObj is TComponent then
                StrVal := TComponent(TmpObj).Name
              else
                StrVal := '(' + TmpObj.ClassName + ')';
          end
          else
            StrVal := GetPropValue(ClassX, PropList[I]^.Name);
          S := Format('%s: %s = %s', [PropList[I]^.Name,
            PropList[I]^.PropType^.Name, StrVal]);
          if AddObjects then
            ListPropertys.AddObject(S, TObject(PropList[I]^.PropType^))
          else
            ListPropertys.Add(S);
        end;
      // Поиск свойств-событий
      PropertyCount := GetPropList(ClassX.ClassInfo, [tkMethod], PropList);
      ListPropertys.Add('*** Свойства-события ***');
      // Добавляются свойства-события
      for i := 0 to PropertyCount - 1 do
      begin
        S := Format('%s: %s', [PropList[I]^.Name, PropList[I]^.PropType^.Name]);
        if AddObjects then
          ListPropertys.AddObject(S, TObject(PropList[I]^.PropType^))
        else
          ListPropertys.Add(S);
      end;
    finally
      // Освобождение ранее выделенной памяти
      FreeMem(PropList, SizeOf(PPropInfo) * Class_PTD.PropCount);
    end;
  end;
end;
Процедур GetListClassInfo и GetListProperty вполне достаточно, чтобы изучить необходимый объект. Но самой полезной является, конечно, процедура GetListProperty. Как видите процедура GetListProperty достаточно сложная по виду. Еще бы, ведь структуры PTypeInfo и особенно PTypeData довольно-таки «ветвистые» по своему строению. Понимание выше приведенных процедур осложняет еще и то, что Borland не документирует данную технологию так как, она может изменяться от одной версии Delphi к другой, вследствие чего это не может гарантировать работоспособность одного и того же кода в различных версиях Delphi.

Для просмотра результата работы приведенных выше двух можно применить следующие строки кода:

...
if ListObjects.Items.Objects[ListObjects.ItemIndex] <> nil then
begin
  GetListClassInfo(MemoInfo.Lines,
    ListObjects.Items.Objects[ListObjects.ItemIndex]);
  GetListProperty(ListProperty.Items,
    ListObjects.Items.Objects[ListObjects.ItemIndex], True);
end;
...

Но и это еще не все возможности технологии RTTI. Также есть возможность получить достаточно полную информацию о свойстве или методе исследуемого объекта. Для извлечения столь немаловажной информации следует воспользоваться приведенной ниже процедурой.

// Извлечение информации о свойствах и методах объекта
procedure GetPropertyInfo(ListInfo: TStrings; PTI: PTypeInfo; PTD: PTypeData;
  ClearList: Boolean = True);
type
  // Структура для извлечения информации из методов (свойства-события)
  PParamRec = ^TParamRec;
  TParamRec = packed record
    Flags: TParamFlags;
    ParamName: ShortStringBase;
    TypeName: ShortStringBase;
  end;
var
  I: Integer;
  S, S2: string;
  TeStr,
    RStr: ^ShortStringBase;
  ParamRec: PParamRec;
  // Базовая информация для всех свойств
  procedure Name_Info;
  begin
    ListInfo.Add(Format('Тип свойства:             %s', [PTI.Name]));
    ListInfo.Add(Format('Подтип свойства:       %s',
      [GetEnumName(TypeInfo(TTypeKind),
        Integer(PTI^.Kind))]));
  end;
  // Информация для целочисленных, множеств и перечисляемых типов свойств
  procedure Int_Info;
  begin
    ListInfo.Add(Format('Минимальное значение:  %d', [PTD^.MinValue]));
    ListInfo.Add(Format('Максимальное значение: %d', [PTD^.MaxValue]));
  end;
  // Информация для типов свойств с плавающей точкой
  procedure Float_Info;
  begin
    // Определение подтипа свойства
    case PTD^.FloatType of
      ftSingle: S := 'ftSingle';
      ftDouble: S := 'ftDouble';
      ftExtended: S := 'ftExtended';
      ftComp: S := 'ftComp';
      ftCurr: S := 'ftCurr';
    end;
    ListInfo.Add(Format('Подтип tkFloat:        %s', [S]));
    ListInfo.Add('Минимальное значение:  ' + FloatToStr(PTD^.MinInt64Value));
    ListInfo.Add('Максимальное значение: ' + FloatToStr(PTD^.MaxInt64Value));
  end;
  // Информация для свойства представленного как класс
  procedure Class_Info;
  begin
    ListInfo.Add(Format('Предок класса свойства:           %s',
      [PTD^.ParentInfo^.Name]));
    ListInfo.Add(Format('Доступно свойств у объекта:    %d', [PTD^.PropCount]));
    ListInfo.Add(Format('Описан в модуле:                       %s',
      [PTD^.UnitName]));
  end;
  // Информация для методов (свойства-события)
  procedure Method_Info;
  var
    J: Integer;
  begin
    // Определение типа метода
    case PTD^.MethodKind of
      mkProcedure: S := 'Procedure ';
      mkFunction: S := 'Function ';
      mkConstructor: S := 'Constructor ';
      mkDestructor: S := 'Destructor ';
      mkClassProcedure: S := 'ClassProcedure ';
      mkClassFunction: S := 'ClassFunction ';
      mkSafeProcedure: S := 'SafeProcedure ';
      mkSafeFunction: S := 'SafeFunction ';
    end;
    // Извлечение информации о передаваемом параметре
    ParamRec := @PTD^.ParamList;
    J := 1;
    ListInfo.Add(Format('Передаваемых параметров:        %d',
      [PTD^.ParamCount]));
    while J <= PTD^.ParamCount do
    begin
      if J = 1 then
        S := S + '(';
      // Определение метода передачи параметра
      if pfVar in ParamRec.Flags then
        S2 := 'var ';
      if pfConst in ParamRec.Flags then
        S2 := 'const ';
      if pfArray in ParamRec.Flags then
        S2 := 'array of ';
      if pfOut in ParamRec.Flags then
        S2 := 'out ';
      // Извлечение информации о типе передаваемого параметра
      TeStr := Pointer(Integer(@ParamRec^.ParamName) +
        Length(ParamRec^.ParamName) + 1);
      S := S + S2;
      S := Format('%s%s: %s', [S, ParamRec^.ParamName, TeStr^]);
      Inc(J);
      // Извлечение информации о следующем передаваемом параметре
      ParamRec := PParamRec(Integer(ParamRec) + SizeOf(TParamFlags) +
        (Length(ParamRec^.ParamName) + 1) + (Length(TeStr^) + 1));
      if J > PTD^.ParamCount then
        S := S + ')';
      // Если метод является функцией, то извлекается информация о возвращаемом параметре
      if PTD^.MethodKind = mkFunction then
      begin
        RStr := Pointer(ParamRec);
        S := Format('%s: %s;', [S, RStr^]);
      end
      else
        S := S + '; ';
    end;
    ListInfo.Add(S);
  end;
  // Вывод информации для строковых типов свойств
  procedure Str_Info;
  begin
    ListInfo.Add(Format('Максимальная длина:  %d', [PTD^.MaxLength]));
  end;
begin
  if (ListInfo = nil) or (PTI = nil) or (PTD = nil) then
    EXIT;
  if ClearList then
    ListInfo.Clear;
  Name_Info;
  // Извлечение информации для свойств множеств
  if PTI^.Kind = tkSet then
  begin
    PTI := PTD^.CompType^;
    PTD := GetTypeData(PTI);
    Name_Info;
  end;
  case PTI^.Kind of
    tkInteger: Int_Info;
    tkFloat: Float_Info;
    tkString: Str_Info;
    tkLString: Str_Info;
    tkWString: Str_Info;
    // Извлечение информации для перечисляемых свойств
    tkEnumeration:
      begin
        Int_Info;
        ListInfo.Add('Варианты значений:');
        for I := PTD^.MinValue to PTD^.MaxValue do
          ListInfo.Add(Format('Значение: %s', [GetEnumName(PTI, I)]));
      end;
    tkClass: Class_Info;
    tkMethod: Method_Info;
  end;
end;
Примечание: Для перечисляемых свойств, информацию «Минимальное значение» и «Максимальное значение» выводимую процедурой GetPropertyInfo следует понимать как первый и последний индекс элемента перечисляемого свойства. Использовать процедуру GetPropertyInfo можно так:
procedure TFormRTR.ListPropertyClick(Sender: TObject);
var
  TI: PTypeInfo;
  TD: PTypeData;
begin
  if ListProperty.Items.Objects[ListProperty.ItemIndex] = nil then
    EXIT;
  TI := PTypeInfo(ListProperty.Items.Objects[ListProperty.ItemIndex]);
  TD := GetTypeData(TI);
  GetPropertyInfo(MemoInfo.Lines, TI, TD);
end;
Имея на вооружении такие замечательные процедуры, наконец, то можно изучить интересующие классы, узнать свойства и методы, а также тип свойств исследуемых классов. Получив достаточно подробную информацию с помощью технологии RTTI теперь, наконец, можно перейти к работе с проектом отчета Rave Report в режиме RunTime, но сначала ознакомимся с основными классами, которые встречаются в проекте отчета Rave Report. Описание классов TRaveXXX В этом разделе статьи содержится описание классов, из которых в основном состоит проект отчета. Проект отчета формируют три основных класса: TRaveProjectManager, TRaveReport, TRavePage. Для работы с источниками данных могут использоваться классы TRaveDataView, TRaveDataField, а так же TRaveRegion и TRaveDataBand. Разберем эти основные классы более конкретно. Как описывалось выше, класс TRaveProjectManager обеспечивает всю базовую работу с проектом отчета Rave. Осуществляет такие основные задачи как: чтение/сохранение проекта отчета, работа с коллекцией отчетов и глобальными страницами, поиск необходимого отчета и компонентов TRaveXXX и многое другое. Ниже приведено описание основных свойств класса TRaveProjectManager. Примечание: * – предположительное описание свойства, ввиду того, что в справочной системе Rave не предоставлена информация по данному свойству. (скрытый) – данное свойство доступно через технологию RTTI, но скрыто в инспекторе объектов среды разработки Rave Report.

TRaveProjectManager

AdminPassword Пароль для доступа к проекту отчета * Categories Хранит список наименования категорий. Далее отдельному отчету можно указать тип категории, что помогает организовать более удобную работу и произвести поиск отчетов по категориям CompileNeeded Необходима компиляция (скрытый) * Description Сюда записывается более подробная информация о компоненте DevLocked Блокировка компонента от случайных изменений его свойств FullName Альтернативное наименование компонента Locked Блокировка компонента от случайных изменений его свойств Name Имя компонента Parameters Описание параметров, которые могут использоваться для сохранения временных вычислений или другой информации PIVars По назначению подобны Parameters, но присваиваются значения, которые определены после передачи команды на печать (After Print) SecurityControl Определяет параметры доступа к серверам баз данных для ввода имени и пароля пользователя Tag Тег, хранит целое число, которое используется разработчиком для собственных нужд Units Определяет единицу измерения для всех отчетов UnitsFactor Коэффициент для перевода текущей единицы измерения в дюймы OnBeforeReport Обработчик события перед генерацией отчета OnAfterReport Обработчик события после генерации отчета OnBeforePrint Обработчик события пред посылкой задания на печать OnAfterPrint Обработчик события после завершения печати Примечание: Если вы исследовали, какой либо класс TRaveXXX средствами RTTI, то наверно обратили внимание, что события OnBeforeReport, OnAfterReport, OnBeforePrint, OnAfterPrint не являются методами как в VCL Delphi, а являются ссылками на класс TRaveSimpleEvent. Теперь познакомимся с классом TRaveReport. Данный класс представляет собой отдельный отчет, который является контейнером, хранящим в себе страницы отчета. Описание основных свойств класса TRaveReport приведены ниже.

TRaveReport

AlwaysGenerate Перед печатью отчета заполняет переменные типа TotalPages, чтобы их значение было известно перед печатью первой страницы Category Позволяет установить принадлежность отчета к заданной категории. Список доступных категорий задается в свойстве «Categories» менеджера отчетов TRaveProjectManager Collate Определяет тип упорядочивания задания на печать CompileNeeded Необходима компиляция (скрытый) * Copies Хранит количество копий, после печати отчета Description Сюда записывается более подробная информация о компоненте DevLocked Блокировка компонента от случайных изменений его свойств Duplex Установка типа дуплексной печати для принтера (не для всех принтеров) FirstPage Первая страница отчета FullName Альтернативное наименование компонента Locked Блокировка компонента от случайных изменений его свойств MaxPages Ограничивает число генерируемых страниц при генерации отчета после вызова метода Execute, 0 – генерируются все страницы Name Имя компонента PageList Список страниц для печати. Здесь можно задать какие страницы печатать и в каком порядке Parameters Описание параметров, которые могут использоваться для сохранения временных вычислений или другой информации PIVars По назначению подобны Parameters, но присваиваются значения, которые определены после передачи команды на печать (After Print) Printer Задается имя принтера, на который выводится печать. Если поле пустое, то вывод данных осуществляется на текущий принтер Resolution Установка качества печати SecurityControl Определяет параметры доступа к серверам баз данных для ввода имени и пароля пользователя Tag Тег, хранит целое число, которое используется разработчиком для собственных нужд OnAfterPrint Обработчик события после завершения печати OnAfterReport Обработчик события после генерации отчета OnBeforePrint Обработчик события пред посылкой задания на печать OnBeforeReport Обработчик события перед генерацией отчета Переходим к классу TRavePage. Данный класс реализует страницу отчета и также является контейнером, в который помещаются различные элементы оформления отчета, а также вспомогательные не визуальные элементы, например как TRaveFontMaster. Рассмотрим свойства класса TRavePage в приведенной ниже таблице.

TRavePage

Bin Указывается тип лотка для подачи бумаги BinCustom Если в представленном списке Bin нет необходимого типа лотка, то указывается пользовательская константа лотка, поддерживаемая принтером CompileNeeded Необходима компиляция (скрытый) * Description Сюда записывается более подробная информация о компоненте DevLocked Блокировка компонента от случайных изменений его свойств FullName Альтернативное наименование компонента GotoMode Определяет метод перехода по страницам «GotoPage» при печати GotoPage Печать указанной страницы после печати текущей страницы GridLines Определяет шаг видимой линии в координатной сетке GridSpacing Размер шага между линиями в координатной сетке Locked Блокировка компонента от случайных изменений его свойств Name Имя компонента Orientation Вид ориентации страницы (книжная/альбомная) PageHeight Высота страницы PageWidth Ширина страницы PaperSize Выбор формата страницы поддерживаемый текущим принтером Parameters Описание параметров, которые могут использоваться для сохранения временных вычислений или другой информации PIVars По назначению подобны Parameters, но присваиваются значения, которые определены после передачи команды на печать (After Print) Tag Тег, хранит целое число, которое используется разработчиком для собственных нужд WasteFit Запрещает или разрешает при генерации отчета, пропорционально располагать элементы оформления на всю рабочую область страницы OnAfterPrint Обработчик события после завершения печати OnAfterReport Обработчик события после генерации отчета OnBeforePrint Обработчик события пред посылкой задания на печать OnBeforeReport Обработчик события перед генерацией отчета TRaveDataView – данный класс-посредник обеспечивает работу и связь между источниками данных и отчетом (данное назначение класса предположительно ввиду отсутствия справочной информации о нем). Описан он в модуле RvDirectDataView.

TRaveDataView

CompileNeeded Необходима компиляция * ConnectionName Хранит имя подключенного источника данных Description Сюда записывается более подробная информация о компоненте DevLocked Блокировка компонента от случайных изменений его свойств FullName Альтернативное наименование компонента Locked Блокировка компонента от случайных изменений его свойств Name Имя компонента Tag Тег, хранит целое число, которое используется разработчиком для собственных нужд OnBeforeReport Обработчик события перед генерацией отчета OnAfterReport Обработчик события после генерации отчета OnBeforePrint Обработчик события пред посылкой задания на печать OnAfterPrint Обработчик события после завершения печати TRaveDataField – данный класс представляет собой поле данных и предоставляет вывод информации из источника данных. Этот класс расположен в модуле RvDataField.

TRaveDataField

Calculated Вычисляемое поле или нет * Description Сюда записывается более подробная информация о компоненте DevLocked Блокировка компонента от случайных изменений его свойств FieldName Имя поля FullName Альтернативное наименование компонента Locked Блокировка компонента от случайных изменений его свойств Name Имя компонента NullText Текст, выводимый по умолчанию (если нет данных в источнике данных) Size Размер поля Tag Тег, хранит целое число, которое используется разработчиком для собственных нужд OnBeforeReport Обработчик события перед генерацией отчета OnAfterReport Обработчик события после генерации отчета OnBeforePrint Обработчик события пред посылкой задания на печать OnAfterPrint Обработчик события после завершения печати Ниже в таблице приведено описание классов, которые можно встретить в палитре компонентов среды разработки отчетов Rave Report. Визуальные компоненты – это те самые элементы оформления отчетов, такие как: линия, текст, штриховые коды и др. Не визуальные компоненты выполняют вспомогательные функции в оформлении отчета (TRaveFontMaster, TRavePageNumInit), или выполняют вычисления при генерации отчета, такие компоненты как TRaveCalcController, TRaveCalcOp и другие. Также к не визуальным элементам отчетов относятся компоненты TRaveRegion, TRaveBand, TRaveDataBand и компоненты доступа к источникам данных. В заголовке таблицы приведено имя модуля, где данные классы реализованы.

RvCsDraw

TRaveGraphicBase Является базовым классом для всех классов данного модуля. Произошел данный класс от TRaveControl. TRaveSurface Происходит от класса TRaveGraphicBase и уже содержит в себе свойства реализующие базовые элементы оформления и стилей. TRaveLine Элемент оформления – линия. TRaveHLine Элемент оформления – горизонтальная линия. TRaveVLine Элемент оформления – вертикальная линия TRaveRectangle Элемент оформления – прямоугольник TRaveSquare Элемент оформления – квадрат. TRaveEllipse Элемент оформления – эллипс. TRaveCircle Элемент оформления – окружность.

RvCsBars

TRaveBaseBarCode Базовый класс штрихового кода. TRavePostNetBarCode Постсетевой штриховой код (PostNet), используется американской почтовой службой в доставке почты. TRaveI2of5BarCode Числовой штриховой код (I2of5). TRaveCode39BarCode Алфавитно-цифровой штриховой код (Code39). Символ может хранить кодируемые данные. Разработан, чтобы кодировать 26 прописных букв, 10 цифр и 7 специальных символов. TRaveCode128BarCode Алфавитно-цифровой штриховой код с высокой плотностью (Code128). Символ может хранить кодируемые данные. Разработан, чтобы кодировать первые 128 ASCII символов. TRaveUPCBarCode Универсальный штриховой код изделия (UPC). TRaveEANBarCode Европейский международный номер, штриховой код подобен штриховому коду UPC (EAN).

RvCsStd

TRaveText Элемент оформления – текст. TRaveMemo Элемент оформления – текстовое поле MEMO. TRaveSection Вспомогательный элемент оформления, реализующий группировку объектов. TRaveBitmap Элемент оформления для вывода растрового изображения. TRaveMetaFile Элемент оформления для вывода метафайла. TRaveFontMaster Вспомогательный элемент оформления, для установки свойства шрифта у текстовых компонентов. Компоненты позволяющие работать с компонентом TRaveFontMaster содержат свойство «FontMirror». TRavePageNumInit Вспомогательный элемент оформления позволяющий производить нумерацию страниц.

RvCsRpt

TRaveRegion Компонент-контейнер, размещающий в себе элементы оформления отчета. Также позволяет создать печать отчета в несколько столбцов. Используется для работы с источниками баз данных. TRaveBand Компонент-контейнер, также размещающий в себе элементы оформления отчета. В основном используется для создания верхних и нижних колонтитулов или других сносок. TRaveDataBand Компонент-контейнер, размещающий в себе элементы оформления отчета для вывода информации из баз данных. TRaveDataCycle Используется для вычислений, сортировки или фильтрации.

RvCsData

TRaveDataText Элемент оформления отчета, для вывода однострочных данных из источника баз данных. TRaveDataMemo Элемент оформления отчета, для вывода многострочных данных из источника баз данных. TRaveDataMirrorSection Компонент-контейнер, размещающий в себе элементы оформления отчета для доступа к источникам баз данных и объединяющий их в одну группу. TRaveCalcText Элемент оформления отчета, позволяющий производить вычисления по указанному полю источника данных. TRaveCalcOp Вспомогательный элемент, позволяющий производить вычисления по двум указанным полям источника данных. TRaveCalcTotal Вспомогательный элемент, позволяющий производить вычисления по указанному полю источника данных и имеет возможность передать вычисленное значение какому либо элементу оформления отчета. TRaveCalcController Вспомогательный элемент, позволяющий задать параметры вычислений. Также данный класс выполняет все заданные функции вычислений. Доступ к объектам проекта отчета Rave Report в режиме RunTime

Ну вот, когда проведена большая работа по исследованию классов в проекте отчета Rave Report, наконец, можно приступить непосредственно к работе с отчетом в режиме RunTime. Еще раз напомню порядок доступа к активному (текущему) отчету Rave:

Для доступа к проекту отчета необходимо обратиться к классу TRaveProjectManager. Для доступа к классу TRaveProjectManager следует обратиться к свойству «ProjMan» класса TRvProject. Чтобы получить доступ к активному отчету, следует обратиться к свойству «ActiveReport» класса TRaveProjectManager. То есть последовательность такого типа: TRvProject.ProjMan.ActiveReport.

Как вы уже знаете в отчете содержаться еще и страницы. К сожалению, разработчики не предоставили методов для навигации по страницам отчета (представлено только одно свойство «FirstPage» – первая страница), но это проблема вполне решаема. Ниже приведена процедура, которая возвращает список указателей на объекты (если таковые имеются), порожденных от указанного класса.

// Возвращает список указателей на объекты, порожденных от указанного класса FindClass
procedure GetObjectList(RootComponent: TComponent; FindClass: TClass; var
  PageList: TList);
var
  I: Integer;
begin
  PageList.Clear;
  if (RootComponent = nil) or (PageList = nil) then
    EXIT;
  for I := 0 to RootComponent.ComponentCount - 1 do
    if RootComponent.Components[I] is FindClass then
      PageList.Add(RootComponent.Components[I]);
end;

Пример вызова процедуры:

GetObjectList(RvProjectRTR.ProjMan.ActiveReport, TRavePage, RavePageList);
Данный вызов процедуры заполняет список RavePageList указателями на все найденные страницы в текущем отчете. Теперь используя список RavePageList можно удобно осуществлять навигацию по страницам отчета, точно так же, как по списку отчетов представленный менеджером отчетов TRaveProjectManager. Список отчетов можно получить, обратившись к свойству «ReportList» класса TRaveProjectManager. Используя процедуру GetObjectList можно получить список и других объектов произошедших от определенного класса, что облегчает навигацию по объектам определенного типа. Примечание: У класса TRaveReport в наличии есть свойство «PageList». Данное свойство определяет порядок страниц при печати. Допустим, в отчете имеется 10 страниц, а в «PageList» указанно печатать 3-ю и 8-ю страницу. Тогда свойство «PageList.Count», будет равным 2-ум, и доступ вы сможете получить только к 3-й и 8-й странице отчета. Если же в свойстве «PageList» нет ссылок на страницы отчета (PageList = NIL), то при попытке обратиться к данному свойству будет получено сообщение об ошибке. Поэтому не следует забывать специфику данного свойства. Примечание: TRaveReport также предоставляет свойство «Page» доступное только для чтения. Можно было бы предположить, что данное свойство хранит ссылку на активную страницу текущего отчета, но по каким то причинам в данном свойстве все время присутствует значение равное NIL. Так что назначение данного свойства для меня пока неизвестно. Слов сказано уже много, но на практике еще мало чего сделано. Думаю, настало время перейти к практической части. За основу, как оговаривалось в начале статьи, взят демонстрационный проект отчета «RaveDemo.rav», вот над ним и будут производиться все опыты на практике. Для экспериментов возьмем, к примеру, отчет «Mirror Report».

Попробуем изменить заголовок первой страницы у отчета «Mirror Report». Для оттого необходимо:

Найти и активизировать отчет «Mirror Report» средством вызова метода «SelectReport» класса TRvProject. Произвести поиск текстового элемента оформления TRaveText на первой странице отчета TRavePage. Для этого можно воспользоваться методом «FindRaveComponent» класса TRaveProjectManager, который в случае успешного поиска в качестве возвращаемого параметра вернет найденный объект, в противном случае возвратится NIL. Убедиться, что искомый объект произошел от нужного класса. Если искомый объект найден, то произвести с ним все необходимые манипуляции. После внесенных изменений сгенерировать отчет методом «Execute» или «ExecuteReport» класса TRvProject.

Ниже представлен пример реализации всего выше изложенного.

...
var
  I: Integer;
  TmpRaveComponent: TRaveComponent;
begin
  // Поиск и активизация необходимого отчета. Метод вернет false если отчет не найден
  if not RvProjectRTR.SelectReport('Mirror Report', true) then
    EXIT;
  // Поиск компонента с именем 'Text1' на первой странице отчета
  TmpRaveComponent := RvProjectRTR.ProjMan.FindRaveComponent('Text1',
    RvProjectRTR.ProjMan.ActiveReport.FirstPage);
  // Если объект найден, и он произошел от класса TRaveText
  if (TmpRaveComponent <> nil) and (TmpRaveComponent is TRaveText) then
  begin
    // Замена выводимого текста
    TRaveText(TmpRaveComponent).Text := 'Это мой новый заголовок';
    // Изменение стиля шрифта
    TRaveText(TmpRaveComponent).Font.Style := [fsItalic];
  end;
  // Генерация активного отчета
  RvProjectRTR.Execute;
end;
...
Как видите, ничего особо сложного нет. Найдя необходимый объект в отчете с ним можно делать почти все что угодно. Почему почти? Когда вы исследовали визуальные элементы оформления отчета средствами RTTI, то, может быть, обратили внимание на то, что все эти компоненты не имеют свойства «Visible», хотя данное свойство уже доступно начиная с класса TRaveComponent. К сожалению, разработчики по каким то причинам не стали учитывать значение свойства «Visible», что в принципе огорчает. Все-таки иногда может возникнуть необходимость скрыть, что-либо из отчета в зависимости от событий в программе.

Есть возможность имитировать свойство «Visible» используя свойство «Parent» нужного объекта. Чаще всего в качестве родителя объектов выступает страница отчета, но для группировки визуальных компонентов отчета в качестве родителя может выступать компонент-контейнер TRaveSection (также группировку объектов выполняют TRaveRegion и TRaveDataBand). Если визуальному компоненту не будет указан родитель в свойстве «Parent», т.е. значение равное NIL, то данный объект не будет отображен в сгенерированном отчете. Для имитации свойства «Visible» объектов вновь обратимся к отчету «Mirror Report». В данном отчете скроем все элементы оформления TRaveRectangle путем скрытия объектов TRaveSection. Последовательность действий будет следующей:

Найти и активизировать отчет «Mirror Report» средством вызова метода «SelectReport» класса TRvProject. Пробежаться по всем дочерним компонентам страницы и найти компоненты производные от класса TRaveSection. У компонентов TRaveSection в свойстве «Parent» проверить наличие родителя. Если родительский компонент назначен, то данному свойству присвоить значение NIL (скрытие объекта), в ином случае данному свойству в качестве родителя присвоить страницу отчета (отображение объекта на странице). После внесенных изменений сгенерировать отчет.

Возможно, некоторые задались вопросом, – почему бы сразу не скрыть TRaveRectangle напрямую, а через объект TRaveSection? Все возможно, но в данном случае:

Элементы оформления TRaveRectangle объединены в группу компонентом TRaveSection. Следовательно, у объектов TRaveRectangle общий родитель TRaveSection. При назначении свойству «Parent» значения NIL у объекта TRaveSection, данный объект теряет родителя и скрывается со всеми его дочерними компонентами TRaveRectangle. Если каждому объекту TRaveRectangle в свойстве «Parent» указать значение равное NIL, а затем в данном свойстве в качестве нового родителя указать страницу отчета, то данные компоненты окажутся не на своем месте. Это вызвано тем, что значения в свойствах «Left» и «Top» компонента TRaveRectangle получены относительно клиентской части компонента TRaveSection. Отсюда вывод, что для корректного восстановления объектов для каждого TRaveRectangle нужно указать своего прежнего родителя, а это несколько усложняет подход к данной задаче. Но при необходимости все это возможно реализовать.

Ниже представлен фрагмент кода, имитирующий скрытие объектов.

...
var
  I: Integer;
  TmpRaveComponent: TRaveComponent;
begin
  // Поиск и активизация необходимого отчета. Метод вернет false если отчет не найден
  if not RvProjectRTR.SelectReport('Mirror Report', true) then
    EXIT;
  // Проход по всем объектам первой страницы отчета
  for I := 0 to RvProjectRTR.ProjMan.ActiveReport.FirstPage.ComponentCount - 1
    do
    // Если объект является TRaveSection
    if RvProjectRTR.ProjMan.ActiveReport.FirstPage.Components[I] is TRaveSection
      then
    begin
      TmpRaveComponent :=
        TRaveComponent(RvProjectRTR.ProjMan.ActiveReport.FirstPage.Components[I]);
      // Если данный объект не имеет родителя, то в качестве
      // родителя указывается первая страница отчета
      if TmpRaveComponent.Parent = nil then
        TmpRaveComponent.Parent := RvProjectRTR.ProjMan.ActiveReport.FirstPage
          // Если объект имеет родителя, то уничтожается ссылка на родителя,
        // что в следствии приводит к скрытию объекта
      else
        TmpRaveComponent.Parent := nil;
    end;
  // Генерация активного отчета
  RvProjectRTR.Execute;
  ...
Примечание: Вместо свойства «Visible» разработчики предусмотрели свойство «DisplayOn» у визуальных элементов оформления. Данное свойство позволяет установить, в каком случае отображать данный элемент оформления. Отображать: только при предварительном просмотре, только при выводе на печать, отображать в обоих случаях или наследовать настройки родителя. Изменение значения данного свойства упорно игнорируется визуальными компонентами. Очень жаль. Рассмотрим еще одну проблему при оформлении отчета. Следует обратить внимание на отсутствие привычных свойств у некоторых визуальных компонентов. Возьмем, например компонент для вывода текста (TRaveText – аналог компонента TLabel). Данный компонент не предоставляет свойства для выбора фонового цвета (цвет кисти Brush) и по умолчанию является прозрачным. На заметку: Фоновый цвет TRaveText останется прозрачным в том случае, если сгенерировать отчет, вызвав метод «Execute» или «ExecuteReport» класса TRvProject. Если отчет сгенерировать из среды разработки Rave Report, то фоновая заливка белого цвета под текстом останется непрозрачной. Такая вот недоработка присутствует в Rave Report. Также в данном компоненте отсутствует свойство, определяющее его высоту. Не найдется там и привычное свойство «AutoSize» для выравнивания клиентского размера объекта под размер выводимого текста. Отчасти некоторые недостатки можно имитировать. Для придания фонового цвета можно подложить под компонент, к примеру, элемент оформления прямоугольник (TRaveRectangle). Создать имитацию фонового цвета для текста в редакторе событий «Event Editor» весьма проблематично. Данная проблема выражается в следующем: т.к. в TRaveText не предоставлено свойство «Height» определяющее его высоту, то соответственно нет возможности получить точный размер по высоте компонента. Следовательно, при изменении размера шрифта компонента придется вручную подгонять высоту компонента TRaveRectangle. Также попытка получить доступ к свойству «BoundsRect, Height» (или к другому свойству или методу, не предоставленному в инспекторе объектов среды разработки отчета Rave Report) компонента может привести к плачевным результатам (даже если компиляция кода в «Event Editor» прошла успешно). В лучшем случае можно отделаться сообщением об ошибке, в худшем – критическое завершение работы генератора отчета Rave Report c потерей всех несохраненных данных, а при работе под Windows 98 возможен полный «крах» системы.

Как видите, редактор событий «Event Editor» не предоставляет таких гибких возможностей для работы, как доступ к объектам в режиме RunTime. Для имитации фонового цвета как упоминалось выше вполне можно применить TRaveRectangle. Для этого необходимо:

Создать объект производный от TRaveRectangle и придать ему нужное оформление. Подогнать размеры TRaveRectangle под размеры TRaveText. Поместить объект TRaveRectangle на задний план, воспользовавшись методом объекта «SendToBack».

Реализация имитации фонового цвета под текстом представлена ниже. В данном случае не удалось только создать имитацию «AutoSize» для объекта TRaveText:

...
var
  I: Integer;
  TmpRaveComponent: TRaveComponent;
  TmpRavePage: TRavePage;
  BGRect: TRaveRectangle;
begin
  // Поиск и активизация необходимого отчета. Метод вернет false если отчет не найден
  if not RvProjectRTR.SelectReport('Mirror Report', true) then
    EXIT;
  TmpRavePage := RvProjectRTR.ProjMan.ActiveReport.FirstPage;
  // Поиск компонента с именем 'Text1' на первой странице отчета
  TmpRaveComponent := RvProjectRTR.ProjMan.FindRaveComponent('Text1',
    TmpRavePage);
  // Если объект найден, и он произошел от класса TRaveText
  if (TmpRaveComponent <> nil) and (TmpRaveComponent is TRaveText) then
  begin
    // Создание графического примитива - прямоугольник
    BGRect := TRaveRectangle.Create(TmpRavePage);
    // Заполнение свойств вновь созданного объекта
    with BGRect do
    begin
      Name := 'BackGroundRect';
      FillColor := clYellow;
      BorderStyle := psClear;
      // Указание родителя компонента
      Parent := TmpRavePage;
      // Подгонка размеров прямоугольника под размеры компонента TRaveText
      BoundsRect := TRaveText(TmpRaveComponent).BoundsRect;
      // Перемещение объекта на задний план
      SendToBack;
    end;
  end;
  // Генерация активного отчета
  RvProjectRTR.Execute;
end;
...
В режиме RunTime вполне самостоятельно возможно создать: отчет, страницу отчета, визуальные элементы оформления или другие объекты, поддерживаемые отчетом Rave Report. Для создания нового отчета необходимо обратиться к методу «NewReport» менеджера отчетов TRaveProjectManager. В качестве возвращаемого параметра, возвращается объект порожденный от класса TRaveReport вновь созданного отчета. Для создания новой страницы для отчета следует обратиться к методу «NewPage» класса TRaveReport. Данный метод, как и в предыдущей ситуации, в качестве возвращаемого параметра возвращает объект типа TRavePage вновь созданной страницы отчета. Примечание: При создании нового отчета автоматически создается и новая страница. При создании многостраничного отчета не следует забывать про свойство «GotoPage» компонента TRavePage или свойство «PageList» компонента TRaveReport, ведь данные свойства содержат указатели на следующую генерируемую страницу отчета. Если не воспользоваться ни одним из представленных свойств, то при генерации отчета будет сгенерированна только первая страница отчета. При создании новой страницы или отчета в RunTime нет необходимости указывать вновь созданным объектам хозяина или родителя компонента, все это, данные методы «NewReport» и «NewPage» осуществляют самостоятельно. При создании визуальных и не визуальных компонентов следует указать хозяина объекта. Если в качестве хозяина будет передано значение равное NIL, то после завершения работы с данными объектами, программист должен уничтожить их самостоятельно, воспользовавшись методом «Free» данного объекта. Для всех визуальных элементов оформления отчета следует обязательно указывать нужного родителя в свойстве «Parent». Вспомните имитацию скрытия объектов в отчете. Также учитывайте установленную единицу измерения в проекте отчета, для верного размещения визуальных объектов в отчете, иначе можете удивиться полученному результату. В ниже приведенном примере создается отчет с двумя страницами. На первой странице создается текстовый элемент оформления TRaveText, а на второй странице компонент

TRaveBitMap для вывода графического изображения.

const
  NewRep = 'MyNewReport'; // Имя отчета (динамически создаваемого)
  FieldStr = 'FieldStr'; // Имя строкового поля
  FieldInt = 'FieldInt'; // Имя целочисленного поля
  NewDataView = 'NewDataView';
  // Имя компонента для посредника с источником данных
  ...
  // Вычисление позиции точки в процентах относительно
  // ширины/высоты визуального компонента
function SetPointInPercent(RaveControlWH: TRaveUnits; Percent: Byte):
  TRaveUnits;
begin
  Result := (RaveControlWH / 100) * Percent;
end;
...
const
  Picture = '1.bmp';
var
  TmpRaveReport: TRaveReport;
  TmpRavePage: TRavePage;
  TmpRaveText: TRaveText;
  TmpRaveBitMap: TRaveBitmap;
  TmpPageList: TRaveComponentList;
begin
  // Если отчет уже создан, то выходим
  if RvProjectRTR.SelectReport(NewRep, true) then
    EXIT;
  // Добавление нового отчета в проект и заполнение его свойств
  TmpRaveReport := RvProjectRTR.ProjMan.NewReport;
  with TmpRaveReport do
  begin
    Name := NewRep;
    FullName := NewRep;
    // Создание новой страницы для отчета (здесь будет графическое изображение)
    TmpRavePage := NewPage;
    // Указывается следующая страница для генерации отчета,
    // после того, как будет сгенерированна первая страница отчета
    TmpRaveReport.FirstPage.GotoPage := TmpRavePage;
  end;
  // Создание компонента для вывода графического изображения и заполнение его свойств
  TmpRaveBitMap := TRaveBitMap.Create(TmpRavePage);
  with TmpRaveBitMap do
  begin
    Name := 'MyNewRaveBitMap';
    Left := SetPointInPercent(TmpRavePage.PageWidth, 5);
    Top := SetPointInPercent(TmpRavePage.PageHeight, 20);
    Width := SetPointInPercent(TmpRavePage.PageWidth, 30);
    Height := SetPointInPercent(TmpRavePage.PageHeight, 30);
    // Загрузка графического изображения (если найден указанный файл)
    if FileExists(Picture) then
      Image.LoadFromFile(Picture);
    // Размеры изображения будут подогнаны под клиентские размеры компонента
    MatchSide := msBoth;
    // Родителем данного компонента будет страница TmpRavePage
    Parent := TmpRavePage;
  end;
  // Создание элемента TRaveText и заполнение его свойств
  TmpRaveText := TRaveText.Create(TmpRaveReport.FirstPage);
  with TmpRaveText do
  begin
    Left := SetPointInPercent(TmpRaveReport.FirstPage.PageWidth, 10);
    ;
    Top := SetPointInPercent(TmpRaveReport.FirstPage.PageHeight, 15);
    ;
    Font.Size := 18;
    Name := 'MyNewRaveText';
    // Родителем данного объекта будет первая страница отчета
    Parent := TmpRaveReport.FirstPage;
    Text := 'Этот текст расположен на первой странице отчета';
  end;
  // Заполение свойств страницы TmpRavePage
  with TmpRavePage do
  begin
    Name := 'MyNewRavePage';
    FullName := 'Моя новая страница';
  end;
  // Создание списка генерируемых страниц
  TmpPageList := TRaveComponentList.Create;
  TmpPageList.Add(TmpRavePage);
  TmpPageList.Add(TmpRaveReport.FirstPage);
  TmpRaveReport.PageList := TmpPageList;
  // Обновление списка доступных отчетов
  RvProjectRTR.GetReportList(ListReport.Items, True);
end;
...

В данном примере для перехода к следующей генерируемой странице применены оба свойства «PageList» и «GotoPage». Чтобы лучше понять их принцип работы создайте новый отчет по выше представленному примеру и сгенерируйте его.

...
if RvProjectRTR.SelectReport('Мой новый отчет', true) then
  RvProjectRTR.Execute;
...

Как видите, отчет сгенерировал три страницы. Это происходит потому, что сначала осуществляется проход по списку в свойстве «PageList» (если он не пуст). Вновь генерируемая страница из этого списка также проверяет свое свойство «GotoPage» на наличие перехода на другую страницу. После генерации страницы указанной в свойстве «GotoPage» (если данное свойство указывает на страницу) продолжается обход по списку «PageList». Следует соблюдать осторожность при указании порядка генерации страниц отчета, при задании неверного порядка может произойти бесконечный цикл генерации страниц (если данный случай имеет место быть). Схематично это можно отобразить так (в скобках указан порядок генерации страниц):

Статьи по Delphi (014)…

Если очистить список страниц у отчета в свойстве «PageList» (все в том же выше приведенном примере) и вновь сгенерировать отчет, то результат соответственно будет совсем другим.

...
if RvProjectRTR.SelectReport('Мой новый отчет', true) then
begin
  RvProjectRTR.ProjMan.ActiveReport.PageList := nil;
  RvProjectRTR.Execute;
end;
...

Можно в отчете в RunTime подключить и источник данных. Как оговаривалось выше эти функции выполняют основные классы: TRaveDataView, TRaveDataField и собственно сам источник данных, например TRvDataSetConnection.

Условимся, что некий источник данных в проекте уже существует и содержит некоторые данные. В данном случае рассмотрим пример подключения к текстовому типу поля таблицы. Для начала нам потребуется «связной» с источником данных – TRaveDataView. Для того, чтобы созданный «связной» присутствовал в проекте отчета Rave Report его нужно добавить в список подключенный модулей данных, воспользовавшись методом «Add» свойства «DataObjectList» у класса TRaveProjectManager. У созданного объекта TRaveDataView достаточно заполнить четыре основных свойства: Name – имя компонента, Parent – родительский компонент (TRaveProjectManager), DataCon.Connection – подключаемый источник данных (в данном примере TRvDataSetConnection), ConnectionName – имя подключаемого источника данных. Далее необходимо найти необходимое поле в источнике данных TDataSet воспользовавшись методом «FindField» и выяснить к какому типу данных это поле принадлежит. После определения типа поля в источнике данных необходимо создать соответствующее (совместимое) поле данных TRaveDataField для отчета Rave Report. Для TRaveDataField также достаточно заполнить четыре основных свойства: Name – имя компонента, FieldName – имя поля, Parent – родительский компонент (TRaveDataView), DataIndex – порядковый номер в списке полей источника данных (таблице). При необходимости в отчете можно создать TRaveRegion, TRaveDataBand, TRaveDataText (если не созданны). Будем считать, что данные объекты уже созданы в проекте отчета Rave Report. Для TRaveDataBand, TRaveDataText следует указать поставщика источников данных (TRaveDataView) воспользовавшись свойством «DataView» этих классов. Для TRaveDataText следует дополнительно указать имя поля, из которого будет производиться выборка данных, для чего следует обратиться к свойству «DataField» этого класса.

Пример реализации подключения источника данных приведен ниже. Данный пример присоединяет источник данных к вновь созданному отчету ('MyNewReport' – рассмотренному выше).

...
var
  TmpRaveReport: TRaveReport;
  TmpDataView: TRaveDataView;
  TmpDataField: TRaveDataField;
  TmpRaveRegion: TRaveRegion;
  TmpRaveDataBand: TRaveDataBand;
  TmpRaveDataText: TRaveDataText;
  TmpField: TField;
begin
  TmpDataField := nil;
  if not RvProjectRTR.SelectReport(NewRep, true) then
    EXIT;
  if (ADOTableRTR.FieldCount < 1) or (not ADOTableRTR.Active) then
    EXIT;
  // Поиск строкового поля
  TmpField := RvDataSetConnectionRTR.DataSet.FindField(FieldStr);
  if TmpField = nil then
    EXIT;
  TmpRaveReport := RvProjectRTR.ProjMan.ActiveReport;
  // Создание TRaveDataView обеспечивающий работу с источником данных
  TmpDataView := TRaveDataView.Create(RvProjectRTR.ProjMan);
  RvProjectRTR.ProjMan.DataObjectList.Add(TmpDataView);
  with TmpDataView do
  begin
    Name := NewDataView;
    Parent := RvProjectRTR.ProjMan;
    DataCon.Connection := RvDataSetConnectionRTR;
    ConnectionName := TmpDataView.DataCon.Connection.Name;
  end;
  // В зависимости от типа данных поля создается поле данных для отчета
  // Rave Report соотвествующего типа
  case TmpField.DataType of
    ftString,
      ftWideString: TmpDataField := TRaveStringField.Create(TmpDataView);
    ftInteger: TmpDataField := TRaveIntegerField.Create(TmpDataView);
  end;
  if TmpDataField = nil then
    EXIT;
  // Заполение свойств поля данных
  with TmpDataField do
  begin
    Name := TmpField.Name; // Имя компонента
    FieldName := TmpField.FieldName; // Имя поля
    FullName := TmpField.DisplayName; // Альтернативное имя поля
    // Порядковый номер в списке полей источника данных (таблице)
    DataIndex := TmpField.Index;
    Parent := TmpDataView; // Родительский компонент
  end;
  // Создание TRaveRegion на котром будут расположены компоненты для работы с источником данных
  TmpRaveRegion := TRaveRegion.Create(TmpRaveReport.FirstPage);
  with TmpRaveRegion do
  begin
    Name := 'NewRaveRegion';
    Parent := TmpRaveReport.FirstPage;
    Left := SetPointInPercent(TmpRaveReport.FirstPage.PageWidth, 5);
    Top := SetPointInPercent(TmpRaveReport.FirstPage.PageHeight, 15);
    Width := SetPointInPercent(TmpRaveReport.FirstPage.PageWidth, 70);
    Height := SetPointInPercent(TmpRaveReport.FirstPage.PageHeight, 60);
  end;
  // Создание TRaveDataBand для размещения елементов оформления отчета на нем
  TmpRaveDataBand := TRaveDataBand.Create(TmpRaveRegion);
  with TmpRaveDataBand do
  begin
    Name := 'NewDataBand';
    Parent := TmpRaveRegion;
    Left := SetPointInPercent(TmpRaveRegion.Width, 5);
    Top := SetPointInPercent(TmpRaveRegion.Height, 5);
    Width := SetPointInPercent(TmpRaveRegion.Width, 90);
    Height := SetPointInPercent(TmpRaveRegion.Height, 5);
    DataView := TmpDataView;
  end;
  // TRaveDataText обеспечит отображение информации из источника данных
  TmpRaveDataText := TRaveDataText.Create(TmpRaveDataBand);
  with TmpRaveDataText do
  begin
    Name := 'NewDataText';
    Parent := TmpRaveDataBand;
    Left := SetPointInPercent(TmpRaveDataBand.Width, 1);
    Top := SetPointInPercent(TmpRaveDataBand.Height, 1);
    Width := SetPointInPercent(TmpRaveDataBand.Width, 50);
    DataView := TmpDataView;
    DataField := TmpDataField.FieldName;
  end;
  RvProjectRTR.ExecuteReport(NewRep);
  ...

Создав новый отчет или изменив существующий можно сохранить внесенные изменения. Чтобы ваши труды не пропали даром, измененный проект отчета можно сохранить, воспользовавшись методом «Save». Если возникнет необходимость сохранить проект отчета Rave Report под другим именем, то следует изменить имя файла проекта в свойстве «ProjectFile» или воспользоваться методом «SetProjectFile» перед сохранением проекта. Все данные свойства и методы доступны у менеджера отчета TRvProject или TRaveProjectManager. Пример сохранения проекта отчета под другим именем:

...
// Сохраниение новго отчета под именем 'TestSave.rav'
RvProjectRTR.SetProjectFile('TestSave.rav');
RvProjectRTR.Save;
...
Скачать пример можно здесь P.S. Ну, вот вроде и все о чем хотелось поведать. Надеюсь после выхода данной статьи, у пользователей генератора отчета Rave Report работа хоть сколько-то облегчится. Если вы заметили какие-либо ошибки, недочеты в данной статье, а также если у вас есть какие либо предложения или дополнения к данной статье, то все ваши отзывы будут приятны здесь: Sun-bittern@mail.ru Турушев Виталий (Sun bittern ©) Нижний Тагил 2004г.

Литература: 1. Стив Тейксейра и Ксавье Пачеко «DELPHI 5 Руководство разработчика. Том 2. Разработка компонентов и работа с базами данных».

Показать TRadioButtons в TListView.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   StdCtrls, ComCtrls;
 type
   TMain = class(TForm)
     ListView1: TListView;
     Button1: TButton;
     Button2: TButton;
     procedure FormCreate(Sender : TObject);
     procedure FormDestroy(Sender : TObject);
     procedure Button1Click(Sender : TObject);
     procedure Button2Click(Sender : TObject);
   private
     ListViewRadioList: TList;
     procedure ListViewRadioCreate(List : TListView; R : TRect);
     procedure ListViewRadioClear(List : TListView; Liste : TList);
     procedure ListViewRadioFree(List : TListView; Liste : TList);
     procedure ListViewListSelected(Item : integer; List : TListView);
     procedure ListViewRadioClick(Sender : TObject);
   public
     { Public-Deklarationen }
   end;
 var
   Main : TMain;
 implementation
 {$R *.DFM}
 procedure TMain.FormCreate(Sender : TObject);
 begin
   ListViewRadioList := TList.Create;
 end;
 procedure TMain.FormDestroy(Sender : TObject);
 begin
   ListViewRadioFree(ListView1, ListViewRadioList);
 end;
 procedure TMain.Button1Click(Sender : TObject);
 var
   R : TRect;
   NewColumn : TListColumn;
   ListItem : TListItem;
   I : integer;
 begin
   with ListView1 do
   begin
     ViewStyle := vsReport;
     Align := alLeft;
     RowSelect := True;
     ReadOnly := True;
     ShowColumnHeaders := False;
     NewColumn := Columns.Add;
     NewColumn.Width := 15;
     NewColumn := Columns.Add;
     NewColumn.Width := Width - 15 - 19; // - breite Button - breite vom Scrollbalken 
    Items.BeginUpdate;
     for I := 0 to 9 do
     begin
       ListItem := Items.Add;
       R := Items[I].DisplayRect(drBounds);
       R.Left := 1;
       R.Right := Columns[0].Width;
       ListViewRadioCreate(ListView1, R);
       Items[I].Data := TRadioButton(ListViewRadioList[I]);
       ListItem.SubItems.Add('Test' + IntToStr(I));
     end;
     Items.EndUpdate;
   end;
 end;
 procedure TMain.Button2Click(Sender : TObject);
 begin
   ListViewRadioClear(ListView1, ListViewRadioList);
 end;
 //------------------------------------------------------------------------------ 
procedure TMain.ListViewRadioCreate(List : TListView; R : TRect);
   function NewViewRadio: TRadioButton;
   begin
     Result := TRadioButton.Create(Self);
     Result.Parent := List;
     Result.BoundsRect := R;
     Result.OnClick := ListViewRadioClick;
   end;
 begin
   ListViewRadioList.Add(NewViewRadio);
 end;
 //------------------------------------------------------------------------------ 
procedure TMain.ListViewRadioFree(List : TListView; Liste : TList);
 var
   I : integer;
 begin
   if Liste.Count - 1 < 0 then exit;
   for I := 0 to Liste.Count - 1 do TRadioButton(Liste[I]).Free;
   Liste.Free;
 end;
 //------------------------------------------------------------------------------ 
procedure TMain.ListViewRadioClear(List : TListView; Liste : TList);
 var
   I : integer;
 begin
   if Liste.Count - 1 < 0 then exit;
   List.Items.BeginUpdate;
   for I := 0 to Liste.Count - 1 do TRadioButton(Liste[I]).Free;
   List.Items.Clear;
   Liste.Clear;
   List.Items.EndUpdate;
 end;
 //------------------------------------------------------------------------------ 
procedure TMain.ListViewListSelected(Item : integer; List : TListView);
 begin
   with List do
   begin
     try
       SetFocus;
     except
     end;
     Selected := Items[Item];
     ItemFocused := Selected;
   end;
 end;
 //------------------------------------------------------------------------------ 
procedure TMain.ListViewRadioClick(Sender : TObject);
 var
   I : integer;
 begin
   for I := 0 to ListViewRadioList.Count - 1 do
     if TRadioButton(ListViewRadioList[I]).Checked then
     begin
       ListViewListSelected(I, ListView1);
       break;
     end;
 end;
 end.

Модуль для RC5 шифрования.

Автор: Матвеев Игорь "Я бы изменил мир - но Бог не даёт исходников..." - ALI.

В предыдущей статье "Модуль для IDEA шифрования" речь шла об алгоритме шифрования IDEA - International Data Encryption Algorithm. В данной статье я продолжу тему шифрования, на этот раз мы разберем алгоритм RC5.

Прежде всего немного о самом алгоритме: алгоритм был разработан Рональдом Ривестом (Ronald L. Rivest) для компании RSA Data Security. На основе RC5 в свое время был создан алгоритм шифрования RC6, который участвовал в конкурсе на звание AES (Advanced Encryption Standard - "Продвинутый" алгоритм шифрования). объявленном национальным институтом стандартизации и технологий для замены уже устаревшего алгоритма DES. Тогда RC6 не выиграл только из-за низкой производительности аппаратных реализаций алгоритма. А программные реализации RC5 и RC6 являются, пожалуй, самыми быстрыми среди всех алгоритмов шифрования, обеспечивающих достаточную стойкость перед атаками.

Например, по некоторым данным скорость грамотно построенной аппаратной реализации алгоритма RC5 на компьютере с скоростью процессора 200 МГц может достигать 10..11 Мбайт/сек, а алгоритма RC6 - 11..12 Мбайт/сек. Для сравнения - скорость работы алгоритма Rijndael (победитель конкурса на звание AES) при вышеозначенных характеристиках процессора может достигать максимум 7 Мбайт/сек.

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

Итак, RC5 работает с блоками по восемь байт - два 32 битных слова. В отличии от IDEA и Rijndael в RC5 после развертывания ключа вычисляется один подключ, который используется и при шифровании, и при дешифровании. RC5, как и большинство алгоритмов - раундовый алгоритм, другими словами, при шифрации блока над ним производятся одни и те же операции несколько раз, в RC5 таких раундов 12.

Вот пожалуй и все, что вам нужно знать о теории алгоритма RC5, все остальное видно из модуля. Модуль RC5 похож на модуль IDEA из моей предыдущей статьи. Если вы уже используете модуль IDEA в своих программах - для использования RC5 вам достаточно просто уброть из uses модуль IDEA и включить туда модуль RC5 (все имена методов сохраняются).

{ *********************************************************************** }
{                                                                         }
{ Delphi Еnсгурtiоn Library                                               }
{ Еnсгурtiоn / Decryption stream - RC5                                    }
{                                                                         }
{ Copyright (c) 2004 by Matveev Igor Vladimirovich                        }
{ With offers and wishes write: teap_leap@mail.ru                         }
{                                                                         }
{ *********************************************************************** }
unit RC5;
interface
uses
  SysUtils, Classes;
type
  TRC5Block = array[1..2] of LongWord;
const
  Rounds     = 12;
  BlockSize  = 8;
  BufferSize = 2048;
  KeySize    = 64;
  KeyLength  = 2 * (Rounds + 1);
  P32 = $b7e15163;
  Q32 = $9e3779b9;
var
  Key        : string;
  KeyPtr     : PChar;
  S : array[0..KeyLength-1] of LongWord;
////////////////////////////////////////////////////////////////////////////////
// Дополнительные функции
procedure Initialize(AKey: string);          // Инициализация
procedure CalculateSubKeys;                  // Подготовка подключей
function EncipherBlock(var Block): Boolean;  // Шифрация блока (8 байт)
function DecipherBlock(var Block): Boolean;  // Дешифрация блока
////////////////////////////////////////////////////////////////////////////////
// Главные функции
function EncryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;    // Зашифровать данные из одного потока в другой
function DecryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;    // Расшифровать данные из одного потока в другой
function EncryptStream(DataStream: TStream; Count: Int64;
  Key: string): Boolean;     // Зашифровать содержимое потока
function DecryptStream(DataStream: TStream; Count: Int64;
  Key: string): Boolean;     // Расшифровать содержимое потока
implementation
////////////////////////////////////////////////////////////////////////////////
function ROL(a, s: LongWord): LongWord;
asm
  mov    ecx, s
  rol    eax, cl
end;
////////////////////////////////////////////////////////////////////////////////
function ROR(a, s: LongWord): LongWord;
asm
  mov    ecx, s
  ror    eax, cl
end;
////////////////////////////////////////////////////////////////////////////////
procedure InvolveKey;
var
  TempKey : string;
  i, j    : Integer;
  K1, K2  : LongWord;
begin
 // Разворачивание ключа до длинны 64 символа
 TempKey := Key;
 i := 1;
 while ((Length(TempKey) mod KeySize) <> 0) do
   begin
     TempKey := TempKey + TempKey[i];
     Inc(i);
   end;
 // Now shorten the key down to one KeySize block by combining the bytes
 i := 1;
 j := 0;
 while (i do
   begin
     Move((KeyPtr+j)^, K1, 4);
     Move(TempKey[i], K2, 4);
     K1 := ROL(K1, K2) xor K2;
     Move(K1, (KeyPtr+j)^, 4);
     j := (j + 4) mod KeySize;
     Inc(i, 4);
   end;
end;
////////////////////////////////////////////////////////////////////////////////
procedure Initialize(AKey: string);
begin
 Key := AKey;
 GetMem(KeyPtr, KeySize);
 FillChar(KeyPtr^, KeySize, #0);
 InvolveKey;
end;
////////////////////////////////////////////////////////////////////////////////
{$R-,Q-}
procedure CalculateSubKeys;
var
  i, j, k : Integer;
  L       : array[0..15] of LongWord;
  A, B    : LongWord;
begin
 // Copy the key into L
 Move(KeyPtr^, L, KeySize);
 // Now initialize the S table
 S[0] := P32;
 for i := 1 to KeyLength-1 do
   S[i] := S[i-1] + Q32;
 // Now scramble the S table with the key
 i := 0;
 j := 0;
 A := 0;
 B := 0;
 for k := 1 to 3*KeyLength do
   begin
     A := ROL((S[i] + A + B), 3);
     S[i] := A;
     B := ROL((L[j] + A + B), (A + B));
     L[j] := B;
     i := (i + 1) mod KeyLength;
     j := (j + 1) mod 16;
   end;
end;
////////////////////////////////////////////////////////////////////////////////

function EncipherBlock(var Block): Boolean;
var
  RC5Block : TRC5Block absolute Block;
  i        : Integer;
begin
 Inc(RC5Block[1], S[0]);
 Inc(RC5Block[2], S[1]);
 for i := 1 to Rounds do
   begin
     RC5Block[1] := ROL((RC5Block[1] xor RC5Block[2]), RC5Block[2]) + S[2*i];
     RC5Block[2] := ROL((RC5Block[2] xor RC5Block[1]), RC5Block[1]) + S[2*i+1];
   end;
 Result := TRUE;
end;
////////////////////////////////////////////////////////////////////////////////
function DecipherBlock(var Block): Boolean;
var
  RC5Block : TRC5Block absolute Block;
  i        : Integer;
begin
 for i := Rounds downto 1 do
   begin
     RC5Block[2] := ROR((RC5Block[2]-S[2*i+1]), RC5Block[1]) xor RC5Block[1];
     RC5Block[1] := ROR((RC5Block[1]-S[2*i]),   RC5Block[2]) xor RC5Block[2];
   end;
 Dec(RC5Block[2], S[1]);
 Dec(RC5Block[1], S[0]);
 Result := TRUE;
end;
////////////////////////////////////////////////////////////////////////////////
// Реализация главных функций
function EncryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;
var
  Buffer   : TRC5Block;
  PrCount  : Int64;
  AddCount : Byte;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DestStream.CopyFrom(SourseStream, Count);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       SourseStream.Read(Buffer, BlockSize);
       EncipherBlock(Buffer);
       DestStream.Write(Buffer, BlockSize);
       Inc(PrCount, 8);
     end;
   AddCount := Count - PrCount;
   if Count - PrCount <> 0 then
     begin
       SourseStream.Read(Buffer, AddCount);
       DestStream.Write(Buffer, AddCount);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////

function DecryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;
var
  Buffer   : TRC5Block;
  PrCount  : Int64;
  AddCount : Byte;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DestStream.CopyFrom(SourseStream, Count);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       SourseStream.Read(Buffer, BlockSize);
       DecipherBlock(Buffer);
       DestStream.Write(Buffer, BlockSize);
       Inc(PrCount, 8);
     end;
   AddCount := Count - PrCount;
   if Count - PrCount <> 0 then
     begin
       SourseStream.Read(Buffer, AddCount);
       DestStream.Write(Buffer, AddCount);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function EncryptStream(DataStream: TStream; Count: Int64; Key: string): Boolean;
var
  Buffer   : TRC5Block;
  PrCount  : Int64;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DataStream.Seek(Count, soFromCurrent);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       DataStream.Read(Buffer, BlockSize);
       EncipherBlock(Buffer);
       DataStream.Seek(-BlockSize, soFromCurrent);
       DataStream.Write(Buffer, BlockSize);
       Inc(PrCount, 8);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function DecryptStream(DataStream: TStream; Count: Int64; Key: string): Boolean;
var
  Buffer   : TRC5Block;
  PrCount  : Int64;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DataStream.Seek(Count, soFromCurrent);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       DataStream.Read(Buffer, BlockSize);
       DecipherBlock(Buffer);
       DataStream.Seek(-BlockSize, soFromCurrent);
       DataStream.Write(Buffer, BlockSize);
       Inc(PrCount, 8);
     end;
 except
   Result := False;
 end;
end;
// Завершение главных функций ...
////////////////////////////////////////////////////////////////////////////////
{$R+,Q+} 
end.

Вот и все что касается RC5 шифрования, в следующей статье мы рассмотрим алгоритм RC6 - заранее могу сказать, что он очень похож на RC5.

Перепечатка данной статьи разрешается автором только без изменения, модуль, представленный в данной статье абсолютно свободен для использования - пользуйтесь!

Матвеев Игорь Владимирович.

Модуль для RC6 шифрования.

Автор: Матвеев Игорь Переквалифицировался хакер в священника и однажды приходит на проповедь после большого перепоя. Заплетающимся языком: - И да поможет тебе F1, и сохранит тебя F2! Во имя Controla, Alta и святого Dela, да будет так - Reset!

Данная статья является логическим завершением темы шифрования данных, которой я посвятил три статьи (Модуль для IDEA шифрования, Модуль для RC5 шифрования и данная). Данная статья посвящена алгоритму шифрования RC6.

Как я уже говорил, RC6 Очень похож на RC5, во всяком случае расчет подключа производится тем же способом, что и в RC5. RC6 участвовал в конкурсе на звание AES и, по некоторым данным, не выиграл только из-за медленной работы аппаратных реализаций. Программные же реализации RC6, пожалуй, являются самыми быстрыми среди алгоритмов шифрования, при обеспечении достаточной стойкости шифра.

Нижепредставленный модуль построен по тому же принципу, что и предыдущие - имена функций совпадают и если вы хотите заменить в своей программе шифрование с IDEA или RC5 на RC6 - просто добавьте модуль RC6, а IDEA или RC5 - удалите из списка uses.

Шифр RC6, в отличии от RC5, оперирует блоками по 16 байт, а модуль построен так, что если при шифрации методами: EncryptCopy, DecryptCopy, EncryptStream, DecryptStream размер данных не будет кратен 16 - последний блок длинной 1..15 байт не шифруется и в "чистом" виде добавляется к зашифрованным. Также и при дешифровании - если последний блок размером 1..15 байт он не дешифруется а добавляется к расшифрованным данным. Такой подход обеспечивает в полной мере "симметричное" шифрование так как размеры входных и выходных данных полностью совпадают. Однако такой подход приводит к некоторым сложностям, если последний блок также нуждается в шифровании. На мой взгляд лучший выход - добавить при шифровании к исходным данным строку определенной длинны и после дешифрования отсечь с конца строку той же длинны.

Также в заключении цыкла статей, посвященных шифрованию я подготовил примеры по всем представленным алгоритмам (IDEA, RC5, RC6).

Загрузить демо примеры.

А вот последний модуль:

{ *********************************************************************** }
{                                                                         }
{ Delphi Еnсгурtiоn Library                                               }
{ Еnсгурtiоn / Decryption stream - RC6                                    }
{                                                                         }
{ Copyright (c) 2004 by Matveev Igor Vladimirovich                        }
{ With offers and wishes write: teap_leap@mail.ru                         }
{                                                                         }
{ *********************************************************************** }
unit RC6;
interface
uses
  SysUtils, Classes;
const
  Rounds    = 20;
  KeyLength = 2 * (Rounds + 2);
  BlockSize = 16;
  KeySize   = 16 * 4;
  P32       = $b7e15163;
  Q32       = $9e3779b9;
  lgw       = 5;
type
  TRC6Block = array[1..4] of LongWord;
var
  S      : array[0..KeyLength-1] of LongWord;
  Key    : string;
  KeyPtr : PChar;
////////////////////////////////////////////////////////////////////////////////
// Дополнительные функции
procedure Initialize(AKey: string);          // Инициализация
procedure CalculateSubKeys;                  // Подготовка подключей
function EncipherBlock(var Block): Boolean;  // Шифрация блока (16 байт)
function DecipherBlock(var Block): Boolean;  // Дешифрация блока
////////////////////////////////////////////////////////////////////////////////
// Главные функции
function EncryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;    // Зашифровать данные из одного потока в другой
function DecryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;    // Расшифровать данные из одного потока в другой
function EncryptStream(DataStream: TStream; Count: Int64;
  Key: string): Boolean;     // Зашифровать содержимое потока
function DecryptStream(DataStream: TStream; Count: Int64;
  Key: string): Boolean;     // Расшифровать содержимое потока
implementation
////////////////////////////////////////////////////////////////////////////////
function ROL(a, s: LongWord): LongWord;
asm
  mov    ecx, s
  rol    eax, cl
end;
////////////////////////////////////////////////////////////////////////////////
function ROR(a, s: LongWord): LongWord;
asm
  mov    ecx, s
  ror    eax, cl
end;
////////////////////////////////////////////////////////////////////////////////
procedure InvolveKey;
var
  TempKey : string;
  i, j    : Integer;
  K1, K2  : LongWord;
begin
 // Разворачивание ключа до длинны KeySize = 64
 TempKey := Key;
 i := 1;
 while ((Length(TempKey) mod KeySize) <> 0) do
   begin
     TempKey := TempKey + TempKey[i];
     Inc(i);
   end;
 i := 1;
 j := 0;
 while (i do
   begin
     Move((KeyPtr+j)^, K1, 4);
     Move(TempKey[i], K2, 4);
     K1 := ROL(K1, K2) xor K2;
     Move(K1, (KeyPtr+j)^, 4);
     j := (j + 4) mod KeySize;
     Inc(i, 4);
   end;
end;
////////////////////////////////////////////////////////////////////////////////
procedure CalculateSubKeys;
var
  i, j, k : Integer;
  L       : array[0..15] of LongWord;
  A, B	 : LongWord;
begin
 // Копирование ключа в L
 Move(KeyPtr^, L, KeySize);
 // Инициализация подключа S
 S[0] := P32;
 for i := 1 to KeyLength-1 do
   S[i] := S[i-1] + Q32;
 // Смешивание S с ключом
 i := 0;
 j := 0;
 A := 0;
 B := 0;
 for k := 1 to 3*KeyLength do
   begin
     A := ROL((S[i] + A + B), 3);
     S[i] := A;
     B := ROL((L[j] + A + B), (A + B));
     L[j] := B;
     i := (i + 1) mod KeyLength;
     j := (j + 1) mod 16;
   end;
end;
////////////////////////////////////////////////////////////////////////////////
procedure Initialize(AKey: string);
begin
 GetMem(KeyPtr, KeySize);
 FillChar(KeyPtr^, KeySize, #0);
 Key := AKey;
 InvolveKey;
end;
////////////////////////////////////////////////////////////////////////////////
function EncipherBlock(var Block): Boolean;
var
  RC6Block : TRC6Block absolute Block;
  i	   : Integer;
  t, u	   : LongWord;
  Temp	   : LongWord;
begin
 // Инициализация блока
 Inc(RC6Block[2], S[0]);
 Inc(RC6Block[4], S[1]);
 for i := 1 to Rounds do
   begin
     t := ROL((RC6Block[2] * (2*RC6Block[2] + 1)), lgw);
     u := ROL((RC6Block[4] * (2*RC6Block[4] + 1)), lgw);
     RC6Block[1] := ROL((RC6Block[1] xor t), u) + S[2*i];
     RC6Block[3] := ROL((RC6Block[3] xor u), t) + S[2*i+1];
     Temp := RC6Block[1];
     RC6Block[1] := RC6Block[2];
     RC6Block[2] := RC6Block[3];
     RC6Block[3] := RC6Block[4];
     RC6Block[4] := Temp;
   end;
 RC6Block[1] := RC6Block[1] + S[2*Rounds+2];
 RC6Block[3] := RC6Block[3] + S[2*Rounds+3];
 Result := TRUE;
end;
////////////////////////////////////////////////////////////////////////////////
function DecipherBlock(var Block): Boolean;
var
  RC6Block : TRC6Block absolute Block;
  i	   : Integer;
  t, u	   : LongWord;
  Temp	   : LongWord;
begin
 // Инициализация блока
 RC6Block[3] := RC6Block[3] - S[2*Rounds+3];
 RC6Block[1] := RC6Block[1] - S[2*Rounds+2];
 for i := Rounds downto 1 do
   begin
     Temp := RC6Block[4];
     RC6Block[4] := RC6Block[3];
     RC6Block[3] := RC6Block[2];
     RC6Block[2] := RC6Block[1];
     RC6Block[1] := Temp;
     u := ROL((RC6Block[4] * (2*RC6Block[4] + 1)),lgw);
     t := ROL((RC6Block[2] * (2*RC6Block[2] + 1)),lgw);
     RC6Block[3] := ROR((RC6Block[3]-S[2*i+1]), t) xor u;
     RC6Block[1] := ROR((RC6Block[1]-S[2*i]), u) xor t;
   end;
 Dec(RC6Block[4], S[1]);
 Dec(RC6Block[2], S[0]);
 Result := TRUE;
end;
////////////////////////////////////////////////////////////////////////////////
// Реализация главных функций
function EncryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;
var
  Buffer   : TRC6Block;
  PrCount  : Int64;
  AddCount : Byte;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DestStream.CopyFrom(SourseStream, Count);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= BlockSize do
     begin
       SourseStream.Read(Buffer, BlockSize);
       EncipherBlock(Buffer);
       DestStream.Write(Buffer, BlockSize);
       Inc(PrCount, BlockSize);
     end;
   AddCount := Count - PrCount;
   if Count - PrCount <> 0 then
     begin
       SourseStream.Read(Buffer, AddCount);
       DestStream.Write(Buffer, AddCount);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function DecryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;
var
  Buffer   : TRC6Block;
  PrCount  : Int64;
  AddCount : Byte;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DestStream.CopyFrom(SourseStream, Count);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= BlockSize do
     begin
       SourseStream.Read(Buffer, BlockSize);
       DecipherBlock(Buffer);
       DestStream.Write(Buffer, BlockSize);
       Inc(PrCount, BlockSize);
     end;
   AddCount := Count - PrCount;
   if Count - PrCount <> 0 then
     begin
       SourseStream.Read(Buffer, AddCount);
       DestStream.Write(Buffer, AddCount);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function EncryptStream(DataStream: TStream; Count: Int64; Key: string): Boolean;
var
  Buffer   : TRC6Block;
  PrCount  : Int64;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DataStream.Seek(Count, soFromCurrent);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= BlockSize do
     begin
       DataStream.Read(Buffer, BlockSize);
       EncipherBlock(Buffer);
       DataStream.Seek(-BlockSize, soFromCurrent);
       DataStream.Write(Buffer, BlockSize);
       Inc(PrCount, BlockSize);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function DecryptStream(DataStream: TStream; Count: Int64; Key: string): Boolean;
var
  Buffer   : TRC6Block;
  PrCount  : Int64;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DataStream.Seek(Count, soFromCurrent);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= BlockSize do
     begin
       DataStream.Read(Buffer, BlockSize);
       DecipherBlock(Buffer);
       DataStream.Seek(-BlockSize, soFromCurrent);
       DataStream.Write(Buffer, BlockSize);
       Inc(PrCount, BlockSize);
     end;
 except
   Result := False;
 end;
end;
// Завершение главных функций ...
////////////////////////////////////////////////////////////////////////////////
end.

Использование команды RDTSC процессора Pentium для работы с малыми временными интервалам.

Оформил: DeeCo

Автор: Шевченко В.В.

Приветствую Вас, жители королевства! В одной толстой книге нашел интересное использование команды RDTSC процессора Pentium для работы с малыми временными интервалами. Я думаю, что эта функция может найти широкое применение (в таймерах, управлении внешними устройствами, научных исследованиях).

Пример прилагается Project_RDTSC.Zip.(4K).

Этот счетчик увеличивается на 1 на каждом такте CPU. Он стартует при включении компьютера или при нажатии кнопки RESET. Обычно функцию RDTSC используют при определении тактовой частоты процессора. Применяя программные ухищрения можно добиться измерения очень малых временных величин в реальном масштабе времени или применять для калибровки таймеров (предварительно определив при помощи этой же функции тактовую частоту процессора). Готовые примеры определения тактовой частоты при помощи функции RDTSC есть в интернете, например, на сайте Мастера Delphi" : "Скорость работы процессора, точный таймер"
function RDTSC: comp;
var
  TimeStamp: record
    case byte of
      1: (Whole: comp);
      2: (Lo, Hi: Longint);
  end;
begin
  asm
    db $0F; db $31;
  {$ifdef Cpu386}
    mov [TimeStamp.Lo], eax
    mov [TimeStamp.Hi], edx
  {$else}
    db D32
    mov word ptr TimeStamp.Lo, AX
    db D32
    mov word ptr TimeStamp.Hi, DX
  {$endif}
  end;
  Result := TimeStamp.Whole;
end;

Readln для более чем 255 символов.

Построили новый полностью роботизированный завод. Идет экскурсия по цехам. Экскурсовод: - Внимание, господа, в этом цехе все роботы управаляются операцинной системой MS-DOS. Посмотрели, идут дальше. - В этом цехе все роботы работают под управлением операционной системы Unix. Посмотрели, идут дальше. - Вот в этом цехе все роботы работают под управлением операционной системы QNX. Проходят в след. цех. - А в этом цехе все роботы управляются операционной системой Microsoft Windows'98. Всем присутствующим просьба надеть защитные каски.
ReadLn акцептует массив символов array [0..something] of Char и использует его в качестве буфера для чтения символов, замыкая цепочку терминирующим нулем. Единственное ограничение: компилятор должен иметь возможность вычисления размера буфера во время компиляции, что делает невозможным объявление переменой типа PChar и ее распределение во время выполнения программы.

Обходной путь:

type
  {используем самое большое количество символов в
  строке, с которым вы можете иметь дело}
  TLine = array[0..1024] of Char;
  PLine = ^TLine;
var
  pBuf: PLine;
  ...
  New(pBuf);
  ...
  ReadLn(F, pBuf^);
Для передачи pBuf функциям, которым требуется параметр типа Pchar, используйте приведение типа подобно PChar( pBuf ).

Примечание: вы, конечно, можете использовать объявление переменной типа TLine или непосредственно массив символов, но я предпочитаю распределять из кучи нечто большее, чем 4 байта...

Читаем файл Access используя ADO.

// Читаем файл (любой версии) 
// Проверяем что это ACCESS MDB 
// Нужны компаненты: 
// TADOtable,TDataSource,TOpenDialog,TDBGrid,TBitBtn. 
unit uMain; 
interface 
uses 
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
Db, DBTables, ADODB, Grids, DBGrids, ExtCtrls, DBCtrls, StdCtrls, Buttons; 
type 
  TfrmMain = class(TForm) 
    DSUsers: TDataSource; 
    DBGridUsers: TDBGrid; 
    BitBtn1: TBitBtn; 
    OpenDialog1: TOpenDialog; 
    TUsers: TADOTable; 
    procedure FormCreate(Sender: TObject); 
    procedure ValidateAccessDB; 
    function CheckIfAccessDB(lDBPathName: string): boolean; 
  private 
    { Private declarations } 
  public 
    { Public declarations } 
  end; 
var 
  frmMain: TfrmMain; 
const 
  DBNAME = 'ADODemo.MDB'; 
  DBPASSWORD = '123'; // Access DB Password Protected 
implementation 
{$R *.DFM} 
procedure TfrmMain.FormCreate(Sender: TObject); 
begin 
  validateAccessDB; 
end; 
procedure TfrmMain.ValidateAccessDB; 
var 
  lDBpathName : String; 
  lDBcheck : boolean; 
begin 
  if FileExists(ExtractFileDir(Application.ExeName) + '\' + DBNAME) then 
    lDBPathName := ExtractFileDir(Application.ExeName) + '\' + DBNAME 
  else if OpenDialog1.Execute then 
    // Set the OpenDialog Filter for ADOdemo.mdb only 
    lDBPathName := OpenDialog1.FileName; 
  lDBCheck := False; 
  if Trim(lDBPathName) <> '' then 
    lDBCheck := CheckIfAccessDB(lDBPathName); 
  if lDBCheck = True then 
  begin 
    // ADO Connection String to the MS-ACCESS DB 
    TUsers.ConnectionString := 
      'Provider=Microsoft.Jet.OLEDB.4.0;' + 
      'Data Source=' + lDBPathName + ';' + 
      'Persist Security Info=False;' + 
      'Jet OLEDB:Database Password=' + DBPASSWORD; 
    TUsers.TableName := 'Users'; 
    TUsers.Active := True; 
  end 
  else 
    frmMain.Free; 
end; 
// Check if it is a valid ACCESS DB File Before opening it. 
function TfrmMain.CheckIfAccessDB(lDBPathName: string): Boolean; 
var 
  UnTypedFile: file of byte; 
  Buffer: array[0..19] of byte; 
  NumRecsRead: Integer; 
  i: Integer; 
  MyString: string; 
begin 
  AssignFile(UnTypedFile, lDBPathName); 
  reset(UnTypedFile); 
  BlockRead(UnTypedFile, Buffer, High(Buffer), NumRecsRead); 
  CloseFile(UnTypedFile); 
  for i := 1 to High(Buffer) do 
    MyString := MyString + Trim(Chr(Ord(Buffer[i]))); 
  Result := False; 
  if Mystring = 'StandardJetDB' then 
    Result := True; 
  if Result = False then 
    MessageDlg('Invalid Access Database', mtInformation, [mbOK], 0); 
end; 
end.

Читаем Adobe Acrobat PDF файлы из нашего приложения.

Статьи по Delphi (014)…

Adobe Acrobat PDF - хорошо извесный формат, который нравится многим пользователям. Давайте посмотрим, как можно заставить приложение на Delphi прочитать файл такого формата.

Итак, Вы должны быть уверены, что у вас проинсталлирован Acrobat Reader, елси таковой программы нет, то её можно скачать с http://www.adobe.com

После этого необходимо проинсталировать типовую библиотеку для Acrobat (Project -> Import Type Library из меню Delphi) выберите "Acrobat Control for ActiveX (version x)". Где x - текущая версия библиотеки. Нажмите кнопку инсталяции.

Теперь создайте новое приложение, поместите на форму проинсталлированный компонент TPDF, далее добавите OpenDialog, и в заключении кнопку, при на нажатии на которую будет вызываться процедура открытия файла:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    pdf1.src := OpenDialog1.FileName;
end; 

В юните PdfLib_TLB вы можете найти интерфейс класса TPdf:

TPdf = class(TOleControl)
  private
    FIntf: _DPdf;
    function GetControlInterface: _DPdf;
  protected
    procedure CreateControl;
    procedure InitControlData; override;
  public
    function LoadFile(const fileName: WideString): WordBool;
    procedure setShowToolbar(On_: WordBool);
    procedure gotoFirstPage;
    procedure gotoLastPage;
    procedure gotoNextPage;
    procedure gotoPreviousPage;
    procedure setCurrentPage(n: Integer);
    procedure goForwardStack;
    procedure goBackwardStack;
    procedure setPageMode(const pageMode: WideString);
    procedure setLayoutMode(const layoutMode: WideString);
    procedure setNamedDest(const namedDest: WideString);
    procedure Print;
    procedure printWithDialog;
    procedure setZoom(percent: Single);
    procedure setZoomScroll(percent: Single; left: Single; top: Single);
    procedure setView(const viewMode: WideString);
    procedure setViewScroll(const viewMode: WideString; offset: Single);
    procedure setViewRect(left: Single; top: Single; width: Single; height: Single);
    procedure printPages(from: Integer; to_: Integer);
    procedure printPagesFit(from: Integer; to_: Integer; shrinkToFit: WordBool);
    procedure printAll;
    procedure printAllFit(shrinkToFit: WordBool);
    procedure setShowScrollbars(On_: WordBool);
    procedure AboutBox;
    property ControlInterface: _DPdf read GetControlInterface;
    property DefaultInterface: _DPdf read GetControlInterface;
  published
    property TabStop;
    property Align;
    property DragCursor;
    property DragMode;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property TabOrder;
    property Visible;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnStartDrag;
    property src: WideString index 1 read GetWideStringProp write SetWideStringProp stored False;
end;

В заключение можно добавить следующее:

Если Вы не уверены, что у конечного пользователя Вашей программы установлен Acrobat Reader, то необходимо, чтобы приложение проверяло эту ситуацию, прежде чем будут производится различные манипуляции с компонентой TPdf. И второе, если файл PDF имеет различные связи, например с AVI файлами, то они не будут работать из Delphi.

Надеюсь этот пример будет Вам полезен.

Прочитать данные из другого дочернего окна.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

type
   TMDIChildForm = class(TForm)
     Edit1: TEdit;
   private
     { Private declarations }
     procedure ReadDataFromOtherMDIChildForm;
   end;
 var
   MDIChildForm: TMDIChildForm;
 implementation
 {$R *.DFM}
 uses
   MainForm;
   // Property FormStyle of this form is fsMDIForm 
procedure TMDIChildForm.ReadDataFromOtherMDIChildForm;
 var
   i: Integer;
   DataFromOtherForm: string;
 begin
   // Suppose you have created three different MDIChild forms of the same type, 
  // each with the following caption: "aaa", "bbb", "ccc". 
  // You are currently on form with caption "aaa" and want to read data 
  // contained on form with caption "ccc". 
  // You can find here the code, you have to use the "as" clause and 
  // properties MDIChildCount and MDIChildren: 
  // First you have to find where the form "ccc" is in memory; 
  for i := 0 to MDIForm.MDIChildCount - 1 do
   begin
     if (Pos('ccc', MDIForm.MDIChildren[i].Caption)  0) then
       Break;
   end;
   // Check to see if the form is the last on MDIChildren array and 
  // correct I variable 
  if (i = MDIChildCount) then Dec(i);
   // I variable contains the index of the form with caption 'ccc' 
  if (Pos('ccc', MDIForm.MDIChildren[i].Caption)  0) then
   begin
     // If the form with caption 'ccc' exists then you access data and show it 
    // The following line of code is very interesting, look at the "as" clause, 
    // if you have different types of MDIChild forms, you simply change 
    // the type of form after the "as" clause 
    // The data you want is contained on Edit1.Text 
    with (MDIForm.MDIChildren[I] as TMDIChildForm).Edit1 do
       DataFromOtherForm := Text;
     ShowMessage(DataFromOtherForm);
   end;
 end;

Чтение бинарного файла.

var
  f: File;
  c: Char;
begin
  AssignFile(f, 'this.bin');
  Reset(f, 1);
  BlockRead(f, c, sizeof(c));
  CloseFile(f);
end;
function FindInFile(cFileName: string; cCh: char): boolean;
var
  fFile: file;
  aBuf: array[1..1024] of char;
  lFound: boolean;
  x, nRead: integer;
begin
  Assign(fFile, cFileName);
  Reset(fFile, 1);
  lFound := False;
  repeat
    BlockRead(fFile, aBuf, SizeOf(aBuf), nRead);
    x := 1;
    while not lFound and (x <= nRead) do
    begin
      lFound := (aBuf[x] = cCh)
        Inc(x)
    end;
  until
    (nRead < SizeOf(aBuf)) or lFound;
  FindInFile := lFound
end;

Взгляните на следующий код:

var
  f: file;
  c: Char;
begin
  AssignFile(f, 'c:\autoexec.bat');
  Reset(f, 1);                        <- Примечание: Размер записи = 1 байту это нормально!
  while not Eof(f) do
  begin
    BlockRead(f, c, SizeOf(c));
    {Теперь обрабатываем c}
  end;
  CloseFile(f);
end;

Для ускорения этой процедуры не следует за один проход читать по одному символу. Возможно, лучшим решением будет объявление PChar скажем, размером 200, и чтением за один проход блоков размером 200 байт. {например, BlockRead(f, p, 200);} Но для этого требуется немного больше кода, чем показано здесь... (Используйте все тот же recordsize, равный 1, меняется только blocksize).

Как прочитать байт из параллельного порта.

Как объяснить тупому, что такое бит? Элементарно - это байт минус налоги. Как объяснить тупому, что такое байт? Элементарно - объясните ему 8 раз, что такое бит. Что такое килобайт? Это слишком долго рассказывать...
var
  BytesRead: BYTE;
begin
  asm { Читаем порт (LPT1) через встроенный ассемблер }
    MOV dx,$379;
    in al,dx;
    MOV BytesRead,al;
  end;
  BytesRead := (BytesRead or $07); { OR а затем XOR данных }
  BytesRead := (BytesRead xor $80); { маскируем неиспользуемые биты }
end;

Как прочитать байт из параллельного порта 2.

Используем команды Turbo Pascal ...

value:=port[$379]; { Прочитать из порта } 
port[$379]:=value; { Записать в порт } 

Как динамически прочитать информацию о классе.

Автор: http://www.lmc-mediaagentur.de

procedure TForm1.FormCreate(Sender: TObject);
begin
  {This only works for classes registered using RegisterClass}
  RegisterClasses([TButton, TForm]);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  CRef: TPersistentClass;
  PTI: PTypeInfo;
  AControl: TControl;
begin
  CRef := GetClass('TButton');
  if CRef <> nil then
  begin
    AControl := TControl(TControlClass(CRef).Create(Self));
    with AControl do
    begin
      Parent := Self;
      Width := 50;
      Height := 30;
    end;
    Inc(Id);
  end
  else
    MessageDlg('No such class', mtWarning, [mbOk], 0);
end;

Как читать файлы Outlook Express (DBX).

Автор: Samum WEB-сайт: http://samum2000.narod.ru Структура файла Сообщения хранятся в таблицах, расположенных друг за другом, по порядку. Каждая таблица состоит из двух частей - заголовка и _входа_. Смещение первой таблицы от начала файла располагается со смещением $30 от начала файла и представляет собой знаковое 32-х битное число (тип integer или longint). Общее же количество записей (т.е. этих самых таблиц) расположено со смещением $C4 от начала файла, и представляет собой число типа integer (longint).

Заголовок таблицы содержит количество _входов_, собственное смещение, а так же смещения предыдущей и следующей таблиц (все смещения, разумеется, от начала файла). Для чтения заголовка таблицы я использую такую структуру:

Toe5_IndexHeader = record
  FilePos: longint; {Это смещение структуры, используется для контроля}
  Unknown1: longint; { ??? }
  PrevIndex: longint; {Смещение предыдущей таблицы}
  NextIndex: longint; {Смещение следующей таблицы}
  Count: longint; {Количество _входов_, реальное количество находится так: count shr 8}
  Unknown2: longint; { ??? }
end;
Чтобы получить реальное количество _входов_, значение поля count надо конвертировать .

Каждый _вход_ содержит смещение (всегда от начала файла) заголовка письма (Message Header) и позицию другой индексной таблицы (эта таблица используется, чтобы обеспечить поддержку наследования (или связывания) сообщения, то есть любой заголовок сообщения, на который ссылается эта таблица - ребенок текущего сообщения). Вот структура входа:

Toe5_IndexItem = record
  HeaderPos: longint; {Смещение (от начала файла) заголовка письма}
  ChildIndex: longint; {Смещение (от начала файла) дочерней индексной таблицы}
  Unknown: longint;
end;
На мой взгляд, лучшим методом чтения таблиц является рекурсивная функция (см. пример).

Заголовок сообщения содержит некоторую информацию, которую Outlook Express использует, чтобы избежать доступа к сообщению, пока в этом нет особой необходимости. Эта структура разделена на три части: заголовок структуры, таблицу параметров типа DWORD и блок данных.

Заголовок структуры - это то, что вам необходимо прочитать в первую очередь, так как он определяет размер оставшихся двух частей. В её состав входят размер всей структуры (все 3 части), суммарный размер таблицы параметров, блока данных и количество параметров в таблице (я называю эти параметры флагами).
THeaderData = record
  position: longint; {смещение данной структуры от BOF, используется только для контроля}
  DataLength: longint; {размер таблицы параметров и блока данных}
  HeaderLength: WORD; {размер всех трех частей}
  FlagCount: WORD; {количество элементов в таблице}
end;
Чтобы получить размер таблицы флагов, можно написать SizeOfTable:=FlagCount*SizeOf(DWord), a размер блока данных используйте DataLength-SizeOfTable. Каждый элемент таблицы параметров должен быть декодирован, для того, чтобы получить его идентификатор и значение: идентификатор находят как element and $FF, а значение флага - element shr 8. Блок данных содержит такую информацию, как дата получения, отправки, тема, адресат, отправитель, аккаунт и т.д. Для её чтения используется таблица флагов (параметров). Вот пример таблицы параметров и соответствующего блока данных:
Flags = 16     
     80: 00000074 
     81: 00000081
     02: 00000000
     84: 0002ECA0
     05: 00000008
     06: 00000025
     07: 0000002D
     08: 0000006E
     0D: 0000008B
     0E: 000000A5
     90: 00000003
     91: 0000376F
     12: 000000D4
     13: 000000DC
     14: 00000102
     1C: 0000012A
Data Block:
      
     00 72 F3 E4 58 22 C0 01 41 63 74 69 76 65 57 65 
         r  o  a  X  "  A  _  A  c  t  i  v  e  W  e 
      
     62 20 44 65 76 65 6C 6F 70 65 72 20 65 58 54 52 
      b     D  e  v  e  l  o  p  e  r     e  X  T  R 
      
     41 20 23 38 00 60 E2 F2 9C 90 35 C0 01 3C 4F 46 
      A     #  8     `  a  o  ?  ?  5  A  _       A  c 
      
     74 69 76 65 57 65 62 20 44 65 76 65 6C 6F 70 65 
      t  i  v  e  W  e  b     D  e  v  e  l  o  p  e 
      
     72 20 65 58 54 52 41 20 23 38 00 41 63 74 69 76 
      r     e  X  T  R  A     #  8     A  c  t  i  v 
      
     65 57 65 62 20 44 65 76 65 6C 6F 70 65 72 20 65 
      e  W  e  b     D  e  v  e  l  o  p  e  r     e 
      
     58 54 52 41 00 61 63 74 69 76 65 77 65 62 64 65 
      X  T  R  A     a  c  t  i  v  e  w  e  b  d  e 
      
     76 65 6C 6F 70 65 72 65 78 74 72 61 40 70 69 6E 
      v  e  l  o  p  e  r  e  x  t  r  a  @  p  i  n 
      
     6E 61 63 6C 65 70 75 62 6C 69 73 68 69 6E 67 2E 
      n  a  c  l  e  p  u  b  l  i  s  h  i  n  g  . 
      
     63 6F 6D 00 00 23 5E 0F 8B 22 C0 01 41 63 74 69 
      c  o  m        #  ^  _  ‹  "  A  _  A  c  t  i 
      
     76 65 57 65 62 20 44 65 76 65 6C 6F 70 65 72 20 
      v  e  W  e  b     D  e  v  e  l  o  p  e  r    
      
     65 58 54 52 41 20 53 75 62 73 63 72 69 62 65 72 
      e  X  T  R  A     S  u  b  s  c  r  i  b  e  r 
      
     20 00 3C 41 63 74 69 76 65 57 65 62 20 44 65 76 
                 ?           _    
      
     02 4E 00 00 00 00 F9 37 00 00 02 4E 00 00 01 00 
      _  N              u  7        _  N        _    
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 40 05 00 00 F9 37 00 00 00 00 
                        @  _        u  7             
      
     00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 
                        _                            
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 6D 70 
                        m  p
Теперь давайте разберёмся, что означает каждый флаг.

Для файла Folders.dbx:

$2: смещение от начала блока данных, по которому расположено название папки (Nullterminated string) $3: смещение от начала блока данных, по которому расположено имя файла, представляющего эту папку (это тоже Nullterminated string) $6: не важный флаг. Если папка его имеет, то значит она - специальная папка, у неё нет собственного файла, и она используется лишь для организации; $80: идентификатор папки; $81: идентификатор родительской папки (флаг $80 родительской папки).

Для других файлов:

$1: Смещение от начала блока данных, по которому расположен статус сообщения. $2: Смещение от начала блока данных, по которому расположена дата отправки (дата представлена в TFileTime) $4: Иногда типа DWord не хватает, чтобы представить и идентификатор флага, и смещения тела сообщения от начала файла, тогда смещение, по которому расположено тело, размещается в блоке данных. Значение этого флага - смещение (от начала блока данных) по которому находится это значение. $7: Смещение от начала блока данных, по которому расположен идентификатор письма MessageID (nullterminated string) $8: Смещение от начала блока данных, по которому расположена тема сообщения (nullterminated string) $9: Смещение от начала блока данных, по которому расположен параметр “From Reply” сообщения (nullterminated string) $A: Смещение от начала блока данных, по которому расположены ссылки (References)сообщения (nullterminated string) $B: Смещение от начала блока данных, по которому расположена новостная группа сообщения (null terminated string) $D: Смещение от начала блока данных, по которому расположен адресат (null terminated string) $E: Смещение от начала блока данных, по которому расположены данные “Reply to:” (null terminated string) $12: Смещение от начала блока данных, по которому расположена дата получения (дата представлена в формате TFileTime) $13: Смещение от начала блока данных, по которому расположен получатель (null terminated string) $1A: Смещение от начала блока данных, по которому расположен аккаунт (null terminated string) $1B: Смещение от начала блока данных, по которому расположен идентификатор аккаунта (null terminated string) $80: Номер сообщения $81: Используется для хранения статуса письма $84: Смещение тела сообщения в файле $91: Размер сообщения.

Сообщения: Каждое письмо или сообщение групп новостей хранятся в блоках по 512 байт, у каждого блока есть заголовок. То есть каждое сообщение делится на части, и к каждой части добавляют заголовок (в котором содержится размер блока, размер занятой части блока, а так же положение следующего блока). Я использую такую структуру для чтения блоков (вместе с их заголовками):

Toe5_MsgItem = record
  FilePos: longint; {смещение структуры от BOF, используется для контроля верности операций}
  Unknown: longint; {я думаю, это размер блока данных}
  ItemSize: longint; {использованная часть блока}
  NextItem: longint; {смещение следующего блока от BOF, и 0, если это последний блок}
  MsgContent: array[0..511] of Char; {блок, содержащий непосредственно данные}
end;
Удаление сообщений

Когда какое либо сообщение удаляется, оно сначала помещается в папку "Удаленные", а физически - в соответствующий этой папке файл. Место же, которое занимало сообщение в прежнем файле, добавляется в список пустого пространства, и когда приходит новое сообщение, Outlook Express использует сначала это место. Смещение первого элемента в списке сободного места сохранено по смещению $48 от начала файла. Каждый элемент этого списка разделен на две части: заголовок и блок свободного пространства. Вот структура заголовка:

Toe5_FreeSpace = record
  FilePos: longint; {это смещение всей структуры от начала файла BOF, используется для контроля}
  ElementSize: longint; {размер структуры - заголовок и свободное место}
  FreeSpaceSize: longint; {Размер свободного пространства}
  PreviousElement: longint; {смещение (от начала файла) предыдущего элемента}
  NextElement: longint; {смещение следующего элемента}
end;
Даты

Все даты в заголовке сообщения сохранены в формате TFileTime, и основаном на UTC. Перед использованием это значение надо перевести в местное время. Вот небольшой пример того, как это можно сделать:

function FiletimeToDatetime(const date: TFileTime): TDateTime;
var
  st: TSystemTime;
  localft: TFileTime;
begin
  FileTimeToLocalFileTime(date, localft);
  FileTimeToSystemTime(localft, st);
  Result := SystemTimeToDateTime(st);
end;
Статус сообщения

Для получения статуса сообщения можно использовать значение флага $81 следующим образом:

...
x := < значение флага $81 >
if (x and constant) <> 0 then
  ...

И на последок, некоторые константы:

DOWNLOADED = $1 MARKED = $20 (Отмечено флажком) READED = $80 DOWNLOAD_LATER = $100 NEWS_MSG = $800

Эти константы надо проверить:

ATTACHMENTS = $4000 REPLY =$80000 INSPECT_CONVERSATION = $400000 IGNORE_CONVERSATION =$800000 Модули к статье можно взять здесь Все дополнения, модификации, предложения, благодарности и т.п. просьба присылать на Samum2000@mail15.com (особенно к русскому переводу) или на walther_e@yahoo.com (на английском языке, касательно оригинала). Walther Estergaard Walther_e@yahoo.com

Перевод: Боднар Денис aka Samum, Samum2000@mail15.com, ICQ: 278395965.

Чтение сложных OLE-документов.

Автор: John Lam Не используйте для этого компонент TOleContainer. Создавая приложение с возможностью доступа к файлам OLE Structured Storage (стуктурное хранилище), реально необходима только пара вызовов OLE API, и 5-6 вызовов API для доступа к функциям-членам интерфейсов IStorage, IRootStorage и IStream. Я подозреваю, что функция-член TOleContainer IStorage является указателем на корневое хранилище файла Structured Storage, создаваемого функциями-членами LoadFromFile/SaveToFile TOleContainer. Необходимые функции API: StgOpenStorage StgIsStorageFile Если у вас есть OLE 2.01 SDK, вы можете найти определения там.

Будьте внимательны, при переносе заголовочных файлов C++ из OLE SDK в Delphi, Borland допустил несколько ошибок. Одна из ошибок при создании апплета обернулась мне тремя бессонными ночами. Вот правильное определение IStorage:

MyIStorage = class(IUnknown)
function CreateStream(const pwcsName: PChar; grfMode: Longint; reserved1: Longint;
reserved2: Longint; var ppstm: IStream): HResult; virtual; cdecl; export; abstract;
function OpenStream(const pwcsName: PChar; reserved1: Pointer; grfMode: Longint;
reserved2: Longint; var ppstm: IStream): HResult; virtual; cdecl; export; abstract;
function CreateStorage(const pwcsName: PChar; grfMode: Longint;
reserved1: Longint; reserved2: Longint; var ppstg: MyIStorage): HResult;
virtual; cdecl; export; abstract;
function OpenStorage(const pwcsName: PChar; pstgPriority: MyIStorage;
grfMode: Longint; snbExclude: PStr; reserved: Longint;
var ppstg: MyIStorage): HResult; virtual; cdecl; export; abstract;
function CopyTo(ciidExclude: Longint; const rgiidExclude: IID;
var snbExclude: PStr; pstgDest: MyIStorage): HResult; virtual; cdecl; export; abstract;
function MoveElementTo(const lpszName: PChar; pstgDest: MyIStorage;
const lpszNewName: PChar; grfFlags: Longint): HResult; virtual; cdecl; export; abstract;
function Commit(grfCommitFlags: Longint): HResult; virtual; cdecl; export; abstract;
function Revert: HResult; virtual; cdecl; export; abstract;
function EnumElements(reserved1: Longint; reserved2: Pointer; reserved3: Longint;
var ppenm: IEnumStatStg): HResult; virtual; cdecl; export; abstract;
function DestroyElement(const pwcsName: PChar): HResult; virtual; cdecl; export; abstract;
function RenameElement(const pwcsOldName: PChar;
const pwcsNewName: PChar): HResult; virtual; cdecl; export; abstract;
function SetElementTimes(const lpszName: PChar; const pctime: TFileTime;
const patime: TFileTime; const pmtime: TFileTime): HResult;
virtual; cdecl; export; abstract;
function SetClass(const clsid: IID): HResult; virtual; cdecl; export; abstract;
function SetStateBits(grfStateBits: Longint; grfMask: Longint): HResult;
virtual; cdecl; export; abstract;
function Stat(var pstatstg: TStatStg; grfStatFlag: Longint): HResult;
virtual; cdecl; export; abstract;
end;
Проблема заключалась в том, что некоторые параметры неправильно были объявлены как VAR, тогда как в действительности они объявлены как CONST - это важно, поскольку иногда функции-члены интерфейса ожидают параметры со значением, равным NIL.

Надеюсь, это поможет.

Следить за изменениями в каталоге.

Автор: FliNT Использование ReadDirectoryChangesW Очень хорошая функция, только " Windows 95/98/Me: Unsupported" . Т.е. в Win9x этой функции нет. Но мы же пишем для NT, а там с этой функцией все в порядке (если 3-ий сервис пак для NT3.1 поставили :)))

Кратко пройдемся по описанию функции (взято из windows.

function ReadDirectoryChangesW(
  hDirectory: THandle; // описатель каталога, за которым надо следить
  lpBuffer: Pointer;  // Указатель на буфер, в который будет записана информация
  nBufferLength: DWORD; // Размер буфера
  bWatchSubtree: Bool; // Следить ли за подкаталогами
  dwNotifyFilter: DWORD; // Фильтр действий
  lpBytesReturned: LPDWORD; // Сколько было записано в буфер
  lpOverlapped: POverlapped; // Для асинхронной работы
  lpCompletionRoutine: FARPROC // Функция, которая будет вызвана при окончании операции
  ): BOOL; stdcall;
Ну а теперь пример работы этой функции (исходник этого примера (пока без комментариев!! и на Delphi6) можно скачать здесь) Чтобы программа могла нормально работать во время ожидания очередного изменения, мы функции мониторинга выделим отдельный поток. Поток " сделан" на WinAPI (функция WorkThread). При нажатии на одну кнопку он будет создаваться, а на другую - жестоко уничтожаться. Вся полезная информация будет выводиться в TListView.

Функция потока будет описана так:

procedure WorkThread(LV: TListView); stdcall;
LV - это то, во что мы будем выводить инфу. И не забывайте stdcall;

А вот ее текст:

procedure WorkThread(LV : TListView);stdcall;
var
 hDir : THandle;
 lpBuf : Pointer;
 Ptr   : Pointer;
 cbReturn : Cardinal;
 FileName : PWideChar;
 Item : TListItem;
 sTime : _SYSTEMTIME;
begin
 // Сначала нам надо получить описатель каталога, за которым мы будем следить
 // В данном примере это будет весь диск C:
 hDir := CreateFile ('C:\',GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE
   or FILE_SHARE_DELETE,nil,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,0);
 // Если ошиблись...
 if hDir = INVALID_HANDLE_VALUE
  then begin ShowMessage(SysErrorMessage(GetLastError)); exit; end;
 // Выделяем память под буфер
 // const BUF_SIZE = 2048 - думаю вполне достаточно
 GetMem(lpBuf,BUF_SIZE);
 repeat
  // очищаем память перед записью в нее (на всякий случай)
  ZeroMemory(lpBuf,BUF_SIZE);
  // Теперь мы будем ждать пока чего-нибудь в интересующем нас каталоге
  // изменится или произойдет ошибка (и мы выйдем из цикла)
  // FILE_NOTIFY_CHANGE - это список флагов - о них ниже.
  if not ReadDirectoryChangesW(hDir,lpBuf,BUF_SIZE,true,
                   FILE_NOTIFY_CHANGE,@cbReturn,nil,nil)
   then Break;
  // Сюда мы попадаем, если функция выполнилась успешно
  // и lpBuf указывает на одну или несколько структур FILE_NOTIFY_INFORMATION
  Ptr:=lpBuf;
Отойдем пока от исходного кода и рассмотрим, что у нас появится в буфере. В данный момент lpBuf и Ptr указывают на первую структуру FILE_NOTIFY_INFORMATION. Вторым полем этой структуры является - Action -тип действия, которое было совершено. Четвертым - FileName - первый символ имени файла. Имя файла не заканчивается нулем #0 и для определения его длины используется 3 параметр - FileNameLength. При этом надо учесть, что имя файла в формате Unicode т.е. каждый символ занимает 2 байта, а FileNameLength дается в байтах. Придется эту длину делить на 2, чтобы узнать кол-во символов.

Но возникает вопрос - как узнать, сколько таких структур было записано в буфер. Для этого используется 1 параметр структуры - NextEntryOffset. Если он не равен нулю, то в нем будет кол-во байт, через которые находится следующая запись и нам надо сдвинуть указатель на это кол-во байт, чтобы " получить" следующую структуру. И так далее, пока NextEntryOffset не будет равен 0 (т.е. эта запись была последней).

  repeat
   // Добавляем новый элемент в TListView (ViewStyle = vsReport )
   Item := LV.Items.Add;
   // Выделяем память под имя файла
   GetMem(FileName,PFileNotifyInformation(Ptr).FileNameLength+2);
   // Очищаем память - чтобы последним символом после копирования
   // был бы #0 нуль
   ZeroMemory(FileName,PFileNotifyInformation(Ptr).FileNameLength+2);
   // WinAPI функция для копирования Unicode строки
   lstrcpynW(FileName,PFileNotifyInformation(Ptr).FileName,
             PFileNotifyInformation(Ptr).FileNameLength div 2+1);
   // Имя файла у нас дается относительно папки
   // т.е.если изменится файл C:\File\test.dat, то FileName
   // будет равно File\test.dat
   Item.Caption:='C:\'+FileName;
   // Имя файла нам больше не нужно - очищаем память
   FreeMem(FileName);
   // Определяем тип произошедшего действия
   case PFileNotifyInformation(Ptr).Action of
    FILE_ACTION_ADDED    : Item.SubItems.Add('Файл был создан');
    FILE_ACTION_REMOVED  : Item.SubItems.Add('Файл был удален');
    FILE_ACTION_MODIFIED : Item.SubItems.Add('Файл был изменен');
    FILE_ACTION_RENAMED_OLD_NAME :
   Item.SubItems.Add('Файл был переименован и в имени файла - предыдущее имя');
    FILE_ACTION_RENAMED_NEW_NAME :
  Item.SubItems.Add('новое имя после переименования');
    else Item.SubItems.Add('Произошло что-то странное');
   end;
   // Время, когда произошло событие
   GetLocalTime(sTime);
   with sTime do
    Item.SubItems.Add(Format('%.2d:%.2d:%.2d',[wHour,wMinute,wSecond])); // 13:54:20
   // Если эта запись не последняя (NextEntryOffset < >  0), то...
   if PFileNotifyInformation(Ptr).NextEntryOffset=0
    then Break
     else begin
   // ... добавляем строку в примечания (если интересно посмотреть смещение)
      Item.SubItems.Add('Offset : '+
       IntToStr(PFileNotifyInformation(Ptr).NextEntryOffset));
   //Передвигаем указатель на NextEntryOffset байт вперед
      Inc(Cardinal(Ptr),PFileNotifyInformation(Ptr).NextEntryOffset);
   // Теперь Ptr указывает на следующую запись
     end;
  // Передвигать надо именно Ptr, а не lpBuf
  until false;
 until false;
 // Очищаем память
 FreeMem(lpBuf);
end;

Параметр функции dwNotifyFilter - действия, информацию о которых мы хотим получать.

FILE_NOTIFY_CHANGE_FILE_NAME - создание, удаление, переименование файла. FILE_NOTIFY_CHANGE_DIR_NAME - созданием и удаление каталога. FILE_NOTIFY_CHANGE_ATTRIBUTES - изменение атрибутов файла. FILE_NOTIFY_CHANGE_SIZE - изменение размера файла. FILE_NOTIFY_CHANGE_LAST_WRITE - изменение файла (это и предыдущее уведомление будет получено только в момент реальной записи файла на диск). FILE_NOTIFY_CHANGE_LAST_ACCESS - изменение времени последнего доступа. FILE_NOTIFY_CHANGE_CREATION - изменение времени создания файла. FILE_NOTIFY_CHANGE_SECURITY - изменение параметров безопасности (прав доступа и т.д.) У меня в примере используются FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_DIR_NAME и FILE_NOTIFY_CHANGE_LAST_WRITE.

А теперь надо только запустить поток.

procedure TForm1.Button1Click(Sender: TObject);
var
 ThID : Cardinal;
begin
 // hThread - THandle - глобальная переменная
 // Создаем поток
 // LV - TListView, WorkThread - функция выше
 hThread:=CreateThread(nil,0,@WorkThread,LV,0,ThID);
 // В случае неудачи выводим сообщение
 if hThread=0 then ShowMessage(SysErrorMessage(GetLastError));
end;

У меня в исходниках поток останавливается функцией TerminateThread(hThread,Cardinal(-1)). Но при таком завершении не будут освобождены все ресурсы, занятые потоком (а это как минимум BUF_SIZE байт памяти. Вместо этой функции было бы лучше использовать SuspendThread(hThread), а при запуске проверять на существование потока WaitForSingleObject(hThread,0)= WAIT_TIMEOUT и если он существует - делать ResumeThread(hThread)... но в исходниках этого пока нет :).

Прочесть текстовый файл DOS.

Лозунг компании Microsoft на презентации Windows ME: "ДОС - НАФИГ!"

Если попытаться открыть текст, написанный в DOS, не меняя свойство Charset, то получится набор непонятных символов. Например в Windows символ с кодом 174 - это значок "®", а в DOS это обычная "о". Свойство Charset, установленное в OEM_CHARSET, заставляет выводить именно "о", если код символа 174. Эта программа читает файлы DOS.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Font.Charset := OEM_CHARSET;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    Memo1.Lines.LoadFromFile(OpenDialog1.FileName)
end;

Считать данные из ячеек Excel.

Автор: Rusher

var
 Excel: variant;
  i, j: word;
     S: string; 
begin
     Excel := CreateOleObject('Excel.Application');
     Excel.Workbooks.Open(FileName);
     Excel.Visible:=True;
     for i := 1 to 5 do
       for j := 1 to 5 do 
        begin
          S := Excel.Sheets[1].Cells[i,j].Text;
          ShowMessage(S);
        end;
end;

Читаем заголовок exe файла.

Автор: http://www.swissdelphicenter.ch

{ You'll need a OpenDialog to open a Exe-File
and a Memo to show the file informations }
procedure DumpDOSHeader(const h: IMAGE_DOS_HEADER; Lines: TStrings);
begin
  Lines.Add('Dump of DOS file header');
  Lines.Add(Format('Magic number: %d', [h.e_magic]));
  Lines.Add(Format('Bytes on last page of file: %d', [h.e_cblp]));
  Lines.Add(Format('Pages in file: %d', [h.e_cp]));
  Lines.Add(Format('Relocations: %d', [h.e_crlc]));
  Lines.Add(Format('Size of header in paragraphs: %d', [h.e_cparhdr]));
  Lines.Add(Format('Minimum extra paragraphs needed: %d', [h.e_minalloc]));
  Lines.Add(Format('Maximum extra paragraphs needed: %d', [h.e_maxalloc]));
  Lines.Add(Format('Initial (relative) SS value: %d', [h.e_ss]));
  Lines.Add(Format('Initial SP value: %d', [h.e_sp]));
  Lines.Add(Format('Checksum: %d', [h.e_csum]));
  Lines.Add(Format('Initial IP value: %d', [h.e_ip]));
  Lines.Add(Format('Initial (relative) CS value: %d', [h.e_cs]));
  Lines.Add(Format('File address of relocation table: %d', [h.e_lfarlc]));
  Lines.Add(Format('Overlay number: %d', [h.e_ovno]));
  Lines.Add(Format('OEM identifier (for e_oeminfo): %d', [h.e_oemid]));
  Lines.Add(Format('OEM information; e_oemid specific: %d', [h.e_oeminfo]));
  Lines.Add(Format('File address of new exe header: %d', [h._lfanew]));
  Lines.Add('');
end;
procedure DumpPEHeader(const h: IMAGE_FILE_HEADER; Lines: TStrings);
var
  dt: TDateTime;
begin
  Lines.Add('Dump of PE file header');
  Lines.Add(Format('Machine: %4x', [h.Machine]));
  case h.Machine of
    IMAGE_FILE_MACHINE_UNKNOWN: Lines.Add(' MACHINE_UNKNOWN ');
    IMAGE_FILE_MACHINE_I386: Lines.Add(' Intel 386. ');
    IMAGE_FILE_MACHINE_R3000:
      Lines.Add(' MIPS little-endian, 0x160 big-endian ');
    IMAGE_FILE_MACHINE_R4000: Lines.Add(' MIPS little-endian ');
    IMAGE_FILE_MACHINE_R10000: Lines.Add(' MIPS little-endian ');
    IMAGE_FILE_MACHINE_ALPHA: Lines.Add(' Alpha_AXP ');
    IMAGE_FILE_MACHINE_POWERPC: Lines.Add(' IBM PowerPC Little-Endian ');
    // some values no longer defined in winnt.h
    $14D: Lines.Add(' Intel i860');
    $268: Lines.Add(' Motorola 68000');
    $290: Lines.Add(' PA RISC');
  else
    Lines.Add(' unknown machine type');
  end; { Case }
  Lines.Add(Format('NumberOfSections: %d', [h.NumberOfSections]));
  Lines.Add(Format('TimeDateStamp: %d', [h.TimeDateStamp]));
  dt := EncodeDate(1970, 1, 1) + h.Timedatestamp / SecsPerDay;
  Lines.Add(FormatDateTime(' c', dt));
  Lines.Add(Format('PointerToSymbolTable: %d', [h.PointerToSymbolTable]));
  Lines.Add(Format('NumberOfSymbols: %d', [h.NumberOfSymbols]));
  Lines.Add(Format('SizeOfOptionalHeader: %d', [h.SizeOfOptionalHeader]));
  Lines.Add(Format('Characteristics: %d', [h.Characteristics]));
  if (IMAGE_FILE_DLL and h.Characteristics) <> 0 then
    Lines.Add(' file is a DLL')
  else if (IMAGE_FILE_EXECUTABLE_IMAGE and h.Characteristics) <> 0 then
    Lines.Add(' file is a program');
  Lines.Add('');
end;
procedure DumpOptionalHeader(const h: IMAGE_OPTIONAL_HEADER; Lines: TStrings);
begin
  Lines.Add('Dump of PE optional file header');
  Lines.Add(Format('Magic: %d', [h.Magic]));
  case h.Magic of
    $107: Lines.Add(' ROM image');
    $10B: Lines.Add(' executable image');
  else
    Lines.Add(' unknown image type');
  end; { If }
  Lines.Add(Format('MajorLinkerVersion: %d', [h.MajorLinkerVersion]));
  Lines.Add(Format('MinorLinkerVersion: %d', [h.MinorLinkerVersion]));
  Lines.Add(Format('SizeOfCode: %d', [h.SizeOfCode]));
  Lines.Add(Format('SizeOfInitializedData: %d', [h.SizeOfInitializedData]));
  Lines.Add(Format('SizeOfUninitializedData: %d', [h.SizeOfUninitializedData]));
  Lines.Add(Format('AddressOfEntryPoint: %d', [h.AddressOfEntryPoint]));
  Lines.Add(Format('BaseOfCode: %d', [h.BaseOfCode]));
  Lines.Add(Format('BaseOfData: %d', [h.BaseOfData]));
  Lines.Add(Format('ImageBase: %d', [h.ImageBase]));
  Lines.Add(Format('SectionAlignment: %d', [h.SectionAlignment]));
  Lines.Add(Format('FileAlignment: %d', [h.FileAlignment]));
  Lines.Add(Format('MajorOperatingSystemVersion: %d',
    [h.MajorOperatingSystemVersion]));
  Lines.Add(Format('MinorOperatingSystemVersion: %d',
    [h.MinorOperatingSystemVersion]));
  Lines.Add(Format('MajorImageVersion: %d', [h.MajorImageVersion]));
  Lines.Add(Format('MinorImageVersion: %d', [h.MinorImageVersion]));
  Lines.Add(Format('MajorSubsystemVersion: %d', [h.MajorSubsystemVersion]));
  Lines.Add(Format('MinorSubsystemVersion: %d', [h.MinorSubsystemVersion]));
  Lines.Add(Format('Win32VersionValue: %d', [h.Win32VersionValue]));
  Lines.Add(Format('SizeOfImage: %d', [h.SizeOfImage]));
  Lines.Add(Format('SizeOfHeaders: %d', [h.SizeOfHeaders]));
  Lines.Add(Format('CheckSum: %d', [h.CheckSum]));
  Lines.Add(Format('Subsystem: %d', [h.Subsystem]));
  case h.Subsystem of
    IMAGE_SUBSYSTEM_NATIVE:
      Lines.Add(' Image doesnot require a subsystem. ');
    IMAGE_SUBSYSTEM_WINDOWS_GUI:
      Lines.Add(' Image runs in the Windows GUI subsystem. ');
    IMAGE_SUBSYSTEM_WINDOWS_CUI:
      Lines.Add(' Image runs in the Windows character subsystem. ');
    IMAGE_SUBSYSTEM_OS2_CUI:
      Lines.Add(' image runs in the OS/2 character subsystem. ');
    IMAGE_SUBSYSTEM_POSIX_CUI:
      Lines.Add(' image run in the Posix character subsystem. ');
  else
    Lines.Add(' unknown subsystem')
  end; { Case }
  Lines.Add(Format('DllCharacteristics: %d', [h.DllCharacteristics]));
  Lines.Add(Format('SizeOfStackReserve: %d', [h.SizeOfStackReserve]));
  Lines.Add(Format('SizeOfStackCommit: %d', [h.SizeOfStackCommit]));
  Lines.Add(Format('SizeOfHeapReserve: %d', [h.SizeOfHeapReserve]));
  Lines.Add(Format('SizeOfHeapCommit: %d', [h.SizeOfHeapCommit]));
  Lines.Add(Format('LoaderFlags: %d', [h.LoaderFlags]));
  Lines.Add(Format('NumberOfRvaAndSizes: %d', [h.NumberOfRvaAndSizes]));
end;
// Example Call, Beispielaufruf:
procedure TForm1.Button1Click(Sender: TObject);
var
  fs: TFilestream;
  signature: DWORD;
  dos_header: IMAGE_DOS_HEADER;
  pe_header: IMAGE_FILE_HEADER;
  opt_header: IMAGE_OPTIONAL_HEADER;
begin
  memo1.Clear;
  with Opendialog1 do
  begin
    Filter := 'Executables (*.EXE)|*.EXE';
    if Execute then
    begin
      fs := TFilestream.Create(FileName, fmOpenread or fmShareDenyNone);
      try
        fs.read(dos_header, SizeOf(dos_header));
        if dos_header.e_magic <> IMAGE_DOS_SIGNATURE then
        begin
          memo1.Lines.Add('Invalid DOS file header');
          Exit;
        end;
        DumpDOSHeader(dos_header, memo1.Lines);
        fs.seek(dos_header._lfanew, soFromBeginning);
        fs.read(signature, SizeOf(signature));
        if signature <> IMAGE_NT_SIGNATURE then
        begin
          memo1.Lines.Add('Invalid PE header');
          Exit;
        end;
        fs.read(pe_header, SizeOf(pe_header));
        DumpPEHeader(pe_header, memo1.Lines);
        if pe_header.SizeOfOptionalHeader > 0 then
        begin
          fs.read(opt_header, SizeOf(opt_header));
          DumpOptionalHeader(opt_header, memo1.Lines);
        end;
      finally
        fs.Free;
      end; { finally }
    end;
  end;
end;

Access to table disabled because of previous error - Read failure.

Автор: Наталия Елманова WEB-сайт: http://www.sources.ru При добавлении новых записей с помощью метода TTable.AppendRecord в индексированную таблицу FoxPro через какое-то время (то есть при одновременном добавлении большого количества записей) возникает ошибка: "Access to table disabled because of previous error. Read failure. File ". Возможно, причина заключается в том, что операции чтения-записи в файл, содержащий таблицу FoxPro, особенно при использовании кэширования, предоставляемого операционной системой, конфликтуют с содержимым индексного файла (это часто происходит при многопользовательской работе). Дело в том, что ранние версии dBase, FoxPro, Clipper работали с неподдерживаемыми индексами, то есть индексные файлы не обновлялись одновременно с таблицей, и для их синхронизации требовалось выполнять специальные операции. Но соответствующие средства разработки, применявшиеся в то время, обычно не поддерживали никаких аналогов транзакций - записи чаще всего вставлялись по одной. В случае применения старых версий формата FoxPro следует избегать кэширования при выполнении дисковых операций с файловым сервером, содержащим базу данных FoxPro. Кроме того, следует проверить и, если необходимо, изменить в настройках BDE параметры MINBUFSIZE, MAXBUFSIZE, LOCAL SHARE - возможно, проблема заключается в недостаточной величине буферов BDE для кэширования данных или в одновременном доступе к данным приложений, использующих и не использующих BDE.

Еще одним из способов решения этой проблемы (самым радикальным) является замена FoxPro на какую-нибудь из серверных СУБД. Например, InterBase неплохо справляется с одновременным вводом большого количества записей благодаря некоторым специфическим особенностям архитектуры этого сервера.

Как прочесть атрибут файла Last Accessed (последний доступ).

procedure TForm1.Button1Click(Sender: TObject);
var
  FileHandle: THandle;
  LocalFileTime: TFileTime;
  DosFileTime: DWORD;
  LastAccessedTime: TDateTime;
  FindData: TWin32FindData;
begin
  FileHandle := FindFirstFile('AnyFile.FIL', FindData);
  if FileHandle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(Handle);
    if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
    begin
      FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
      FileTimeToDosDateTime(LocalFileTime,
        LongRec(DosFileTime).Hi, LongRec(DosFileTime).Lo);
      LastAccessedTime := FileDateToDateTime(DosFileTime);
      Label1.Caption := DateTimeToStr(LastAccessedTime);
    end;
  end;
end;

Чтение из файла длинной строки.

Автор: LazyMan

Для решения этой задачи на помощь можно призвать потоки (TFileStream, TMemoryStream). Для поиска конца строк нужно искать пары CR/LF, но это делается очень легко, приблизительно так (я сегодня вечером слишком ленивый для реального кода):

Start := Stream.Position;
End := Start;
Repeat
Stream.Read(Buffer^, 1024);
CRPos := FindCR(Buffer^);   { где FindCR возвращает 0..1023 для CR,
и 1024, если он не найден}
Inc(End, CRPos);
Until CRPos < 1024;
GetMem(MyPChar, End - Start);  { Здесь может быть +-1 -- мне лень сегодня проверять! }
Stream.Seek(Start);
Stream.Read(MyPChar^, End - Start)

Затем установите CR в конце MyPChar в 0, и сделайте Seek в конец (End + 1), или что-то еще, чтобы пропустить LF.

Чтение из файла.

Пусть на диске есть некоторый текстовый файл. Нужно в диалоговое окно вывести содержимое этого файла. Решение задачи довольно очевидно: надо открыть файл, прочитать первую строку, затем вторую, третью и т. д. до тех пор, пока не будет достигнут конец файла. Но как определить, что прочитана последняя строка, достигнут конец файла? Для определения конца файла можно воспользоваться функцией EOF (End of File — конец файла). У функции EOF один параметр — файловая переменная. Значение функции EOF равно False, если прочитанный элемент данных не является последним в файле, т. е. возможно дальнейшее чтение. Если прочитанный элемент данных является последним, то значение EOF равно True. Значение функции EOF можно проверить сразу после открытия файла. Если при этом оно окажется равным True, то это значит, что файл не содержит ни одного элемента данных, т. е. является пустым (размер такого файла равен нулю).

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

unit rd_;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes,
  Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons;
type
  TForm1 = class(TForm)
    Button2: TButton;
    Edit1: TEdit;
    Memo1: TMemo;
    Button1: TButton;
    procedure Button2Click(Sender: TObject);
    procedure ButtonlClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForml;
implementation
{$R *.dfm}
// щелчок на кнопке Открыть
procedure TForm1.Button1Click(Sender: TObject);
var
  f: TextFile; // файл
  fName: String[80]; // имя файла
  buf: string[80]; // буфер для чтения из файла
begin
  fName := Edit1.Text; AssignFile(f, fName);
  {$!-}
  Reset(f); // открыть для чтения
  {$I+}
  if IOResult <> 0 then
  begin
    MessageDlgt 'Ошибка доступа к файлу ' + fName,
      mtError, [mbOk], 0); exit;
  end;
  // чтение из файла
  while not EOF(f) do
  begin
    readln(f, buf); // прочитать строку из файла
    Memo1.Lines.Add(buf); // добавить строку в поле Memo1
  end;
  CloseFile(f); // закрыть файл
end;
// щелчок на кнопке Сохранить — запись в файл
procedure TForml.Button2Click(Sender: TObject);
var
  f: TextFile; // файл
  fName: string[80]; // имя файла
  i: integer;
begin
  fName := Edit1.Text;
  AssignFile(f, fName);
  Rewrite(f); // открыть для перезаписи
  // запись в файл
  for i := 0 to Memo1.Lines.Count do // строки нумеруются с нуля
    writeln(f, Memo1.Lines[i]);
  CloseFile(f); // закрыть файл
  MessageDlg('Данные записаны в файл ', mtlnformation, [mbOk], 0);
end;
end.
Для организации обработки файла использована инструкция цикла while, которая обеспечивает проверку значения функции EOF перед каждым чтением, в том числе и перед первым. Наличие кнопки Сохранить и соответствующей ей процедуры позволяет сохранить содержимое поля Memo в файле, т. е. программа чтение из файла представляет собой примитивный редактор текста.

Добавление очередной прочитанной из файла строки в поле Memo выполняется применением метода Add к свойству Lines.

Как прочитать из модема.

Помимо двух настоящих извращений - хоккея на траве и балета на льду, существует и третье - диалап через ip-телефонию.

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

var
  PortSpec : array[0..255] of char;
  PortNo : Word;
  success : Boolean;
  error:integer;
begin
  FillChar(PortSpec,Sizeof(PortSpec),#0);
  StrPCopy(PortSpec,'Com1:19200,n,8,1');
  PortSpec[3]:=Char(Ord(PortSpec[3])+Ord(PortNo));
  if not BuildCommDCB(PortSpec,Mode) then
  begin
    //какая-то ошибка...
    Exit;
  end;
  PortSpec[5]:=#0; { 'Com1:' }
  Mode.Flags:=EV_RXCHAR + EV_EVENT2; { $1001 }
  Com := CreateFile(PortSpec,GENERIC_READ or GENERIC_WRITE,
  0,             { comm устройство открывается с эксклюзивным доступом }
  nil,           { нет security битов }
  OPEN_EXISTING, { comm устройства должны использовать OPEN_EXISTING }
  0,             { not overlapped I/O }
  0              { hTemplate должен быть NULL для comm устройств }
  );
  if Com = INVALID_HANDLE_VALUE then
    Error := GetLastError;
  Success := GetCommState(Com,Mode);
  if not Success then // Обработчик ошибки.
  begin
  end;
  Mode.BaudRate := 19200;
  Mode.ByteSize := 8;
  Mode.Parity := NOPARITY;
  Mode.StopBits := ONESTOPBIT;//нужен был для перезаписи в NT
  Success := SetCommState(Com, Mode);
  if not Success then // Обработчик ошибки.
  begin
  end;
end;

Переменная "com" типа dword.

Вы так же можете очистить буффер COM порта.

PurgeComm(Com, PURGE_RXCLEAR or PURGE_TXCLEAR); 

И прочитать из него.

function ReadCh(var Ch: Byte): dword; 
var 
  n: dword; 
begin 
  Readfile(Com, ch, 1, result, nil); 
end;

Читаем из файла, открытого другим приложением.

Даже если файл открыт с низкими привелегиями (используя ReadOnly, ShareReadWrite) , иногда открытие уже открытого файла может приводить к ошибкам, особенно, если это файл интенсивно используется другим приложением. Самый простой способ решить эту проблемму - это использовать MemoryStream вместо непосредственного доступа к файлу:

var
  Memory: TMemoryStream;
begin
  Memory := TMemoryStream.Create;
  try
    Memory.LoadFromFile('DelphiWorld.dat'); // это он!!
    ...
    Memory.read(...); // Вы можете использовать методы чтения как у файлов
    Memory.Seek(...);
    FileSize := Memory.Size;
    ...
  finally
    Memory.Free;
  end;
end;

Данный способ никогда не открывает файл, а заместо этого создаёт копию его в памяти. Конечно Вы можете и записать в поток (Stream) в Памяти(Memory), но изменения не будут записаны на диск до тех пор, пока Вы не запишете их в файл (командой SaveToFile).

Прочитать и изменить текстовый файл строка за строкой.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure TForm1.Button1Click(Sender: TObject);
 var
   i, z: Integer;
   f: TextFile;
   t: string;
   Data: array of string;
 begin
   if OpenDialog1.Execute then
   begin
     //Read line by line in to the array data 
    AssignFile(f, OpenDialog1.FileName);
     Reset(f);
     z := 0;
     SetLength(Data, 0);
     //Repeat for each line until end of file 
    repeat
       Inc(z);
       readln(f, t);
       SetLength(Data, Length(Data) + Length(t));
       Data[z] := t;
     until EOF(f);
     SetLength(Data, Length(Data) + 3 * z);
     //Add to each line the line number 
    for i := 1 to z do Data[i] := IntToStr(i) + ' ' + Data[i];
     SetLength(Data, Length(Data) + 2);
     //Add a carriage return and line feed 
    Data[1] := Data[1] + #13 + #10;
     i       := Length(Data[5]);
     Data[5] := '';
     SetLength(Data, Length(Data) - i);
     //create a new textfile with the new data 
    AssignFile(f, OpenDialog1.FileName + '2');
     ReWrite(f);
     //write all lines 
    for i := 1 to z do writeln(f, Data[i]);
     //save file and close it 
    CloseFile(f);
   end;
 end;

Чтение OLE из Blob поля Paradox.

Автор: Eryk ...после моих дискуссий с людьми из службы технической поддержки Borland вывод один -- это невозможно!

Попробуйте так:

procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  b: TBlobStream;
begin
  try
    b := TBlobStream.Create((Table1.FieldByName('OLE') as TBlobField),bmRead);
    OLEContainer1.LoadFromStream(b);
  finally
    b.free;
  end;
end;

...и:

procedure TForm1.SpeedButton2Click(Sender: TObject);
var
  b: TBlobStream;
begin
  try
    Table1.Insert;
    b := TBlobstream.Create((Table1.FieldByName('OLE') as TBlobField),bmReadWrite);
    OLEContainer1.SaveToStream(b);
    Table1.Post;
  finally
    b.free;
  end;
end;

Я, кажется, припоминаю несколько ошибок GPFs с этим кодом, но это, вероятно, связано с тем, что я использую WinNT с другим распределением памяти... тем не менее, основные функции работали как положено (т.е. данные сохранялись и загружались). Основная специфика проявилась в том, что PdoxWIN не смог прочесть данные TOLEContainer. Но это результаты моих экспериментов и предположений, исходя из которых PdoxWIN ожидает 8-байтовый заголовок BLOB-поля, который ему просто не дает TOLEContainer... если это так, то это легко обойти.

Прочитать адрес отправителя сообщения для MS Outlook.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  If you tried to work with messages from Delphi, you know that received message have the 
  SenderName property (name of sender) but doesn't allow to read the real address of sender. 
  Something like SenderAddress is not available. 
  There exist a few methods to retrieve this information: 
  1. help file says that sender is in Recipients collection 
  with Type property - 0 (olOriginator). 
  But this way does work for any version of MS Outlook. 
  So just iterate thru collection of Recipients and find an item with Type=0 
  couldn't return required value 
  2. as alternative you can read a ReplyTo property - there you'll receive an address 
  (but generally ReplyTo and Sender could be different). 
  For example, in messages which I send from own mail account these values are different. 
  3. to create a new MailItem (just will be destroyed without saving in end of work), 
  define a Recipient as value which you received from SenderName of your original 
  message and call a Resolve method - after that you'll recieve a correct email address of this sender. 
  4. more correct and fast solution is the next: 
}
 var
  s: string;
  objCDO: OLEVariant;
 begin
   objCDO := CreateOLEObject('MAPI.Session');
   objCDO.Logon('', '', False, False);
   objMsg := objCDO.GetMessage(itemOL.EntryID, itemOL.Parent.StoreID);
   s := objMsg.Sender.Address;
   ShowMessage(s);
   objMsg := Unassigned;
   objCDO := Unassigned;
 end
 { where itemOL is a MailItem which contain a SenderName but doesn't contain a SenderAddress }

Установка атрибута Только для чтения у столбцов компонента StringGrid.

Сидят двое программистов в кафе и, натурально, пьют пиво. Мимо проходит такая девушка, вся из себя девушка. Один программист другому: - Ты посмотри какие propertes!!! Другой: - Вчера проверял, все read only.

Манипулирование вышеуказанным атрибутом возможно в обработчике события OnSelectCell:

if Col mod 2 = 0 then
  grd.Options := grd.Options + [goEditing]
else
  grd.Options := grd.Options - [goEditing];

Чтение из открытого файла.

Автор: Neutrino WEB-сайт: http://forum.vingrad.ru

Даже если файл открыт с низкими привелегиями (используя ReadOnly, ShareReadWrite) , иногда открытие уже открытого файла может приводить к ошибкам, особенно, если это файл интенсивно используется другим приложением. Самый простой способ решить эту проблемму - это использовать MemoryStream вместо непосредственного доступа к файлу:

var
  Memory: TMemoryStream;
begin
  Memory := TMemoryStream.Create;
  try
    Memory.LoadFromFile('busyfile.dat'); // это он!!
    ..
    Memory.Read(...); // Вы можете использовать методы чтения как у файлов
    Memory.Seek(...);
    FileSize := Memory.Size;
    ..
  finally
    Memory.Free;
  end;
end;
Данный способ никогда не открывает файл, а заместо этого создаёт копию его в памяти. Конечно Вы можете и записать в поток (Stream) в Памяти(Memory), но изменения не будут записаны на диск до тех пор, пока Вы не запишете их в файл (командой SaveToFile).

Решение хорошее, но накладно если файл большой...

Чтение из открытого файла 2.

Автор: Vit WEB-сайт: http://forum.vingrad.ru

var
  b: string[15];
begin
  with TFileStream.create('c:\MyFile.doc', fmShareDenyNone) do
  try
    read(b, 14);
    showmessage(b);
  finally
    Free;
  end;
end;

Чтение из открытого файла 3.

Автор: PILOTIK WEB-сайт: http://forum.vingrad.ru

procedure TForm1.Button1Click(Sender: TObject);
type
  AnyType = byte;
var
  F: file of AnyType;
const
  FName = 'D:/Exp.exe';
begin
  begin
    AssignFile(F, FName); { File selected in dialog }
    FileMode := fmOpenRead;
    Reset(F);
    // ...
    // ...
    CloseFile(F);
    FileMode := fmOpenReadWrite;
  end;
end;

Как прочитать очередь печати.

Автор: http://www.swissdelphicenter.ch

uses
  Winspool, Printers;
function GetCurrentPrinterHandle: THandle;
var
  Device, Driver, Port: array[0..255] of Char;
  hDeviceMode: THandle;
begin
  Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
  if not OpenPrinter(@Device, Result, nil) then
    RaiseLastWin32Error;
end;
function SavePChar(p: PChar): PChar;
const
  error: PChar = 'Nil';
begin
  if not Assigned(p) then
    Result := error
  else
    Result := p;
end;
procedure TForm1.Button1Click(Sender: TObject);
type
  TJobs = array[0..1000] of JOB_INFO_1;
  PJobs = ^TJobs;
var
  hPrinter: THandle;
  bytesNeeded, numJobs, i: Cardinal;
  pJ: PJobs;
begin
  hPrinter := GetCurrentPrinterHandle;
  try
    EnumJobs(hPrinter, 0, 1000, 1, nil, 0, bytesNeeded,
      numJobs);
    pJ := AllocMem(bytesNeeded);
    if not EnumJobs(hPrinter, 0, 1000, 1, pJ, bytesNeeded,
      bytesNeeded, numJobs) then
      RaiseLastWin32Error;
    memo1.Clear;
    if numJobs = 0 then
      memo1.Lines.Add('No jobs in queue')
    else
      for i := 0 to Pred(numJobs) do
        memo1.Lines.Add(Format('Printer %s, Job %s, Status (%d): %s',
          [SavePChar(pJ^[i].pPrinterName), SavePChar(pJ^[i].pDocument),
          pJ^[i].Status, SavePChar(pJ^[i].pStatus)]));
  finally
    ClosePrinter(hPrinter);
  end;
end;

Как прочитать свойство напрямую из его ресурса.

Автор: Michael Duerig WEB-сайт: http://www.lmc-mediaagentur.de

Does anyone know if there is an easy way to load the value of a component's property directly from its resource without creating the component? Something like:

if ReadPropertyValue('Form1.Button1', 'width') > 1000 then
  ShowMessage('You are about to create a big button!');
function TForm1.ReadProp(r: TReader): string;
begin
  result := '';
  {Determine the value type of the property, read it with the appropriate method of TReader
  and convert it to string. Not all value types are implemented here but you get the idea.}
  case r.NextValue of
    vaInt8, vaInt16, vaInt32:
      result := IntToStr(r.ReadInteger);
    vaExtended:
      result := FloatToStr(r.ReadFloat);
    vaString:
      result := r.ReadString;
  else
    r.SkipValue; {Not implemented}
  end;
end;
procedure TForm1.ReadRes(PropPath: string; r: TReader);
var
  p: string;
begin
  {Skip the class name}
  r.ReadStr;
  {Construct the property path}
  if PropPath = '' then
    p := r.ReadStr
  else
    p := PropPath + '.' + r.ReadStr;
  {Read all properties and its values and fill them into the memo}
  while not r.EndOfList do
    Memo1.Lines.Add(p + '.' + r.ReadStr + ' = ' + ReadProp(r));
  {Skip over the end of the list of the properties of this component}
  r.CheckValue(vaNull);
  {Recursively read the properties of all sub-components}
  while not r.EndOfList do
  begin
    ReadRes(p, r);
    r.CheckValue(vaNull);
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  strm: TResourceStream;
  Reader: TReader;
begin
  strm := TResourceStream.Create(HInstance, 'TForm1', RT_RCDATA);
  Reader := TReader.Create(strm, 1024);
  try
    Memo1.Clear;
    Reader.ReadSignature;
    ReadRes('', Reader);
  finally
    Reader.Free;
    strm.Free;
  end;
end;

Only one small problem. r.SkipValue was protected (in D5) but I hacked that out with the following code:

type
  THackReader = class(TReader);
  { ... }
  THackReader(r).SkipValue;

And now it works like a charm.

Чтение текста RichEdit из базы данных.

Автор: Сергей Лагонский

Запись TRichEdit в файл и сохранение этого файла в БД является наиболее простым способом сохранения текста компонента в таблице, но тот же способ может быть достигнут и без использования промежуточного файла, а именно с помощью TBlobStream. Пример, приведенный ниже, демонстрирует чтение текста TRichEdit из таблицы. Надеюсь с записью текста проблем у вас уже не возникнет.

procedure ReadRichEditFromTable(Table : TTable; var RichEdit : TRichEdit);
var
  BlobStream: TBlobStream;
begin
  try
    BlobStream := TBlobStream.Create(Table.FieldByName('BODY') as TBlobField, bmRead);
    if (not Table.FieldByName('BLOBFieldName').IsNull) then
    begin
      RichEdit.Lines.LoadFromStream (BlobStream);
    end;
  finally
    BlobStream.Free;
  end;
end;

Прочитать ROM-BIOS.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// An Example of this unit is availabe as Demo Download. 
// Ein Beispiel zur Anwendung dieser Unit kann als Demo heruntergeladen werden. 
//////////////////////////////////////////////////////////////////////////////// 
// 
//                            BIOS Helper for Delphi 
// 
//               BIOS related utilities for Win9x and WinNT(i386) 
// 
//////////////////////////////////////////////////////////////////////////////// 
// 
//  The Original Code is: 
//   BiosHelp.pas, released 2001-09-02. 
// 
//  The Initial Developer of the Original Code is Nico Bendlin. 
// 
//  Portions created by Nico Bendlin are 
//   Copyright (C) 2001-2003 Nico Bendlin. All Rights Reserved. 
// 
//  Contributor(s): 
//   Nico Bendlin<nicode@gmx.net> 
// 
//  The contents of this file are subject to the Mozilla Public License Version 
//  1.1 (the "License"); you may not use this file except in compliance with the 
//  License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
// 
//  Software distributed under the License is distributed on an "AS IS" basis, 
//  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for 
//  the specific language governing rights and limitations under the License. 
// 
//  Alternatively, the contents of this file may be used under the terms of 
//  either the GNU General Public License Version 2 or later (the "GPL"), or 
//  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 
//  in which case the provisions of the GPL or the LGPL are applicable instead 
//  of those above. If you wish to allow use of your version of this file only 
//  under the terms of either the GPL or the LGPL, and not to allow others to 
//  use your version of this file under the terms of the MPL, indicate your 
//  decision by deleting the provisions above and replace them with the notice 
//  and other provisions required by the GPL or the LGPL. If you do not delete 
//  the provisions above, a recipient may use your version of this file under 
//  the terms of any one of the MPL, the GPL or the LGPL. 
// 
//////////////////////////////////////////////////////////////////////////////// 
// 
//  Revision: 
// 
//    2003-02-15  2.00 [NicoDE] 
//                     - generic dump method completely rewritten 
//                     - default range is now E000:0000-F000:FFFF 
// 
//////////////////////////////////////////////////////////////////////////////// 
{$IFDEF CONDITIONALEXPRESSIONS}
   {$DEFINE DELPHI6UP}
   {$IF NOT DEFINED(VER140)}
     {$DEFINE DELPHI7UP}
   {$IFEND}
 {$ENDIF}
 unit BiosHelp {$IFDEF DELPHI6UP} platform {$ENDIF};
 {$MINENUMSIZE 4}
 {$WEAKPACKAGEUNIT}
 {$IFDEF DELPHI7UP}
   {$WARN UNSAFE_TYPE OFF}
   {$WARN UNSAFE_CODE OFF}
 {$ENDIF}
 interface
 uses
   Windows;
 const
   RomBiosDumpBase = $000E0000;
   RomBiosDumpEnd  = $000FFFFF;
   RomBiosDumpSize = RomBiosDumpEnd - RomBiosDumpBase + 1;
 type
   PRomBiosDump = ^TRomBiosDump;
   TRomBiosDump = array [RomBiosDumpBase..RomBiosDumpEnd] of Byte;
 type
   TRomDumpMethod = (rdmAutomatic,  // Autodetect OS type and use proper method 
    rdmGeneric,    // Use 16-bit EXE program to dump the BIOS 
    rdmMemory,     // Dump from process's address space (Win9x) 
    rdmPhysical    // Dump from physical memory object (WinNT) 
    );
 function DumpRomBios(out Dump: TRomBiosDump;
   Method: TRomDumpMethod = rdmAutomatic; Timeout: DWORD = 5000): Boolean;
 function DumpRomBiosEx(RomBase: Pointer; RomSize: Cardinal; out Dump;
   Method: TRomDumpMethod = rdmAutomatic; Timeout: DWORD = 5000): Boolean;
 procedure ReadRomDumpBuffer(const Dump: TRomBiosDump; Addr: Pointer;
   var Buffer; Size: Cardinal);
 procedure ReadRomDumpBufferEx(const Dump; Base, Addr: Pointer;
   var Buffer; Size: Cardinal);
 function GetRomDumpAddr(const Dump: TRomBiosDump; Addr: Pointer): Pointer;
 function GetRomDumpAddrEx(const Dump; Base, Addr: Pointer): Pointer;
 implementation
 //////////////////////////////////////////////////////////////////////////////// 
// 
//  DumpRomBios16 (rdmGeneric) 
// 
//    Creates an 16-bit EXE program in TEMP and runs it redirected to an file. 
// 
//    WARNING: One day 16-bit code will not run on future Windows. 
//    WARNING: You are dumping the BIOS inside the MS-DOS 'emulator'. 
// 
function _RomDumpCode(RomBase: Pointer; RomSize: Cardinal;
   out Code: Pointer; out Size: Cardinal): Boolean;
 const
   BlockSize = $1000;
 type                                     // ; RomDump (dumps mem to STDOUT) 
  PRomDumpCode = ^TRomDumpCode;          // ; BlockSize MUST be multiple of 10h. 
  TRomDumpCode = packed record           // 
    _header: TImageDosHeader;            // 
    _notice: array[0..$4F] of AnsiChar;  // @@note: db      'RomDump 2.0', ... 
    init: packed record                  // @@init: 
      _mov_44: array[0..2] of Byte;      //         mov     ax, 4400h 
      _mov_bx: array[0..2] of Byte;      //         mov     bx, 0001h 
      _dos_21: array[0..1] of Byte;      //         int     21h 
      _jcf_18: array[0..1] of Byte;      //         jc      @@code 
      _and_dx: array[0..3] of Byte;      //         and     dx, 0082h 
      _cmp_dx: array[0..3] of Byte;      //         cmp     dx, 0082h 
      _jne_0E: array[0..1] of Byte;      //         jne     @@code 
      _psh_cs: Byte;                     //         push    cs 
      _pop_ds: Byte;                     //         push    ds 
      _mov_dx: array[0..2] of Byte;      //         mov     dx, offset @@note 
      _mov_09: array[0..1] of Byte;      //         mov     ah, 09h 
      _int_21: array[0..1] of Byte;      //         int     21h 
      _mov_4C: array[0..2] of Byte;      //         mov     ax, 4C01h 
      _int_20: array[0..1] of Byte;      //         int     21h 
    end;                                 // 
    code: packed record                  // @@code: 
      _mov_cx: Byte;
        BlockCount: Word;   //         mov     cx, <BlockCount> 
      _mov_dx: Byte;
        DatSegment: Word;   //         mov     dx, <DatSegment> 
      _jcx_1C: array[0..1] of Byte;      //         jcxz    @@rest 
    end;                                 // 
    loop: packed record                  // @@loop: 
      _psh_cx: Byte;                     //         push    cx 
      _psh_dx: Byte;                     //         push    dx 
      _mov_ds: array[0..1] of Byte;      //         mov     ds, dx 
      _mov_dx: Byte;
        DatOffset: Word;    //         mov     dx, <DatOffset> 
      _mov_cx: array[0..2] of Byte;      //         mov     cx, <BlockSize> 
      _mov_bx: array[0..2] of Byte;      //         mov     bx, 0001h 
      _mov_ax: array[0..2] of Byte;      //         mov     ax, 4000h 
      _int_21: array[0..1] of Byte;      //         int     21h 
      _pop_dx: Byte;                     //         pop     dx 
      _pop_cx: Byte;                     //         pop     cx 
      _jcf_1C: array[0..1] of Byte;      //         jc      @@exit 
      _add_dx: array[0..3] of Byte;      //         add     dx, <BlockSize/10h> 
      _lop_E4: array[0..1] of Byte;      //         loop    @@loop 
    end;                                 // 
    rest: packed record                  // @@rest: 
      _mov_ds: array[0..1] of Byte;      //         mov     ds, dx 
      _mov_dx: Byte;
        DatOffset: Word;    //         mov     dx, <DatOffset> 
      _mov_cx: Byte;
        LenghtMod: Word;    //         mov     cx, <LenghtMod> 
      _mov_bx: array[0..2] of Byte;      //         mov     bx, 0001h 
      _mov_ax: array[0..2] of Byte;      //         mov     ax, 4000h 
      _jcx_06: array[0..1] of Byte;      //         jcxz    @@exit 
      _int_21: array[0..1] of Byte;      //         int     21h 
      _jcf_02: array[0..1] of Byte;      //         jc      @@exit 
      _mov_al: array[0..1] of Byte;      //         mov     al, 00h 
    end;                                 // 
    Exit: packed record                  // @@exit: 
      _mov_ah: array[0..1] of Byte;      //         mov     ah, 4Ch 
      _int_21: array[0..1] of Byte;      //         int     21h 
    end;                                 // 
  end;
 const
   RomDumpCodeSize = SizeOf(TRomDumpCode) - SizeOf(TImageDosHeader);
   RomDumpCode: TRomDumpCode = (_header: (e_magic: IMAGE_DOS_SIGNATURE;
     e_cblp: Word(RomDumpCodeSize) and $1FF;
     e_cp: Word((RomDumpCodeSize - 1) shr 9) + 1;
     e_crlc: $0000;
     e_cparhdr: SizeOf(TImageDosHeader) shr 4;
     e_minalloc: $0000;
     e_maxalloc: $FFFF;
     e_ss: $0000;
     e_sp: $1000;
     e_csum: $0000;
     e_ip: SizeOf(RomDumpCode._notice);
     e_cs: $0000;
     e_lfarlc: SizeOf(TImageDosHeader);
     e_ovno: $0000;
     e_res: ($0000, $0000, $0000, $0000);
     e_oemid: $0000;
     e_oeminfo: $0000;
     e_res2: ($0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,
     $0000, $0000);
     _lfanew: $00000000
     );
     _notice: #13#10+
    'RomDump 2.0'#13#10 +
     'Copyright (c) 2003 Nico Bendlin'#13#10 +
     #13#10+
    'Usage: RomDump > filename'#13#10 +
     #13#10$';
     init: (_mov_44: ($B8, $00, $44);
     _mov_bx: ($BB, $01, $00);
     _dos_21: ($CD, $21);
     _jcf_18: ($72, $18);
     _and_dx: ($81, $E2, $82, $00);
     _cmp_dx: ($81, $FA, $82, $00);
     _jne_0E: ($75, $0E);
     _psh_cs: $0E;
     _pop_ds: $1F;
     _mov_dx: ($BA, $00, $00);
     _mov_09: ($B4, $09);
     _int_21: ($CD, $21);
     _mov_4C: ($B8, $01, $4C);
     _int_20: ($CD, $21);
     );
     code: (_mov_cx: $B9; BlockCount: $0010;
     _mov_dx: $BA; DatSegment: $F000;
     _jcx_1C: ($E3, $1C)
     );
     loop: (_psh_cx: $51;
     _psh_dx: $52;
     _mov_ds: ($8E, $DA);
     _mov_dx: $BA; DatOffset: $0000;
     _mov_cx: ($B9, Lo(BlockSize), Hi(BlockSize));
     _mov_bx: ($BB, $01, $00);
     _mov_ax: ($B8, $00, $40);
     _int_21: ($CD, $21);
     _pop_dx: $5A;
     _pop_cx: $59;
     _jcf_1C: ($72, $1C);
     _add_dx: ($81, $C2, Lo(BlockSize shr 4), Hi(BlockSize shr 4));
     _lop_E4: ($E2, $E4)
     );
     rest: (_mov_ds: ($8E, $DA);
     _mov_dx: $BA; DatOffset: $0000;
     _mov_cx: $B9; LenghtMod: $0000;
     _mov_bx: ($BB, $01, $00);
     _mov_ax: ($B8, $00, $40);
     _jcx_06: ($E3, $06);
     _int_21: ($CD, $21);
     _jcf_02: ($72, $02);
     _mov_al: ($B0, $00)
     );
     Exit: (_mov_ah: ($B4, $4C);
     _int_21: ($CD, $21)
     )
     );
 begin
   Result := False;
   if (RomSize > 0) and (RomSize <= $100000) and
     (Cardinal(RomBase) < $100000) and
     (Cardinal(RomBase) + RomSize <= $100000) then
   begin
     Size := SizeOf(TRomDumpCode);
     Code := Pointer(LocalAlloc(LPTR, Size));
     if Code <> nil then
       try
         PRomDumpCode(Code)^ := RomDumpCode;
         with PRomDumpCode(Code)^ do
         begin
           code.BlockCount := Word(RomSize div BlockSize);
           code.DatSegment := Word(Cardinal(RomBase) shr 4);
           loop.DatOffset  := Word(Cardinal(RomBase)) and $000F;
           rest.DatOffset  := loop.DatOffset;
           rest.LenghtMod  := Word(RomSize mod BlockSize);
         end;
         Result := True;
       except
         LocalFree(HLOCAL(Code));
         Code := nil;
         Size := 0;
       end;
   end;
 end;
 function _SaveRomDumpCodeToFile(RomBase: Pointer; RomSize: Cardinal;
   const FileName: string): Boolean;
 var
   Code: Pointer;
   Size: Cardinal;
   Hand: THandle;
   Num: DWORD;
 begin
   Result := False;
   if _RomDumpCode(RomBase, RomSize, Code, Size) then
     try
       Hand := CreateFile(PChar(FileName), GENERIC_WRITE, FILE_SHARE_READ, nil,
         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
       if Hand <> INVALID_HANDLE_VALUE then
         try
           Result := WriteFile(Hand, Code^, Size, Num, nil) and (Num = Size);
           if not Result then
             DeleteFile(PChar(FileName));
         finally
           CloseHandle(Hand);
         end;
     finally
       LocalFree(HLOCAL(Code));
     end;
 end;
 function _ExecuteRomDumpCode(const Code, Dump: string; Timeout: DWORD): Boolean;
 var
   ComSpec: string;
   StartInfo: TStartupInfo;
   ProcInfo: TProcessInformation;
   ErrorMode: Cardinal;
 begin
   Result := False;
   SetLength(ComSpec, MAX_PATH + 1);
   SetLength(ComSpec,
     GetEnvironmentVariable('ComSpec', PChar(@ComSpec[1]), MAX_PATH));
   if Length(ComSpec) <= 0 then
     Exit;
   FillChar(StartInfo, SizeOf(TStartupInfo), 0);
   StartInfo.cb := SizeOf(TStartupInfo);
   StartInfo.dwFlags := STARTF_USESHOWWINDOW;
   StartInfo.wShowWindow := SW_HIDE;
   ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOGPFAULTERRORBOX or
     SEM_NOALIGNMENTFAULTEXCEPT or SEM_NOOPENFILEERRORBOX);
   try
     if CreateProcess(nil, PChar(ComSpec + ' /C ' + Code + ' > ' + Dump),
       nil, nil, False, HIGH_PRIORITY_CLASS, nil, nil, StartInfo, ProcInfo) then
       try
         Result :=
           (WaitForSingleObject(ProcInfo.hProcess, Timeout) <> WAIT_TIMEOUT);
         if not Result then
           TerminateProcess(ProcInfo.hProcess, STATUS_TIMEOUT);
       finally
         CloseHandle(ProcInfo.hThread);
         CloseHandle(ProcInfo.hProcess);
       end;
   finally
     SetErrorMode(ErrorMode);
   end;
 end;
 function DumpRomBios16(RomBase: Pointer; RomSize: Cardinal; var Dump;
   Timeout: DWORD): Boolean;
 var
   Tmp: array [0..MAX_PATH] of Char;
   Dmp: array [0..MAX_PATH] of Char;
   Exe: array [0..MAX_PATH] of Char;
   Hnd: THandle;
   Num: DWORD;
 begin
   Result := False;
   if GetTempPath(MAX_PATH, Tmp) > 0 then
     GetShortPathName(Tmp, Tmp, MAX_PATH)
   else
     lstrcpy(Tmp, '.');
   if GetTempFileName(Tmp, 'rom', 0, Dmp) > 0 then
     try
       lstrcpy(Exe, Dmp);
       lstrcat(Exe, '.exe');  // Win9x requires .EXE extention 
      if _SaveRomDumpCodeToFile(RomBase, RomSize, Exe) then
         try
           if _ExecuteRomDumpCode(Exe, Dmp, Timeout) then
           begin
             Hnd := CreateFile(Dmp, GENERIC_READ, FILE_SHARE_READ or
               FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
             if Hnd <> INVALID_HANDLE_VALUE then
               try
                 Result := ReadFile(Hnd, Dump, RomSize, Num, nil) and (Num = RomSize);
               finally
                 CloseHandle(Hnd);
               end;
           end;
         finally
           DeleteFile(Exe);
         end;
     finally
       DeleteFile(Dmp);
     end;
 end;
 //////////////////////////////////////////////////////////////////////////////// 
// 
//  DumpRomBios9x (rdmMemory) 
// 
//    Win9x maps the BIOS into every process - therefore it's directly accessed. 
// 
function DumpRomBios9x(RomBase: Pointer; RomSize: Cardinal; var Dump): Boolean;
 begin
   Result := False;
   try
     Move(RomBase^, Dump, RomSize);
     Result := True;
   except
     // ignore exeptions 
  end
 end;
 //////////////////////////////////////////////////////////////////////////////// 
// 
//  DumpRomBiosNt (rdmPhysical) 
// 
//    On WinNT the BIOS is accessable through section '\Device\PhysicalMemory'. 
//    This object can only be opened by members of local 'Adminstrators' group. 
//    ZwOpenSection and RtlNtStatusToDosError are documented in newer MSDN/DDK. 
// 
type
   NTSTATUS = Integer;
   PUnicodeString = ^TUnicodeString;
   TUnicodeString = packed record
     Length: Word;
     MaximumLength: Word;
     Buffer: PWideChar;
   end;
   PObjectAttributes = ^TObjectAttributes;
   TObjectAttributes = record
     Length: ULONG;
     RootDirectory: THandle;
     ObjectName: PUnicodeString;
     Attributes: ULONG;
     SecurityDescriptor: PSecurityDescriptor;
     SecurityQualityOfService: PSecurityQualityOfService;
   end;
   TFNZwOpenSection = function(out Section: THandle; Access: ACCESS_MASK;
     Attributes: PObjectAttributes): NTSTATUS;
    stdcall;
   TFNRtlNtStatusToDosError = function(Status: NTSTATUS): DWORD;
    stdcall;
 const
   PhysMemDevName = '\Device\PhysicalMemory';
   PhysMemName: TUnicodeString = (Length: Length(PhysMemDevName) * SizeOf(WideChar);
     MaximumLength: Length(PhysMemDevName) * SizeOf(WideChar) + SizeOf(WideChar);
     Buffer: PhysMemDevName;
     );
   PhysMemMask: ACCESS_MASK = SECTION_MAP_READ;
   PhysMemAttr: TObjectAttributes = (Length: SizeOf(TObjectAttributes);
     RootDirectory: 0;
     ObjectName: @PhysMemName;
     Attributes: $00000040;  // OBJ_CASE_INSENSITIVE 
    SecurityDescriptor: nil;
     SecurityQualityOfService: nil;
     );
 var
   ZwOpenSection: TFNZwOpenSection;
   RtlNtStatusToDosError: TFNRtlNtStatusToDosError;
 function DumpRomBiosNt(RomBase: Pointer; RomSize: Cardinal; var Dump): Boolean;
 var
   HMod: HMODULE;
   Stat: NTSTATUS;
   Sect: THandle;
   View: Pointer;
 begin
   Result := False;
   HMod   := GetModuleHandle('ntdll.dll');
   if HMod = 0 then
     SetLastError(ERROR_CALL_NOT_IMPLEMENTED)
   else
   begin
     if not Assigned(ZwOpenSection) then
       ZwOpenSection := GetProcAddress(HMod, 'ZwOpenSection');
     if not Assigned(RtlNtStatusToDosError) then
       RtlNtStatusToDosError := GetProcAddress(HMod, 'RtlNtStatusToDosError');
     if not Assigned(ZwOpenSection) or not Assigned(RtlNtStatusToDosError) then
       SetLastError(ERROR_CALL_NOT_IMPLEMENTED)
     else
     begin
       Stat := ZwOpenSection(Sect, PhysMemMask, @PhysMemAttr);
       if Stat >= 0 then
         try
           View := MapViewOfFile(Sect, PhysMemMask, 0, Cardinal(RomBase), RomSize);
           if View <> nil then
             try
               Move(View^, Dump, RomSize);
               Result := True;
             finally
               UnmapViewOfFile(View);
             end;
         finally
           CloseHandle(Sect);
         end
       else
         SetLastError(RtlNtStatusToDosError(Stat));
     end;
   end;
 end;
 //////////////////////////////////////////////////////////////////////////////// 
// 
//  DumpRomBios(Ex) 
// 
//    Public functions to call OS-dependent implementations. 
// 
function DumpRomBios(out Dump: TRomBiosDump;
   Method: TRomDumpMethod = rdmAutomatic; Timeout: DWORD = 5000): Boolean;
 begin
   Result := DumpRomBiosEx(Pointer(RomBiosDumpBase), RomBiosDumpSize, Dump,
     Method, Timeout);
 end;
 function DumpRomBiosEx(RomBase: Pointer; RomSize: Cardinal; out Dump;
   Method: TRomDumpMethod = rdmAutomatic; Timeout: DWORD = 5000): Boolean;
 begin
   Result := False;
   case Method of
     rdmAutomatic:
       if (GetVersion() and $80000000) <> 0 then
         Result := DumpRomBios9x(RomBase, RomSize, Dump)
       else
         begin
           Result := DumpRomBiosNt(RomBase, RomSize, Dump);
           if not Result then
             DumpRomBios16(RomBase, RomSize, Dump, DWORD(Timeout));
         end;
       rdmGeneric:
       Result := DumpRomBios16(RomBase, RomSize, Dump, DWORD(Timeout));
     rdmMemory:
       Result := DumpRomBios9x(RomBase, RomSize, Dump);
     rdmPhysical:
       Result := DumpRomBiosNt(RomBase, RomSize, Dump);
     else
       SetLastError(ERROR_INVALID_PARAMETER);
   end;
 end;
 //////////////////////////////////////////////////////////////////////////////// 
// 
//  ReadRomDumpBuffer(Ex) / GetRomDumpAddr(Ex) 
// 
//    Utilities to simplify the access to dumps. 
// 
procedure ReadRomDumpBuffer(const Dump: TRomBiosDump; Addr: Pointer;
   var Buffer; Size: Cardinal);
 begin
   Move(Dump[Cardinal(Addr)], Buffer, Size);
 end;
 procedure ReadRomDumpBufferEx(const Dump; Base, Addr: Pointer;
   var Buffer; Size: Cardinal);
 begin
   Move(Pointer(Cardinal(@Dump) + Cardinal(Addr) - Cardinal(Base))^,
     Buffer, Size);
 end;
 function GetRomDumpAddr(const Dump: TRomBiosDump; Addr: Pointer): Pointer;
 begin
   Result := @Dump[Cardinal(Addr)];
 end;
 function GetRomDumpAddrEx(const Dump; Base, Addr: Pointer): Pointer;
 begin
   Result := Pointer(Cardinal(@Dump) + Cardinal(Addr) - Cardinal(Base));
 end;
 end.

Как прочитать русский текст MS-DOS.

- Почему не было Windows 96? - Очень похоже на 69.

Для перекодировки из Win(1251) кодовой страницы в Dos(866) кодовую страницу и обратно используются функции:

CharToOEM OEMToChar CharToOEMBuff OEMToCharBuff OemToAnsi AnsiToOem:

Пример чтения текста dos из файла в memo.

procedure TForm1.FormCreate(Sender: TObject);
var
  N: PChar;
begin
  memo1.Lines.LoadFromFile('c:\file.txt');
  N := Memo1.Lines.GetText;
  OemToAnsi(N, N);
  Memo1.Lines.Text := StrPas(N);
end;

Как прочитать русский текст MS-DOS 2.

Нам не заменят ДОС'а простоту заманчивые окны пополамов!
procedure TForm1.FormCreate(Sender: TObject);
var
  i, j: integer;
  s: string;
  c: set of char;
begin
  c := ['А'..'Я', 'а'..'я'];
  memo1.Lines.LoadFromFile('c:\11.txt');
  for i:=0 to memo1.Lines.Count do
  begin
    s:=memo1.Lines.Strings[i];
    for j:=1 to length(s) do
      if chr(ord(S[j])+64) in c then
        s[j]:=chr(ord(S[j])+64);
    memo1.Lines.Strings[i]:=s;
  end;
end;

Как прочитать русский текст MS-DOS 3.

- У меня проблема с ДОСОМ! - Что, компьютер не запускается? - Нет. Дасморк.

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

const
  ConvertSet: array[0..255] of byte =
  {таблица перекодировки ASCII с альтернативной кодовой страницой 866 в
  WIN 1251. Украинские символы - по кодовой таблице PRINTFXU. Непечатные
  символы заменяются пробелами}
  {основная таблица}
  { 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F }
  {00} ( 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  {10} 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  {20} 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
  {30} 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
  {40} 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
  {50} 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
  {60} 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
  {70} 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
  {дополнительная таблица}
  {80} 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
  {90} 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
  {A0} 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
  {B0} 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  {C0} 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  {B0} 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  {E0} 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,
  {F0} 168,184,178,179, 32, 32,175,191,170,186, 32,177,185, 32, 32, 32);
var
  TextString : string[250];
  TextTmpArr : array[0..250] of byte absolute TextString;
  WinString : string[250];
  WinTmpArr : array[0..250] of byte absolute WinString;
  DosFile : Text;
  TextFName : string;
  TextFDir : string;
  WinFName : string;
procedure TMainFm.ConvertFile;
var
  I: Integer;
begin
  AssignFile(DosFile,TextFName);
  ReSet(DosFile);
  while not(EOF(DosFile)) do
  begin
    ReadLn(DosFile,TextString);
    WinTmpArr[0] := TextTmpArr[0];
    for I := 1 to TextTmpArr[0] do
      WinTmpArr[I] := ConvertSet[TextTmpArr[I]];
    Memo.Lines.Add(WinString);
  end;
end;

Как прочитать русский текст MS-DOS 4.

"Компьютерщики" - это такие люди, у которых слова "вчера с матерью трахался" вызывают не возмущение, а сочувствие.
function ConvertAnsiToOem(const S: string): string;
{ ConvertAnsiToOem translates a string into the OEM-defined character set }
{$IFNDEF WIN32}
var
  Source, Dest : array[0..255] of Char;
{$ENDIF}
begin
  {$IFDEF WIN32}
  SetLength(Result, Length(S));
  if Length(Result) > 0 then
    AnsiToOem(PChar(S), PChar(Result));
  {$ELSE}
  if Length(Result) > 0 then
  begin
    AnsiToOem(StrPCopy(Source, S), Dest);
    Result := StrPas(Dest);
  end;
  {$ENDIF}
end; { ConvertAnsiToOem }
function ConvertOemToAnsi(const S: string): string;
{ ConvertOemToAnsi translates a string from the OEM-defined
character set into either an ANSI or a wide-character string }
{$IFNDEF WIN32}
var
  Source, Dest : array[0..255] of Char;
{$ENDIF}
begin
  {$IFDEF WIN32}
  SetLength(Result, Length(S));
  if Length(Result) > 0 then
    OemToAnsi(PChar(S), PChar(Result));
  {$ELSE}
  if Length(Result) > 0 then
  begin
    OemToAnsi(StrPCopy(Source, S), Dest);
    Result := StrPas(Dest);
  end;
  {$ENDIF}
end; { ConvertOemToAnsi }

Считать строковые значения из ресурса.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

implementation
 {$R *.DFM}
 {$R test.res} //Your resource file 
procedure TForm1.Button1Click(Sender: TObject);
 var
   buffer: array[0..255] of Char;
 begin
   Loadstring(hinstance, 1, @buffer, 255);  //1 = first string 
  label1.Caption := StrPas(buffer);
 end;

Запись и чтение чисел в Blob-поле.

Автор: Ralph Friedman
Болтают трое молодых программистов о жизни. Двое холостых и один женатый. - Зря вы говорите, что жениться sux, вот я жене на домашнем компе права админа дал!
Мне нужно записать серию чисел в файл Paradox в blob-поле. Числа получаются из значений компонент, размещенных на форме. Затем мне нужно будет считывать числа из blob-поля и устанавливать согласно им значения компонент. Как мне сделать это?

Вы можете начать свое исследование со следующего модуля:

unit BlobFld;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Buttons, DBTables, DB, ExtCtrls, DBCtrls,
  Grids, DBGrids;
type
  TFrmBlobFld = class(TForm)
    BtnWrite: TBitBtn;
    Table1: TTable;
    DataSource1: TDataSource;
    DBNavigator1: TDBNavigator;
    LbxDisplayBlob: TListBox;
    Table1pubid: TIntegerField;
    Table1comments: TMemoField;
    Table1UpdateTime: TTimeField;
    Table1Real1: TFloatField;
    Table1Real2: TFloatField;
    Table1Real3: TFloatField;
    Table1Curr1: TCurrencyField;
    Table1Blobs: TBlobField;
    Table1Bytes: TBytesField;
    CbxRead: TCheckBox;
    procedure BtnWriteClick(Sender: TObject);
    procedure DataSource1DataChange(Sender: TObject; Field: TField);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;
var
  FrmBlobFld: TFrmBlobFld;
implementation
{$R *.DFM}
type
  ADouble = array[1..12] of double;
  PADouble = ^ADouble;
procedure TFrmBlobFld.BtnWriteClick(Sender: TObject);
var
  i: integer;
  myBlob: TBlobStream;
  v: longint;
begin
  Table1.Edit;
  myBlob := TBlobStream.Create(Table1Blobs, bmReadWrite);
  try
    v := ComponentCount;
    myBlob.Write(v, sizeof(longint));
    for i := 0 to ComponentCount - 1 do
    begin
      v := Components[i].ComponentIndex;
      myBlob.Write(v, sizeof(longint));
    end;
  finally
    Table1.Post;
    myBlob.Free;
  end;
end;
procedure TFrmBlobFld.DataSource1DataChange(Sender: TObject; Field: TField);
var
  i: integer;
  myBlob: TBlobStream;
  t: longint;
  v: longint;
begin
  if CbxRead.Checked then
  begin
    LbxDisplayBlob.Clear;
    myBlob := TBlobStream.Create(Table1Blobs, bmRead);
    try
      myBlob.Read(t, sizeof(longint));
      LbxDisplayBlob.Items.Add(IntToStr(t));
      for i := 0 to t - 1 do
      begin
        myBlob.Read(v, sizeof(longint));
        LbxDisplayBlob.Items.Add(IntToStr(v));
      end;
    finally
      myBlob.Free;
    end;
  end;
end;
procedure TFrmBlobFld.FormShow(Sender: TObject);
begin
  Table1.Open;
end;
procedure TFrmBlobFld.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  Table1.Close;
end;
end.

Чтение и запись компонента.

Автор: Blake Вот участок кода из предстоящей статьи о VBX/OCX, который я написал чтобы протестировать поточность своих компонентов. Если компонент поточнен, код сможет его сохранить и загрузить. В противном случае вы получите сообщение об ошибке, отладка которой сравнительна легка для определении проблем с VCL.

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

procedure TForm1.StoreClick(Sender: TObject);
var
  W: TWriter;
begin
  W := TWriter.Create(
    TFileStream.Create('c:\source\delphi\example.dob', fmCreate), 4096);
  W.WriteSignature;
  W.WriteComponent(Spin);
  W.Free;
end;
procedure TForm1.LoadClick(Sender: TObject);
var
  R: TReader;
begin
  R := TReader.Create(
    TFileStream.Create('c:\source\delphi\example.dob', fmOpenRead), 4096);
  Spin := R.ReadRootComponent(nil) as TSpinner;
  Spin.Top := 0;
  Spin.Left := 0;
  Spin.Parent := Self;
  InsertComponent(Spin);
  R.Free;
end;

Чтение и запись файлов.

1) Направление выходного потока вашей программы в файл. 2) Направление выходного потока вашей программы на принтер. 3) Чтение из входного файла. Направление выходного потока вашей программы в файл ...часто мои профессора, чтобы убедиться в моей честности и поверить в то, что программа создана моими руками, требуют ее полный листинг или упоминания обо мне в качестве одного из авторов. Далее они хотят, чтобы все генерируемые программой данные выводились в файл. Но как это сделать на Delphi или на простом Паскале???

Просто в Delphi ....

program CrtApp;
uses WinCrt;
var outfile: TextFile;
begin
AssignFile(outfile, 'c:\outfile.txt');
Rewrite(outfile);
writeln(outfile, 'Привет из Delphi');
writeln(outfile, 'Моя программа работает, и выводит ' +
'данный текст, чтобы доказать это...');
CloseFile(outfile);
end.

Просто в Паскале.....

Program HelloWorld;
var
outfile: text;
begin
assign(outfile, 'c:\output.txt');
rewrite(outfile);
writeln(outfile, 'Здравствуй, мир');
writeln(outfile, 'Моя программа работает, и выводит
данный текст, чтобы доказать это...');
close(outfile);
end.
Направление выходного потока вашей программы на принтер В некоторых случаях может возникнуть необходимость печатать генерируемые программой данные непосредственно на принтер. Вот как это можно сделать:

В Delphi ...

program CrtApp;
uses WinCrt;
var outfile: TextFile;
begin
assignfile(outfile, 'LPT1');
rewrite(outfile);
writeln(outfile, 'Привет из Delphi');
writeln(outfile, 'Моя программа работает, и выводит ' +
'данный текст, чтобы доказать это...');
closefile(outfile);
end.

В Паскале ...

Program HelloWorld;
var
outfile: text;
begin
assign(outfile, 'LPT1');
rewrite(outfile);
writeln(outfile, 'Здравствуй, мир');
writeln(outfile, 'Моя программа работает, и выводит
данный текст, чтобы доказать это...');
close(outfile);
end.
Чтение из входного файла Весьма полезным будет иметь функцию чтения из файла, который может передаваться другим человеком. Вот пример того, как это можно осуществить.

В Delphi ...

program CrtApp;
uses WinCrt;
var
infile, outfile: TextFile;
num_lines, x: integer;
line: string;
begin
assignfile(infile, 'C:\INFILE.TXT');
assignfile(outfile, 'C:\OUTFILE.TXT');
reset(infile);  {перемещаем указатель}
{в начало файла.}
rewrite(outfile);  {очищаем содержимое файла}
readln(infile, num_lines);
for x:= 1 to num_lines do
begin
readln(infile, line);
writeln(outfile, line);
end;
closefile(infile);
closefile(outfile);
end.

В Паскале ...

Program ReadInput;
var
infile, outfile: text;
num_lines, x: integer;
line: string;
begin
assign(infile, 'C:\INFILE.TXT');
assign(outfile, 'C:\OUTFILE.TXT');
reset(infile);  {перемещаем указатель}
{в начало файла.}
rewrite(outfile);  {очищаем содержимое файла}
readln(infile, num_lines);
for x:= 1 to num_lines do
begin
readln(infile, line);
writeln(outfile, line);
end;
close(infile);
close(outfile);
end.
{НАЧАЛО INFILE.TXT}
2
Здравствуй, мир
Моя программа работает, и этот текст доказательство этому.
{КОНЕЦ INFILE.TXT}
Для получения дополнительной информации обратитесь к Руководству Разработчика. Ознакомьтесь с описанием функций AssignFile, Assign, Reset, Rewrite, readln, writeln, Close, CloseFile.

Данный документ был написан автором под впечатлением просьбы умоляющего студента и чувством симпатии к нему, поскольку он сам недавно сидел на студенческой скамье..!!

Как читать-писать в I-O порты.

A := Port[20];   {чтение байта из порта 20}
Port[20] := A;   {запись байта в порт 20}
B := PortW[20];  {чтение слова из порта 20-21}
PortW[20] := B;  {запись слова в порт 20-21}

Запись и чтение из файла массива записей.

Это не очень Delphi-подобно (тем не менее, работа происходит с действительно паскалевскими записями), но вы можете писать и читать записи из/в файл, используя паскалевские процедуры для работы с файлами:

type
  TMyRec = record ;
    Field1: integer;
    Field2: string;
  end;
  TMyRecArray = array[0..9] of TMyRec;
var
  MyArray: TMyRecArray;
  MyRec: TMyRec;
  RecFile: file of TMyRec;
begin
  {...здесь должен быть расположен код инициализации MyArray...}
  AssignFile(RecFile, 'MYREC.FIL');
  ReWrite(RecFile);
  for i := 0 to 9 do
  begin
    Write(RecFile, MyRec[i]);
  end;
  CloseFile(RecFile);
end;
Также, вы можете использовать Read() для чтения записи из вашего файла, и Seek() для перемещения на его конкретную запись (начиная с 0). Для получения дополнительной информации обратитесь к разделу "I/O Routines" электронной справки по Delphi.

Если вы хотите делать это с Data Aware компонентами (компонентами для работы с базами данных), вы должны создать базу данных, где база данных "records" должна отражать структуру ваших паскалевских записей, при этом необходимо создать механизмы трансляции данных из одной среды в другую. Я не готов сейчас сказать вам, как это можно сделать, но, во всяком случае, всю функциональность можно инкапсулировать в отдельном специализированном компоненте.

Запись и чтение из файла массива записей 2.

type TR=Record
        Name:string[100]; 
        Age:Byte; 
        Income:Real; 
       end; 
var f:file of TR; 
    r:TR; 
begin 
//assign file 
  assignFile(f, 'MyFileName'); 
//open file 
  if FileExists('MyFileName') then 
    reset(f) 
  else 
    rewrite(f); 
//чтение 10й записи 
  seek(f,10); 
  read(f,r); 
//запись 20й записи 
 seek(f, 20); 
 write(f,r); 
 closefile(f); 
end;

Чтение и запись звука.

Автор: Даниил Карапетян WEB сайт: http://program.dax.ru Все функции чтения и записи звука я выделил в отдельный модуль. Он приведен после текста программы. При нажатии Button1 создается звуковой файл в памяти (то есть в памяти создается заголовок, затем идут данные - все точно так же, как в обычном wav-файле), сохраняется на диск и одновременно начинает воспроизводиться. Для этого используется функция playsound. Остановить воспроизведение можно кнопкой Button2. При нажатии Button3 открывается файл ex.wav (если Вы уже нажимали Button1, то он существует). Далее из файла считываются данные и для каждого канала находится средняя громкость. Не уверен, что это самый правильный способ, но здесь за громкость я взял просто среднее арифметическое. Результаты выводятся в заголовок окна. Для каждого канала выводится значение в процентах от максимально возможной громкости. Теперь о самой структуре данных. Она очень проста. Если канал один, то данные записаны подряд: первое значение, второе значение, третье значение ... Если же в файле два канала, то они чередуются: первое значение первого канала, первое значение второго канала, второе значение первого канала, второе значение второго канала, третье значение первого канала, третье значение второго канала, ... Если файл восьми битный, то каждое значение занимает 1 байт, если шестнадцати битный - 2 байта. Это соответствует типам shortint и smallint соответственно. В этой программе данные записываются при помощи процедуры GetData. SaveSound вызывает ее для каждого значения. В качестве параметров передаются канал и номер. А возвращаемое значение передается через нетипизированный параметр res. Такой подход позволяет избежать проблем с типами данных. При чтении все данные копируются в память, а затем находится сумма всех значений для каждого канала. При выводе громкости эти суммы делятся на максимально возможные суммы и умножаются на сто.

Скачать все необходимые для компиляции файлы проекта можно на program.dax.ru.

uses MMSystem, wavfile;
procedure TForm1.Button1Click(Sender: TObject);
const
  fr = 11025; {Частота в герцах}
  len = 1; {Длина звука в секундах}
  procedure GetData(ch: smallint; index: integer; var res);
  var
    v: smallint absolute res; // конечное значение
    amp: single; // амплитуда
  begin
    if ch = 0
      then amp := sin(index * 2 * Pi / (fr * len))
      else amp := cos(index * 2 * Pi / (fr * len));
    v := round(amp * (random(60000) - 30000));
  end;
var
  M: TMemoryStream; // поток для хранения информации в памяти
  F: TFileStream; // Поток для созранения файла
begin
  M := nil; F := nil;
  try
    M := TMemoryStream.Create;
    randomize;
    SaveSound(M {Куда записывать}, round(fr * len) {len секунд},
      fr {частота}, 16 {16 бит}, 2 {2 каналла}, @GetData);
    // Воспроизведение звука:
    if not playsound(M.Memory, 0, SND_MEMORY or SND_LOOP or SND_ASYNC)
      then ShowMessage('Can not play the sound');
    F := TFileStream.Create('ex.wav', fmCreate);
    M.Position := 0;
    F.CopyFrom(M, M.Size);
  finally
    M.Free; F.Free;
  end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  playsound(nil, 0, 0); // Остановка воспроизведения
end;
procedure TForm1.Button3Click(Sender: TObject);
var
  SampleCount, SamplesPerSec: integer;
  BitsPerSample, Channeles: smallint;
  F: TFileStream;
  Volume: array [0..1] of single;
  ToPercent: single;
  buf: pointer;
  buf8: ^shortint;
  buf16: ^smallint;
  i, ch: integer;
begin
  F := nil; buf := nil;
  try
    Volume[0] := 0; Volume[1] := 0;
    F := TFileStream.Create('ex.wav', fmOpenRead);
    ReadWaveHeader(F, SampleCount, SamplesPerSec,
      BitsPerSample, Channeles);
    // Чтение данных:
    GetMem(buf, SampleCount * Channeles * BitsPerSample);
    F.Read(buf^, SampleCount * Channeles * BitsPerSample);
    if BitsPerSample = 8 then begin
      buf8 := buf;
      for i := 0 to SampleCount - 1 do
        for ch := 0 to Channeles - 1 do begin
          Volume[ch] := Volume[ch] + abs(buf8^);
          inc(buf8); // Переход к следующему элементу
        end
    end else begin
      buf16 := buf;
      for i := 0 to SampleCount - 1 do
        for ch := 0 to Channeles - 1 do begin
          Volume[ch] := Volume[ch] + abs(buf16^);
          inc(buf16); // Переход к следующему элементу
        end;
    end;
    // Вывод результатов:
    ToPercent := (1 shl BitsPerSample) / 100 * SampleCount;
    if Channeles = 1
      then Form1.Caption := Format('volume: %2.2f%%',
        [Volume[0] / ToPercent])
      else Form1.Caption := Format('left: %2.2f%%, right: %2.2f%%',
        [Volume[0] / ToPercent, Volume[1] / ToPercent]);
  finally
    F.Free;
    FreeMem(buf);
  end;
end;
--------------------------------------------------------------------------------
unit wavfile;
interface
uses classes, sysutils;
type
  TWaveHeader = record
    idRiff: array [0..3] of char;
    RiffLen: longint;
    idWave: array [0..3] of char;
    idFmt: array [0..3] of char;
    InfoLen: longint;
    WaveType: smallint;
    Ch: smallint;
    Freq: longint;
    BytesPerSec: longint;
    align: smallint;
    Bits: smallint;
  end;
  TDataHeader = record
    idData: array [0..3] of char;
    DataLen: longint;
  end;
  TGetData = procedure(ch: smallint; index: integer; var res);
  TSetData = procedure(ch: smallint; index: integer; data: smallint);
procedure CreateWaveHeader(SampleCount, SamplesPerSec: integer;
  BitsPerSample, Channeles: smallint; var WaveHeader: TWaveHeader;
  var DataHeader: TDataHeader);
procedure ReadWaveHeader(Stream: TStream;
  var SampleCount, SamplesPerSec: integer;
  var BitsPerSample, Channeles: smallint);
procedure SaveSound(Stream: TStream; SampleCount, SamplesPerSec: integer;
  BitsPerSample, Channeles: smallint; GetData: TGetData);
implementation
procedure Creat
  BitsPerSample, Channeles: smallint; var WaveHeader: TWaveHeader;
  var DataHeader: TDataHeader);
var
  len: integer;
begin
  if (SampleCount <  0) or (SamplesPerSec <  1) or
    (not BitsPerSample in [8, 16]) or
    (not Channeles in [1, 2])
    then raise Exception.Create('Wrong params');
  len := SampleCount * BitsPerSample div 8 * Channeles;
  with WaveHeader do begin
    idRiff := 'RIFF';
    RiffLen := len + 38;
    idWave := 'WAVE';
    idFmt := 'fmt ';
    InfoLen := 16;
    WaveType := 1;
    Ch := Channeles;
    Freq := SamplesPerSec;
    BytesPerSec := SamplesPerSec * BitsPerSample div 8 * Channeles;
    align := Channeles * BitsPerSample div 8;
    Bits := BitsPerSample;
  end;
  with DataHeader do begin
    idData := 'data';
    DataLen := len;
  end;
end;
procedure ReadWaveHeader(Stream: TStream;
  var SampleCount, SamplesPerSec: integer;
  var BitsPerSample, Channeles: smallint);
var
  WaveHeader: TWaveHeader;
  DataHeader: TDataHeader;
begin
  Stream.Read(WaveHeader, sizeof(TWaveHeader));
  with WaveHeader do begin
    if idRiff < >  'RIFF' then raise EReadError.Create('Wrong idRIFF');
    if idWave < >  'WAVE' then raise EReadError.Create('Wrong idWAVE');
    if idFmt < >  'fmt ' then raise EReadError.Create('Wrong idFmt');
    if WaveType < >  1 then raise EReadError.Create('Unknown format');
    Channeles := Ch;
    SamplesPerSec := Freq;
    BitsPerSample := Bits;
    Stream.Seek(InfoLen - 16, soFromCurrent);
  end;
  Stream.Read(DataHeader, sizeof(TDataHeader));
  if DataHeader.idData = 'fact' then begin
    Stream.Seek(4, soFromCurrent);
    Stream.Read(DataHeader, sizeof(TDataHeader));
  end;
  with DataHeader do begin
    if idData < >  'data' then raise EReadError.Create('Wrong idData');
    SampleCount := DataLen div (Channeles * BitsPerSample div 8)
  end;
end;
procedure SaveSound(Stream: TStream; SampleCount, SamplesPerSec: integer;
  BitsPerSample, Channeles: smallint; GetData: TGetData);
var
  WaveHeader: TWaveHeader;
  DataHeader: TDataHeader;
  buf: smallint;
  BytesPerSample: smallint;
  i: integer;
begin
  CreateWaveHeader(SampleCount, SamplesPerSec, BitsPerSample,
    Channeles, WaveHeader, DataHeader);
  Stream.Write(WaveHeader, sizeof(TWaveHeader));
  Stream.Write(DataHeader, sizeof(TDataHeader));
  BytesPerSample := BitsPerSample div 8;
  if Channeles = 1
  then
    for i := 0 to SampleCount - 1 do begin
      GetData(0, i, buf);
      Stream.Write(buf, BytesPerSample);
    end
  else
    for i := 0 to SampleCount - 1 do begin
      GetData(0, i, buf);
      Stream.Write(buf, BytesPerSample);
      GetData(1, i, buf);
      Stream.Write(buf, BytesPerSample);
    end;
end;
end.

Скопировать строку в Буфер и прочитать её.

По понятиям "чайников" -- "кофейники" просто ламеры...
uses 
  ClipBrd; 
procedure StrToClipbrd(StrValue: string); 
var 
  S: string; 
  hMem: THandle; 
  pMem: PChar; 
begin 
  hMem := GlobalAlloc(GHND or GMEM_SHARE, Length(StrValue) + 1); 
  if hMem <> 0 then 
  begin 
    pMem := GlobalLock(hMem); 
    if pMem <> nil then 
    begin 
      StrPCopy(pMem, StrValue); 
      GlobalUnlock(hMem); 
      if OpenClipboard(0) then 
      begin 
        EmptyClipboard; 
        SetClipboardData(CF_TEXT, hMem); 
        CloseClipboard; 
      end 
      else 
        GlobalFree(hMem); 
    end 
    else 
      GlobalFree(hMem); 
  end; 
end; 
function GetStrFromClipbrd: string; 
begin 
  if Clipboard.HasFormat(CF_TEXT) then 
    Result := Clipboard.AsText 
  else 
  begin 
    ShowMessage('There is no text in the Clipboard!'); 
    Result := ''; 
  end; 
end; 
// write "Hallo" to the clipboard and read it back. 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  StrToClipbrd('Hallo'); 
  ShowMessage(GetStrFromClipbrd); 
end;

Реализация событий COM+ в среде Delphi.

Введение

Обработка событий является одним из ключевых моментов в COM. Существует масса программ, для нормального функционирования которых требуется поддержка событий.

GUI пользователя должен уметь обрабатывать различное количество событий, например, таких как: нажатие на кнопку мыши, перемещение мыши по экрану и т.д. Приблизительно так же может возникнуть потребность обрабатывать события внутри объектов COM. В данной статье мы рассмотрим принцип работы свободно связанных событий и создадим наглядное приложение для демонстрации использования такого типа событий в COM+. (Для более детальной информации о события в COM+ смотрите статью А.Новика «Система поддержки событий COM+» на сайте журнала «Клиент-Сервер»).

Что такое свободно связанные события? Понятие «Издатель-Подписчик»

До появления COM+, модель COM поддерживала систему событий, реализованную через интерфейс IConnectionPointContainer. Это жестко связанные события. (В данной статье, мы не будем рассматривать реализацию этого подхода). В COM+ появилось новое понятие: СВОБОДНО СВЯЗАННЫЕ СОБЫТИЯ (Loosely coupled events - LCE), разработанные для удовлетворения потребностей распределенных вычислений.

В COM+ Инициатор события (Издатель) и потребитель (Подписчик) свободно связаны.

Информация от различных издателей хранится в каталоге COM+, а подписчики указывают, какую информацию они хотят получать, регистрируясь в каталоге.

Архитектура событий в COM+

Для реализации свободно связанных событий вы должны создать компонент EventClass, который будет зарегистрирован в каталоге COM+. Подписчики вызываются объектом события, который определяет и активизирует объекты, подписанные на него.

Следует различать виды подписки. Существует временная и постоянная подписки:

Временная подписка (transient) создается средствами административного API. Для более детальной информации можно обратиться в MSDN. Управлять жизненным циклом такой подписки нужно программными средствами. А не средствами ComponentServices. Постоянная подписка (persistent) создается средствами ComponentServices. Такая подписка в состоянии пережить перезапуск системы.

Фильтрация существует только в системе COM+. Такой возможности нет в системе жестко связанных событий. Её суть мы рассмотрим дальше, при более детальном изучении примера.

Пример реализации компонента EventClass

Допустим, у нас существует задача на базе существующей системы, функционирующей в среде COM+, реализовать систему ведения собственно журнала событий в текстовом файле. Для начала нам нужно реализовать компонент EventClass, о котором речь шла выше. Он будет представлять собой пустую заглушку для подписчика. Именно через него будут запускаться наши подписчики в каталоге COM+.

Когда компонент-издатель будет создавать событие, компонент EventClass передаст все входящие параметры события и активизирует всех подписчиков.

Важно! Метод события может содержать только входные параметры [in]. Выходным может быть только результирующий тип HRESULT, принятый в COM для определения статуса завершения S_OK, в результате удачи или E_FAIL, в результате неудачи выполнения метода.

Запустим среду Delphi, создадим простую ActiveX библиотеку и поместим в неё объект автоматизации (Automation Object). Определим нумератор типов ошибок и создадим интерфейс ISysLogEvent с методом ReportLog.

type
LogMessageTypes = TOleEnum;
const
  lmtInformation = $00000000;
  lmtWarning = $00000001;
  lmtError = $00000002;
  lmtFatal = $00000003;
  lmtDebug = $00000004;
  lmtUnknown = $00000005;
type
  TSysLogEvent = class(TAutoObject, ISysLogEvent)
  protected
  { Protected declarations }
    procedure ReportLog(enMsgType: LogMessageTypes; const strUserName,
      strModuleName, strMsgText: WideString); safecall;
end;

В разделе Implementation создадим заглушку метода для EventClass:

implementation
uses
  ComServ;
procedure TSysLogEvent.ReportLog(enMsgType: LogMessageTypes;
  const strUserName, strModuleName, strMsgText: WideString);
begin
// Event class methods are not implemented.
end;
initialization
TAutoObjectFactory.Create(ComServer, TSysLogEvent, Class_SysLogEvent,
  ciMultiInstance, tmApartment);
end.

На этом закончим. Остается зарегистрировать заглушку в нашем приложении COM+. Если приложение не создано, создайте его через средства ComponentServices.

Пример реализации Объекта-подписчика

После регистрации компонента EventClass создадим компонент-подписчик:

Точно так же, как при создании компонента EventClass создадим библиотеку и объект автоматизации. Немного будет отличаться наполнение реализации методов и метод регистрации. Создадим интерфейс с методом, аналогичным методу интерфейса ILogEvent – ISysLog

Важно! Не забудьте подключить в вашу библиотеку типов зарегистрированную в ComponentServices библиотеку с заглушкой EventClass и укажите интерфейс ISysLogEvent в разделе Implements.

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

unit SysLogUnit;
interface
uses
  ComObj, ActiveX, SystemLogger_TLB, StdVcl, LogEvent_TLB, Dialogs;
type
  TSysLog = class(TAutoObject, ISysLog, ISysLogEvent)
  protected
    { Protected declarations }
    procedure ReportLog(enMsgType: LogMessageTypes; const strUserName,
      strModuleName, strMsgText: WideString); safecall;
end;
implementation
uses
  ComServ, SysUtils;
procedure TSysLog.ReportLog(enMsgType: LogMessageTypes; const strUserName,
  strModuleName, strMsgText: WideString);
begin
  ShowMessage('MessageType : '+IntToStr(enMsgtype)+#10#13+
  'ModuleName : '+strModuleName+#10#13+
  'UserName : '+strUserName+#10#13+
  'TextMessage : '+strMsgText);
end;
initialization
  TAutoObjectFactory.Create(ComServer, TSysLog, Class_SysLog,
    ciMultiInstance, tmApartment);
end.

Зарегистрируйте компонент в каталоге COM+ и подпишите его к компоненту EventClass.

Далее следуйте инструкциям визарда.

Итак, у вас на компьютере установлены объекты EventClass и подписчик.

Пример реализации методов издателя

Создадим простенькое приложение и проверим существующею связку. Создайте бизнес-объект COM+ инициирующий в любом своем методе метод-событие ReportLog.

Пример реализации объекта приведен ниже:

unit BsObjectUnit;
interface
uses
  ComObj, ActiveX, BsObject_TLB, StdVcl, LogEvent_TLB;
type
  TBusinessObject = class(TAutoObject, IBusinessObject)
  protected
    { Protected declarations }
    function NewObject(param1: Integer): HResult; safecall;
end;
implementation
uses
  ComServ;
function TBusinessObject.NewObject(param1: Integer): HResult;
var
  LogEvent: ISysLogEvent;
begin
  LogEvent := CoSysLogEvent.Create;
  try
    LogEvent.ReportLog(lmtInformation, 'Nonamed', 'BsObjectUnit',
      'TBusinessObject.NewObject executed!')
  except
    LogEvent.ReportLog(lmtInformation, 'Nonamed', 'BsObjectUnit',
      'TBusinessObject.NewObject failed!')
  end;
end;
initialization
TAutoObjectFactory.Create(ComServer, TBusinessObject,
  Class_BusinessObject, ciMultiInstance, tmApartment);
end.

После вызова метода NewObject у объекта BusinessObject будет создано событие, которое создаст объект SysLog и запишет и отобразит информацию в диалоговом окне. Подписчиков у созданного объекта EventClass может быть неограниченное количество с самыми разнообразными функциями, от отображения диалогового окна до записи данных в отдельную БД.

Фильтры

Механизм фильтрации подписчиков использует строку условия фильтрации, являющуюся свойством подписки. Такая фильтрация выполняется для каждого метода и каждой подписки. Вы можете использовать строку, используя имена параметров из библиотеки типов. Можно использовать так же стандартные операции отношения, вложенные скобки и ключевые слова AND, OR, NOT. Строка может быть определена с помощью средств ComponentServices или средств административного API.

Сравнение чисел с плавающей точкой.

Автор: Олег Кулабухов У меня два числа с плавающей запятой неправильно сравниваются! Задаю if d1=d2 ..., или if d1<>d2, а результат иногда неверный. В чем дело? Это баг Дельфи? Нет. Просто в отличие от целочисленных IEEE числа с плавающей запятой являются приблизительными значениями и вы не должны использовать = или <> для сравнивания двух чисел такого типа. Вместо этого вычтите из одного числа другое и сравните разницу с очень малой величиной.

Например.

if abs(d1-d2) < 0.00001 then
  ShowMessage('D1 и D2 равны');

Окно в виде кольца с изогнутой заголовочной полосой.

Статьи по Delphi (014)…
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, Buttons, StdCtrls;
type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    rTitleBar : THandle;
    Center : TPoint;
    CapY : Integer;
    Circum : Double;
    SB1 : TSpeedButton;
    RL, RR : Double;
    procedure TitleBar(Act : Boolean);
    procedure WMNCHITTEST(var Msg: TWMNCHitTest); message WM_NCHITTEST;
    procedure WMNCACTIVATE(var Msg: TWMNCACTIVATE); message WM_NCACTIVATE;
    procedure WMSetText(var Msg: TWMSetText); message WM_SETTEXT;
  public
  { Public declarations }
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
const
  TitlColors : array[Boolean] of TColor = (clInactiveCaption, clActiveCaption);
  TxtColors : array[Boolean] of TColor = (clInactiveCaptionText, clCaptionText);
procedure TForm1.FormCreate(Sender: TObject);
var
  rTemp, rTemp2 : THandle;
  Vertices : array[0..2] of TPoint;
  X, Y : INteger;
begin
  Caption := 'Delphi World is great!';
  BorderStyle := bsNone; {required}
  if Width > Height then
    Width := Height
  else
    Height := Width; {harder to calc if width <> height}
  Center := Point(Width div 2, Height div 2);
  CapY := GetSystemMetrics(SM_CYCAPTION)+8;
  rTemp := CreateEllipticRgn(0, 0, Width, Height);
  rTemp2 := CreateEllipticRgn((Width div 4), (Height div 4),
  3*(Width div 4), 3*(Height div 4));
  CombineRgn(rTemp, rTemp, rTemp2, RGN_DIFF);
  SetWindowRgn(Handle, rTemp, True);
  DeleteObject(rTemp2);
  rTitleBar := CreateEllipticRgn(4, 4, Width-4, Height-4);
  rTemp := CreateEllipticRgn(CapY, CapY, Width-CapY, Height-CapY);
  CombineRgn(rTitleBar, rTitleBar, rTemp, RGN_DIFF);
  Vertices[0] := Point(0,0);
  Vertices[1] := Point(Width, 0);
  Vertices[2] := Point(Width div 2, Height div 2);
  rTemp := CreatePolygonRgn(Vertices, 3, ALTERNATE);
  CombineRgn(rTitleBar, rTitleBar, rTemp, RGN_AND);
  DeleteObject(rTemp);
  RL := ArcTan(Width / Height);
  RR := -RL + (22 / Center.X);
  X := Center.X-Round((Center.X-1-(CapY div 2))*Sin(RR));
  Y := Center.Y-Round((Center.Y-1-(CapY div 2))*Cos(RR));
  SB1 := TSpeedButton.Create(Self);
  with SB1 do
  begin
    Parent := Self;
    Left := X;
    Top := Y;
    Width := 14;
    Height := 14;
    OnClick := Button1Click;
    Caption := 'X';
    Font.Style := [fsBold];
  end;
end;
procedure TForm1.WMNCHITTEST(var Msg: TWMNCHitTest);
begin
  inherited;
  with Msg do
    with ScreenToClient(Point(XPos,YPos)) do
      if PtInRegion(rTitleBar, X, Y) and
      (not PtInRect(SB1.BoundsRect, Point(X,Y))) then
        Result := htCaption;
end;
procedure TForm1.WMNCActivate(var Msg: TWMncActivate);
begin
  inherited;
  TitleBar(Msg.Active);
end;
procedure TForm1.WMSetText(var Msg: TWMSetText);
begin
  inherited;
  TitleBar(Active);
end;
procedure TForm1.TitleBar(Act: Boolean);
var
  TF : TLogFont;
  R : Double;
  N, X, Y : Integer;
begin
  if Center.X = 0 then
    Exit;
  with Canvas do
  begin
    Brush.Style := bsSolid;
    Brush.Color := TitlColors[Act];
    PaintRgn(Handle, rTitleBar);
    R := RL;
    Brush.Color := TitlColors[Act];
    Font.name := 'Arial';
    Font.Size := 12;
    Font.Color := TxtColors[Act];
    Font.Style := [fsBold];
    GetObject(Font.Handle, SizeOf(TLogFont), @TF);
    for N := 1 to Length(Caption) do
    begin
      X := Center.X-Round((Center.X-6)*Sin(R));
      Y := Center.Y-Round((Center.Y-6)*Cos(R));
      TF.lfEscapement := Round(R * 1800 / pi);
      Font.Handle := CreateFontIndirect(TF);
      TextOut(X, Y, Caption[N]);
      R := R - (((TextWidth(Caption[N]))+2) / Center.X);
      if R then
        Break;
    end;
    Font.name := 'MS Sans Serif';
    Font.Size := 8;
    Font.Color := clWindowText;
    Font.Style := [];
  end;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
  with Canvas do
  begin
    Pen.Color := clBlack;
    Brush.Style := bsClear;
    Pen.Width := 1;
    Pen.Color := clWhite;
    Arc(1, 1, Width-1, Height-1, Width, 0, 0, Height);
    Arc((Width div 4)-1, (Height div 4)-1,
    3*(Width div 4)+1, 3*(Height div 4)+1, 0, Height, Width, 0);
    Pen.Color := clBlack;
    Arc(1, 1, Width-1, Height-1, 0, Height, Width, 0);
    Arc((Width div 4)-1, (Height div 4)-1,
    3*(Width div 4)+1, 3*(Height div 4)+1, Width, 0, 0, Height);
    TitleBar(Active);
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;
end.

Преобразование дробной и целой части REAL-числа в два целых.

Я написал программу, которая делает это. Это DOS-программа. Вы вызываете ее с десятичным числом, передаваемым в качестве параметра. После чего программка выведет 3 колонки, в первой будет находиться исходное число, две остальные будут содержать числитель и знаменатель. Вы можете преобразовать программу в функцию и применять ее в своих приложениях, но, думаю, это несложно, и с этим вы справитесь сами. Для ее запуска достаточно в подсказке DOS набрать ее имя и число:
CONTFRAC 3.141592654
program contfrac; { непрерывные дроби }
{$N+}
const
  order = 20;
var
  y, lasterr, error, x: extended;
  a: array[0..order] of longint;
  i, j, n: integer;
  op, p, q: longint;
begin
  lasterr := 1E30;
  val(paramstr(1), y, n);
  if n <> 0 then
    halt;
  x := y;
  a[0] := trunc(x);
  writeln;
  writeln(a[0]: 20, a[0]: 14, 1: 14);
  { это может вызвать резкую головную боль и галлюцинации }
  for i := 1 to order do
  begin
    x := 1.0 / frac(x);
    a[i] := trunc(x);
    p := 1;
    q := a[i];
    for j := pred(i) downto 0 do
    begin
      op := p;
      p := q;
      q := a[j] * q + op;
    end;
    error := abs(y - int(q) / int(p));
    if abs(error) >= abs(lasterr) then
      halt;
    writeln(a[i]: 20, q: 14, p: 14, error: 10);
    if error < 1E-18 then
      halt;
    lasterr := error;
  end;
end.
Теперь попытаюсь объяснить мой алгоритм (он, по-моему, достаточно быстрый). Вот схема: Допустим, мы используем число 23.56. Берем наше натуральное число и производим целочисленное деление на 1. 23.56 div 1 = 23 Теперь вычитаем результат из числа, с которого мы начали. 23.56 - 23 = .56 Для преобразования значения в целое мы просто умножаем его на 100, и, при необходимости, приводим его к целому.
             valA := (val div 100);
             valB := (valA - val); or valB := (valA - val) * 100;
                val = 23.56
                ValA = 23
                ValB = .56 or 56

Как завершить сеанс работы или перезагрузить Windows NT.

Для этого нам потребуются определённые привелегии:

function SetPrivilege(aPrivilegeName: string;
  aEnabled: boolean): boolean;
var
  TPPrev,
    TP: TTokenPrivileges;
  Token: THandle;
  dwRetLen: DWord;
begin
  Result := False;
  OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES
    or TOKEN_QUERY, @Token);
  TP.PrivilegeCount := 1;
  if (LookupPrivilegeValue(nil, PChar(aPrivilegeName),
    TP.Privileges[0].LUID)) then
  begin
    if (aEnabled) then
      TP.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED
    else
      TP.Privileges[0].Attributes := 0;
    dwRetLen := 0;
    Result := AdjustTokenPrivileges(Token, False, TP,
      SizeOf(TPPrev),
      TPPrev, dwRetLen);
  end;
  CloseHandle(Token);
end;
function WinExit(iFlags: integer): boolean;
//   возможные флаги:
//   EWX_LOGOFF
//   EWX_REBOOT
//   EWX_SHUTDOWN
begin
  Result := True;
  if (SetPrivilege('SeShutdownPrivilege', true)) then
  begin
    if (not ExitWindowsEx(iFlags, 0)) then
    begin
      Result := False;
    end;
    SetPrivilege('SeShutdownPrivilege', False)
  end
  else
  begin
    Result := False;
  end;
end;

Сколько времени прошло с момента перезагрузки.

Автор: Павел Чтобы определить сколько времени прошло с момента последней перезагрузки системы необходимо воспользоваться функцией GetCurrentTime: LongInt; Возвращаемое значение: время в миллисекундах.

Пример использования:

Edit1.Text := FloatToStr(GetCurrentTime / 1000) + ' секунд с момента перезагрузки';

Как пересчитать все вычисляемые поля (Calculated fields) без переоткрытия TDataSet.

Автор: Nomadic.

Resync( [rmExact, rmCenter] ); 

RecCount в таблицах ASCII.

Автор: Mark Edington В Delphi 1.0 для получения количества записей в ASCII файле (.TXT- и .SCH-файлы) я пользовался свойством RecordCount компонента TTable. В Delphi 2.0 эта функциональность не поддерживается! Я прав или не прав? Во всяком случае как мне получить количество записей, содержащихся в ASCII таблице? В Delphi 2.0, свойство RecordCount отображается на недокументированную функцию BDE DbiGetExactRecordCount. Данное изменение было сделано для обеспечения правильных величин при работе с "живыми" запросами. Очевидно, данное API по какой-то причине не поддерживает текстовые файлы.

Вы можете обойти эту проблему, вызывая функцию API BDE DbiGetRecordCount напрямую (добавьте BDE к списку используемых модулей):

procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;
var
  RecCount: Integer;
begin
  Check(DbiGetRecordCount(Table1.Handle, RecCount);
end;

Недавно использовавшиеся файлы в главном меню.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  Die folgende Unit enthalt eine Klasse, die eine 
  RecentListe in ein TMainMenu einfugt. 
}
 { 
  This unit contains a class to add a 
  RecentList in a TMainMenu. 
}
 unit UnitRecentList;
 interface
 uses Windows, SysUtils, menus, Registry, Forms, Dialogs, Classes;
 type
   TOnClickPorc = procedure(Path : string) of object;
 type
   TRecentList = class(TObject)
   private
     Regis: TRegistryIniFile;
     Count: integer;
     Parent: TForm;
     MainMenu: TMainMenu;
     Items: TStringList;
     AddPos: integer;
     ClickProc: TOnClickPorc;
     Ident: integer;
   public
     // Key      : Porduktname zum Beispiel "SuperSoftware" 
    // Count    : Anzahl der angezeigten MenuItems 
    // MainMenu : Das MainMenu auf dem Formular in dem angezeigt werden soll 
    // Parent   : Das Parent Formular vom MainMenu (z.B. Form1) 
    // Proc     : Prozedur, die ausgefurt werden soll, wenn auf ein MenuItem geklickt wird. 
    //            z. B. "Procedure TForm1.RecentListClick(FileName : string);" 
    constructor Create(Key : string; Count, MenuIndex : integer;
       MainMenu : TMainMenu; Parent : TForm;
       Proc : TOnClickPorc);
     destructor Destroy; override;
     procedure CustomItemAdd(Value : string);
     procedure ItemsToMenu;
     procedure ItemClick(Sender : TObject);
   end;
 implementation
 constructor TRecentList.Create(Key : string; Count, MenuIndex : integer;
   MainMenu : TMainMenu; Parent : TForm;
   Proc : TOnClickPorc);
 var
   I : integer;
   NewItem : TMenuItem;
 begin
   // Initialisieren 
  Items := TStringList.Create;
   // Klasseneigenschaften setzen 
  Self.Count := Count;
   Self.Parent := Parent;
   Self.MainMenu := MainMenu;
   Self.Ident := MenuIndex;
   AddPos := MainMenu.Items[Ident].Count + 1;
   // Ubergebene Prozedur gleich der der lokalen "ClickPorc" setzen 
  ClickProc := Proc;
   // TRegistryIniFile instanzieren 
  Regis := TRegistryIniFile.Create('Software\' + Key);
   // Trennstrisch realisieren 
  NewItem := TMenuItem.Create(Parent);
   NewItem.Caption := '-';
   MainMenu.Items[Ident].Add(NewItem);
   // uber Schliefe alles einlesen 
  for i := 0 to Count - 1 do CustomItemAdd(Regis.ReadString('RecentFiles',
       'Item' + IntToStr(i) + IntToStr(Ident), ''));
 end;
 procedure TRecentList.ItemsToMenu;
 var
   NewItem : TMenuItem;
   I : integer;
 begin
   // Beim ersten mal "count" MenuItems erstellen 
  if MainMenu.Items[Ident].Count = AddPos then
     for i := 0 to Count - 1 do
     begin
       NewItem := TMenuItem.Create(Parent);
       NewItem.Caption := '';
       NewItem.OnClick := ItemClick;
       MainMenu.Items[Ident].Add(NewItem);
     end;
   // Die Liste "Items" duchgehen und die MenuItems danach benennen 
  for I := 0 to Items.Count - 1 do
     MainMenu.Items[Ident][i + AddPos].Caption := Items[i];
   // Dei leeren Eintrage unsichtbar machen die vollen sichtbar 
  for I := 0 to Count - 1 do
     MainMenu.Items[Ident][i + AddPos].Visible :=
       not (MainMenu.Items[Ident][i + AddPos].Caption = '');
   if Items.Count = 0 then MainMenu.Items[Ident][AddPos - 1].Visible := False
   else
     MainMenu.Items[Ident][AddPos - 1].Visible := True;
 end;
 procedure TRecentList.ItemClick(Sender : TObject);
 begin
   ClickProc((Sender as TMenuItem).Caption);
 end;
 procedure TRecentlIst.CustomItemAdd(Value : string);
 var
   i : integer;
 begin
   // Prufen 
  if Length(Value) = 0 then
   begin
     ItemsToMenu;
     Exit;
   end;
   // Testen, ob "Value" schon vorkommt 
  for i := 0 to Items.Count - 1 do
     if (Items[i] = Value) then
     begin
       Items.Delete(i);
       Break;
     end;
   // Items.Delete(i); 
  // Eintrag anhangen 
  Items.Insert(0, Value);
   // Wenn Liste voll, dann letztes rausschmeissen 
  if Items.Count > Count then
   begin
     //For i := 1 to Items.Count - 1 do Items[ i - 1 ] := Items[ i ]; 
    Items.Delete(Items.Count - 1);
   end;
   // Anzeige aktuallisieren 
  ItemsToMenu;
 end;
 // FreeAndNil(TRecentList) bei OnClose nicht vergessen 
destructor TRecentList.Destroy;
 var
   i : integer;
 begin
   // DAten in der Registry speichern 
  for i := 0 to Items.Count - 1 do
     Regis.WriteString('RecentFiles', 'Item' + IntToStr(I) + IntToStr(Ident), Items[i]);
   // Speicherleichen ausradieren 
  Regis.Free;
 end;
 end.
 { Und so instanziert man die Klasse: }
 //  Createn 
// 1. Parameter : Registry Schlussel 
// 2. Parameter : Anzal der Eintrage in der RecentList 
// 3. Parameter : Nummer des MenuItems unter das die RecentList kommen soll (meistens 0) 
// 4. Parameter : Das Menu, in die die Recentlist eingefugt werden soll 
// 5. Parameter : Das Formular, in dem sich das Menu befindet 
// 6. Parameter : Die Procedure, die ausgefuhrt werden soll, wenn auf einen Eintrag 
//                in der Recentlist gecklickt wird. 
procedure TForm1.FormCreate(Sender: TObject);
 begin
  RecentList.Create('SuperSoftware', 5, 0, MainMenu1, Self, Click);
 end;
 // Die Procedure wird ausgefuhrt, wenn auf einen Eintrag in der 
// RecentList gecklickt wird. 
// "Value" ist der Dateiname 
procedure TForm1.Click(FilePath: string );
 begin
  DateiOeffnen(FilePath);
 end;
 // Einen Eintrag in die Rechenlist vorhehmen 
procedure TForm1.Button1Click(Sender: TObject);
 begin
  RecentList.CustomItemAdd(Edit1.Text);
 end;
 // nicht vergessen, sonst werden die Anderungen nicht gespeichert 
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
 begin
  FreeAndNil(RecentList);
 end;
 // von Sven Laufersweiler & Phillip Kroll 

Получение имени обработчика события.

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

procedure TForm1.Button3Click(Sender: TObject);
var
  P: record case Integer of
      1: (E: TNotifyEvent);
      2: (P: Pointer);
  end;
begin
  P.E := Button1.OnClick;
  Panel1.Caption := 'Обработчик события = ' + MethodName(P.P);
  ShowMessage(Format('%p', [P.P]));
end;

Как получить номер записи в dBASE или Paradox.

Попали в одну камеру разбойник и хакер. Разбойник: - Я вот сижу за ограбление магазина... А ты за что сидишь? Хакер: - Ограбил банк на 7 миллионов долларов... Разбойник: - Да ты че... ни фига себе.. как же ты унес их из банка - денег-то офигенно много? Хакер рассказывает в общих чертах о компьютерах, сетях, взломах и т. д. Разбойник: - Ну ни фига себе, а как же ты попался? Хакер: - Брандмауэр засек и сработала защита.. Разбойник: - Ах ты... Брандмауэр, твою мать... Всегда знал, что евреев опасаться надо...
function FindRecordNumber (aDataSet : TDataSet): longint;
 var
  cP: CurProps;
  rP: RECProps;
  DBRes: DBiResult;
 begin
  {Return 0 if dataset is not Paradox or dBase}
  Result := 0;
 
  with aDataset do
   begin
    if state = dsInactive then exit;
 
    {we need to make this call to grab the cursor's iSeqNums}
    DBRes := DBiGetCursorProps(Handle,cP);
    if DBRes <> DBIERR_NONE then exit;
 
    {synchronize the BDE cursor with the dataset's cursor}
    UpdateCursorPos;
 
    {fill rP with the current record's properties}
    DBRes := DBiGetRecord(Handle,DBiNOLOCK,nil,@rP);
    if DBRes <> DBIERR_NONE then exit;
 
    {what kind of dataset are we looking at?}
    case cP.iSeqNums of
     0: result := rP.iPhyRecNum; {dBase}
     1: result := rP.iSeqNum;    {Paradox}
    end;
   end;
 end;

Запись RecNo из RecordCount.

Автор: Mike Downey ...какой метод вовращает номер текущей записи? (т.е.: Я хочу использовать это вместе с функцией RecordCount и выводить для пользователя в строке состояния нечто вроде: "Запись #n из x")

Вот функция, возвращающая номер текущей записи в наборе данных DataSet. В основном я все скопировал и расставил комментарии, которые теперь лишают меня возможности поговорить на эту тему. Могли бы вы получить эти цифирьки из DBIPROCS.INT?

function RecordNumber(Dataset: TDataset): Longint;
var
  CursorProps: CurProps;
  RecordProps: RECProps;
begin
  { Возвращаем 0, если набор данных не Paradox или dBASE }
  Result := 0;
  with Dataset do
  begin
    { Набор данных активен? }
    if State = dsInactive then
      DBError(SDataSetClosed);
    { Нам необходимо сделать этот вызов, чтобы "захватить" курсор iSeqNums }
    Check(DbiGetCursorProps(Handle, CursorProps));
    { Синхронизируем курсор BDE с курсором набора данных }
    UpdateCursorPos;
    { Заполняем RecordProps текущими свойствами записи }
    Check(DbiGetRecord(Handle, dbiNOLOCK, nil, @RecordProps));
    { С каким типом набора данных мы работаем? }
    case CursorProps.iSeqNums of
      0: Result := RecordProps.iPhyRecNum; { dBASE   }
      1: Result := RecordProps.iSeqNum; { Paradox }
    end; { case }
  end; { with }
end; { function }

Затем, в обработчике события OnDataChange DataSet, я использовал команду:

MyTextVariable := 'Запись ' + IntToStr( RecordNumber( tImport ) ) +
' из ' + IntToStr( tImport.RecordCount ) ;

Перекодирование.

Этот алгоритм позволяет перекодировать текст. Реализованы кодировки Windows-1251, KOI8-R, ISO-8859-5 и DOS. Кодировка – это таблица, в которой указано, например, что символ под номером 160 - это русская буква "а", а под номером 150 – "Ц" и т. д. Кодировки различаются номерами русских букв (как располагать английские буквы договорились). Разные компьютеры в Интернете используют разные кодировки. И поэтому, когда русский текст идет по Интернету, его многократно перекодируют.

Этот алгоритм обеспечивает высокую скорость перекодирования больших объемов данных.

procedure TForm1.Button1Click(Sender: TObject);
var
  code1, code2: TCode;
  s: string;
  c: char;
  i: integer;
  chars: array [char] of char;
  str: array [TCode] of string;
begin
  case ComboBox1.ItemIndex of
    1: code1 := koi;
    2: code1 := iso;
    3: code1 := dos;
    else code1 := win;
  end;
  case ComboBox2.ItemIndex of
    1: code2 := koi;
    2: code2 := iso;
    3: code2 := dos;
    else code2 := win;
  end;
  s := Memo1.Text;
  Str[win] := 'АаБбВвГгДдЕеЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя';
  Str[koi] := 'юЮаАбБцЦдДеЕфФгГхХиИйЙкКлЛмМнНоОпПяЯрРсСтТуУжЖвВьЬыЫзЗшШэЭщЩчЧъЪ';
  Str[iso] := 'РрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯяа¹б¸в?гƒдºе¾ж³з¿и¼йšкœлžм?н§о¢пŸ';
  Str[dos] := '€ ?¡‚¢ƒ£„¤…¥†¦‡§ˆ¨‰©Šª‹"Œ¬?­Ž®?¯?а'б'в“г”д•е–ж—з˜и™йšк›лœм?нžоŸп';
  for c := #0 to #255 do
    Chars[c] := c;
  for i := 1 to Length(Str[win]) do
    Chars[Str[code2][i]] := Str[code1][i];
  for i := 1 to Length(s) do
    s[i] := Chars[s[i]];
  Memo2.Text := s;
end;

Как можно перекодировать сообщение (содержание) из Win в КОИ8-Р для отправки по EMail.

Статьи по Delphi (014)…
Разговаривают две фидошницы: - Знаешь, мы с одним парнем сексом по переписке занимаемся! - ?! Ну, и как? Оргазм когда-нибудь получаешь? - Ага... Когда не получаю его дурацких писем.
const
Koi: array[0..66] of Char = (
'T', 'Ё', 'ё', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ж',
'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р',
'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ',
'Ы', 'Ь', 'Э', 'Ю', 'Я', 'а', 'б', 'в', 'г', 'д',
'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о',
'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш',
'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я');
Win: array[0..66] of Char = (
'ё', 'Ё', 'T', 'ю', 'а', 'б', 'ц', 'д', 'е', 'ф',
'г', 'х', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',
'я', 'р', 'с', 'т', 'у', 'ж', 'в', 'ь', 'ы', 'з',
'ш', 'э', 'щ', 'ч', 'ъ', 'Ю', 'А', 'Б', 'Ц', 'Д',
'Е', 'Ф', 'Г', 'Х', 'И', 'Й', 'К', 'Л', 'М', 'Н',
'О', 'П', 'Я', 'Р', 'С', 'Т', 'У', 'Ж', 'В', 'Ь',
'Ы', 'З', 'Ш', 'Э', 'Щ', 'Ч', 'Ъ');
function WinToKoi(Str: string): string;
var
  i, j, index: Integer;
begin
  Result := '';
  for i := 1 to Length(Str) do
  begin
    index := -1;
    for j := Low(Win) to High(Win) do
      if Win[j] = Str[i] then
      begin
        index := j;
        Break;
      end;
    if index = -1 then
      Result := Result + Str[i]
    else
      Result := Result + Koi[index];
  end;
end;
function KoiToWin(Str: string): string;
var
  i, j, index: Integer;
begin
  Result := '';
  for i := 1 to Length(Str) do
  begin
    index := -1;
    for j := Low(Win) to High(Win) do
      if Koi[j] = Str[i] then
      begin
        index := j;
        Break;
      end;
    if index = -1 then
      Result := Result + Str[i]
    else
      Result := Result + Win[index];
  end;
end;
procedure SendFileOnSMTP(Host: string; Port: Integer;
Subject, FromAddress, ToAddress, Body, FileName: string);
var
  NMSMTP: TNMSMTP;
begin
  if DelSpace(ToAddress) = '' then
    Exit;
  if ToAddress[1] = '' then
    Exit;
  if (DelSpace(FileName) <> '') and not FileExists(FileName) then
    raise Exception.Create('SendFileOnSMTP: file not exist: ' + FileName);
  NMSMTP := TNMSMTP.Create(nil);
  try
    NMSMTP.Host := Host;
    NMSMTP.Port := Port;
    NMSMTP.Charset := 'koi8-r'
    NMSMTP.PostMessage.FromAddress := FromAddress;
    NMSMTP.PostMessage.ToAddress.Text := ToAddress;
    NMSMTP.PostMessage.Attachments.Text := FileName;
    NMSMTP.PostMessage.Subject := Subject;
    NMSMTP.PostMessage.Date := DateTimeToStr(Now);
    NMSMTP.UserID := 'netmaster'
    NMSMTP.PostMessage.Body.Text := WinToKoi(Body);
    NMSMTP.FinalHeader.Clear;
    NMSMTP.TimeOut := 5000;
    NMSMTP.Connect;
    NMSMTP.SendMail;
    NMSMTP.Disconnect;
  finally
    NMSMTP.Free;
  end;
end;

Перекодировка текста DOS-Windows-Koi8.

Статьи по Delphi (014)…
Подходит компьютерщик (К) к газетному киоску и спрашивает у продавца (П): К: - Есть ли у вас свежий номер 'Компьютерного обозрения'? П: - Есть. К: - Ну тогда, пожалуйста, один номер в кодировке KOI8-R
procedure WinToDos;
var
  Src, Str: PChar;
begin
  Src := Memo1.Lines.GetText; //Берем текст из TMemo как тип PChar
  CharToOem(Src, Str); //API функция для перевода текста
  Memo2.Lines.Text := StrPas(Str);//Записываем назад
end;
procedure DosToWin;
var
  Src, Str: PChar;
begin
  Src := Memo1.Lines.GetText; //Берем текст из TMemo как тип PChar
  OemToChar(Src, Str); //API функция для перевода текста
  Memo2.Lines.Text := StrPas(Str);//Записываем назад
end;
var
  koi8toalt : array [0..127] of char = (
  CHR($c4), Chr($b3), Chr($da), Chr($bf),
  Chr($c0), Chr($d9), Chr($c3), Chr($b4),
  Chr($c2), Chr($c1), Chr($c5), Chr($df),
  Chr($dc), Chr($db), Chr($dd), Chr($de),
  Chr($b0), Chr($b1), Chr($b2), Chr($f4),
  Chr($fe), Chr($f9), Chr($fb), Chr($f7),
  Chr($f3), Chr($f2), Chr($ff), Chr($f5),
  Chr($f8), Chr($fd), Chr($fa), Chr($f6),
  Chr($cd), Chr($ba), Chr($d5), Chr($f1),
  Chr($d6), Chr($c9), Chr($b8), Chr($b7),
  Chr($bb), Chr($d4), Chr($d3), Chr($c8),
  Chr($be), Chr($bd), Chr($bc), Chr($c6),
  Chr($c7), Chr($cc), Chr($b5), Chr($f0),
  Chr($b6), Chr($b9), Chr($d1), Chr($d2),
  Chr($cb), Chr($cf), Chr($d0), Chr($ca),
  Chr($d8), Chr($d7), Chr($ce), Chr($fc),
  Chr($ee), Chr($a0), Chr($a1), Chr($e6),
  Chr($a4), Chr($a5), Chr($e4), Chr($a3),
  Chr($e5), Chr($a8), Chr($a9), Chr($aa),
  Chr($ab), Chr($ac), Chr($ad), Chr($ae),
  Chr($af), Chr($ef), Chr($e0), Chr($e1),
  Chr($e2), Chr($e3), Chr($a6), Chr($a2),
  Chr($ec), Chr($eb), Chr($a7), Chr($e8),
  Chr($ed), Chr($e9), Chr($e7), Chr($ea),
  Chr($9e), Chr($80), Chr($81), Chr($96),
  Chr($84), Chr($85), Chr($94), Chr($83),
  Chr($95), Chr($88), Chr($89), Chr($8a),
  Chr($8b), Chr($8c), Chr($8d), Chr($8e),
  Chr($8f), Chr($9f), Chr($90), Chr($91),
  Chr($92), Chr($93), Chr($86), Chr($82),
  Chr($9c), Chr($9b), Chr($87), Chr($98),
  Chr($9d), Chr($99), Chr($97), Chr($9a));
function Koi8toWin(const Data: PChar; DataLen: Integer): PChar;
var
  PCh: PChar;
  i: Integer;
begin
  PCh := Data;
  for i := 1 to DataLen do
  begin
    if Ord(Pch^) > 127 then
      Pch^ := koi8toalt[Ord(Pch^) - 128];
    Inc(PCh);
  end;
  PCh := Data;
  OemToCharBuff(PCh, PCh, DWORD(DataLen));
  Result := Data;
end;

Перекодировка текста из Win1251 в KOI8-R и наоборот.

Статьи по Delphi (014)…
Встречается компьютерщик(К) со своим старым другом(Д), бывшем алкашем. - (К) Ну что, больше не пьешь? - (Д) Закодировался. - (К) В какой кодировке? - (Д) KOI-8. - (К) Говорят, в Windows-1251 дольше не пьют.
type
  TConvertChars = array [#128..#255] of char;
const
  Win_KoiChars: TConvertChars = (
  #128,#129,#130,#131,#132,#133,#134,#135,#136,#137,#060,#139,#140,#141,#142,#143,
  #144,#145,#146,#147,#148,#169,#150,#151,#152,#153,#154,#062,#176,#157,#183,#159,
  #160,#246,#247,#074,#164,#231,#166,#167,#179,#169,#180,#060,#172,#173,#174,#183,
  #156,#177,#073,#105,#199,#181,#182,#158,#163,#191,#164,#062,#106,#189,#190,#167,
  #225,#226,#247,#231,#228,#229,#246,#250,#233,#234,#235,#236,#237,#238,#239,#240,
  #242,#243,#244,#245,#230,#232,#227,#254,#251,#253,#154,#249,#248,#252,#224,#241,
  #193,#194,#215,#199,#196,#197,#214,#218,#201,#202,#203,#204,#205,#206,#207,#208,
  #210,#211,#212,#213,#198,#200,#195,#222,#219,#221,#223,#217,#216,#220,#192,#209);
  Koi_WinChars: TConvertChars = (
  #128,#129,#130,#131,#132,#133,#134,#135,#136,#137,#138,#139,#140,#141,#142,#143,
  #144,#145,#146,#147,#148,#149,#150,#151,#152,#153,#218,#155,#176,#157,#183,#159,
  #160,#161,#162,#184,#186,#165,#166,#191,#168,#169,#170,#171,#172,#173,#174,#175,
  #156,#177,#178,#168,#170,#181,#182,#175,#184,#185,#186,#187,#188,#189,#190,#185,
  #254,#224,#225,#246,#228,#229,#244,#227,#245,#232,#233,#234,#235,#236,#237,#238,
  #239,#255,#240,#241,#242,#243,#230,#226,#252,#251,#231,#248,#253,#249,#247,#250,
  #222,#192,#193,#214,#196,#197,#212,#195,#213,#200,#201,#202,#203,#204,#205,#206,
  #207,#223,#208,#209,#210,#211,#198,#194,#220,#219,#199,#216,#221,#217,#215,#218);
function Win_KoiConvert(const St: string): string;
var
  i: integer;
begin
  Result:=St;
  for i:=1 to Length(St) do
    if St[i]>#127 then
      Result[i]:=Win_KoiChars[St[i]];
end;

Распознавание кодировки. Перекодировка.

Приходит программист к окулисту. Тот его усаживает напротив таблицы, берет указку: - Читайте! - "БНОПНЯ"... Доктор, у вас что-то не то с кодировкой!

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

type
  TCode = (win, koi, iso, dos);
const
  CodeStrings: array [TCode] of string = ('win','koi','iso','dos');
procedure TForm1.Button1Click(Sender: TObject);
var
  str: array [TCode] of string;
  norm: array ['А'..'я'] of single;
  code1, code2: TCode;
  min1, min2: TCode;
  count: array [char] of integer;
  d, min: single;
  s, so: string;
  chars: array [char] of char;
  c: char;
  i: integer;
begin
  so := Memo1.Text;
  norm['А'] := 0.001;
  norm['Б'] := 0;
  norm['В'] := 0.002;
  norm['Г'] := 0;
  norm['Д'] := 0.001;
  norm['Е'] := 0.001;
  norm['Ж'] := 0;
  norm['З'] := 0;
  norm['И'] := 0.001;
  norm['Й'] := 0;
  norm['К'] := 0.001;
  norm['Л'] := 0;
  norm['М'] := 0.001;
  norm['Н'] := 0.001;
  norm['О'] := 0.001;
  norm['П'] := 0.002;
  norm['Р'] := 0.002;
  norm['С'] := 0.001;
  norm['Т'] := 0.001;
  norm['У'] := 0;
  norm['Ф'] := 0;
  norm['Х'] := 0;
  norm['Ц'] := 0;
  norm['Ч'] := 0.001;
  norm['Ш'] := 0.001;
  norm['Щ'] := 0;
  norm['Ъ'] := 0;
  norm['Ы'] := 0;
  norm['Ь'] := 0;
  norm['Э'] := 0.001;
  norm['Ю'] := 0;
  norm['Я'] := 0;
  norm['а'] := 0.057;
  norm['б'] := 0.01;
  norm['в'] := 0.031;
  norm['г'] := 0.011;
  norm['д'] := 0.021;
  norm['е'] := 0.067;
  norm['ж'] := 0.007;
  norm['з'] := 0.013;
  norm['и'] := 0.052;
  norm['й'] := 0.011;
  norm['к'] := 0.023;
  norm['л'] := 0.03;
  norm['м'] := 0.024;
  norm['н'] := 0.043;
  norm['о'] := 0.075;
  norm['п'] := 0.026;
  norm['р'] := 0.038;
  norm['с'] := 0.034;
  norm['т'] := 0.046;
  norm['у'] := 0.016;
  norm['ф'] := 0.001;
  norm['х'] := 0.006;
  norm['ц'] := 0.002;
  norm['ч'] := 0.011;
  norm['ш'] := 0.004;
  norm['щ'] := 0.004;
  norm['ъ'] := 0;
  norm['ы'] := 0.012;
  norm['ь'] := 0.012;
  norm['э'] := 0.003;
  norm['ю'] := 0.005;
  norm['я'] := 0.015;
  Str[win] := 'АаБбВвГгДдЕеЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя';
  Str[koi] := 'юЮаАбБцЦдДеЕфФгГхХиИйЙкКлЛмМнНоОпПяЯрРсСтТуУжЖвВьЬыЫзЗшШэЭщЩчЧъЪ';
  Str[iso] := 'РрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯяа№бёвђгѓдєеѕжізїијйљкњлћмќн§оўпџ';
  Str[dos] := 'Ђ ЃЎ‚ўѓЈ"¤…Ґ†¦‡§€Ё‰©ЉЄ‹"ЊЌЋ®ЏЇђа'б'в"г"дое-ж-зи™йљк›лњмќнћоџпз?и™йљк›лњмќнћоџп';
  for c := #0 to #255 do
    Chars[c] := c;
  min1 := win;
  min2 := win;
  min := 0;
  s := so;
  fillchar(count, sizeof(count), 0);
  for i := 1 to Length(s) do
    inc(count[s[i]]);
  for c := 'А' to 'я' do
    min := min + sqr(count[c] / Length(s) - norm[c]);
  for code1 := low(TCode) to high(TCode) do
  begin
    for code2 := low(TCode) to high(TCode) do
    begin
      if code1 = code2 then
        continue;
      s := so;
      for i := 1 to Length(Str[win]) do
        Chars[Str[code2][i]] := Str[code1][i];
      for i := 1 to Length(s) do
        s[i] := Chars[s[i]];
      fillchar(count, sizeof(count), 0);
      for i := 1 to Length(s) do
        inc(count[s[i]]);
      d := 0;
      for c := 'А' to 'я' do
        d := d + sqr(count[c] / Length(s) - norm[c]);
      if d then
      begin
        min1 := code1;
        min2 := code2;
        min := d;
      end;
    end;
  end;
  s := Memo1.Text;
  if min1 <> min2 then
  begin
    for c := #0 to #255 do
      Chars[c] := c;
    for i := 1 to Length(Str[win]) do
      Chars[Str[min2][i]] := Str[min1][i];
    for i := 1 to Length(s) do
      s[i] := Chars[s[i]];
  end;
  Form1.Caption := CodeStrings[min2] + ' ' + CodeStrings[min1];
  Memo2.Text := s;
end;

Передача массива записей символов в Memo.

Юзер заходит в магазин: - А какой компьютер лучше? Windows или Pentium.

Обработка больших строк в 16-битной версии Delphi задача далеко непростая. Особенно когда строки являются частью структуры записи и вы хотите передать их в TMemo. В данном совете показано как создать структуру записи размером 1000 символов, прочесть в нее содержимое Memo и затем записать ее обратно в Memo. Основной метод, который мы здесь используем - метод Memo GetTextBuf. Используемая структура записи представляет собой простую строку и массив из 1000 символов, но структура могла бы быть сложнее.

unit URcrdIO;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, dbtables;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
type
  TMyRec = record
    MyArray: array[1..1000] of char;
    mystr: string;
  end;
var
  Form1: TForm1;
  MyRec: TMyRec;
  mylist: TStringlist;
  PMyChar: PChar;
  myfile: file;
  mb: TStream;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  assignfile(myfile, 'c:\testblob.txt');
  rewrite(myfile, 1);
  fillchar(MyRec.MyArray, sizeof(MyRec.MyArray), #0);
  pmychar := @MyRec.MyArray;
  StrPCopy(pmychar, memo1.text);
  Blockwrite(MyFile, MyRec, SizeOf(MyRec));
  closefile(MyFile);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  assignfile(myfile, 'c:\testblob.txt');
  reset(myfile, 1);
  fillchar(MyRec.MyArray, sizeof(MyRec.MyArray), #0);
  Blockread(MyFile, MyRec, SizeOf(MyRec));
  pmychar := @MyRec.MyArray;
  Memo1.SetTextBuf(pmychar);
end;
end.

Присвоить значение полям записи с помощью Assemblera.

Автор: Xavier Pacheco

unit Main;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure Foo(var I: integer);
begin
  { some code }
  asm
    mov eax, I
    inc dword ptr [eax]
  end;
  { i has now been incremented by one }
  { some more code }
end;
type
  TDumbRec = record
    i: integer;
    c: char;
  end;
procedure ManipulateRec(var DR: TDumbRec);
asm
  mov [eax].TDumbRec.i, 24
  mov [eax].TDumbRec.c, 's'
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  R: TDumbRec;
begin
  ManipulateRec(R);
  ShowMessage(IntToStr(R.i));
  Caption := Caption + R.c;
end;
end.

При обращении клиента, к уже редактируемой записи другим клиентом, выдаётся сообщение.

Ответ 1:

Самое дешевое решение - завести поле, в которое будет записываться ID usera перед началом операции. Перед началом обновления данных выполняете следующее. 1. При старте программы каждому юзеру раздаете уникальный ID. 2. Перед началом обновления данных записываете ID в поле в и коммитете данные. После этого поле будет залочено для этого юзера. 3. Вносите изменения. 4. В блокирующее поле записываете 0 и коммитете. Перед началом обновления, проверяете чему равно значение блокирующего поля. Если оно не нулевое, то вы не только сможете сообщить, что запись заблокирована, но и сказать кем именно. Если не делать ничего подобного то запросто при многопользовательской работе можно получить deadlock на сервере а есть не очень хорошо.

Ответ 2:

Так сделать нельзя. Можно по другому. Оцени критические ситуации и поставь проверку на триггер или еще лучше сделай с помощью CHECK. Например: колличество на складе не может быть меньше 0 CHECK TOVAR_COUNT>0 Если один из клиентов изменил количество , а второй попытался снять больше чем есть на складе - то сервер выдаст ошибку, которую ты можешь обработать программой. Очень хорошая книга по этой теме П.В. Шумаков - Дельфи 3 и разработка приложений баз данных. После ее прочтения 80% твоих вопросов будут решены.

Ответ 3:

Думаю, что Вашу задачу можно решить следующим путем. На сервере метод, который осуществляет обработку данных. "закрыть" критической секцией. В начале критическую секцию необходимо объявить в составе класса или отдельно:

SectionLock: TCriticalSection;

Затем:

procedure TForm1.FormCreate(Sender: TObject);
begin
  SectionLock := TCriticalSection.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  SectionLock.Free;
end;

Теперь при инициализации Вашего метода можно использовать SectionLock:

SectionLock.Acquire;
try
  DoSomething;
finally
  SectionLock.Release;
end;
// или
SectionLock.Enter;
try
  DoSomething;
finally
  SectionLock.Leave;
end;

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

if Lock then
  "Ответ - занято"
SectionLock.Enter;
try
  Lock := True;
  DoSomething;
finally
  SectionLock.Leave;
  Lock := False;
end;

P.S. Что бы сервер мог одновременно реагировать на несколько запросов, его необходимо реализовать в СОМ технологии.

Запись nnn из nnn.

Автор: OAmiry (Borland) Нет ли метода или свойства, позволяющих осуществить сабж из Delphi?

Для этого необходимо поработать с BDE. Нижеприведенный код у меня работает прекрасно. Условия выполнения: A) Вы используете таблицы dBASE B) На форме расположен компонент DBNavigator B) На форме расположен табличный компонент с именем Table1 C) На форме расположен компонент Label с именем Label1 D) Обработчик события OnClick компонента DBNavigator имеет следующий код:

procedure TForm1.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
var
  pRecProperty: pRECProps;
begin
  if Button in [nbFirst, nbPrior, nbNext, nbLast] then
  begin
    if MaxAvail > SizeOf(RECProps) then
    begin
      GetMem(pRecProperty, SizeOf(RECProps));
      DbiGetRecord(Table1.Handle, dbiNOLOCK, nil, pRecProperty);
      Label1.Caption := Concat('Запись: ', IntToStr(pRecProperty^.iPhyRecNum),
        ' из ', IntToStr(Table1.RecordCount));
      FreeMem(pRecProperty, SizeOf(RECProps));
    end;
  end;
end;

Получить номер записи можно в момент показа формы или в момент открытия таблицы.

Запись звука.

Статьи по Delphi (014)…

Автор: John Mertus

var
  WaveRecorder : TWaveRecorder;
  // 4 размером 2048 байт
  WaveRecorder := TwaveRecorder(2048, 4);
{ Устанавливает параметры дискретизации }
with WaveRecorder.pWavefmtEx do
begin
  wFormatTag := WAVE_FORMAT_PCM;
  nChannels := 1;
  nSamplesPerSec := 20000;
  wBitsPerSample := 16;
  nAvgBytesPerSec := nSamplesPerSec*(wBitsPerSample div 8)*nChannels;
end;
// Затем используем вариантную запись, поскольку я не знаю
// как получить адрес самого объекта
WaveRecorder.SetupRecord(@WaveRecorder);
// Начинаем запись
WaveRecorder.StartRecord;
// При каждом заполнении буфера вызывается процедура
WaveRecorder.Processbuffer.
// Заканчиваем запись
WaveRecorder.StopRecord;
WaveRecorder.Destroy;
{
Имя файла: RECUNIT.PAS V 1.01
Создан: Авг 19 1996 в 21:56 на IBM ThinkPad
Ревизия #7: Авг 22 1997, 15:01 на IBM ThinkPad
-John Mertus
Данный модуль содержит необходимые процедуры для записи звука.
Версия 1.00 - первый релиз
1.01 - добавлен TWaveInGetErrorText
}
{-Unit-RECUNIT----------John Mertus---Авг 96---}
unit RECUNIT;
interface
uses
  Windows, MMSystem, SysUtils, MSACM;
{ Ниже определен класс TWaveRecorder для обслуживания входа звуковой }
{ карты. Ожидается, что новый класс будет производным от TWaveRecorder }
{ и перекроет TWaveRecorder.ProcessBuffer. После начала записи данная }
{ процедура вызывается каждый раз при наличии в буфере аудио-данных. }
const
  MAX_BUFFERS = 8;
type
  PWaveRecorder = ^TWaveRecorder;
  TWaveRecorder = class(TObject)
    constructor Create(BfSize, TotalBuffers : Integer);
    destructor Destroy; override;
    procedure ProcessBuffer(uMsg : Word; P : Pointer; n : Integer); virtual;
  private
    fBufferSize : Integer; // Размер буфера
    BufIndex : Integer;
    fTotalBuffers : Integer;
    pWaveHeader : array [0..MAX_BUFFERS-1] of PWAVEHDR;
    hWaveHeader : array [0..MAX_BUFFERS-1] of THANDLE;
    hWaveBuffer : array [0..MAX_BUFFERS-1] of THANDLE;
    hWaveFmtEx : THANDLE;
    dwByteDataSize : DWORD;
    dwTotalWaveSize : DWORD;
    RecordActive : Boolean;
    bDeviceOpen : Boolean;
    { Внутренние функции класса }
    function InitWaveHeaders : Boolean;
    function AllocPCMBuffers : Boolean;
    procedure FreePCMBuffers;
    function AllocWaveFormatEx : Boolean;
    procedure FreeWaveFormatEx;
    function AllocWaveHeaders : Boolean;
    procedure FreeWaveHeader;
    function AddNextBuffer : Boolean;
    procedure CloseWaveDeviceRecord;
  public
    { Public declarations }
    pWaveFmtEx : PWaveFormatEx;
    WaveBufSize : Integer; { Размер поля nBlockAlign }
    InitWaveRecorder : Boolean;
    RecErrorMessage : string;
    QueuedBuffers,
    ProcessedBuffers : Integer;
    pWaveBuffer : array [0..MAX_BUFFERS-1] of lpstr;
    WaveIn : HWAVEIN; { Дескриптор Wav-устройства }
    procedure StopRecord;
    function 477576218068StartRecord : Boolean;
    function 477576218068SetupRecord(P : PWaveRecorder) : Boolean;
end;
implementation
function TWaveInGetErrorText(iErr : Integer) : string;
{ Выдает сообщения об ошибках WaveIn в формате Pascal }
{ iErr - номер ошибки }
var
  PlayInErrorMsgC : array [0..255] of Char;
begin
  waveInGetErrorText(iErr,PlayInErrorMsgC,255);
  TWaveInGetErrorText := StrPas(PlayInErrorMsgC);
end;
function TWaveRecorder.AllocWaveFormatEx : Boolean;
{ Распределяем формат большого размера, требуемый для инсталляции ACM-в}
var
  MaxFmtSize : UINT;
begin
  { maxFmtSize - сумма sizeof(WAVEFORMATEX) + pwavefmtex.cbSize }
  if( acmMetrics( 0, ACM_METRIC_MAX_SIZE_FORMAT, maxFmtSize ) <> 0) >then
  begin
    RecErrorMessage := 'Ошибка получения размера формата максимального сжатия';
    AllocWaveFormatEx := False;
    Exit;
  end;
  { распределяем структуру WAVEFMTEX }
  hWaveFmtEx := GlobalAlloc(GMEM_MOVEABLE, maxFmtSize);
  if (hWaveFmtEx = 0) then
  begin
    RecErrorMessage := 'Ошибка распределения памяти для структуры WaveFormatEx';
    AllocWaveFormatEx := False;
    Exit;
  end;
  pWaveFmtEx := PWaveFormatEx(GlobalLock(hWaveFmtEx));
  if (pWaveFmtEx = nil) then
  begin
    RecErrorMessage := 'Ошибка блокировки памяти WaveFormatEx';
    AllocWaveFormatEx := False;
    Exit;
  end;
  { инициализация формата в стандарте PCM }
  ZeroMemory( pwavefmtex, maxFmtSize );
  pwavefmtex.wFormatTag := WAVE_FORMAT_PCM;
  pwavefmtex.nChannels := 1;
  pwavefmtex.nSamplesPerSec := 20000;
  pwavefmtex.nBlockAlign := 1;
  pwavefmtex.wBitsPerSample := 16;
  pwavefmtex.nAvgBytesPerSec := pwavefmtex.nSamplesPerSec*
  (pwavefmtex.wBitsPerSample div 8)*pwavefmtex.nChannels;
  pwavefmtex.cbSize := 0;
  { Все успешно, идем домой }
  AllocWaveFormatEx := True;
end;
function TWaveRecorder.InitWaveHeaders : Boolean;
{ Распределяем память, обнуляем заголовок wave и инициализируем }
var
  i : Integer;
begin
  { делаем размер буфера кратным величине блока... }
  WaveBufSize := fBufferSize - (fBufferSize mod pwavefmtex.nBlockAlign);
  { Устанавливаем wave-заголовки }
  for i := 0 to fTotalBuffers-1 do
    with pWaveHeader[i]^ do
    begin
      lpData := pWaveBuffer[i];      // адрес буфера waveform
      dwBufferLength := WaveBufSize; // размер, в байтах, буфера
      dwBytesRecorded := 0;          // смотри ниже
      dwUser := 0;                   // 32 бита данных пользователя
      dwFlags := 0;                  // смотри ниже
      dwLoops := 0;                  // смотри ниже
      lpNext := nil;                 // зарезервировано; должен быть ноль
      reserved := 0;                 // зарезервировано; должен быть ноль
    end;
  InitWaveHeaders := TRUE;
end;
function TWaveRecorder.AllocWaveHeaders : Boolean;
{ Распределяем и блокируем память заголовка }
var
  i : Integer;
begin
  for i := 0 to fTotalBuffers-1 do
  begin
    hwaveheader[i] := GlobalAlloc( GMEM_MOVEABLE or GMEM_SHARE or
    GMEM_ZEROINIT, sizeof(TWAVEHDR));
    if (hwaveheader[i] = 0) then
    begin
      { Примечание: Это может привести к утечке памяти, надеюсь скоро исправить }
      RecErrorMessage := 'Ошибка распределения памяти для wave-заголовка';
      AllocWaveHeaders := FALSE;
      Exit;
    end;
    pwaveheader[i] := GlobalLock (hwaveheader[i]);
    if (pwaveheader[i] = nil ) then
    begin
      { Примечание: Это может привести к утечке памяти, надеюсь скоро исправить }
      RecErrorMessage := 'Не могу заблокировать память заголовка для записи';
      AllocWaveHeaders := FALSE;
      Exit;
    end;
  end;
  AllocWaveHeaders := TRUE;
end;
procedure TWaveRecorder.FreeWaveHeader;
{ Просто освобождаем распределенную AllocWaveHeaders память. }
var
  i : Integer;
begin
  for i := 0 to fTotalBuffers-1 do
  begin
    if (hWaveHeader[i] <> 0) then
    begin
      GlobalUnlock(hwaveheader[i]);
      GlobalFree(hwaveheader[i]);
      hWaveHeader[i] := 0;
    end
  end;
end;
function TWaveRecorder.AllocPCMBuffers : Boolean;
{ Распределяем и блокируем память waveform. }
var
  i : Integer;
begin
  for i := 0 to fTotalBuffers-1 do
  begin
    hWaveBuffer[i] := GlobalAlloc( GMEM_MOVEABLE or GMEM_SHARE, fBufferSize );
    if (hWaveBuffer[i] = 0) then
    begin
      { Здесь возможна утечка памяти }
      RecErrorMessage := 'Ошибка распределения памяти wave-буфера';
      AllocPCMBuffers := False;
      Exit;
    end;
    pWaveBuffer[i] := GlobalLock(hWaveBuffer[i]);
    if (pWaveBuffer[i] = nil) then
    begin
      { Здесь возможна утечка памяти }
      RecErrorMessage := 'Ошибка блокирования памяти wave-буфера';
      AllocPCMBuffers := False;
      Exit;
    end;
    pWaveHeader[i].lpData := pWaveBuffer[i];
  end;
  AllocPCMBuffers := TRUE;
end;
procedure TWaveRecorder.FreePCMBuffers;
{ Освобождаем использованную AllocPCMBuffers память. }
var
  i : Integer;
begin
  for i := 0 to fTotalBuffers-1 do
  begin
    if (hWaveBuffer[i] <> 0) then
    begin
      GlobalUnlock( hWaveBuffer[i] );
      GlobalFree( hWaveBuffer[i] );
      hWaveBuffer[i] := 0;
      pWaveBuffer[i] := nil;
    end;
  end;
end;
procedure TWaveRecorder.FreeWaveFormatEx;
{ Просто освобождаем заголовки ExFormat headers }
begin
  if (pWaveFmtEx = nil) then
    Exit;
  GlobalUnlock(hWaveFmtEx);
  GlobalFree(hWaveFmtEx);
  pWaveFmtEx := nil;
end;
constructor TWaveRecorder.Create(BFSize, TotalBuffers : Integer);
{ Устанавливаем wave-заголовки, инициализируем указатели данных и }
{ и распределяем буферы дискретизации }
{ BFSize - размер буфера в байтах }
var
  i : Integer;
begin
  inherited Create;
  for i := 0 to fTotalBuffers-1 do
  begin
    hWaveHeader[i] := 0;
    hWaveBuffer[i] := 0;
    pWaveBuffer[i] := nil;
    pWaveFmtEx := nil;
  end;
  fBufferSize := BFSize;
  fTotalBuffers := TotalBuffers;
  { распределяем память для структуры wave-формата }
  if(not AllocWaveFormatEx) then
  begin
    InitWaveRecorder := FALSE;
    Exit;
  end;
  { ищем устройство, совместимое с доступными wave-характеристиками }
  if (waveInGetNumDevs then
  begin
    RecErrorMessage := 'Не найдено устройств, способных записывать звук';
    InitWaveRecorder := FALSE;
    Exit;
  end;
  { распределяем память wave-заголовка }
  if (not AllocWaveHeaders) then
  begin
    InitWaveRecorder := FALSE;
    Exit;
  end;
  { распределяем память буфера wave-данных }
  if (not AllocPCMBuffers) then
  begin
    InitWaveRecorder := FALSE;
    Exit;
  end;
  InitWaveRecorder := TRUE;
end;
destructor TWaveRecorder.Destroy;
{ Просто освобождаем всю память, распределенную InitWaveRecorder. }
begin
  FreeWaveFormatEx;
  FreePCMBuffers;
  FreeWaveHeader;
  inherited Destroy;
end;
procedure TWaveRecorder.CloseWaveDeviceRecord;
{ Просто освобождаем (закрываем) waveform-устройство. }
var
  i : Integer;
begin
  { если устройство уже закрыто, то выходим }
  if (not bDeviceOpen) then
    Exit;
  { работа с заголовками - unprepare }
  for i := 0 to fTotalBuffers-1 do
    if (waveInUnprepareHeader(WaveIn, pWaveHeader[i],
    sizeof(TWAVEHDR)) <> 0 ) then
      RecErrorMessage := 'Ошибка в waveInUnprepareHeader';
  { сохраняем общий объем записи и обновляем показ }
  dwTotalwavesize := dwBytedatasize;
  { закрываем входное wave-устройство }
  if (waveInClose(WaveIn) <> 0) then
    RecErrorMessage := 'Ошибка закрытия входного устройства';
  { сообщаем вызвавшей функции, что устройство закрыто }
  bDeviceOpen := FALSE;
end;
procedure TWaveRecorder.StopRecord;
{ Останавливаем запись и устанавливаем некоторые флаги. }
var
  iErr : Integer;
begin
  RecordActive := False;
  iErr := waveInReset(WaveIn);
  { прекращаем запись и возвращаем стоящие в очереди буферы }
  if (iErr <> 0) then
    RecErrorMessage := 'Ошибка в waveInReset';
  CloseWaveDeviceRecord;
end;
function TWaveRecorder.AddNextBuffer : Boolean;
{ Добавляем буфер ко входной очереди и переключаем буферный индекс. }
var
  iErr : Integer;
begin
  { ставим буфер в очередь для получения очередной порции данных }
  iErr := waveInAddBuffer(WaveIn, pwaveheader[bufindex], sizeof(TWAVEHDR));
  if (iErr <> 0) then
  begin
    StopRecord;
    RecErrorMessage := 'Ошибка добавления буфера' + TWaveInGetErrorText(iErr);
    AddNextBuffer := FALSE;
    Exit;
  end;
  { переключаемся на следующий буфер }
  bufindex := (bufindex+1) mod fTotalBuffers;
  QueuedBuffers := QueuedBuffers + 1;
  AddNextBuffer := TRUE;
end;
procedure BufferDoneCallBack(
hW : HWAVE;         // дескриптор waveform-устройства
uMsg : DWORD;       // посылаемое сообщение
dwInstance : DWORD; // экземпляр данных
dwParam1 : DWORD;   // определяемый приложением параметр
dwParam2 : DWORD;   // определяемый приложением параметр
); stdcall;
{ Вызывается при наличии у wave-устройства какой-либо информации, }
{ например при заполнении буфера }
var
  BaseRecorder : PWaveRecorder;
begin
  BaseRecorder := Pointer(DwInstance);
  with BaseRecorder^ do
  begin
    ProcessBuffer(uMsg, pWaveBuffer[ProcessedBuffers mod fTotalBuffers],
    WaveBufSize);
    if (RecordActive) then
      case uMsg of
        WIM_DATA:
        begin
          BaseRecorder.AddNextBuffer;
          ProcessedBuffers := ProcessedBuffers+1;
        end;
      end;
  end;
end;
function TWaveRecorder.StartRecord : Boolean;
{ Начало записи. }
var
  iErr, i : Integer;
begin
  { начало записи в первый буфер }
  iErr := WaveInStart(WaveIn);
  if (iErr <> 0) then
  begin
    CloseWaveDeviceRecord;
    RecErrorMessage := 'Ошибка начала записи wave: ' +
    TWaveInGetErrorText(iErr);
  end;
  RecordActive := TRUE;
  { ставим в очередь следующие буферы }
  for i := 1 to fTotalBuffers-1 do
    if (not AddNextBuffer) then
    begin
      StartRecord := FALSE;
      Exit;
    end;
  StartRecord := True;
end;
function TWaveRecorder.SetupRecord(P : PWaveRecorder) : Boolean;
{ Данная функция делает всю работу по созданию waveform-"записывателя". }
var
  iErr, i : Integer;
begin
  dwTotalwavesize := 0;
  dwBytedatasize := 0;
  bufindex := 0;
  ProcessedBuffers := 0;
  QueuedBuffers := 0;
  { открываем устройство для записи }
  iErr := waveInOpen(@WaveIn, WAVE_MAPPER, pWaveFmtEx,
  Integer(@BufferDoneCallBack),
  Integer(P), CALLBACK_FUNCTION + WAVE_ALLOWSYNC );
  if (iErr <> 0) then
  begin
    RecErrorMessage := 'Не могу открыть входное устройство для записи: ' + ^M
    + TWaveInGetErrorText(iErr);
    SetupRecord := FALSE;
    Exit;
  end;
  { сообщаем CloseWaveDeviceRecord(), что устройство открыто }
  bDeviceOpen := TRUE;
  { подготавливаем заголовки }
  InitWaveHeaders();
  for i := 0 to fTotalBuffers-1 do
  begin
    iErr := waveInPrepareHeader( WaveIn, pWaveHeader[I], sizeof(TWAVEHDR));
    if (iErr <> 0) then
    begin
      CloseWaveDeviceRecord;
      RecErrorMessage := 'Ошибка подготовки заголовка для записи: ' + ^M +
      TWaveInGetErrorText(iErr);
      SetupRecord := FALSE;
      Exit;
    end;
  end;
  { добавляем первый буфер }
  if (not AddNextBuffer) then
  begin
    SetupRecord := FALSE;
    Exit;
  end;
  SetupRecord := TRUE;
end;
procedure TWaveRecorder.ProcessBuffer(uMsg: Word; P : Pointer;
n: Integer);
{ Болванка процедуры, вызываемой при готовности буфера. }
begin
end;
end.

Нарушение уникальности записи.

try
  tMyTable.Post;
except
  on E : EDBEngineError do
    if E.Message = 'Key violation' then
    begin
      MessageDlgC ('Дублирование записи не допускается.', mtError, [mbOk], 0);
      // Я не уверен в том, что это нужно делать:
      tMyTable.Cancel;
    end
    else 
      Raise;
end;
Хорошим примером может служить проект DBERRORS.DPR, расположенный в каталоге Delphi 2 Demos. Выглядит это примерно так:

Создайте функцию типа этой:

function DBError(DataSet: TDataSet;
  E: EDatabaseError; var Action: TDataAction);
const
  eKeyViol = 9729;
var
  iDBIError: Integer;
begin
  if (E is EDBEngineError) then
  begin
    iDBIError := (E as EDBEngineError).Errors[0].Errorcode;
    case iDBIError of
      eKeyViol:
      begin
        MessageDlg('Нарушение уникальности записи ', mtWarning, [mbOK], 0);
        Abort;
      end;
  end;

Затем для каждой таблицы вашего приложения создайте следующий обработчик события:

procedure TMainForm.Table1EditError(DataSet: TDataSet;
  E: EDatabaseError; var Action: TDataAction);
begin
  DBError(Table1, E, Action);
end;

Таким образом вы можете перехватить множество ошибок. Смотрите примеры от Borland, там много чего есть полезного.

Запись с помощью MediaPlayer.

Автор: Nick Hodges
Origin: - Ори, джинн! Ори, с#ка!

Компонент Mediaplayer работает только с корректными звуковыми файлами и не работает с файлами нулевого размера. Нижеприведенная функция создаст звуковой файл с размером 1. Вариантная запись. Хотелось бы видеть лучшее решение, но пока такой вариант работает у меня без проблем.

function CreateNewWave(NewFileName: string): Boolean;
var
  DeviceID: Word;
  Return: LongInt;
  MciOpen: TMCI_Open_Parms;
  MciRecord: TMCI_Record_Parms;
  MciPlay: TMCI_Play_Parms;
  MciSave: TMCI_SaveParms;
  MCIResult: LongInt;
  Flags: Word;
  TempFileName: array[0..255] of char;
begin
  MediaPlayer.Close;
  try
    StrPCopy(TempFileName, NewFilename);
    MciOpen.lpstrDeviceType := 'waveaudio';
    MciOpen.lpstrElementName := '';
    Flags := Mci_Open_Element or Mci_Open_Type;
    MCIResult := MciSendCommand(0, MCI_OPEN, Flags, LongInt(@MciOpen));
    DeviceID := MciOpen.wDeviceId;
    MciRecord.dwTo := 1;
    Flags := Mci_To or Mci_Wait;
    MCIResult := MciSendCommand(DeviceID, Mci_Record, Flags,
      LongInt(@MciRecord));
    mciPlay.dwFrom := 0;
    Flags := Mci_From or Mci_Wait;
    MciSendCommand(DeviceId, Mci_Play, Flags, LongInt(@MciPlay));
    mciSave.lpfileName := TempFilename.CString;
    Flags := MCI_Save_File or Mci_Wait;
    MCIResult := MciSendCommand(DeviceID, MCI_Save, Flags, LongInt(@MciSave));
    Result := MciSendCommand(DeviceID, Mci_Close, 0, LongInt(nil)) = 0;
  end;
end;

Восстановление поврежденных таблиц.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

How to recover Data in a damaged Header of DbTables. (Paradox or Dbase) Tables If this problem occurs and we have not copies of data. Paradox can''t directly open those damaged Tables so Paradox can''t repair those tables. solution : T1: the Damaged Table 1- We Have to create an empty Table (T2.Db or T2.Dbf) that have the same structure of damaged table (T1.DB or T1.Dbf). 2- With Dos Prompts or excutable batch File we have to execute this command: Copy T2.Db+T1.db T3.Db or Copy T2.Dbf+T1.dbf T3.Dbf 3-Finally with paradox browser we can open T3 Table we have to delete bad records. and copy t3 to t1 table.

Как бороться с квадратичностью Image.

Статьи по Delphi (014)…

Автор: Аркадий
Билл-Гей. Тс-с-с...
При вставке какой-либо картинки в Image с произвольными очертаниями, можно легко обратиться к самой картинке а не к ненужному "куску" Image. Решение:

Вставляем картинку ,например с белым фоном, transpert:=true и на OnMouseOver, или еще где, пишем:

if image1.picture.bitmep.canvas.pixels[X, Y]<>clwhite then
  image1.cursor:=crHourGlass
else
  image1.cursor:=crDefault

Пояснение: при наведении прога проверяет цвет пиксела под указатем и если оно отличается от белого, т.е. цвета фона, то указатель меняет свой вид!

Проверка пересечения двух прямоугольников (TRect).

Автор: MikeZ WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Проверка пересечения двух прямоугольников (TRect)
Функция использовалась для проверки пересекаемости 2-х форм,
панелей и т.п. Причем пересекаться они могут и не углами, а,
например, находиться один полностью в другом.
Зависимости: нет
Автор:       MikeZ, Zhuravsky2@Yandex.ru, Kiev
Copyright:   MikeZ (C) 2003
Дата:        8 октября 2003 г.
***************************************************** }
function OverlapRects(R1, R2: TRect): Boolean;
var
  Temp: TRect;
begin
  Result := False;
  if not UnionRect(Temp, R1, R2) then
    Exit;
  if (Temp.Right - Temp.Left <= R1.Right - R1.Left + R2.Right - R2.Left) and
    (Temp.Bottom - Temp.Top <= R1.Bottom - R1.Top + R2.Bottom - R2.Top) then
    Result := True;
end;

Рекурсивные механизмы спуска по дереву.

- Каких деревьев не хватает в ботаническом саду? - Бинарных, - ответил программист.

Нужно использовать рекурсивные механизмы спуска по дереву и иметь метод определения наличия child узлов у текущего узла.

function  TDBTreeView.RecurseChilds(node: TTreeNode): double;
begin
  while node <> nil do begin
    if node.HasChildren then
       Result := RecurseChilds(node.GetFirstChild);
    Result := Result + GetResultForNode(node));
    node := node.GetNextSibling;
  end;
end;
function  TDBTreeView.GetResult(curnode: TTreeNode;): double;
begin
  Result := 0;
  if curnode = nil then Exit;
  Result := RecurseChilds(curnode.GetFirstChild);
end;

Рекурсия и опережающее описание.

Рекурсия - это такой способ организации вычислительного процесса, при котором подпрограмма в ходе выполнения составляющих ее операторов обращается сама к себе. Рассмотрим классический пример - вычисление факториала. Программа получает от компонента edinput целое число n и выводит в компонент lboutput значение N!, которое вычисляется с помощью рекурсивной функции Factorial.

При выполнении правильно организованной рекурсивной подпрограммы осуществляется многократный переход от некоторого текущего уровня организации алгоритма к нижнему уровню последовательно до тех пор, пока, наконец, не будет получено тривиальное решение поставленной задачи. В нашем случае решение при n = 0 тривиально и используется для остановки рекурсии.

procedure TfmExample.bbRunClick(Sender: TObject);
  function Factorial(N: Word): Extended;
  begin
    if N = 0 then
      Result: = 1 else
      Result := N * Factorial(N - 1)
  end;
var
  N: Integer;
begin
  try
    N := StrToInt(Trim(edinput.Text));
  except
    Exit; end;
  IbOutput.Caption := FloatToStr(Factorial(N))
end;
Рекурсивная форма организации алгоритма обычно выглядит изящнее итерационной и дает более компактный текст программы, но при выполнении, как правило, медленнее и может вызвать переполнение стека (при каждом входе в подпрограмму ее локальные переменные размещаются в организованной особым образом области памяти, называемой программным стеком).

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

procedure A(i: Byte);
begin
  В(i);
end;
procedure В(j: Byte);
begin
  а(j);
end;

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

procedure В(j: Byte); forward;
procedure A(i: Byte);
begin
  В(i);
end;
procedure B; begin
  A(j);
end;

Как видим, опережающее описание заключается в том, что объявляется лишь заголовок процедуры в, а ее тело заменяется стандартной директивой Forward. Теперь в процедуре а можно использовать обращение к процедуре в - ведь она уже описана, точнее, известны ее формальные параметры, и компилятор может правильным образом организовать ее вызов. Обратите внимание: тело процедуры в начинается заголовком, в котором уже не указываются описанные ранее формальные параметры.

Класс для рекурсивного обхода дерева каталогов.

Автор: Святослав WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Класс (компонент) для рекурсивного обхода дерева каталогов
(всего диска или заданной папки). На каждый файл и директорию
вызывается соответствующий event.
TTreeWalker.TargetPath - путь для обхода.
TTreeWalker.OnNewDir - event вызывается при обнаружении каждой поддиректории
TTreeWalker.OnNewFile - файла.
Зависимости: Windows, Messages, SysUtils, Classes;
Автор:       Святослав, lisin@asicdesign.ru, ICQ:138752432, Saint Petersburg
Copyright:   (C) NetBreaker666[AWD]<SP666>@Svjatoslav_Lisin - т.е. я сам
Дата:        11 августа 2002 г.
***************************************************** }
unit TreeWalker;
//Просто рекурсивный обход. Я думаю комментарии не требуются.
interface
uses
  Windows, Messages, SysUtils, Classes;
type
  PNewDir = procedure(Sender: TObject; Dir: string) of object;
  PNewFile = procedure(Sender: TObject; F: TSearchRec) of object;
type
  TTreeWalker = class(TComponent)
  private
    { Private declarations }
    TargetPathV: string;
    OnNewFileV: PNewFile;
    OnNewDirV: PNewDir;
    procedure SetTargetPath(const S: string);
    function GetTargetPath: string;
    procedure SetOnFile(const ONF: PNewFile);
    function GetOnFile: PNewFile;
    procedure SetOnDir(const OND: PNewDir);
    function GetOnDir: PNewDir;
    procedure Recurs(S: string; D: Integer);
  protected
    { Protected declarations }
  public
    { Public declarations }
    procedure Process;
  published
    { Published declarations }
    property TargetPath: string read GetTargetPath write SetTargetPath;
    property OnNewFile: PNewFile read GetOnFile write SetOnFile;
    property OnNewDir: PNewDir read GetOnDir write SetOnDir;
  end;
procedure Register;
implementation
procedure TTreeWalker.SetTargetPath(const S: string);
begin
  TargetPathV := S;
  if TargetPathV[Length(TargetPathV)] <> '\' then
    TargetPathV := TargetPathV + '\';
end;
function TTreeWalker.GetTargetPath: string;
begin
  Result := TargetPathV;
end;
procedure TTreeWalker.SetOnFile(const ONF: PNewFile);
begin
  OnNewFileV := ONF;
end;
function TTreeWalker.GetOnFile: PNewFile;
begin
  Result := OnNewFileV;
end;
procedure TTreeWalker.SetOnDir(const OND: PNewDIr);
begin
  OnNewDirV := OND;
end;
function TTreeWalker.GetOnDir: PNewDir;
begin
  Result := OnNewDirV;
end;
procedure TTreeWalker.Process;
begin
  Recurs(TargetPathV, 0);
end;
procedure TTreeWalker.Recurs(S: string; D: Integer);
var
  F: TSearchRec;
  R: Integer;
begin
  if D > 512 then
    Exit;
  if @OnNewDirV <> nil then
    OnNewDirV(self, S);
  R := FindFirst(S + '*.*', faAnyFile, F);
  while R = 0 do
  begin
    if @OnNewFileV <> nil then
      OnNewFileV(self, F);
    if (F.Attr and faDirectory <> 0)
      and (F.Name <> '.') and (F.Name <> '..') then
      Recurs(S + F.Name + '\', D + 1);
    R := FindNext(F);
  end;
  FindClose(F);
end;
procedure Register;
begin
  RegisterComponents('NetBreakers', [TTreeWalker]);
end;
end.

Рекурсивное удаление файлов и подкаталогов.

Один программист очень любил рекурсию. И когда шеф начинал требовать у него отчет о проделанной работе, он отсылал его к ... себе. И так до переполнения стека у шефа.
{
Здесь я привожу немного сокращенный код, который я создавал для Borland
Pascal 5.5 под DOS (оригинальный код не делал rmDir, поэтому вы можете
поэкспериментировать с этим, передав указатель на каталог функции rmDir
в конце этого кода). Я подозреваю, что Delphi-версия может быть или
идентичной, или иметь некоторые различии в написании имен функций
(рекомендую ознакомиться с электронной документацией по Delphi, с темой,
где описаны функции для работы с файлами). Данный код не предусматривает
проверку атрибутов файлов, которые могут быть установлены для
предотвращения удаления файла. (В Pascal 5.5 вам необходимо между парой
{$I-}{$I+} {поместить функцию, которая вызывает проблему, не знаю,
делаете ли вы это в Delphi.)
}
procedure removeTree(DirName: string);
var
  FileSearch: SearchRec;
begin
  { для начала пробегаемся, и удаляем все файлы }
  chDir(DirName);
  FindFirst('*.*', Directory, FileSearch);
  while (DosError = 0) do
  begin
    if (FileSearch.name <> '.') and (FileSearch.name <> '..') and
      ((FileSearch.attr and Directory) <> 0) then
    begin
      if DirName[length(DirName)] = '\' then
        removeTree(DirName + FileSearch.Name)
      else
        removeTree(DirName + '\' + FileSearch.Name);
      ChDir(DirName);
    end;
    FindNext(FileSearch)
  end;
  { затем пробегаемся, и удаляем все каталоги }
  FindFirst('*.*', AnyFile, FileSearch);
  while (DosError = 0) do
  begin
    if (FileSearch.name <> '.') and (FileSearch.name <> '..') then
      Remove(workdir);
  end;
  FindNext(FileSearch)
end;
rmDir(DirName)
end;

Ищем файл рекурсивно.

Встречаются двое юзеров. Один говорит: - Я тут свежий антивирус достал, не хочешь себе установить? - Нет, мне это не нужно. - Почему? - Да мой комп так глючит, что на нем ни один вирус не запустится.
procedure GetAllFiles(mask: string); 
var 
  search: TSearchRec; 
  directory: string; 
begin 
  directory := ExtractFilePath(mask); 
  // find all files 
  if FindFirst(mask, $23, search) = 0 then 
  begin 
    repeat 
      // add the files to the listbox 
      Form1.ListBox1.Items.Add(directory + search.Name); 
      Inc(Count); 
    until FindNext(search) <> 0; 
  end; 
  // Subdirectories/ Unterverzeichnisse 
  if FindFirst(directory + '*.*', faDirectory, search) = 0 then 
  begin 
    repeat 
      if ((search.Attr and faDirectory) = faDirectory) and (search.Name[1] <> '.') then 
        GetAllFiles(directory + search.Name + '\' + ExtractFileName(mask)); 
    until FindNext(search) <> 0; 
    FindClose(search); 
  end; 
end; 
procedure TForm1.Button2Click(Sender: TObject); 
var 
  directory: string; 
  mask: string; 
begin 
  Count := 0; 
  Listbox1.Items.Clear; 
  directory := 'C:\temp\'; 
  mask := '*.*'; 
  Screen.Cursor := crHourGlass; 
  try 
    GetAllFiles(directory + mask); 
  finally 
    Screen.Cursor := crDefault; 
  end; 
  ShowMessage(IntToStr(Count) + ' Files found'); 
end; 
{**************************************} 
{ Code from P. Below: } 
// recursively scanning all drives 
  { excerpt from form declaration, form has a listbox1 for the 
    results, a label1 for progress, a button2 to start the scan, 
    an edit1 to get the search mask from, a button3 to stop 
    the scan. } 
  private 
    { Private declarations } 
    FScanAborted: Boolean; 
  public 
    { Public declarations } 
     
function ScanDrive(root, filemask: string; hitlist: TStrings): Boolean; 
implementation 
function TForm1.ScanDrive(root, filemask: string; hitlist: TStrings): Boolean; 
  function ScanDirectory(var path: string): Boolean; 
  var 
    SRec: TSearchRec; 
    pathlen: Integer; 
    res: Integer; 
  begin 
    label1.Caption := path; 
    pathlen := Length(path); 
    { first pass, files } 
    res := FindFirst(path + filemask, faAnyfile, SRec); 
    if res = 0 then 
      try 
        while res = 0 do  
        begin 
          hitlist.Add(path + SRec.Name); 
          res := FindNext(SRec); 
        end; 
      finally 
        FindClose(SRec) 
      end; 
    Application.ProcessMessages; 
    Result := not (FScanAborted or Application.Terminated); 
    if not Result then Exit; 
    {second pass, directories} 
    res := FindFirst(path + '*.*', faDirectory, SRec); 
    if res = 0 then 
      try 
        while (res = 0) and Result do  
        begin 
          if ((Srec.Attr and faDirectory) = faDirectory) and 
            (Srec.Name <> '.') and 
            (Srec.Name <> '..') then  
          begin 
            path := path + SRec.Name + '\'; 
            Result := ScanDirectory(path); 
            SetLength(path, pathlen); 
          end; 
          res := FindNext(SRec); 
        end; 
      finally 
        FindClose(SRec) 
      end; 
  end; 
begin 
  FScanAborted := False; 
  Screen.Cursor := crHourglass; 
  try 
    Result := ScanDirectory(root); 
  finally 
    Screen.Cursor := crDefault 
  end; 
end; 
procedure TForm1.Button2Click(Sender: TObject); 
var 
  ch: Char; 
  root: string; 
begin 
  root := 'C:\'; 
  for ch := 'A' to 'Z' do  
  begin 
    root[1] := ch; 
    case GetDriveType(PChar(root)) of 
      DRIVE_FIXED, DRIVE_REMOTE: 
        if not ScanDrive(root, edit1.Text, listbox1.Items) then 
          Break; 
    end; 
  end; 
end; 
procedure TForm1.Button3Click(Sender: TObject); 
begin // aborts scan 
  FScanAborted := True; 
end;

Получить количество файлов в корзине и их общий размер.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

type
   PSHQueryRBInfo = ^TSHQueryRBInfo;
   TSHQueryRBInfo = packed record
     cbSize: DWORD;
     // Size of the structure, in bytes. 
    // This member must be filled in prior to calling the function. 
    i64Size: Int64;
     // Total size of all the objects in the specified Recycle Bin, in bytes. 
    i64NumItems: Int64;
     // Total number of items in the specified Recycle Bin. 
  end;
 const
   shell32 = 'shell32.dll';
 function SHQueryRecycleBin(szRootPath: PChar; SHQueryRBInfo: PSHQueryRBInfo): HResult;
   stdcall; external shell32 Name 'SHQueryRecycleBinA';
 function GetDllVersion(FileName: string): Integer;
 var
   InfoSize, Wnd: DWORD;
   VerBuf: Pointer;
   FI: PVSFixedFileInfo;
   VerSize: DWORD;
 begin
   Result   := 0;
   InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd);
   if InfoSize <> 0 then
   begin
     GetMem(VerBuf, InfoSize);
     try
       if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then
         if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then
           Result := FI.dwFileVersionMS;
     finally
       FreeMem(VerBuf);
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   DllVersion: integer;
   SHQueryRBInfo: TSHQueryRBInfo;
   r: HResult;
 begin
   DllVersion := GetDllVersion(PChar(shell32));
   if DllVersion >= $00040048 then
   begin
     FillChar(SHQueryRBInfo, SizeOf(TSHQueryRBInfo), #0);
     SHQueryRBInfo.cbSize := SizeOf(TSHQueryRBInfo);
     R := SHQueryRecycleBin(nil, @SHQueryRBInfo);
     if r = s_OK then
     begin
       label1.Caption := Format('Size:%d Items:%d',
         [SHQueryRBInfo.i64Size, SHQueryRBInfo.i64NumItems]);
     end
     else
       label1.Caption := Format('Err:%x', [r]);
   end;
 end;
 { 
The SHQueryRecycleBin API used in this method is 
only available on systems with the latest shell32.dll installed with IE4 / 
Active Desktop. 
}

Использовать Запись для сохранения информации из свойств различных классов.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
    Es ist manchmal interessant, Informationen die zusammen gehoren in Records 
    abzuspeichern. In einer Klassendeklaration kann der Record dann als 
    Sammelstelle benutzt werden, was zum einen die Ubersichtlichkeit des 
    Programmes erhoht, zum anderen eine gewisse Logik widderspiegelt. 
    Der Zugriff auf die einzelnen Felder des Records kann von au?erhalb 
    der Klasse mittels Eigenschaften (Properties) erfolgen, was zu besseren 
    Code fuhrt... 
    Letzlich konnen Records z.B. sehr einfach dann in Dateien abgespeichert 
    bzw. von dort aus wieder geladen werden. This wird dann in einem einzigen 
    Vorgang erledigt: das Speichern des Records. 
}
 { 
   You sometimes wish to store multiple information in a given class like 
   in the example: alltogether when it belongs together. 
   Thus accessing this information from out of the class can be achieved 
   using property declaration. Its a good way to "clean your code" and 
   make it as "logic" as possible. 
   You also may have to store or load information from your class using 
   file or stream technology. This can be done at once for the recorded 
   information from within the given class. 
}
 type
   TPersonRecord = Record
      FirstName: String;
      LastName: String;
      BirthDate: TDate;
      End;
   TForm4 = class(TForm)
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
   private
     fActualUser: TPersonRecord;
     ...
     procedure SaveActualUser(S: TFileStream); // it's an example 
    procedure LoadActualUser(S: TFileStream);
     ...
   public
     property FirstName: string read  fActualUser.FirstName
                                write fActualUser.FirstName;
     property LastName : string read  fActualUser.LastName
                                write fActualUser.LastName;
     property BirthDate: TDate  read  fActualUser.BirthDate
                                write fActualUser.BirthDate;
   end;
 procedure TForm4.SaveActualUser(S: TFileStream);
 begin
   // All that stuff at once in your Stream 
  S.Write(fActualUser, SizeOf(fActualUser))
 end;
 procedure TForm4.LoadActualUser(S: TFileStream);
 begin
   // All that stuff at once back in your class 
  S.Read(fActualUser, SizeOf(fActualUser));
 end;

Переопределить параметры формы при её создании.

Данный код позволяет создать окно без неклиентской области, но с толстым бордюром:

unit MainFrm;
interface
uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TMainForm = class(TForm)
    btnClose: TButton;
    procedure btnCloseClick(Sender: TObject);
  private
    { Private declarations }
  protected
    // Capture the WM_NCHITTEST message to enable moving the form.
    procedure WMNCHitTest(var message: TWMNCHitTest); message WM_NCHITTEST;
  public
    { Public declarations }
    procedure CreateParams(var Params: TCreateParams); override;
end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.WMNCHitTest(var message: TWMNCHitTest);
begin
  inherited;
  message.Result := HTCAPTION;
end;
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := WS_THICKFRAME or WS_POPUP or WS_BORDER;
end;
procedure TMainForm.btnCloseClick(Sender: TObject);
begin
  Close;
end;
end.

FTP - докачка файлов.

Периоды зачатия инетчика: 1. Connect 2. Download 3. Disconnect 4. UnZip (Estimated time: 9 месяцев)

»»» Диман.

Как качать файлы через интернет по протоколу FTP я разобрался - послать команду RETR - и эдать файл. А какую команду нужно послать, что бы файл начал скачиваться не с начала а с определённого места - т.е. при обрыве связи докачать его. Может кто знает - подскажите. Или может знаете где раскопать инфу по этому вопросу?

»»» Wonder.

Читайте RFC 959 "File Transfer Protocol". А делается это просто:

REST [С_КАКОГО_БАЙТА_ПРОДОЛЖИТЬ]
RETR [ИМЯ_ФАЙЛА]

Но это только в том случае сработает, если FTP сервер поддерживает докачку.

»»» Диман.

В связи с этим 2 вопроса: Подскажите по какому адресу можно прочитать спецификацию RCF 959, а так же другие из этой серии Как определить - поддерживает ли сервер докачку файлов. Заранее спасибо.

»»» Wonder.

Как определить:

Как правило при входе на FTP сервер в т.н. Welcome message либо написано либо нет о поддержке докачки. Что-то типа "This server can resume broken downloads". Просто проверить, поддерживает сервер команду REST или нет. Проверить в смысле кода ответа (в RFC написаны все возможные коды ответов на все команды)

Где взять:

Пойти на (например) http://www.yahoo.com и поискать "RFC" (ссылок море) Или воспользоваться (например) ссылкой http://src.doc.ic.ac.uk/computing/internet/rfc/rfc959.txt

Как заставить перерисоваться все окна.

InvalidateRect(0, nil, true);

Как уменьшить размер EXE-файла.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

Generally, EXE files created with Delphi are larger than EXE files created with another programming language. The reason is the VCL. (Sure, VCL has many advantages...) There are several ways to reduce a EXE''s size: 01) Use a EXE-Packer (UPX, ASPack,...) 02) Use KOL. 03) Write your application without VCL 04) Use the ACL (API Controls Library) 05) Use StripReloc. 06) Deactivate remote debugging information and TD32. 07) You might want to put code in a dll. 08) Don''t put the same images several times on a form. Load them at runtime. 09) Use compressed images (JPG and not BMP) 10) Store less properties in DFM files (See Link below "How To Make Your EXE''s Lighter") 11) Use the TStringList replacement by ~LOM~ Use the Minireg - TRegistry replacement by Ben Hochstrasser {*****************************************} Mit Delphi erstellte Exe-Dateien sind im allgemeinen einiges gгцssег als solche, welche mit anderen Programmiersprachen erzeugt wurden. Der Grund dаfьг ist die VCL. (Klar, Die VCL hat viele Vorteile...) Es gibt verschiedene Мцg1iсhкеitеn, um die Exe-Gгцssе zu reduzieren. 01) Einen EXE-Packer verwenden (UPX, ASPack, ....) 02) KOL verwenden. 03) Die Anwendung ohne VCL schreiben (nur mit API, nonVCL) 04) Die ACL (API Controls Library) verwenden. 05) StripReloc verwenden. 06) Debug Informationen und TD32 ausschalten. 07) Code in eine Dll auslagern. 08) Wenn Bilder mehrmals verwendet werden, dann nur einmal einbinden und die anderen zur Laufzeit laden. 09) Bilder komprimieren (nicht bmp sondern z.B das jpg Format verwenden) 10) Weniger Properties in den DFM Dateien speichern (Siehe Link unten ("How To Make Your EXE''s Lighter") 11) Verwende den TStringList Ersatz von ~LOM~ Verwende die Minireg - TRegistry Ersatz von Ben Hochstrasser {*****************************************} // Further descriptions and links: // Beschreibungen in Englisch und Links: {****************************************************************} 01) UPX is a free, portable, extendable, high-performance executable packer for several different executable formats. It achieves an excellent compression ratio and offers very fast decompression. Your executables suffer no memory overhead or other drawbacks. http://upx.sourceforge.net/ ASPack is an advanced Win32 executable file compressor, capable of reducing the file size of 32-bit Windows programs by as much as 70%. (ASPack''s compression ratio improves upon the industry-standard zip file format by as much as 10-20%.) ASPack makes Windows 95/98/NT programs and libraries smaller, and decrease load times across networks, and download times from the internet; it also protects programs against reverse engineering by non-professional hackers. Programs compressed with ASPack are self-contained and run exactly as before, with no runtime performance penalties. http://www.aspack.com/aspack.htm {****************************************************************} 02) KOL - Key Objects Library is a set of objects to develop power (but small) 32 bit Windows GUI applications using Delphi but without VCL. It is distributed free of charge, with source code. http://bonanzas.rinet.ru/ {****************************************************************} 03) nonVCL Delphi lets you have it both ways. If you want tiny EXE's, then don't use the VCL. Its entirely possible to use all the rich features of Delphi IDE using 100% WinAPI calls, standard resources, etc. http://nonvcl.luckie-online.de http://www.erm.tu-cottbus.de/delphi/stuff/Tutorials/nonVCL/index.html http://www.angelfire.com/hi5/delphizeus/ http://www.tutorials.delphi-source.de/nonvcl/ {****************************************************************} 04) ACL (API Controls Library) To write the program on pure API certainly it is possible, but I have deci- ded to reach both goals - both to make that program and to receive the tool, through which it would be possible in further to build similar programs, almost, as on Delphi with VCL. So the idea to create my own TWinControl and all standard Windows controls classes, derived from it has appeared. http://www.apress.ru/pages/bokovikov/delphi/index.html/ {****************************************************************} 05) StripReloc is a free (GPL license) command line utility that removes the relocation (".reloc") section from Win32 PE EXE files, reducing their size. Most compilers/linkers (including Delphi) put a relocation section in EXE files, but this is actually not necessary since EXEs never get relocated. Hence, a relocation section only wastes space. Why not use an EXE compressor? http://www.jrsoftware.org/striprlc.php {****************************************************************} 06) Deactivating the Debug Information Exclude any debug information for the final build (project-Options Compiler - Debugging and project-Options Linker EXE and DLL options) Dependeing on the amount of Debug information, Debugging can take up until half of the size. The options that are going to singificantly reduce your file size are "Include TD32 debug info" and "Build with runtime packages". If you are shipping commercial applications, you usually don''t need the debug info linked with your project. {****************************************************************} 08/09) About Images The forms in your project have any bitmaps on them, then these are compiled into the EXE. If you use the same bitmap multiple times, don''t assign them at design-time in the IDE as it will be included in the EXE multiple times, assign them in code instead. This can help reduce the size of the EXE, especially if you use large bitmaps. Use JPEG-files instead of BMP-files. This also reduces the EXE size. {****************************************************************} 10) How To Make Your EXE''s Lighter: http://www.undu.com/DN970301/00000064.htm {****************************************************************} 11) TStringList replacement by ~LOM~ Minireg - TRegistry replacement

Можно ли как-то уменьшить мерцание при перерисовке компонента.

Если добавить флаг csOpaque (непрозрачный) к свойству ControlStyle компонента - то фон компонента перерисовываться не будет.

constructor TMyControl.Create;
begin
  inherited;
  ControlStyle := ControlStyle + [csOpaque];
end;

Обновление вычисляемых полей.

Автор: OAmiry (Borland)

Разместите строчку типа нижеприведенной в конце кода обработчика события OnCalcFields:

{предположим, что вы используете DBGrid1}
if DBGrid1.Showing then
  DBGrid1.Invalidate ;

Как заставить Рабочий Стол обновится.

procedure TForm1.Button1Click(Sender: TObject);
begin
  SendMessage(FindWindow('Progman', 'Program Manager'),
  WM_COMMAND, $A065, 0);
end;

Как заставить Рабочий Стол обновится 2.

procedure RefreshDesktop;
var
  hDesktop: HWND;
begin
  hDesktop := FindWindowEx(FindWindowEx(
    FindWindow('Progman', 'Program Manager'), 0,
    'SHELLDLL_DefView', ''), 0, 'SysListView32', '');
  PostMessage(hDesktop, WM_KEYDOWN, VK_F5, 0);
  PostMessage(hDesktop, WM_KEYUP, VK_F5, 1 shl 31);
end;

Как заставить Рабочий Стол обновится 3.

Автор: Radmin WEB-сайт: http://forum.vingrad.ru

winexec(Pchar('rundll32 user,repaintscreen'), sw_Show);

Обновить рабочий стол.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

 uses
   ShlObj;
 procedure RefreshDesktop1;
 begin
   SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
 end;

Обновить список дисков TDriveComboBox с сетевыми дсками и Plug&Play.

И вновь я не замечен Plug-n-Рlау'ем...
type
  //это наш "class cracer"
  TNewDriveComboBox = class(TDriveComboBox)
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  Drive : char;
begin
  Drive := DriveComboBox1.Drive;
  //вызываем защищенный метод родительского класса
  TNewDriveComboBox(DriveComboBox1).BuildList;
  DriveComboBox1.Drive := Drive;
end;

Обновление файла после перезагрузки системы.

Автор: Dimka Maslov WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Обновление файла после перезагрузки системы
Данная процедура настраивает систему для обновления файлов после перезагрузки.
TargetFileName - имя файла, содержиоме которого при перезагрузке будет заменено
на содержимое файла, имя которого укзаывается в параметре SourceFileName.
Если первый аргумент процедуры представляет собой пустую строку, при
перезагрузке файл SourceFileName будет удалён.
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        18 ноября 2002 г.
***************************************************** }
procedure BootReplaceFile(TargetFileName, SourceFileName: string)
var
  WinInitName: string;
  P: PChar;
  procedure InternalGetShortPathName(var S: string);
  begin
    UniqueString(S);
    GetShortPathName(PChar(S), PChar(S), Length(S));
    SetLength(S, StrLen(@S[1]));
    CharToOEM(PChar(S), PChar(S));
  end;
begin
  if Win32Platform = VER_PLATFORM_WIN32_NT then
  begin
    if TargetFileName <> '' then
      P := PChar(TargetFileName)
    else
      P := nil;
    MoveFileEx(PChar(SourceFileName), P,
      MOVEFILE_DELAY_UNTIL_REBOOT or MOVEFILE_REPLACE_EXISTING);
  end
  else
  begin
    SetLength(WinInitName, MAX_PATH);
    GetWindowsDirectory(@WinInitName[1], MAX_PATH);
    SetLength(WinInitName, StrLen(@WinInitName[1]));
    WinInitName := IncludeTrailingBackslash(WinInitName) + 'WININIT.INI';
    if TargetFileName = '' then
      TargetFileName := 'NUL'
    else
      InternalGetShortPathName(TargetFileName);
    InternalGetShortPathName(SourceFileName);
    WritePrivateProfileString('Rename', PChar(TargetFileName),
      PChar(SourceFileName), PChar(WinInit));
  end;
end;

Пример использования:

// После перезагрузки содержимое первого файла будет заменено на
// содержимое второго файла, а второй файл будет удалён
BootReplaceFile('c:\Program Files\proga.exe', 'c:\temp\proga.exe');
// Указанный файл будет удалён после перезагрузки
BootReplaceFile('', 'c:\temp\proga.exe');

Как обновить TQuery не потеряв при этом текущей записи.

procedure RefreshQuery(AQuery: TQuery; const FieldsForSearch: string);
var
  AList: TList;
  AVarArray: Variant;
  i: Byte;
begin
  AList := TList.Create;
  try
    AQuery.GetFieldList(AList, FieldsForSearch);
    AVarArray := VarArrayCreate([0, AList.Count - 1], varVariant);
    for i := 0 to Pred(AList.Count) do
      AVarArray[i] := TField(AList.Items[i]).AsVariant;
    AQuery.Close;
    AQuery.Open;
    AQuery.Locate(FieldsForSearch, AVarArray, []);
  finally
    AList.Free;
    AVarArray.Free;
  end;
end;

Обновить иконки на экране.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  Microsoft's Tweak UI has a feature 'rebuild icon cache now'. 
  Windows then rebuilds its internal cache of icons. 
  Unfortunately, there is no single API to do this. 
}
 uses
   Registry;
 function RefreshScreenIcons : Boolean;
 const
   KEY_TYPE = HKEY_CURRENT_USER;
   KEY_NAME = 'Control Panel\Desktop\WindowMetrics';
   KEY_VALUE = 'Shell Icon Size';
 var
   Reg: TRegistry;
   strDataRet, strDataRet2: string;
  procedure BroadcastChanges;
  var
    success: DWORD;
  begin
    SendMessageTimeout(HWND_BROADCAST,
                       WM_SETTINGCHANGE,
                       SPI_SETNONCLIENTMETRICS,
                       0,
                       SMTO_ABORTIFHUNG,
                       10000,
                       success);
  end;
 begin
   Result := False;
   Reg := TRegistry.Create;
   try
     Reg.RootKey := KEY_TYPE;
     // 1. open HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics 
    if Reg.OpenKey(KEY_NAME, False) then
     begin
       // 2. Get the value for that key 
      strDataRet := Reg.ReadString(KEY_VALUE);
       Reg.CloseKey;
       if strDataRet <> '' then
       begin
         // 3. Convert sDataRet to a number and subtract 1, 
        //    convert back to a string, and write it to the registry 
        strDataRet2 := IntToStr(StrToInt(strDataRet) - 1);
         if Reg.OpenKey(KEY_NAME, False) then
         begin
           Reg.WriteString(KEY_VALUE, strDataRet2);
           Reg.CloseKey;
           // 4. because the registry was changed, broadcast 
          //    the fact passing SPI_SETNONCLIENTMETRICS, 
          //    with a timeout of 10000 milliseconds (10 seconds) 
          BroadcastChanges;
           // 5. the desktop will have refreshed with the 
          //    new (shrunken) icon size. Now restore things 
          //    back to the correct settings by again writing 
          //    to the registry and posing another message. 
          if Reg.OpenKey(KEY_NAME, False) then
           begin
             Reg.WriteString(KEY_VALUE, strDataRet);
             Reg.CloseKey;
             // 6.  broadcast the change again 
            BroadcastChanges;
             Result := True;
           end;
         end;
       end;
     end;
   finally
     Reg.Free;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   RefreshScreenIcons
 end;
 { 
  The result is Window's erasing all its icons, and recalculating them 
  based on the registry settings. 
  This means if you have changed a DefaultIcon key within the registry for 
  some application or file, Windows will display the new icon when the 
  refresh is completed. 
  Original source: 
  www.mvps.org/vbnet/index.html?code/reg/screenrefresh.htm 
  Translated from VB by Thomas Stutz 
}

Перегенерировать все индексы с истекшей датой.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure TForm1.Button1Click(Sender: TObject);
 begin
   Table.Close;
   Table.Exclusive := True;
   Table.Open;
   DbiRegenIndexes(Table.Handle);
   Table.Close;
 end;

Регионарные стандарты.

Автор: Vit WEB-сайт: http://forum.vingrad.ru

В Дельфи есть предопределенные переменные языковых установок и форматов:

// SysUtils
 var CurrencyString: string;
 var CurrencyFormat: Byte;
 var NegCurrFormat: Byte;
 var ThousandSeparator: Char;
 var DecimalSeparator: Char;
 var CurrencyDecimals: Byte;
 var DateSeparator: Char;
 var ShortDateFormat: string;
 var LongDateFormat: string;
 var TimeSeparator: Char;
 var TimeAMString: string;
 var TimePMString: string;
 var ShortTimeFormat: string;
 var LongTimeFormat: string;
 var ShortMonthNames: array[1..12] of string;
 var LongMonthNames: array[1..12] of string;
 var ShortDayNames: array[1..7] of string;
 var LongDayNames: array[1..7] of string;
 var SysLocale: TSysLocale;
 var EraNames: array[1..7] of string;
 var EraYearOffsets: array[1..7] of Integer;
 var TwoDigitYearCenturyWindow: Word = 50;
 var TListSeparator: Char;

Как зарегистрировать ActiveX-компонент (OCX-файл) на компьютере пользователя.

Статьи по Delphi (014)…
Программер (П) привел к себе домой девушку (Д). Посидели, поболтали, программеру не терпится в постель перейти. Он думает "Щаз вот покажу ей, какой у меня Виндоуз последней версии стоит, она скажет "Ого, какой у тебя Виндоуз!", а я ей- "Да ну его, пошли лучше потрахаемся." Показал, она - ноль эмоций. П опять думает: "Так... Ну ладно, щаз покажу ей, какой у меня модем крутой, на 33600, она скажет "Ого, какой у тебя модем весь крутой!", а я ей - "Да ну его, пошли лучше потрахаемся!" Показал, та же реакция. Программер думает: "Блин, ну что бы ей еще показать? Во, придумал, покажу ей, какой у меня ноутбук весь белой сборки, она скажет "Ого, какой у тебя ноутбук весь белый!", а я ей- "Да ну его, пошли лучше потрахаемся!" Показал. Девушка: (Д): Слушай, а может, да ну все это, пошли лучше потрахаемся! (П): Какое потрахаемся, ты смотри, какой у меня ноутбук весь белый!

Из командной строки:

C:\WINDOWS\SYSTEM\REGSVR32.EXE "c:\my path\myocx.ocx"

Регистрация компонент.

C:\WINNT\system32\regsvr32.exe "c:\AGUN\VoiceMan\FRALIBCS.DLL"

Зарегистрировать новый тип файлов.

Статьи по Delphi (014)…
Возлюбленная компьютерщика мурлычет ему на ушко: - Любимый, ну когда, когда мы будем регистрироваться? - А на хр#на? Я сейчас и так взломаю...

Не хуже M$ получается! У них свои типы файлов, и у нас будут свои! Всё, что для этого нужно - точно выполнять последовательность действий и научиться копировать в буфер, чтобы не писать все те коды, что будут тут изложены :)).

Сначала, естественно, объявляем в uses модуль Registry.

uses
  Registry;

Затем в публичных объявлениях объявляем процедуру регистрации нового типа файлов:

public
  { Public declarations }
  procedure RegisterFileType(ext: string; FileName: string);

Описываем её так:

procedure TForm1.RegisterFileType(ext: string; FileName: string);
var
  reg: TRegistry;
begin
  reg:=TRegistry.Create;
  with reg do
  begin
    RootKey:=HKEY_CLASSES_ROOT;
    OpenKey('.'+ext,True);
    WriteString('',ext+'file');
    CloseKey;
    CreateKey(ext+'file');
    OpenKey(ext+'file\DefaultIcon',True);
    WriteString('',FileName+',0');
    CloseKey;
    OpenKey(ext+'file\shell\open\command',True);
    WriteString('',FileName+' "%1"');
    CloseKey;
    Free;
  end;
end;

Ну а по нажатию какого-нибудь батона регистрируем!

procedure TForm1.Button1Click(Sender: TObject);
begin
  RegisterFileType('DelphiWorld', Application.ExeName);
end;

Как зарегистрировать OCX.

Вы правы, прежде чем использовать OCX, его необходимо зарегистрировать в системном регистре. Предположим вы создали OCX с именем "test.ocx".

Теперь его необходимо зарегистрировать в системе:

var
  OCXHand: THandle;
  RegFunc: TDllRegisterServer; //добавьте OLECtl в список используемых модулей
begin
  OCXHand := LoadLibrary('c:\windows\system\test.ocx');
  RegFunc := GetProcAddress(OCXHand, 'DllRegisterServer');
    //чуствительность к регистру?
  if RegFunc <> 0 then
    ShowMessage('Ошибка!');
  FreeLibrary(OCXHand);
end;
Для обратной процедуры, "отрегистрации" OCX, вам необходимо всего лишь заменить 'DllRegisterServer' на 'DllUnregisterServer'. Также для полноты картины вы можете добавить код, извещающий пользователя о процессе регистрации, например так: "Does the file exist" (файл не найден), "Was the call to LoadLibrary successful?" (был ли вызов LoadLibrary успешным?), ... Некоторые пояснения:

OCX - специфический форма dll, поэтому вы можете загрузить ее в память, используя знакомую уже API функцию LoadLibrary(). OCX экспортирует две функции, осуществляющие ее регистрацию и "отрегистрацию". Для получения адреса этих функций используется соответствующая функция GetProcAddress. После этого остается только вызвать необходимую функцию. И это все! После этого для успокоения души с помощью редактора регистров (regedit.exe) вы можете проверить регистрацию вашего OCX.

Регистрация редактора свойства.

Автор: Mike Scott(Mobius Ltd.)

Скажем, вы имеете компонент TContainer, содержащий TContainedClass в свойстве с именем 'Contained' - попробуйте добавить следующую строку к процедуре Register вашего компонента:

RegisterPropertyEditor( TypeInfo( TContainedClass ),
TContainer, 'Contained', TClassProperty ) ;
Не забудьте добавить TypInfo и DsgnIntf в список используемых модулей.

Все это задокументировано в справке помощи. Первым параметром всегда идет TypeInfo() с "коллекционируемым" классом в качестве параметра, затем контейнерный класс или NIL, если он должен работать для всех экземпляров контейнерного класса с заданным свойством, затем идет имя контейнерного свойства или '', если редактор должен работать для всех свойств, и завершает славную четверку параметров класс TClassProperty, расширяющий классовое свойство, т.е. "создающий" знак "+" в Инспекторе Объектов, позволяющий редактировать вложенные свойства (щелчок на плюсике раскрывает список вложенных свойств описываемого контейнерного класса).

Регистрация WinZip 7.0 (beta).

Если мысль не укладывается в голове, заархивируйте ее.

В этой статье мы попытаемся почувствовать себя настоящими исследователями программ. Конечно, это будут только первые робкие шаги, но именно они обычно являются определяющими для всей остальной работы в этом направлении. Нашей целью будет регистрация всемирно известной программы WinZip 7.0 (beta). В статье рассматривается build 1243, для более новой программы возможны некоторые отличия в адресах.

Устанавливаем и запускаем WinZip. Сразу после старта появляется окно, сообщающее о том, что программа не зарегистрирована. Если программу не зарегистрировать это окно будет появляться при каждом новом запуске. Это неудобно! В меню Help выбираем пункт About WinZip... В появившемся окне нажимаем кнопку Register. Открывается окно регистрации. Вводим в поле Имя имя, под которым хотим зарегистрировать программу (я использовал Fox Mulder). В поле РН вводим любую информацию (я обычно использую 200001, чтобы потом при необходимости его легко было опознать) и нажимаем кнопку ОК. Появляется окно, сообщающее, что введена неполная или неверная информация. Обращаю Ваше внимание на это окно. Это типичное окно MessageBox (окно сообщения). Его отличительными чертами являются:

неактивная кнопка закрытия окна наличие внутри окна иконки (вид иконки зависит от типа сообщения) наличие одной или нескольких кнопок (их названия и количество также зависит от типа сообщения)

В программе окно MessageBox может создаваться с помощью четырех функций: MessageBox(), MessageBoxA(), MessageBoxIndirect() и MessageBoxIndirectA(). Функции, не оканчивающиеся символом A (MessageBox(), MessageBoxIndirect()) используются преимущественно приложениями Win16 (Windows 3.x) и на них мы будем рассчитывать меньше всего. Остаются две функции: MessageBoxA() и MessageBoxIndirectA(). Какая из них используется программой, определяем опытным путем:

Закрываем окно сообщения Активизируем SoftICE (Ctrl+D) Устанавливаем точки прерывания на вызов MessageBoxA() и MessageBoxIndirectA() (bpx MessageBoxA, bpx MessageBoxIndirectA) Возвращаемся в Windows (F5)

Нажимаем кнопку ОК в окне регистрации, программа прерывается при вызове функции и передает управление SoftICE. В окне команд сообщается, что программа прервалась при вызове функции MessageBoxIndirectA() из модуля USER32. И действительно в окне кода мы видим, что курсор стоит на первой строке этой функции и, что имя текущего модуля USER32 (написано внизу окна кода). Дальше мы тоже знаем, что делать:

Нажимаем F12 (команда p ret), для продолжения программы до выполнения команды ret. Перед нами вновь появляется знакомое окно сообщения, при нажатии в нем на кнопку ОК программа прерывается и управление передается SoftICE. Мы видим, что теперь программа остановилась в модуле WINZIP32:
0137:00426920 FF15C4AF4700 call [USER32!MessageBoxIndirectA]
0137:00426926 EB14         jmp 0042693C 

Вот мы и нашли то место, где вызывается функция MessageBoxIndirectA() и создается окно сообщения. Теперь можно удалить установленные точки прерывания (команда bc *), т.к. они нам больше не понадобятся. В этой статье мы больше ничего, связанного с функцией MessageBoxIndirectA(), не узнаем. Использовали мы ее по двум причинам:

Чтобы Вы научились устанавливать точки прерывания на вызовы API-функций, и находит место их вызова в программе. Чтобы прервать программу в модуле WINZIP32. Это необходимо для установки точек прерывания на конкретные адреса в памяти. Как я уже говорил в статье о распределении памяти, каждая программа работает в своем адресном пространстве. Причем, практически у всех запущенных приложений есть разные участки кода с одинаковыми адресами. Поэтому, для установки точки прерывания на некоторый адрес памяти кроме самого этого адреса, необходимо еще правильно указать нужное адресное пространство (или модуль, что в данном случае одно и тоже). Особенностью SoftICE является то, что он устанавливает точку прерывания на адрес в том модуле, в котором прервалось выполнение. Вот почему, для установки точки прерывания на адрес памяти (нам ее еще только предстоит установить) пришлось воспользоваться точкой прерывания на вызов функции MessageBoxIndirectA()

Здесь уместным был бы Ваш вопрос о том, почему для установки этой точки прерывания не пришлось прибегать к каким-либо хитростям? Все очень просто! Точка прерывания на вызов MessageBoxIndirectA() устанавливается на первую команду кода этой функции в модуле USER32. Этот модуль находится в области памяти, которая является общей для всех запущенных приложений Windows (она расположена выше 2-го Гб), поэтому здесь никаких трудностей не возникает.

Итак, продолжим:

Мы все еще находимся в SoftICE. Устанавливаем точку прерывания на выполнение команды по адресу 00407CA5 (команда bpx 407CA5). Выходим в Windows (F5) и вновь пытаемся зарегистрироваться. При этом программа прерывается при выполнении команды по адресу 00407CA5, в окне команд SoftICE сообщается, что программа остановилась на точке прерывания по адресу 0137:00407CA5. Вводим команду db eax (показать в окне данных содержимое памяти в виде байт, начиная с адреса, содержащегося в регистре eax) и переписываем 8 символов нашего регистрационного номера (у меня это 409A11AA). Убираем не нужную уже точку прерывания (команда bc *) и выходим в Windows (F5). Наступает решающий момент. Вводим в поле РН полученный регистрационный номер и нажимаем кнопку ОК. Программа просит подтвердить регистрационную информацию, и после нажатия ОК Вы уже являетесь владельцем личного WinZiр'а.

Я надеюсь, Вас посетило чувство гордости, как оно посещает меня всегда, когда я справляюсь с чем-либо сложным и поначалу непонятным. Иногда, очень приятно почувствовать, что ты тоже чего-то стоишь в этой жизни! На этом наша очередная статья закончена. Не смущайтесь, если Вы чего-то не поняли, я не ставил целью объяснить Вам все в мельчайших подробностях в самый первый Ваш раз. Нет, я хотел, чтобы Вы узнали, с чем мы будем иметь дело, и что после успешной работы чувствует настоящий Исследователь Программ.

Регистры - доступ и использование вместо WIN.INI.

Плачет молодая забеременевшая программистка, слезами заливается. Над ней висит мать: - Да как ты могла? Да как ты посмела? Где твои глаза были? Программистка: - А он обещал стать зарегистрированным пользователем...
uses
  Registry, Windows;
var
  TheReg: TRegistry;
  KeyName: string;
  ValueStr: string;
begin
  TheReg := TRegistry.Create;
  try
    TheReg.RootKey := HKEY_CURRENT_USER;
    KeyName := 'Software\MyTinyApp\StartUp';
    if TheReg.OpenKey(KeyName, False) then
    begin
      ValueStr := TheReg.ReadString('WorkPath');
      TheReg.CloseKey;
    end;
  finally
    TheReg.Free;
  end;
end;

Также имейте в виду, что корректное место для сохранения пути к вашему приложению (EXE-файлу) в регистрах Win95 находится по адресу:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\AppPaths\MYAPP.EXE 
Сохраните полный путь к вашему приложению как значение по умолчанию в этом ключе. Regstr.pas определяет константу для этого пути (...\App Paths\) как REGSTR_PATH_APPPATHS.

Сохранение полного пути к вашему приложению (EXE-файлу) позволит пользователю запустить его, просто набрав MYAPP (зарегистрированное имя) в стартовом меню Start|Run. Вот пример, регистрирующий путь к вашему приложению:

uses
  Registry, Regstr;
var
  TheReg: TRegistry;
  KeyName: string;
begin
  TheReg := TRegistry.Create;
  try
    {Проверяем установку AppPath и при необходимости корректируем}
    TheReg.RootKey := HKEY_LOCAL_MACHINE;
    KeyName := REGSTR_PATH_APPPATHS + ExtractFileName(Application.ExeName);
    if TheReg.OpenKey(KeyName, True) then
    begin
      if CompareText(TheReg.ReadString(''), Application.ExeName) <> 0 then
        TheReg.WriteString('', Application.ExeName);
      TheReg.CloseKey;
    end;
  finally
    TheReg.Free;
  end;
end;

Как работать с реестром, используя только WinAPI.

Автор: i-s-v

unit apiregistry;
interface
uses Windows;
function RegSetString(RootKey: HKEY; Name: string; Value: string): boolean;
function RegSetMultiString(RootKey: HKEY; Name: string; Value: string): boolean;
function RegSetExpandString(RootKey: HKEY; Name: string; Value: string): boolean;
function RegSetDWORD(RootKey: HKEY; Name: string; Value: Cardinal): boolean;
function RegSetBinary(RootKey: HKEY; Name: string; Value: array of Byte): boolean;
function RegGetString(RootKey: HKEY; Name: string; var Value: string): boolean;
function RegGetMultiString(RootKey: HKEY; Name: string; var Value: string): boolean;
function RegGetExpandString(RootKey: HKEY; Name: string; var Value: string): boolean;
function RegGetDWORD(RootKey: HKEY; Name: string; var Value: Cardinal): boolean;
function RegGetBinary(RootKey: HKEY; Name: string; var Value: string): boolean;
function RegGetValueType(RootKey: HKEY; Name: string; var Value: Cardinal): boolean;
function RegValueExists(RootKey: HKEY; Name: string): boolean;
function RegKeyExists(RootKey: HKEY; Name: string): boolean;
function RegDelValue(RootKey: HKEY; Name: string): boolean;
function RegDelKey(RootKey: HKEY; Name: string): boolean;
function RegConnect(MachineName: string; RootKey: HKEY; var RemoteKey: HKEY): boolean;
function RegDisconnect(RemoteKey: HKEY): boolean;
function RegEnumKeys(RootKey: HKEY; Name: string; var KeyList: string): boolean;
function RegEnumValues(RootKey: HKEY; Name: string; var ValueList: string): boolean;
implementation
function LastPos(Needle: Char; Haystack: string): integer;
begin
  for Result := Length(Haystack) downto 1 do
    if Haystack[Result] = Needle then
      Break;
end;
function RegConnect(MachineName: string; RootKey: HKEY; var RemoteKey: HKEY):
  boolean;
begin
  Result := (RegConnectRegistry(PChar(MachineName), RootKey, RemoteKey) =
    ERROR_SUCCESS);
end;
function RegDisconnect(RemoteKey: HKEY): boolean;
begin
  Result := (RegCloseKey(RemoteKey) = ERROR_SUCCESS);
end;
function RegSetValue(RootKey: HKEY; Name: string; ValType: Cardinal; PVal:
  Pointer; ValSize: Cardinal): boolean;
var
  SubKey: string;
  n: integer;
  dispo: DWORD;
  hTemp: HKEY;
begin
  Result := False;
  n := LastPos('\', Name);
  if n > 0 then
  begin
    SubKey := Copy(Name, 1, n - 1);
    if RegCreateKeyEx(RootKey, PChar(SubKey), 0, nil, REG_OPTION_NON_VOLATILE,
      KEY_WRITE,
      nil, hTemp, @dispo) = ERROR_SUCCESS then
    begin
      SubKey := Copy(Name, n + 1, Length(Name) - n);
      Result := (RegSetValueEx(hTemp, PChar(SubKey), 0, ValType, PVal, ValSize)
        = ERROR_SUCCESS);
      RegCloseKey(hTemp);
    end;
  end;
end;
function RegGetValue(RootKey: HKEY; Name: string; ValType: Cardinal; var PVal:
  Pointer;
  var ValSize: Cardinal): boolean;
var
  SubKey: string;
  n: integer;
  MyValType: DWORD;
  hTemp: HKEY;
  Buf: Pointer;
  BufSize: Cardinal;
begin
  Result := False;
  n := LastPos('\', Name);
  if n > 0 then
  begin
    SubKey := Copy(Name, 1, n - 1);
    if RegOpenKeyEx(RootKey, PChar(SubKey), 0, KEY_READ, hTemp) = ERROR_SUCCESS
      then
    begin
      SubKey := Copy(Name, n + 1, Length(Name) - n);
      if RegQueryValueEx(hTemp, PChar(SubKey), nil, @MyValType, nil, @BufSize) =
        ERROR_SUCCESS then
      begin
        GetMem(Buf, BufSize);
        if RegQueryValueEx(hTemp, PChar(SubKey), nil, @MyValType, Buf, @BufSize)
          = ERROR_SUCCESS then
        begin
          if ValType = MyValType then
          begin
            PVal := Buf;
            ValSize := BufSize;
            Result := True;
          end
          else
          begin
            FreeMem(Buf);
          end;
        end
        else
        begin
          FreeMem(Buf);
        end;
      end;
      RegCloseKey(hTemp);
    end;
  end;
end;
function RegSetString(RootKey: HKEY; Name: string; Value: string): boolean;
begin
  Result := RegSetValue(RootKey, Name, REG_SZ, PChar(Value + #0), Length(Value)
    + 1);
end;
function RegSetMultiString(RootKey: HKEY; Name: string; Value: string): boolean;
begin
  Result := RegSetValue(RootKey, Name, REG_MULTI_SZ, PChar(Value + #0#0),
  Length(Value) + 2);
end;
function RegSetExpandString(RootKey: HKEY; Name: string; Value: string):
  boolean;
begin
  Result := RegSetValue(RootKey, Name, REG_EXPAND_SZ, PChar(Value + #0),
    Length(Value) + 1);
end;
function RegSetDword(RootKey: HKEY; Name: string; Value: Cardinal): boolean;
begin
  Result := RegSetValue(RootKey, Name, REG_DWORD, @Value, SizeOf(Cardinal));
end;
function RegSetBinary(RootKey: HKEY; Name: string; Value: array of Byte):
  boolean;
begin
  Result := RegSetValue(RootKey, Name, REG_BINARY, @Value[Low(Value)],
    length(Value));
end;
function RegGetString(RootKey: HKEY; Name: string; var Value: string): boolean;
var
  Buf: Pointer;
  BufSize: Cardinal;
begin
  Result := False;
  if RegGetValue(RootKey, Name, REG_SZ, Buf, BufSize) then
  begin
    Dec(BufSize);
    SetLength(Value, BufSize);
    if BufSize > 0 then
      CopyMemory(@Value[1], Buf, BufSize);
    FreeMem(Buf);
    Result := True;
  end;
end;
function RegGetMultiString(RootKey: HKEY; Name: string; var Value: string):
  boolean;
var
  Buf: Pointer;
  BufSize: Cardinal;
begin
  Result := False;
  if RegGetValue(RootKey, Name, REG_MULTI_SZ, Buf, BufSize) then
  begin
    Dec(BufSize);
    SetLength(Value, BufSize);
    if BufSize > 0 then
      CopyMemory(@Value[1], Buf, BufSize);
    FreeMem(Buf);
    Result := True;
  end;
end;
function RegGetExpandString(RootKey: HKEY; Name: string; var Value: string):
  boolean;
var
  Buf: Pointer;
  BufSize: Cardinal;
begin
  Result := False;
  if RegGetValue(RootKey, Name, REG_EXPAND_SZ, Buf, BufSize) then
  begin
    Dec(BufSize);
    SetLength(Value, BufSize);
    if BufSize > 0 then
      CopyMemory(@Value[1], Buf, BufSize);
    FreeMem(Buf);
    Result := True;
  end;
end;
function RegGetDWORD(RootKey: HKEY; Name: string; var Value: Cardinal): boolean;
var
  Buf: Pointer;
  BufSize: Cardinal;
begin
  Result := False;
  if RegGetValue(RootKey, Name, REG_DWORD, Buf, BufSize) then
  begin
    CopyMemory(@Value, Buf, BufSize);
    FreeMem(Buf);
    Result := True;
  end;
end;
function RegGetBinary(RootKey: HKEY; Name: string; var Value: string): boolean;
var
  Buf: Pointer;
  BufSize: Cardinal;
begin
  Result := False;
  if RegGetValue(RootKey, Name, REG_BINARY, Buf, BufSize) then
  begin
    SetLength(Value, BufSize);
    CopyMemory(@Value[1], Buf, BufSize);
    FreeMem(Buf);
    Result := True;
  end;
end;
function RegValueExists(RootKey: HKEY; Name: string): boolean;
var
  SubKey: string;
  n: integer;
  hTemp: HKEY;
begin
  Result := False;
  n := LastPos('\', Name);
  if n > 0 then
  begin
    SubKey := Copy(Name, 1, n - 1);
    if RegOpenKeyEx(RootKey, PChar(SubKey), 0, KEY_READ, hTemp) = ERROR_SUCCESS
      then
    begin
      SubKey := Copy(Name, n + 1, Length(Name) - n);
      Result := (RegQueryValueEx(hTemp, PChar(SubKey), nil, nil, nil, nil) =
        ERROR_SUCCESS);
      RegCloseKey(hTemp);
    end;
  end;
end;
function RegGetValueType(RootKey: HKEY; Name: string; var Value: Cardinal):
  boolean;
var
  SubKey: string;
  n: integer;
  hTemp: HKEY;
  ValType: Cardinal;
begin
  Result := False;
  Value := REG_NONE;
  n := LastPos('\', Name);
  if n > 0 then
  begin
    SubKey := Copy(Name, 1, n - 1);
    if (RegOpenKeyEx(RootKey, PChar(SubKey), 0, KEY_READ, hTemp) = ERROR_SUCCESS)
      then
    begin
      SubKey := Copy(Name, n + 1, Length(Name) - n);
      Result := (RegQueryValueEx(hTemp, PChar(SubKey), nil, @ValType, nil, nil)
        = ERROR_SUCCESS);
      if Result then
        Value := ValType;
      RegCloseKey(hTemp);
    end;
  end;
end;
function RegKeyExists(RootKey: HKEY; Name: string): boolean;
var
  SubKey: string;
  n: integer;
  hTemp: HKEY;
begin
  Result := False;
  n := LastPos('\', Name);
  if n > 0 then
  begin
    SubKey := Copy(Name, 1, n - 1);
    if RegOpenKeyEx(RootKey, PChar(SubKey), 0, KEY_READ, hTemp) = ERROR_SUCCESS
      then
    begin
      Result := True;
      RegCloseKey(hTemp);
    end;
  end;
end;
function RegDelValue(RootKey: HKEY; Name: string): boolean;
var
  SubKey: string;
  n: integer;
  hTemp: HKEY;
begin
  Result := False;
  n := LastPos('\', Name);
  if n > 0 then
  begin
    SubKey := Copy(Name, 1, n - 1);
    if RegOpenKeyEx(RootKey, PChar(SubKey), 0, KEY_WRITE, hTemp) = ERROR_SUCCESS
      then
    begin
      SubKey := Copy(Name, n + 1, Length(Name) - n);
      Result := (RegDeleteValue(hTemp, PChar(SubKey)) = ERROR_SUCCESS);
      RegCloseKey(hTemp);
    end;
  end;
end;
function RegDelKey(RootKey: HKEY; Name: string): boolean;
var
  SubKey: string;
  n: integer;
  hTemp: HKEY;
begin
  Result := False;
  n := LastPos('\', Name);
  if n > 0 then
  begin
    SubKey := Copy(Name, 1, n - 1);
    if RegOpenKeyEx(RootKey, PChar(SubKey), 0, KEY_WRITE, hTemp) = ERROR_SUCCESS
      then
    begin
      SubKey := Copy(Name, n + 1, Length(Name) - n);
      Result := (RegDeleteKey(hTemp, PChar(SubKey)) = ERROR_SUCCESS);
      RegCloseKey(hTemp);
    end;
  end;
end;
function RegEnum(RootKey: HKEY; Name: string; var ResultList: string; const
  DoKeys: Boolean): boolean;
var
  i: integer;
  iRes: integer;
  s: string;
  hTemp: HKEY;
  Buf: Pointer;
  BufSize: Cardinal;
begin
  Result := False;
  ResultList := '';
  if RegOpenKeyEx(RootKey, PChar(Name), 0, KEY_READ, hTemp) = ERROR_SUCCESS then
  begin
    Result := True;
    BufSize := 1024;
    GetMem(buf, BufSize);
    i := 0;
    iRes := ERROR_SUCCESS;
    while iRes = ERROR_SUCCESS do
    begin
      BufSize := 1024;
      if DoKeys then
        iRes := RegEnumKeyEx(hTemp, i, buf, BufSize, nil, nil, nil, nil)
      else
        iRes := RegEnumValue(hTemp, i, buf, BufSize, nil, nil, nil, nil);
      if iRes = ERROR_SUCCESS then
      begin
        SetLength(s, BufSize);
        CopyMemory(@s[1], buf, BufSize);
        if ResultList = '' then
          ResultList := s
        else
          ResultList := Concat(ResultList, #13#10,s);
       inc(i);
      end;
    end;
    FreeMem(buf);
    RegCloseKey(hTemp);
  end;
end;
function RegEnumValues(RootKey: HKEY; Name: string; var ValueList: string):
  boolean;
begin
  Result := RegEnum(RootKey, Name, ValueList, False);
end;
function RegEnumKeys(RootKey: HKEY; Name: string; var KeyList: string): boolean;
begin
  Result := RegEnum(RootKey, Name, KeyList, True);
end;
end.

Браузер по реестру.

unit main;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Grids, Outline, ComCtrls, ImgList, ExtCtrls;
type
  TRegForm = class(TForm)
    TreeView1: TTreeView;
    ListView1: TListView;
    ImageList1: TImageList;
    ImageList2: TImageList;
    Splitter1: TSplitter;
    procedure FormCreate(Sender: TObject);
    procedure TreeView1Change(Sender: TObject; Node: TTreeNode);
    procedure FormDestroy(Sender: TObject);
    procedure TreeView1Expanded(Sender: TObject; Node: TTreeNode);
    procedure TreeView1GetImageIndex(Sender: TObject; Node: TTreeNode);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure ShowSubKeys(ParentNode: TTreeNode; depth: Integer);
    function GetFullNodeName(Node: TTreeNode): string;
  end;
var
  RegForm: TRegForm;
implementation
uses registry;
{$R *.DFM}
var reg: TRegistry;
function TRegForm.GetFullNodeName(Node: TTreeNode): string;
var CurNode: TTreeNode;
begin
  Result := ''; CurNode := Node;
  while CurNode.Parent <> nil do
  begin
    Result := '\' + CurNode.Text + Result;
    CurNode := CurNode.Parent;
  end;
end;
procedure TRegForm.TreeView1Change(Sender: TObject; Node: TTreeNode);
var s: string;
  KeyInfo: TRegKeyInfo;
  ValueNames: TStringList;
  i: Integer;
  DataType: TRegDataType;
begin
  ListView1.Items.Clear;
  s := GetFullNodeName(Node);
  if not Reg.OpenKeyReadOnly(s) then Exit;
  Reg.GetKeyInfo(KeyInfo);
  if KeyInfo.NumValues <= 0 then Exit;
  ValueNames := TStringList.Create;
  Reg.GetValueNames(ValueNames);
  for i := 0 to ValueNames.Count - 1 do
    with ListView1.Items.Add do
    begin
      Caption := ValueNames[i];
      DataType := Reg.GetDataType(ValueNames[i]);
      case DataType of
        rdString: s := Reg.ReadString(ValueNames[i]);
        rdInteger: s := '0x' + IntToHex(Reg.ReadInteger(ValueNames[i]), 8);
        rdBinary: s := 'Binary';
      else s := '???';
      end;
      SubItems.Add(s);
      ImageIndex := 1;
    end;
  ValueNames.Free;
end;
procedure TRegForm.ShowSubKeys(ParentNode: TTreeNode; depth: Integer);
var ParentKey: string;
  KeyNames: TStringList;
  KeyInfo: TRegKeyInfo;
  CurNode: TTreeNode;
  i: Integer;
begin
  Cursor := crHourglass;
  TreeView1.Items.BeginUpdate;
  ParentKey := GetFullNodeName(ParentNode);
  if ParentKey <> '' then
    Reg.OpenKeyReadOnly(ParentKey)
  else
    Reg.OpenKeyReadOnly('\');
  Reg.GetKeyInfo(KeyInfo);
  if KeyInfo.NumSubKeys <= 0 then Exit;
  KeyNames := TStringList.Create;
  Reg.GetKeyNames(KeyNames);
  while ParentNode.GetFirstChild <> nil do ParentNode.GetFirstChild.Delete;
  if (KeyNames.Count > 0) then for i := 0 to KeyNames.Count - 1 do
    begin
      Reg.OpenKeyReadOnly(ParentKey + '\' + KeyNames[i]);
      Reg.GetKeyInfo(KeyInfo);
      CurNode := TreeView1.Items.AddChild(ParentNode, KeyNames[i]);
      if KeyInfo.NumSubKeys > 0 then
      begin
        TreeView1.Items.AddChild(CurNode, ''); //
      end;
    end;
  KeyNames.Free;
  TreeView1.Items.EndUpdate;
  Cursor := crDefault;
end;
procedure TRegForm.FormCreate(Sender: TObject);
var root: TTreeNode;
begin
  Reg := TRegistry.Create;
  ListView1.ViewStyle := vsReport;
  with ListView1 do
  begin
    with Columns.Add do begin Width := ListView1.Width div 3 - 2; Caption := 'Name'; end;
    with Columns.Add do begin Width := ListView1.Width div 3 * 2 - 2; Caption := 'Value'; end;
  end;
  TreeView1.Items.Clear;
  Reg.RootKey := HKEY_LOCAL_MACHINE;
  Root := TreeView1.Items.Add(nil, 'HKEY_LOCAL_MACHINE');
  TreeView1.Items.AddChild(root, '');
end;
procedure TRegForm.FormDestroy(Sender: TObject);
begin
  Reg.Free;
end;
procedure TRegForm.TreeView1Expanded(Sender: TObject; Node: TTreeNode);
begin
  ShowSubKeys(Node, 1);
end;
procedure TRegForm.TreeView1GetImageIndex(Sender: TObject; Node: TTreeNode);
begin
  with Node do
  begin
    if Expanded then ImageIndex := 2
    else ImageIndex := 3;
  end;
end;
end.

Скачать весь проект

Экспорт и импорт из реестра.

Статьи по Delphi (014)…

Reg-файлы это, как и ожидалось, формат, понимаемый и поддерживаемый сугубо программой regedit.

Командная строка у неё такая:

Импорт в реестр:
regedit RegData.reg
Экспорт из реестра:
regedit /e RegData.reg HKEY_LOCAL_MACHINE\Software\Microsoft\Windows

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

uses
  ShellApi;
procedure TMain.ExportBtnClick(Sender: TObject);
var
  FileName, Key: string;
begin
  FileName := ... //заполнить именем файла (расширение указывать)
  Key := ... //заполнить именем ключа, типа
  //Key := 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion'
  if ShellExecute(Handle, 'open', 'regedit.exe',
  PChar(Format('/e "%s" "%s"', [FileName, Key])),
  '', SW_SHOWDEFAULT) then //если ошибка, то возвращаемый код 
    RaiseLastWin32Error();
end;

Как можно определить доступные сервера приложений на этой машине через Registry.

Автор: Nomadic
Встречаются два системных администратора, и один спрашивает другого: - Ты чего такой грустный? - Да вот сервер вчера "упал". - Ну да ты что, его до сих пор не "поднял"? - Поднял, но он со стола упал...
Прочитайте ключ под HKEY_CLASSES_ROOT\CLSID\*, просматривая его насчёт ключей, которые имеют подключ "Borland DataBroker". Эти вхождения и являются серверами приложений.

Ниже пример, который загружает имена доступных серверов приложений в Listbox:

uses Registry;
procedure TForm1.FormCreate(Sender: TObject);
var
  I: integer;
  TempList: TStringList;
begin
  TempList := TStringList.Create;
  try
    with TRegistry.Create do
    try
      RootKey := HKEY_CLASSES_ROOT;
      if OpenKey('CLSID', False) then
        GetKeyNames(TempList);
      CloseKey;
      for I := 1 to TempList.Count - 1 do
        if KeyExists('CLSID\' + TempList[I] + '\Borland DataBroker') then
        begin
          if OpenKey('CLSID\' + TempList[I] + '\ProgID', False) then
          begin
            Listbox1.Items.Add(ReadString(''));
            CloseKey;
          end;
        end;
    finally
      Free;
    end;
  finally
    TempList.Free;
  end;
end;

Как из Delphi влиять на реестр (Изменить заголовок корзины).

Статьи по Delphi (014)…
- Что такое "Мелкие мягкие делишки для маленьких мягких окошечек"? - Microsoft Works for Microsoft Windows.

Алгоритм взаимодействия Delphi с системным реестром весьма прост.

Для этого нужно:

В области uses объявить модуль Registry:
uses
  Registry;
Объявить переменную класса TRegistry:
var
  a: TRegistry;
Создать эту переменную (имеется в виду - выделить под неё память):
a := TRegistry.Create;
Переменная класса TRegistry имеет тип записи. У переменной типа "запись" есть свои свойства, свои события. И теперь, после того как мы выделили память под эту переменную, нам сперва нужно указать с каким из основных ключей мы хотим взаимодействовать - с помощью свойства RootKey:
a.RootKey := HKEY_CLASSES_ROOT;
Далее мы открываем нужный нам ключ, используя метод OpenKey. Сначала нужно указать путь к нужному ключу (без указания главного, т.к. он уже был указан в предыдущем пункте), а затем логическое значение, обозначающее - будет ли создан ключ в случае его отсутствия (мы написали false - это значит, что ключ создан не будет). Например, мы хотим изменить заголовок корзины (заметьте, обычным способом это сделать нельзя!), тогда код с указанием пути к ключу, отвечающему за эту системную папку будет выглядеть так:
a.OpenKey('\CLSID\{645FF040-5081-101B-9F08-00AA002F954E}', false);
Когда нужный ключ открыт, нам предоставляется возможность редактировать его параметры. Для этого нужно использовать следующие методы: для внесения данных в реестр - WriteString, WriteInteger, WriteFloat, WriteDate и т.д., в зависимости от того какого типа данные мы хотим вносить; для считывания данных из параметра - ReadString, ReadInteger, ReadFloat, ReadDate... :

В данном случае, мы хотим изменить заголовок корзины, т.е. хотим внести данные в реестр, данные строкового типа - поэтому используем метод WriteString:

a.WriteString('', 'Мусорка');

Методу нужно указать 2 параметра: сначала имя параметра, затем заносимое значение. В качестве имени параметра мы не указываем ничего, п.ч. в указанном нами ключе имя корзины - это параметр по умолчанию. В качестве значения можно указать всё, что угодно, например, 'Мусорка'.

После того как мы сделали своё грязное дело, нужно замести следы: сначала закрыть ключ:
a.CloseKey;

А затем освободить выделенную нами память:

a.Free;

ВСЁ! ТЕПЕРЬ МЫ МОЖЕТ СПОКОЙНО ГУЛЯТЬ ПО ВСЕМУ РЕЕСТРУ, И ДЕЛАТЬ ЖИЗНЬ БЕДНОГО ЛАМЕРА НЕВЫНОСИМОЙ!

Удобная загрузка местоположения формы.

Автор: Virtualik Если вы храните параметры местоположения(Top, Left, Width, Height) формы в реестре, то чтобы не загружать данные из нескольких ключей вы можете их записать в один, и из одного же прочитать ;)

По сути, данные записывается в виде rесоrd'а. А как это примерно может выглядеть смотрите в примере.

var
  Ini: TRegIniFile;
...
procedure TForm1.FormCreate(Sender: TObject);
var
  Rct: TRect;
begin
  Ini := TRegIniFile.Create('<Здесь вы пишете путь к вашим настройкам в
    реестре > ');
  // Если есть данные --> загружаем их
  if Ini.ReadBinaryData('FormPosition', Rct, SizeOf(TRect)) > 0 then
    BoundsRect := Rct;
  ...
end;
procedure TReply.FormDestroy(Sender: TObject);
var
  Rct: TRect;
begin
  // Сохранение данных на выходе
  ...
  Rct := BoundsRect;
  Ini.WriteBinaryData('MsgPos', Rct, SizeOf(TRect));
  Ini.Free;
end;

Получить из регистров информацию о временной зоне (DST).

12 Заповедей от Админа. 1. Прав всегда Админ, ибо в трех лицах есть он единая власть высшая в классе дисплейном! 2. Неправ вечно юзер, ибо прав всегда Админ! 3. Не возжелай ни места, ни системника, ни профиля, ни монитора, ни мыши Админа своего, и да пребудет с тобой вечное благословение его! 4. И если вошел Юзер в систему без высшего на то дозволения (Админа) - горе ему, ибо порушится профиль его! 5. Да убоится юзер установить прогу неустановленную на комп казенный - ибо не дозволено сие! 6. Да не будет превышен профиль юзерский, ибо сказал Админ: "Аз воздам за то обрезанием... профиля твоего!" 7. Не возжелай войти под паролем чужим в систему, ибо надолго потом из дисплейки выйдешь ты! 8. А если кто разрешение на папку сменит - горе юзеру этому, ибо всемогущ в системе своей Админ! 9. Да убоятся пользователи толпиться на местах своих подобно стадам овец безмозглых, ибо всеведущ Админ! 10. И да убоится юзер качать вирусы, ибо админомерзкое занятие сие! 11. А если кто из юзеров возжелает порнухи или чата админа своего - горе и позор ему, ибо высшие удовольствия эти лишь Админу дозволены! 12. А тот юзер, который прочел строки эти и не проникся смирением и не осознал, что тварь он ламероидная и чайник нечищенный в сиянии славы высшего существа Админоподобного - горе ему, ибо навеки отлучены они будут от сети великой!!! Во имя отца Билли Гейтса, и сына его Microsofta, и святого духа админовского.
HKEYLocalMachine/Software/Microsoft/Windows/CurrentVersion/TimeZones/ - место, где в регистре хранится информация о временных зонах (Timezone). Двоичный код 'TZI' хранит информацию о начале и конце летнего времени. Есть какие-нибудь идеи насчет извлечения этих дат из этой двоичной величины?

Ок, попробую здесь описать бинарную структуру значения TZI:

int32 Bias;             // Минуты от GMT
                        // (Сидней -600; 0xfffffda8;
                        // или a8, fd, ff, ff)
int32 StandardBias;     // Смещение для стандартного времени (0)
int32 DaylightBias;     // Смещение для летнего времени (-60
                        // или 0xffffffc4 )
int16 ??                          // 0
int16 StandardStartMonth;         // 3 => Март
int16 StandardStartDayOfWeek??;   // 0 => Воскресенье
int16 StandardStartDOWoccurrence; // 1 => 1-й
int16 StandardStartHour;          // 2 => 02:00:00.00
int16 StandardStartMins??;        // 0 => 02:00:00.00
int16 StandardStartSecs??;        // 0 => 02:00:00.00
int16 StandardStartHunds??;       // 0 => 02:00:00.00
int16 ??                          // 0
int16 DaylightStartMonth;         // 0x0a (10) => Октябрь
int16 DaylightStartDayOfWeek??;   // 0 => Воскресенье
int16 DaylightStartDOWoccurrence; // 5 => последний
int16 DaylightStartHour;          // 2 => 02:00:00.00
int16 DaylightStartMins??;        // 0 => 02:00:00.00
int16 DaylightStartSecs??;        // 0 => 02:00:00.00
int16 DaylightStartHunds??;       // 0 => 02:00:00.00

После внесения изменений в реестр, некоторые программы не видят их.

Автор: Олег Кулабухов

Необходимо послать всем окнам сообщение WM_WININICHANGE с указанием полного адреса измененного адреса ключа.

procedure TForm1.Button1Click(Sender: TObject);
begin
  SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0,
    LongInt(PChar('RegistrySection')));
end;

Записать в реестр данные бинарного вида.

Четыре стадии обнищания программиста: 4. Кормить кошку kitekat-ом. 3. Нет денег на хлеб. 2. Нет денег на женщин. 1. Нет денег на апгрейд.
var
  Reg: TRegistry;
  buf : array [0..4] of byte;
  i: Integer;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKey('\Software', True) then begin
      for i:=1 to 4 do buf[i]:=0;
      buf[0]:=1;
      Reg.WriteBinnaryData('Value', buf, sizeof(buf));
      Reg.CloseKey;
    end;
  finally
    Reg.Free;
    inherited;
  end;
  {...}
end;

Исследование RegSnap v 2.8.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Захотелось мне как-то поисследовать программку на VB :) и напоролся я на эту вот вешь. Она умеет делать снапшоты регистра форточек и чего-то там ещё, вобщем неважно. Приступим... Первым делом я поглядел на программку: 1) Огромный NAG при старте с некоторой задержкой при запуске; 2) Напоминательное окно с 3 батонами; 3) Проверка CRC (на изменение кода программы); 4) во-всю юзает (M)ada(F)u(C)ka(42).dll; Начнём с конца, т.е. отключим проверку CRC: - Любым HEX-редактором изменим пару байт в программе и запустим её: мы увидим сообщение "Program corupted" (messagebox) - Ставим бряк на messageboxA: bpx MessageboxA и опять запускаем прогу, при этом мы вывалимся где-то в системной библиотеке, а чтобы из неё выйти и опять попасть в модуль regsnap нужно нажать F12 4 раза, и когда мы это сделаем, то окажемся вот в этом месте:
:00406848 FF1588774100            Call dword ptr [00417788] <--вызов процедуры проверки CRC (в eax 0, если CRC нарушен и 1 - если всё ОК).
:0040684E A1F4494200              mov eax, dword ptr [004249F4]
:00406853 83C408                  add esp, 00000008
:00406856 3BC7                    cmp eax, edi <-- проверяем EAX
:00406858 7520                    jne 0040687A <-- ЕСЛИ всё ОК - прыгаем
:0040685A 6AFF                    push FFFFFFFF
:0040685C 57                      push edi
:00406862 E8DBDA0000              Call 00414342 <-- Вызов сообщения о нарушенном CRC
:00406867 33C0                    xor eax, eax <-- вот тут мы остановились
:00406869 8B4DF4                  mov ecx, dword ptr [ebp-0C]
:0040686C 64890D00000000          mov dword ptr fs:[00000000], ecx
:00406873 5F                      pop edi
:00406874 5E                      pop esi
:00406875 5B                      pop ebx
:00406876 8BE5                    mov esp, ebp
:00406878 5D                      pop ebp
:00406879 C3                      ret
Как видите, по адресу 00406856 проверяют EAX, я предлягаю записать вместо этой команды mov eax,01 чтоюы для последующих проверок, если такие будут, всё было нормально, а вместо команды jne 0040687A вписать jmp 0040687A, чтобы в любом случае перепрыгивать ненужное окно и процедуру закрытия программы. ВНИМАНИЕ!!! команда mov eax,01 сожрёт 5 байт, в то время как cmp eax, edi всего 2 байта, поэтому будет использованы байты, выделенные под процедуру вывода сообщения об ошибке и процедуры закрытия программы, всё равно ведь мы эту процедуру перепрыгиваем и больше никогда использовать не будем!!! Итак, если вы всё сделали как я сказал, то больше программа не будет ругаться на то, что её кто-то изменял. И ещё, в программе реализован полиморфизм, т.е. при обнаружении CRC ошибки, она сама затирает некоторые куски кода другими опкодами, что делает все краки нерабочими, поэтому при внесении изменений, рекомендую делать эти изменения в абсолютно "чистой" версии regsnap.exe. 2)Лирическое отступление: Программа записывает в реестре в ключе:
HKEY_CURRENT_USER\Software\Vitas\regsnap\main
в параметре количество дней оставшегося триала. Для начала уберём Огромный NAG-screen при запуске: - ставим бряк на CreateWindowExA - запускаем прогу и почти сразу вываливаемся в айсе - Жмём F12 ровно 12 раз (какое совпадение :) пока не вернёмся в модуль regsnap.exe и оказываемся тут:
:0040687A E893DD0000              Call 00414612 <-- проверяет наличие OLE
:0040687F 85C0                    test eax, eax
:00406881 751D                    jne 004068A0 <-- если нет таких - прыгаем на выход из проги
...............
...Поскипано...
...............
:004068BA E841DD0000              Call 00414600 <-- проверяет необходимость NAG'a
:004068BF 397D90                  cmp dword ptr [ebp-70], edi
:004068C2 740C                    je 004068D0 <-- если не надо - перепрыгиваем его
:004068C4 397DE8                  cmp dword ptr [ebp-18], edi
:004068C7 7507                    jne 004068D0 <-- то же самое
:004068C9 B801000000              mov eax, 00000001
:004068CE EB02                    jmp 004068D2 <-- а если надо - то прыгаем на наг
Здесь предлагаю вместо je 004068D0 написать jmp 004068D0 чтобы сразу перепрыгивать NAG и он нас больше не мучал :) Осталось разобраться с напоминающим окном "Trial Period"... 3) после :004068C2 jmp 004068D0 скролим то тех пор, пока не появится окно напоминания, это произойдёт в этом месте:
:00406D5D E856D20000              Call 00413FB8 <-- вызываем напоминающее окно
:00406D62 83F801                  cmp eax, 00000001 <-- смотрим: какую кнопку ты нажал
:00406D65 7449                    je 00406DB0 <-- если это кнопка "Try" - прыгаем
здесь я поменял Call 00413FB8 на jmp 00406DB0, т.е. без вызова окна напоминания дал понять программе, что нажата кнопка "Try". Теперь больше не будет этого окна-напоминания и при старте будет сразу открываться главное окно! Последние штрихи... 4) Это необязательная, но самая приятная часть исследования - уберём все признаки незарегистрированности, т.е. надписи типа "This soft UnRgIsTred" Я зашёл в WDasm'e в String Reference и нашёл там строку "Registred to %S" кликнул на неё и оказался тут:
:00404616 6818E34100              push 0041E318
:0040461B 56                      push esi
поднялся чуть выше , вот сюда:
* Referenced by a CALL at Addresses:
|:0040148F   , :004051DD   , :004113F7   
|
:004045D0 A188044200              mov eax, dword ptr [00420488] 
:004045D5 81EC00010000            sub esp, 00000100
:004045DB 85C0                    test eax, eax <-- проверяеи зарегистрированны ли мы 
:004045DD 56                      push esi
:004045DE 7452                    je 00404632 <-- если нет - прыгаем
:004045E0 8BB42408010000          mov esi, dword ptr [esp+00000108]
:004045E7 85F6                    test esi, esi
:004045E9 743A                    je 00404625 <-- если нет - прыгаем
:004045EB 83C01D                  add eax, 0000001D
здесь я вместо mov eax, dword ptr [00420488] написал ret ( с тем соображением, что программа выйдет из процедуры присваивания имени ни с чем и будет надпись "Registred to: " и всё), но оказалось ещё круче: Появилась надпись "Registred to: Vitas Ramanchauskas" а в окне "О программе" как я и предполагал Вместо надписи "Registred to" ничего нет. //Можно написать вместо ret что-то типа mov eax,eax //тогда адрес строки с именем будет случайный, а т.к. программа проверяет //имя владельца лицензии после каждой операции, то наше имя будет меняться //каждый раз, причем имя будет иметь вид "№;%№;%№;%"№;%%::ВАПп" Прикольно!!! Осталось ещё убрать надпись "Trial" внизу программы: ищем эту надпись в HEX-редакторе и затираем её пробелами или например "fuck". ВсЁ! Ну и как всегда, готовый Крак:
-----------вырезать тут-----------
RegSnap v 2.8 Crack by vallkor//PTDS
REmoves All Nag's and limits!!!
regsnap.exe
00006856: 3B B8
00006857: C7 01
00006858: 75 00
00006859: 20 00
0000685A: 6A 00
0000685B: FF EB
0000685C: 57 1D
000068C2: 74 EB
00006D5D: E8 EB
00006D5E: 56 51
000045D0: A1 C3
0001E4B8: 54 46
0001E4B9: 72 75
0001E4BA: 69 63
0001E4BB: 61 6B
0001E4BC: 6C 20
-----------вырезать тут------------
Как всегда, с вами беседовал vallkor //PTDS e-mail: vallkor@chat.ru page: http://vallkor.chat.ru

Использование регулярных выражений в Delphi.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
It is possible to use regular expressions in Delphi thanks to their 
implementation inside "Microsoft(r) Windows(r) Script". 
First of all I want to say that this article isn't a theoretical 
analisys of the regular expressions but an explanation of how to use 
them in Delphi. 
First of all I suggest you to download the latest version of 
"Microsoft(r) Windows(r) Script" at the following url: 
http://msdn.microsoft.com/downloads/default.asp?URL=/downloads/sample.asp? 
url=/msdn-files/027/001/733/msdncompositedoc.xml 
Download the desired package. 
Once downloaded, run it to install. 
It will be installed Microsoft(r) Windows(r) Script wich containes: 
    Visual Basic(r) Script Edition (VBScript.) Version 5.6, 
    JScript(r) Version 5.6, Windows Script Components, 
    Windows Script Host 5.6, 
    Windows Script Runtime Version 5.6. 
We are interested in the implementation of regular expressions, 
which is in the file "vbscript.dll". Every time you will want to 
run a program, wich exploits regular expressions by using 
"Microsoft(r) Windows(r) Script", on a given computer, it will be needed to 
copy the file "vbscript.dll" on the target computer and register it with the 
following command line: 
regsvr32 vbscript.dll 
Note that the auto-installing package, you have downloaded from the 
internet, automatically do it. 
Now let's go to import the type library in Delphi: 
In the delphi menu, select "Project" then "Import type library": it 
shows a mask containing a list. 
Inside the list select "Microsoft VBScript Regular Expressions" 
(followed by a version number). It is possible that there are more then 
one item with this name (it only changes the version number): in this 
situation select the item with the higher version number. 
In date September the 23th 2002, the package that can be downloaded 
from Microsoft's internet site returns the following value: 
    "Microsoft VBScript Regular Expressions 5.5 (Version 5.5)" 
This version supplies the following "Class Names" 
}
     TRegExp
     TMatch
     TMatchCollection
     TSubMatches
      { 
Define the name of the pascal unit, wich will be the type library import unit, in 
the edit box "Unit dir name". 
Uncheck the checkbox "Generate Component Wrapper" (we are only interested in the 
pascal source) 
and press the button "Create Unit" to create the import unit. 
Let's assume to have the latest available version in date 
September the 23th i.e. 
    "Microsoft VBScript Regular Expressions 5.5 (Version 5.5)" 
The following "interface" are declared: 
}
   IRegExp = interface;
   IMatch = interface;
   IMatchCollection = interface;
   IRegExp2 = interface;
   IMatch2 = interface;
   IMatchCollection2 = interface;
   ISubMatches = interface;
 { 
IRegExp and IRegExp2 are different versions (IRegExp2 is the latest) 
of the same "interface". Idem for the other "interface". 
Then there are the declarations of CoClasses defined in Type Library. 
We map each CoClass to its Default Interface: 
}
   RegExp = IRegExp2;
   Match = IMatch2;
   MatchCollection = IMatchCollection2;
   SubMatches = ISubMatches;
 //IRegExp2 is the "main" "interface": 
// 1) properties: 
    a) property Pattern: WideString read Get_Pattern
                                     write Set_Pattern;
     //regular expression 
    b) property IgnoreCase: WordBool read Get_IgnoreCase
                                      write Set_IgnoreCase;
     //"case insensitive" search (TRUE o FALSE) 
    c) property Global: WordBool read Get_Global write Set_Global;
     //TRUE for global search on the input string of the method "Execute" 
    //FALSE if you want to stop after the first match 
    d) property Multiline: WordBool read Get_Multiline
                                     write Set_Multiline;
     //If the input string contains '\n' charachters, it contains several 
    //rows. If Multiline = FALSE (default value) then the regular 
    //expression must be tested distinctly on each row. 
    //If Multiline = TRUE the regular expression must be tested on the 
    //whole input string. 
// 2) methods: 
    a) function  Execute(const sourceString: WideString): IDispatch;
                                                           safecall;
     //it returns a Matches collection object containing a match object 
    //for each succesfull match 
    b) function  Test(const sourceString: WideString): WordBool;
                                                        safecall;
     //it returns TRUE if the regular expression can succesfully be 
    //matched against the string 
    c) function  Replace(const sourceString: WideString;
                       const replaceString: WideString): WideString;
                                                           safecall;
     //it replaces all the matches, inside "sourceString" with the 
    //replace string "replaceString". 
    //You can use the values $1, $2, $3, ... in order to define a 
    //replace-string made by substrings of the pattern. 
{ 
IMatchCollection2 collects all the matches 
For example: 
}
 var
   i: integer;
   MatchesCollection: IMatchCollection2;
 {...}
   MatchesCollection := Execute(InputStr) as IMatchCollection2;
   for i := 1 to MatchesCollection.Count - 1 do
     begin
       Memo1.Lines.Add((MatchesCollection.Item[i] as IMatch2).Value);
     end;
 {...}
 {Remember that you can substitute the "interface" types with 
the CoClass types: 
  RegExp            (IRegExp2) 
  Match             (IMatch2) 
  MatchCollection   (IMatchCollection2) 
  SubMatches        (ISubMatches) 
The main properties of IMatchCollection2 are:}
     a) property Item[index: Integer]: IDispatch read Get_Item;
                                                      default;
     //Matches array; index in [0..n] 
    b) property Count: Integer read Get_Count;
     //Number of matches 
{The Item property returns "IMatch2" values 
IMatch2 represents each succesfull match 
The main properties are}
     a) property Value: WideString read Get_Value;
     //matched value or text 
    b) property FirstIndex: Integer read Get_FirstIndex;
     //the position within the original string where the match occurred. 
    //Note that the first position in a string is 0 
    c) property Length: Integer read Get_Length;
     //length of the matched string 
    d) property SubMatches: IDispatch read Get_SubMatches;
     //substrings ($1, $2, $3, ...) 
{ISubMatches collects the values of $1, $2, $3, ... 
The main properties are}
     a) property Item[index: Integer]: OleVariant read Get_Item;
                                                       default;
     //for example Item[3] is $3; note that index values start from 0 
    property Count: Integer read Get_Count;
     //number of substrings 
{ 
Short description of $1, $2, $3, ... 
They are defined in the following manner: 
let's scan the pattern from left to right: 
$1 is the substring from the first open parenthesis to the corrisponding 
closed one. 
$1 is the substring from the second open parenthesis to the corrisponding 
closed one. 
$1 is the substring from the third open parenthesis to the corrisponding 
closed one. 
.... 
For example: let's consider the following pattern 
(FTP|HTTP)://([_a-z\d\-]+(\.[_a-z\d\-]+)+)((/[ _a-z\d\-\\\.]+)+)* 
$1 = FTP|HTTP 
$2 = [_a-z\d\-]+(\.[_a-z\d\-]+)+ 
$3 = \.[_a-z\d\-]+ 
$4 = (/[ _a-z\d\-\\\.]+)+ 
$5 = /[ _a-z\d\-\\\.]+ 
Finally a couple of examples: 
Save an html file from the internet and name it "Test.htm". 
Create a new delphi project: drop 2 buttons (btSearch e btReplace) 
and a Memo (Memo1); of course include in the "uses" directive the 
name of the import unit. 
}
 //returns all links in "Test.htm" and $1, $2, $3, etc... 
procedure TForm1.btSearchClick(Sender: TObject);
 var
   i, j: integer;
   FileStream: TFileStream;
   InputStr, InputFile: string;
   RegExp1: RegExp;
   MatchCollection1: MatchCollection;
   Match1: Match;
   SubMatches1: ISubMatches;
 begin
   // 
  InputFile := 'Test.htm'; //input file 
  FileStream := TFileStream.Create(InputFile, fmOpenRead);
   SetLength(InputStr, FileStream.Size);
   FileStream.Read(InputStr[1], FileStream.Size);
   //load "Test.htm" in InputString 
  RegExp1 := CoRegExp.Create;
   with RegExp1 do
     begin
       //I want to search all links 
      Pattern := '(FTP|HTTP)://([_a-z\d\-]+(\.[_a-z\d\-]+)+)' +
                  '((/[ _a-z\d\-\\\.]+)+)*';
       IgnoreCase := True; //"case insensitive" search 
      Global := True; //I want to search all the matches 
      MatchCollection1 := Execute(InputStr) as MatchCollection;
     end;
   for i := 0 to MatchCollection1.Count - 1 do
     begin
       Match1 := MatchCollection1.Item[i] as Match;
       Memo1.Lines.Add(Match1.Value);
       SubMatches1 := Match1.SubMatches as SubMatches;
       for j := 0 to SubMatches1.Count - 1 do
         begin
           Memo1.Lines.Add('          ' + '$' + inttostr(j + 1) +
                           ' = ' + VarToStr(SubMatches1.Item[j]));
         end;
     end;
   RegExp1 := nil;
   FileStream.Free;
 end;
 //I replace all links in "Test.htm" with a new string and 
//save the result string in the new file "Test_out.htm" 
procedure TForm1.btReplaceClick(Sender: TObject);
 var
   i: integer;
   InFileStream, OutFileStream: TFileStream;
   InputStr, OutputStr, InputFile, OutputFile: string;
   RegExp1: RegExp;
   MatchCollection1: MatchCollection;
   Match1: Match;
   SubMatches1: ISubMatches;
 begin
   InputFile := 'Test.htm';
   OutputFile := 'Test_out.htm';
   InFileStream := TFileStream.Create(InputFile, fmOpenRead);
   SetLength(InputStr, InFileStream.Size);
   InFileStream.Read(InputStr[1], InFileStream.Size);
   InFileStream.Free;
   RegExp1 := CoRegExp.Create;
   with RegExp1 do
     begin
       Pattern := '(FTP|HTTP)://([_a-z\d\-]+(\.[_a-z\d\-]+)+)' +
                  '((/[ _a-z\d\-\\\.]+)+)*';
       IgnoreCase := True;
       Global := True;
       OutputStr := Replace(InputStr, '$2');
     end;
   OutFileStream := TFileStream.Create(OutputFile, fmCreate);
   SetLength(OutputStr, Length(OutputStr));
   OutFileStream.Write(OutputStr[1], Length(OutputStr));
   OutFileStream.Free;
   RegExp1 := nil;
   ShowMessage('replace completed');
 end;
 //Search on Google: "VBScript Regular Expressions" 

Регистрация ActiveX.

Автор: Fantasist WEB-сайт: http://forum.vingrad.ru 1. Регистрация ActiveX:
function RegActiveX(FileName: string): HRESULT;
var
  hMod: Integer;
  RegProc: function: HRESULT; //HRESULT = Longint
begin
  hMod := LoadLibrary(FileName);
  if hMod = 0 then
    raise Exception.Create('Unable to load library"' + FileName +
      '". GetLastError = ' + IntToStr(GetLastError));
  RegProc := GetProcAddress(hMod, 'DllRegisterServer');
  if RegProc = nil then
    raise Exception.Create('Unable to load "DllRegisterServer" function from "'
      + FileName + '". GetLastError = ' + IntToStr(GetLastError));
  Result := RegProc;
end;

2. Регистрация Type Library:

procedure RegisterTypeLibrary(FileName: string);
var
  Name: WideString;
  HelpPath: WideString;
  TypeLib: ITypeLib;
begin
  if LoadTypeLib(PWideChar(WideString(FileName)), TypeLib) = S_OK then
  begin
    Name := FileName;
    HelpPath := ExtractFilePath(ModuleName);
    RegisterTypeLib(TypeLib, PWideChar(Name), PWideChar(HelpPath));
  end;
end;
Здесь используется интерфейс ITypeLib и API функция RegisterTypeLib. И то и другое объявленно в модуле ActiveX, если я не ошибаюсь.

Hint: если вы регистрируете библиотеку типов изнутри модулчя, то его имя можно получить с помощью следующей функции:

function GetModuleFileName: string;
var
  Buffer: array[0..261] of Char;
begin
  SetString(Result, Buffer, Windows.GetModuleFileName(HInstance,
    Buffer, SizeOf(Buffer)));
end;

Взять все расширения из реестра и их описание.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
 This Code will return the programs associated with each extension. 
 You can obviously modify it to search for a specific extension. 
}
 uses Registry;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   reg: TRegistry;
   keys: TStringList;
   i: Integer;
   typename, displayname, server: string;
 begin
   memo1.Clear;
   reg := TRegistry.Create;
   try
     reg.rootkey := HKEY_CLASSES_ROOT;
     if reg.OpenKey('', False) then
     begin
       keys := TStringList.Create;
       try
         reg.GetKeyNames(keys);
         reg.CloseKey;
         {memo1.lines.addstrings(keys);}
         for i := 0 to keys.Count - 1 do
         begin
           if keys[i][1] = '.' then
           begin
             {this is an extension, get its typename}
             if reg.OpenKey(keys[i], False) then
             begin
               typename := reg.ReadString('');
               reg.CloseKey;
               if typename <> '' then
               begin
                 if reg.OpenKey(typename, False) then
                 begin
                   displayname := reg.ReadString('');
                   reg.CloseKey;
                 end;
                 if reg.OpenKey(typename + '\shell\open\command', False) then
                 begin
                   server := reg.ReadString('');
                   memo1.Lines.Add(Format('Extension: "%s", Typename: "%s", Displayname:"%s"' +
                                          #13#10'  Server: %s',
                                          [keys[i], typename, displayname, server]));
                   reg.CloseKey;
                 end;
               end;
             end;
           end;
         end;
       finally
         keys.Free;
       end;
     end;
   finally
     reg.Free
   end;
 end;

Прячем программу для TaskManager.

Автор: Subfire WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Прячет программу в TaskManager (по Ctrl+Alt+Del)
Фунция прячет программу для TaskManager, регестрируя ее как сервис.
Запускать из приложения.
Передаешь true для регистрации сервиса, false для снятия регистрации.
Возвращает true если успешно.
В win95-98-ME программа исчезнет из списка задач.
В NT/2000/XP просто нет RegisterServiceProcess и функция вернет
false без возникновения ошибки.
Зависимости: *
Автор:       Subfire, subfire@mail.ru, ICQ:55161852, Санкт-Петербург
Copyright:   Subfire
Дата:        3 октября 2002 г.
***************************************************** }
function RegisterAsService(Enable: Boolean): boolean;
type
  TRSP = function(H: THandle; K: word): longword; stdcall;
var
  RSP: TRSP;
begin
  @RSP := GetProcAddress(GetModuleHandle(PChar('kernel32.dll')),
    PChar('RegisterServiceProcess'));
  Result := Assigned(@RSP);
  if Result then
  begin
    if Enable then
      Result := (RSP(0, 1) = 1)
    else
      Result := (RSP(0, 0) = 1);
  end;

Пример использования:

// OnCreate
RegisterAsService(True); // Спрятали
// on **
RegisterAsService(False); // Показали

Помещение записи в одну из секций автозапуска реестра.

Автор: VID WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Помещение записи в одну из секций автозапуска реестра
Функция помещает параметр Name и значение параметра Data, в одну из секций
автозапуска, выбранного раздела реестра.
HkeyTarget:THkeyTarget - указываете раздел реестра, в одну из секций
автозапуска которого должна быть помещена запись:
htLocalMachine - раздел HKEY_LOCAL_MACHINE
htCurrentUser - раздел HKEY_CURRENT_USER
SectionTarget:TSectionTarget - указываете одну из секций автозапуска,
в которую должна быть помещена запись:
stRun - секция RUN
stRunOnce - секция RunOnce
stRunOnceEx - секция RunOnceEx
Name:String - имя параметра (например, 'myApplication')
Data:String - значение параметра (например, Application.Exename)
Зависимости: windows, registry
Автор:       VID, vidsnap@mail.ru, ICQ:132234868, Махачкала
Copyright:   VID
Дата:        23 мая 2002 г.
***************************************************** }
type
  THKEYTarget = (htLocalMachine, htCurrentUser);
type
  TSectionTarget = (stRun, stRunOnce, stRunOnceEx);
function StoreToRunSection(HKEYTarget: THKEYTarget;
  SectionTarget: TSectionTarget; Name, Data: string): boolean;
var
  Reg: TRegistry;
  Section: string;
begin
  Result := TRUE;
  try
    reg := TRegistry.Create;
    if HKEYTarget = htLocalMachine then
      reg.RootKey := HKEY_LOCAL_MACHINE;
    if HKEYTarget = htCurrentUser then
      reg.RootKey := HKEY_CURRENT_USER;
    if SectionTarget = stRun then
      Section := 'Run';
    if SectionTarget = stRunOnce then
      Section := 'RunOnce';
    if SectionTarget = stRunOnceEx then
      Section := 'RunOnceEx';
    reg.LazyWrite := false;
    reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\' + Section, false);
    reg.WriteString(Name, Data);
    reg.CloseKey;
    reg.free;
  except RESULT := FALSE;
  end;
end;
// Пример использования:
begin
  StoreToRunSection(htLocalMachine, stRun, 'Имя программы',
    application.exename);
end;

Сохранение бинарного файла в реестре с последующим его извлечением.

Автор: Sergey WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Сохранение бинарного файла в реестре с последующим его извлечением
Функции позволяют разбить файл на блоки 16к и затем поместить бинарные
данные в реестр, а так же собрать эти данные обратно в файл при необходимости.
Не знаю зачем этот код нужен , но возможно он пригодится для скрытого
хранения данных . Не рекомендуется (!) сохранять слишком большие файлы
в реестре , потому что размеры реестра ограничены .
Зависимости: Registry,SysUtils
Автор:       Sergey, gloom@imail.ru, Koenigsberg
Copyright:   KosilkA
Дата:        4 декабря 2003 г.
***************************************************** }
//////////////////////////////////////////////////
// функция разбивки и сохранения файла в реестр:
function SaveToRegistry(RootKey: HKEY; FileName, KeyName: string): Bool;
//где RootKey-раздел реестра,
//FileName-путь к файлу ,
//KeyName-ключ реестра в который будем сохранять
var
  FromF: file;
  NumRead, i: Integer;
  Buf: array[0..16383] of byte;
  reg: tregistry;
begin
  try
    AssignFile(FromF, filename);
    Reset(FromF, 1);
    reg := tregistry.Create;
    reg.RootKey := rootkey;
    if reg.OpenKey(keyName, true) = false then //если невозможно
      //открыть ключ с правами
      //записи , то что то не так
    begin
      result := false;
      reg.Free;
      exit; //выходим
    end;
    i := 0;
    repeat //читаем и записывае в цикле
      BlockRead(FromF, Buf, SizeOf(Buf), NumRead); //читаем блок из файла
      if numread <> 0 then
      begin
        reg.WriteBinaryData(inttostr(i), buf, numread); //сохраняем блок в реестр
        inc(i);
      end;
    until (NumRead = 0);
    CloseFile(FromF);
    reg.CloseKey;
    reg.Free;
    result := true;
  except
    reg.CloseKey;
    reg.Free;
    result := false;
  end;
end;
//////////////////////////////////////////////////
// функция сборки сохраненного файла:
function BuildFromRegistry(RootKey: HKEY; KeyName, ToFileName: string): Bool;
//где RootKey-раздел реестра,
//KeyName -ключ реестра ,
//ToFileName-имя выходного файла .
var
  numwritten, i: integer;
  ToF: file;
  Buf: array[0..16383] of byte;
  reg: tregistry;
begin
  try
    AssignFile(ToF, tofilename);
    Rewrite(ToF, 1);
    reg := tregistry.Create;
    reg.RootKey := rootkey;
    if reg.OpenKey(keyname, true) = false then
      //если указанный ключ невозможно открыть - выходим
    begin
      result := false;
      reg.Free;
      exit;
    end;
    i := 0;
    repeat
      //в цикле находим значения
      //начиная с "0" , читаем их
      //и добавляем блоки к файлу
      if reg.ValueExists(inttostr(i)) = true then
      begin
        reg.ReadBinaryData(inttostr(i), buf, reg.GetDataSize(inttostr(i)));
        BlockWrite(ToF, Buf, reg.GetDataSize(inttostr(i)));
      end;
      inc(i);
    until reg.ValueExists(inttostr(i)) = false;
    CloseFile(ToF);
    reg.CloseKey;
    reg.Free;
    result := true;
  except
    reg.CloseKey;
    reg.Free;
    result := false;
  end;
end;
// Пример использования:
// сохранить:
SaveToRegistry(HKEY_CURRENT_USER, Application.ExeName, '\Software\MyData\test');
// извлечь:
BuildFromRegistry(HKEY_CURRENT_USER, '\Software\MyData\test', 'MyBuildApp.exe');

Регистрация классов.

Статьи по Delphi (014)…
unit InfoForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  StdCtrls, ExtCtrls, Buttons, Clipbrd, Comctrls, Db, Dbcgrids,
  Dbctrls, Dbgrids, Dblookup, Dbtables, Ddeman, Dialogs,
  Filectrl, Grids, Mask, Menus, Mplayer, Oleconst, Olectnrs,
  Olectrls, Outline, Tabnotbk, Tabs;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Label1: TLabel;
    Edit1: TEdit;
    Label2: TLabel;
    ButtonShow: TButton;
    Label3: TLabel;
    Panel1: TPanel;
    ComboBox1: TComboBox;
    procedure FormCreate(Sender: TObject);
    procedure ButtonShowClick(Sender: TObject);
    procedure ComboBox1DblClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
type
  TClassArray = array [1..140] of TPersistentClass;
const
  ClassArray: TClassArray = (
    TBitmap, TGraphic, TOutlineNode, TGraphicsObject,
    TBrush, THeaderSection, TParams, TCanvas,
    THeaderSections, TPen, TIcon, TPicture,
    TIconOptions, TCollection, TCollectionItem, TColumn,
    TStatusPanel, TColumnTitle, TStatusPanels, TClipboard,
    TControlScrollBar, TListColumn, TStringList, TListItem,
    TStrings, TListItems, TMetafile, TMetafileCanvas,
    TTreeNode, TFont, TParaAttributes, TTreeNodes,
    TApplication, TDDEServerItem, TPanel, TAutoIncField,
    TDirectoryListBox, TPopupMenu, TBatchMove, TDrawGrid,
    TPrintDialog, TBCDField, TDriveComboBox, TPrinterSetupDialog,
    TBevel, TEdit, TProgressBar, TBitBtn,
    TField, TQuery, TBlobField, TFileListBox,
    TRadioButton, TBooleanField, TFilterComboBox, TRadioGroup,
    TButton, TFindDialog, TReplaceDialog, TBytesField,
    TFloatField, TCheckBox, TFontDialog,
    TRichEdit, TColorDialog, TForm, TSaveDialog,
    TComboBox, TGraphicField, TScreen, TCurrencyField,
    TGroupBox, TScrollBar, TDatabase, THeader,
    TScrollBox, TDataSource, THeaderControl, TSession,
    TDateField, THotKey, TShape, TDateTimeField,
    TImage, TSmallIntField, TDBCheckBox, TImageList,
    TSpeedButton, TDBComboBox, TIntegerField, TStatusBar,
    TDBCtrlGrid, TLabel, TStoredProc, TDBEdit,
    TListBox, TStringField, TDBGrid, TListView,
    TStringGrid, TDBImage, TMainMenu, TTabbedNotebook,
    TDBListBox, TMaskEdit, TTabControl, TDBLookupCombo,
    TMediaPlayer, TTable, TMemoField, TDBLookupComboBox,
    TMemo, TTabSet, TDBLookupList, TTabSheet,
    TDBLookupListBox, TMenuItem, TTimeField, TDBMemo,
    TNotebook, TTable, TDBNavigator, TOleContainer,
    TTimer, TDBRadioGroup, TOpenDialog, TTrackBar,
    TDBText, TOutline, TTreeView, TDDEClientConv,
    TOutline, TUpdateSQL, TDDEClientItem, TPageControl,
    TUpDown, TDDEServerConv, TPaintBox, TVarBytesField,
    TWordField);
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  // register all of the classes
  RegisterClasses (ClassArray);
  // copy class names to the listbox
  for I := Low (ClassArray) to High (ClassArray) do
    ComboBox1.Items.Add (ClassArray [I].ClassName);
end;
procedure TForm1.ButtonShowClick(Sender: TObject);
var
  MyClass: TClass;
begin
  MyClass := GetClass (ComboBox1.Text);
  if MyClass = nil then
    Beep
  else
  begin
    Edit1.Text := Format ('Name: %s - Size: %d bytes',
      [MyClass.ClassName, MyClass.InstanceSize]);
    with Listbox1.Items do
    begin
      Clear;
      while MyClass.ClassParent <> nil do
      begin
        MyClass := MyClass.ClassParent;
        Add (MyClass.ClassName);
      end; // while
    end; // with
  end; // else
end;
procedure TForm1.ComboBox1DblClick(Sender: TObject);
begin
  ButtonShowClick (Sender);
end;
end.

Загрузить весь проект

Считать значение REG_DWORD из реестра.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   Registry;
 // Read REG_DWORD 
procedure TForm1.Button1Click(Sender: TObject);
 var
   Reg: TRegistry;
   RegKey: DWORD;
   Key: string;
 begin
   Reg := TRegistry.Create;
   try
     Reg.RootKey := HKEY_USERS;
     Key := '.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Internet Settings\URL History';
     if Reg.OpenKeyReadOnly(Key) then
     begin
       if Reg.ValueExists('DaysToKeep') then
       begin
         RegKey := Reg.ReadInteger('DaysToKeep');
         Reg.CloseKey;
         ShowMessage(IntToStr(RegKey));
       end;
     end;
   finally
     Reg.Free
   end;
 end;
 // Write REG_DWORD 
procedure TForm1.Button2Click(Sender: TObject);
 var
   Reg: TRegistry;
   Key: string;
 begin
   Reg := TRegistry.Create;
   try
     Reg.RootKey := HKEY_USERS;
     Key := '.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Internet Settings\URL History';
     if Reg.OpenKey(Key, True) then
     begin
       Reg.WriteInteger('DaysToKeep', 20);
       Reg.CloseKey;
     end;
   finally
     Reg.Free
   end;
 end;

Перечислить измененные ключи реестра в определенный диапазон времени.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit Main;
 interface
 uses
   Windows, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, Registry, StdCtrls, ComCtrls, ExtCtrls;
 type
   TFrmMain = class(TForm)
     Memo1: TMemo;
     btEnumReg: TButton;
     dtBeginDate: TDateTimePicker;
     dtEndDate: TDateTimePicker;
     dtBeginTime: TDateTimePicker;
     dtEndTime: TDateTimePicker;
     Panel1: TPanel;
     Label1: TLabel;
     Label2: TLabel;
     procedure btEnumRegClick(Sender: TObject);
   private
     { Private declarations }
     RegDate: TDateTime;
     RegKeyInfo: TRegKeyInfo;
     TimeZoneInfo: TTimeZoneInformation;
     function DateTimeToLocalDateTime(DateTime: TDateTime): TDateTime;
     procedure FileTimeToDateTime(ft: TFileTime; var dt: TDateTime);
   public
     { Public declarations }
   end;
 var
   FrmMain: TFrmMain;
 implementation
 {$R *.dfm}
 function TFrmMain.DateTimeToLocalDateTime(DateTime: TDateTime): TDateTime;
 begin
   case GetTimeZoneInformation(TimeZoneInfo) of
     TIME_ZONE_ID_STANDARD:
       Result := DateTime - (TimeZoneInfo.Bias / 60 / 24);
     TIME_ZONE_ID_DAYLIGHT:
       Result := DateTime - ((TimeZoneInfo.Bias + TimeZoneInfo.DaylightBias) / 60 / 24);
     else
       Result := 0;
   end;
 end;
 procedure TFrmMain.FileTimeToDateTime(ft: TFileTime; var dt: TDateTime);
 var
   SystemTime: TSystemTime;
   FileTime: TFileTime;
 begin
   if FileTimeToLocalFileTime(ft, FileTime) then
   begin
     FileTimeToSystemTime(ft, SystemTime);
     dt := SystemTimeToDateTime(SystemTime);
   end;
 end;
 procedure TFrmMain.btEnumRegClick(Sender: TObject);
 var
   Inizio, Fine: TDateTime;
   procedure EnumAllKeys(hkey: THandle; KeyName: string);
   var
     l: TStringList;
     n: Integer;
     KeyName_: string;
   begin
     KeyName_ := KeyName;
     with TRegistry.Create do
       try
         RootKey := hkey;
         OpenKey(EmptyStr, False);
         l := TStringList.Create;
         try
           GetKeynames(l);
           CloseKey;
           for n := 0 to l.Count - 1 do
           begin
             if OpenKey(l[n], False) then
             begin
               GetKeyInfo(RegKeyInfo);
               with RegKeyInfo do
               begin
                 FileTimeToDateTime(FileTime, RegDate);
                 RegDate := DateTimeToLocalDateTime(RegDate);
               end;
               if (RegDate <= Fine) and (RegDate >= Inizio) then
                 memo1.Lines.Add(DateTimeToStr(RegDate) + ' --- ' + KeyName_ + '\' + l[n]);
               EnumAllKeys(CurrentKey, KeyName_ + '\' + l[n]);
               CloseKey;
             end;
           end;
         finally
           l.Free
         end;
       finally
         Free;
       end;
   end;
 begin
   Memo1.Clear;
   Memo1.Lines.BeginUpdate;
   Inizio := int(dtBeginDate.DateTime) + frac(dtBeginTime.DateTime);
   Fine   := int(dtEndDate.Date) + frac(dtEndTime.DateTime);
   try
     EnumAllKeys(HKEY_CURRENT_USER, 'HKey_Current_User');
   finally
     Memo1.Lines.EndUpdate;
   end;
   MessageDlg('Enumeration ended', mtInformation, [mbOK], 0);
 end;
 end.

Экспортировать ветвь реестра.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit RegExpo;
 interface
 uses
   Windows, rRegistry, Classes, SysUtils;
 {$I-}
 {$LONGSTRINGS ON}
 { 
  Regexpo 
  Author : Arthur Hoornweg (arthur.hoornweg@email.de) 
  Version : 1.00, May 1998 
  O/S : Windows 95, 98, ME, NT, 2000, XP 
        Delphi 2+ 
  Function : 
    This unit allows you to backup a branch of the registry into a *.REG file, 
    that is compatible with "regedit". 
    Double-clicking such a file in the explorer will import it. 
  Example: 
    ExportRegistryBranch(HKEY_LOCAL_MACHINE,'SOFTWARE\Borland\Delphi','A:\DELPHI.REG') 
  (c) 1998 A.M. Hoornweg. All rights reserved. 
  You may use this software for all purposes, both commercial and 
  noncommercial, as long as proper credit is given. The sourcecode may be distributed 
  freely, as long as this copyright is included and no more than a marginal fee is 
  asked. 
  Disclaimer: 
  I accept no responsibility whatsoever for any damages caused by these 
  routines. Use them at your own risk. If you find any bugs, please let me know. 
}
 procedure ExportRegistryBranch(Rootsection: Integer; regroot: string;
   FileName: string);
 implementation
 function dblBackSlash(t: string): string;
 var
   k: longint;
 begin
   Result := t; {Strings are not allowed to have}
   for k := Length(t) downto 1 do {single backslashes}
     if Result[k] = '\' then Insert('\', Result, k);
 end;
 procedure ExportRegistryBranch(rootsection: Integer; Regroot: string;
   FileName: string);
 var
   reg: TRegistry;
   f: Textfile;
   p: PChar;
   procedure ProcessBranch(root: string); {recursive sub-procedure}
   var
     values, keys: TStringList;
     i, j, k: longint;
     s, t: string; {longstrings are on the heap, not on the stack!}
   begin
     Writeln(f); {write blank line}
     case rootsection of
       HKEY_CLASSES_ROOT: s   := 'HKEY_CLASSES_ROOT';
       HKEY_CURRENT_USER: s   := 'HKEY_CURRENT_USER';
       HKEY_LOCAL_MACHINE: s  := 'HKEY_LOCAL_MACHINE';
       HKEY_USERS: s          := 'HKEY_USERS';
       HKEY_PERFORMANCE_DATA: s := 'HKEY_PERFORMANCE_DATA';
       HKEY_CURRENT_CONFIG: s := 'HKEY_CURRENT_CONFIG';
       HKEY_DYN_DATA: s       := 'HKEY_DYN_DATA';
     end;
     Writeln(f, '[' + s + '\' + root + ']'); {write section name in brackets}
     reg.OpenKey(root, False);
     try
       values := TStringList.Create;
       try
         keys := TStringList.Create;
         try
           reg.GetValuenames(values); {get all value names}
           reg.GetKeynames(keys); {get all sub-branches}
           for i := 0 to values.Count - 1 do {write all the values first}
           begin
             s := values[i];
             t := s; {s=value name}
             if s = '' then s := '@' {empty means "default value", write as @}
             else
               s := '"' + s + '"'; {else put in quotes}
             Write(f, dblbackslash(s) + '='); {write the name of the key to the file}
             case reg.Getdatatype(t) of {What type of data is it?}
               rdString, rdExpandString: {String-type}
                 Writeln(f, '"' + dblbackslash(reg.ReadString(t) + '"'));
               rdInteger: {32-bit unsigned long integer}
                 Writeln(f, 'dword:' + IntToHex(reg.readinteger(t), 8));
              {write an array of hex bytes if data is "binary." Perform a line feed 
              after approx. 25 numbers so the line length stays within limits}
               rdBinary:
                 begin
                   Write(f, 'hex:');
                   j := reg.GetDataSize(t); {determine size}
                   GetMem(p, j); {Allocate memory}
                   reg.ReadBinaryData(t, p^, J); {read in the data, treat as pchar}
                   for k := 0 to j - 1 do
                   begin
                     Write(f, IntToHex(Byte(p[k]), 2)); {Write byte as hex}
                     if k <> j - 1 then {not yet last byte?}
                     begin
                       Write(f, ','); {then write Comma}
                       if (k > 0) and ((k mod 25) = 0) {line too long?} then
                         Writeln(f, '\'); {then write Backslash +lf}
                     end; {if}
                   end; {for}
                   FreeMem(p, j); {free the memory}
                   Writeln(f); {Linefeed}
                 end;
               else
                 Writeln(f, '""'); {write an empty string if datatype illegal/unknown}
             end;{case}
           end; {for}
         finally
           reg.CloseKey;
         end;
       finally
         {value names all done, no longer needed}
         values.Free;
       end;
       {Now al values are written, we process all subkeys}
       {Perform this process RECURSIVELY...}
       for i := 0 to keys.Count - 1 do
         ProcessBranch(root + '\' + keys[i]);
     finally
       keys.Free; {this branch is ready}
     end;
   end; { ProcessBranch}
 begin
   if RegRoot[Length(Regroot)] = '\' then {No trailing backslash}
     SetLength(regroot, Length(Regroot) - 1);
   Assignfile(f, FileName); {create a text file}
   Rewrite(f);
   if ioResult <> 0 then Exit;
   Writeln(f, 'REGEDIT4'); {"magic key" for regedit}
   reg := TRegistry.Create;
   try
     reg.Rootkey := Rootsection;
     {Call the function that writes the branch and all subbranches}
     ProcessBranch(Regroot);
   finally
     reg.Free; {ready}
     Close(f);
   end;
 end;
 end.

Получить количество вложенных ключей и значений ветви реестра.

Автор: ___Nikolay WEB-сайт: http://delphiworld.narod.ru

uses Registry;
// Количество вложенных ключей и значений
procedure TForm1.Button1Click(Sender: TObject);
const
  sKey = '\SOFTWARE\Microsoft\Windows\CurrentVersion';
var
  rReg: TRegistry;
  ki: TRegKeyInfo;
begin
  rReg := TRegistry.Create;
  with rReg do
  begin
    RootKey := HKEY_LOCAL_MACHINE;
    if KeyExists(sKey) then
    begin
      OpenKey(sKey, false);
      GetKeyInfo(ki);
      CloseKey;
      lbSubkeys.Caption := IntToStr(ki.NumSubKeys);
      lbValues.Caption := IntToStr(ki.NumValues);
    end;
  end;
  rReg.Free;
end;

Перейти на определенный ключ в RegEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit Unit1;
 interface
 uses
   Windows, Messages, Classes, Controls, Forms, StdCtrls;
 type
   TForm1 = class(TForm)
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
     procedure JumpToKey(Key: string);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 uses
   ShellAPI;
 procedure TForm1.JumpToKey(Key: string);
 var
   i, n: Integer;
   hWin: HWND;
   ExecInfo: ShellExecuteInfoA;
 begin
   hWin := FindWindowA(PChar('RegEdit_RegEdit'), nil);
   if hWin = 0 then
   {if Regedit doesn't run then we launch it}
   begin
     FillChar(ExecInfo, 60, #0);
     with ExecInfo do
     begin
       cbSize := 60;
       fMask  := SEE_MASK_NOCLOSEPROCESS;
       lpVerb := PChar('open');
       lpFile := PChar('regedit.exe');
       nShow  := 1;
     end;
     ShellExecuteExA(@ExecInfo);
     WaitForInputIdle(ExecInfo.hProcess, 200);
     hWin := FindWindowA(PChar('RegEdit_RegEdit'), nil);
   end;
   ShowWindow(hWin, SW_SHOWNORMAL);
   hWin := FindWindowExA(hWin, 0, PChar('SysTreeView32'), nil);
   SetForegroundWindow(hWin);
   i := 30;
   repeat
     SendMessageA(hWin, WM_KEYDOWN, VK_LEFT, 0);
     Dec(i);
   until i = 0;
   Sleep(500);
   SendMessageA(hWin, WM_KEYDOWN, VK_RIGHT, 0);
   Sleep(500);
   i := 1;
   n := Length(Key);
   repeat
     if Key[i] = '\' then
     begin
       SendMessageA(hWin, WM_KEYDOWN, VK_RIGHT, 0);
       Sleep(500);
     end
     else
       SendMessageA(hWin, WM_CHAR, Integer(Key[i]), 0);
     i := i + 1;
   until i = n;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   JumpToKey('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer');
 end;
 end.

Считать значение REG_MULTI_SZ параметра реестра.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   Registry;
 procedure ReadREG_MULTI_SZ(const CurrentKey: HKey; const Subkey, ValueName: string;
   Strings: TStrings);
 var
   valueType: DWORD;
   valueLen: DWORD;
   p, buffer: PChar;
   key: HKEY;
 begin
   // Clear TStrings 
  // TStrings leeren 
  Strings.Clear;
   // open the specified key 
  // CurrentKey Schlussel offnen 
  if RegOpenKeyEx(CurrentKey,
                   PChar(Subkey),
                   0, KEY_READ, key) = ERROR_SUCCESS then
   begin
     // retrieve the type and data for a specified value name 
    // Den Typ und Wert des Eintrags Ermitteln. 
    SetLastError(RegQueryValueEx(key,
                  PChar(ValueName),
                  nil,
                  @valueType,
                  nil,
                  @valueLen));
     if GetLastError = ERROR_SUCCESS then
       if valueType = REG_MULTI_SZ then
       begin
         GetMem(buffer, valueLen);
         try
           // receive the value's data (in an array). 
          // Ein Array von Null-terminierten Strings 
          // wird zuruckgegeben 
          RegQueryValueEx(key,
                           PChar(ValueName),
                           nil,
                           nil,
                           PBYTE(buffer),
                           @valueLen);
           // Add values to stringlist 
          // Werte in String Liste einfugen 
          p := buffer;
           while p^ <> #0 do
           begin
             Strings.Add(p);
             Inc(p, lstrlen(p) + 1)
           end
         finally
           FreeMem(buffer)
         end
       end
       else
         raise ERegistryException.Create('Stringlist expected/ String Liste erwartet...')
     else
       raise ERegistryException.Create('Cannot Read MULTI_SZ Value/'+
         'Kann den MULTI_SZ Wert nicht lesen...');
   end;
 end;
 // Test it, Testen: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   ReadREG_MULTI_SZ(HKEY_CURRENT_USER, 'Software\XYZ', 'Test44', Memo1.Lines);
 end;
 {******************************************}
 {2. by Ralph Friedman }
 { 
 Question: 
 I want to read out the binary-value "problems" of the path 
 HKEY_DYN_DATA\Config Manager\Enum\[add the key of a hardware component] to 
 detect if a hardware component is troubled and not working right. 
 But I cannot handle the ReadBinaryData-Method of TRegistry correct. 
 Everytime I use it, it always returns "4" as content of the buffer. 
 How do I detect if the content of the binary-key "problems" is 
 not "00 00 00 00" but something else like "16 00 00 00" or such? 
}
 {Answer: Here's an example of ReadBinaryData }
 procedure TFrmReadBinary.Button1Click(Sender: TObject);
 const
   CKeyName: string = 'System\Setup';
   CValName: string = 'NetcardDlls';
 var
   keyGood: boolean;
   p: integer;
   regKey: TRegistry;
   tmpStr: string;
   vSize: integer;
 begin
   regKey := TRegistry.Create;
   try
     regKey.RootKey := HKEY_LOCAL_MACHINE;
     keyGood  := regKey.OpenKey(CKeyName, False);
     if (keyGood) then
     begin
       vSize := regKey.GetDataSize(CValName);
       if (vSize > 0) then
       begin
         SetLength(tmpStr, vSize);
         regKey.ReadBinaryData(CValName, tmpstr[1], vSize);
         repeat
           p := Pos(#0, tmpStr);
           if p <> 0 then
           begin
             Delete(tmpStr, p, 1);
             Insert(#13#10,tmpStr,p);
           end;
         until p = 0;
         (*StringReplace(tmpStr, #0, #13#10,[rfReplaceAll]);*)
         ListBox1.Items.Text := tmpStr;
       end;
     end;
   finally
     regKey.Free;
   end;
 end;
 {******************************************}
 {3. by Michael Winter }
 procedure RaiseWin32Error(Code: Cardinal);
 var
   Error: EWin32Error;
 begin
   Error := EWin32Error.CreateResFmt(@SWin32Error, [Code,
     SysErrorMessage(Code)]);
   Error.ErrorCode := Code;
   raise Error;
 end;
 // Write REG_MULTI_SZ 
procedure TForm1.Button1Click(Sender: TObject);
 const
   Str = 'multiple'#0'strings'#0'in one'#0'registry'#0'value'#0        ;
 var
   Reg: TRegistry;
   Res: Integer;
 begin
   Reg := TRegistry.Create;
   try
     Reg.RootKey := HKEY_CURRENT_USER;
     if not Reg.OpenKey('\Software\Test\RegMultiSzTest', true) then
       raise Exception.Create('Can''t open key');
     Res := RegSetValueEx(
       Reg.CurrentKey,     // handle of key to set value for 
      'TestValue',        // address of value to set 
      0,                  // reserved 
      REG_MULTI_SZ,       // flag for value type 
      PChar(Str),         // address of value data 
      Length(Str) + 1);   // size of value data 
    if Res <> ERROR_SUCCESS then
       RaiseWin32Error(Res);
   finally
     Reg.Free;
   end;
 end;
 // Read REG_MULTI_SZ 
procedure TForm1.Button2Click(Sender: TObject);
 var
   Reg: TRegistry;
   DataType: Cardinal;
   DataSize: Cardinal;
   Res: Integer;
   Str: String;
   i: Integer;
 begin
   Reg := TRegistry.Create;
   try
     Reg.RootKey := HKEY_CURRENT_USER;
     if not Reg.OpenKeyReadOnly('\Software\Test\RegMultiSzTest') then
       raise Exception.Create('Can''t open key');
     DataSize := 0;
     Res := RegQueryValueEx(
       Reg.CurrentKey,     // handle of key to query 
      'TestValue',        // address of name of value to query 
      nil,                // reserved 
      @DataType,          // address of buffer for value type 
      nil,                // address of data buffer 
      @DataSize);         // address of data buffer size 
    if Res <> ERROR_SUCCESS then
       RaiseWin32Error(Res);
     if DataType <> REG_MULTI_SZ then
       raise Exception.Create('Wrong data type');
     SetLength(Str, DataSize - 1);
     if DataSize > 1 then begin
       Res := RegQueryValueEx(
         Reg.CurrentKey,     // handle of key to query 
        'TestValue',        // address of name of value to query 
        nil,                // reserved 
        @DataType,          // address of buffer for value type 
        PByte(Str),         // address of data buffer 
        @DataSize);         // address of data buffer size 
      if Res <> ERROR_SUCCESS then
         RaiseWin32Error(Res);
     end;
     for i := Length(Str) downto 1 do
       if Str[i] = #0 then
         Str[i] := #13;
     ShowMessage(Str);
   finally
     Reg.Free;
   end;
 end;

Чтение строки из реестра без использования класса TRegistry.

Автор: Dimka Maslov WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Чтение строки из реестра без использования класса TRegistry
Входные параметры:
RootKey - идентификатор корневого раздела реестра, например
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE и т.д.
Key - имя раздела реестра,
Name - имя параметра, для чтения параметра "По умолчанию" ("Default"),
эта строка должна быть пустой
Success - (необязательный параметр) адрес логической переменной, в которую
будет Тгие в случае успеха или False в случае ошибки.
В случае успеха функция возвращает значение параметра, или
пустую строку при возникновении ошибки чтения из реестра
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        13 мая 2002 г.
***************************************************** }
function RegQueryStr(RootKey: HKEY; Key, Name: string;
  Success: PBoolean = nil): string;
var
  Handle: HKEY;
  Res: LongInt;
  DataType, DataSize: DWORD;
begin
  if Assigned(Success) then
    Success^ := False;
  Res := RegOpenKeyEx(RootKey, PChar(Key), 0, KEY_QUERY_VALUE, Handle);
  if Res <> ERROR_SUCCESS then
    Exit;
  Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType, nil, @DataSize);
  if (Res <> ERROR_SUCCESS) or (DataType <> REG_SZ) then
  begin
    RegCloseKey(Handle);
    Exit;
  end;
  SetString(Result, nil, DataSize - 1);
  Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType,
    PByte(@Result[1]), @DataSize);
  if Assigned(Success) then
    Success^ := Res = ERROR_SUCCESS;
  RegCloseKey(Handle);
end;

Пример использования:

procedure TForm1.Button1Click(Sender: TObject);
begin
  edit1.Text := RegQueryStr(HKEY_CLASSES_ROOT, 'AVIFile\shell\open\command', '');
end;

Зарегистрировать или удалить OCX, ActiveX.

Приходит жена программиста с работы, а муж весь расстроенный. - Миша, что случилось? - Мама умерла... да погоди радоваться, не моя - у компа..
{1.} 
type 
  TDllRegisterServer = function: HResult; stdcall; 
function RegisterOCX(FileName: string): Boolean; 
var 
  OCXHand: THandle; 
  RegFunc: TDllRegisterServer; 
begin 
  OCXHand := LoadLibrary(PChar(FileName)); 
  RegFunc := GetProcAddress(OCXHand, 'DllRegisterServer'); 
  if @RegFunc <> nil then 
    Result := RegFunc = S_OK 
  else 
    Result := False; 
  FreeLibrary(OCXHand); 
end; 
function UnRegisterOCX(FileName: string): Boolean; 
var 
  OCXHand: THandle; 
  RegFunc: TDllRegisterServer; 
begin 
  OCXHand := LoadLibrary(PChar(FileName)); 
  RegFunc := GetProcAddress(OCXHand, 'DllUnregisterServer'); 
  if @RegFunc <> nil then 
    Result := RegFunc = S_OK 
  else 
    Result := False; 
  FreeLibrary(OCXHand); 
end; 
{**********} 
{2.} 
function RegisterServer(const aDllFileName: string; aRegister: Boolean): Boolean; 
type 
  TRegProc = function: HResult; 
  stdcall; 
const 
  cRegFuncNameArr: array [Boolean] of PChar = 
    ('DllUnregisterServer', 'DllRegisterServer'); 
var 
  vLibHandle: THandle; 
  vRegProc: TRegProc; 
begin 
  Result := False; 
  vLibHandle := LoadLibrary(PChar(aDllFileName)); 
  if vLibHandle = 0 then Exit; 
    @vRegProc := GetProcAddress(vLibHandle, cRegFuncNameArr[aRegister]); 
  if @vRegProc <> nil then 
    Result := vRegProc = S_OK; 
  FreeLibrary(vLibHandle); 
end;

Использование реестра.

Оформил: DeeCo

Автор: Алексей Федоров.

Реестр - это центральное хранилище информации о параметрах системы и установленных программах. В версиях Windows до Windows 95 программисты сохраняли параметры программ либо в INI-файлах WIN.INI и SYSTEM.INI, либо в дополнительных INI-файлах. Хотя использование INI-файлов поддерживается и в Win32, Microsoft настоятельно рекомендует для хранения необходимых в работе программы параметров пользоваться реестром. Реестр представляет собой иерархическую базу данных, состоящую из секций, подсекций и элементов. Каждая секция имеет свое назначение. Хранить данные о пользовательских программах Microsoft рекомендует в секции HKEY_CURRENT_USER и подсекции Software. В этой подсекции вы создаете подсекцию, идентифицирующую вашу программу или фирму, и уже внутри нее располагаете данные.

Модуль Registry

Для упрощения работы с регистратором в состав Delphi (начиная с версии 2.0) входит модуль REGISTRY, содержащий реализацию трех классов, - TRegistry, TRegistryIniFile и TRegIniFile.

Внимание! Чтобы использовать свойства и методы классов TRegistry, TRegistryIniFile и TRegIniFile, необходимо включить в список uses модуль Registry.

TRegIniFile

Собственно говоря, задача класса TRegIniFile - упростить перенос 16-битных программ в среду Windows 95. Методы этого класса эквивалентны методам класса TIniFile в 16-битной версии Delphi. Класс TRegIniFile позволяет обращаться к секции HKEY_CURRENT_USER, считывать и записывать строки (методы ReadString и WriteString), целочисленные значения (методы ReadInteger и WriteInteger), логические значения (методы ReadBool и WriteBool), секции (методы ReadSection, ReadSections и ReadSectionValues), удалять секции (метод EraseSection) и элементы (метод DeleteKey). Рассмотрим на примерах, как используются функции этого класса.

Microsoft рекомендует записывать данные, относящиеся к вашей программе, в подсекции секции HKEY_CURRENT_USER_Software. Предположим (не особенно фантазируя на эту тему), что ваша программа называется RegDemo, и данные для нее располагаются в секции Software\RegDemo. Ниже мы покажем, как поместить в регистратор строчные, целочисленные и логические данные, а затем считать их, - этих операций будет достаточно для того, чтобы сохранить в регистраторе параметры нашей программы, а затем считать их.

Прежде чем записать данные в определенную секцию, ее необходимо создать. Это происходит при вызове конструктора объекта TRegIniFile. В качестве параметра вы указываете название секции, и если таковой не существует, она создается:

RegFile := TRegIniFile.Create(SubKey); 

После того как файл регистратора открыт (и создана определенная секция), мы можем записать данные. Поддерживаются три типа данных: целочисленные, логические и строчные данные. Для записи этих данных существуют методы WriteInteger, WriteBool и WriteString. В качестве параметров указываются:

название подсекции; название элемента; записываемые данные.

Так, чтобы записать значение элемента MyIntVal в подсекции IntKey, следует выполнить код.

RegFile.WriteInteger(IntKey, 'Int_Val', 32000); 

А для того чтобы прочесть значение, необходимо вызвать метод ReadInteger (в качестве параметров указываются название подсекции, название элемента и значение по умолчанию):

 
RegFile.ReadInteger(IntKey, 'Int_Val', 0)); 

Для чтения логических и строчных данных используются соответственно методы ReadBool и ReadStr, а для их записи – методы WriteBool и WriteString.

Расссмотрим пример использования перечисленных выше методов класса TRegIniFile. Расположим в форме компонент Memo, две группы GroupBox и шесть кнопок – три в группе Write и три в группе Read. Нажатие каждой кнопки в группе Write приведет к записи соответствующего значения в реестр, нажатие каждой кнопки в группе Read – к чтению этого значения.

Статьи по Delphi (014)…

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

unit RDUnit;
interfaceuses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Registry;
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    GroupBox1: TGroupBox;
    GroupBox2: TGroupBox;
    Label1: TLabel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure FormClose(Sender: TObject;
      var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation{$R *.DFM}var
  RegFile: TRegIniFile;
const
  //Подсекция
  SubKey: string = 'Software\RegDemo';
  // Элемент для хранения логических данных
  BoolKey: string = 'BoolKey';
  // Элемент для хранения целочисленных данных
  IntKey: string = 'IntKey';
  // Элемент для хранения строчных данных
  StrKey: string = 'StrKey';
procedure TForm1.FormCreate(Sender: TObject);
begin
  // Создать экземпляр класса
  RegFile := TRegIniFile.Create(SubKey);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  // Записать целочисленное значение
  RegFile.WriteInteger(IntKey, 'Value', 1998);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  // Записать булево значение
  RegFile.WriteBool(BoolKey, 'Value', True);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
  // Записать строку
  RegFile.WriteString(StrKey, 'Value', 'Demo');
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
  // Считать целочисленное значение
  Memo1.Lines.Add('Int Value = ' +
    IntToStr(RegFile.ReadInteger(IntKey,
    'Value', 0)));
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
  // Считать булево значение
  if RegFile.ReadBool(BoolKey, 'Value', False) then
    Memo1.Lines.Add('Bool Value = True')
  else
    Memo1.Lines.Add('Bool Value = False');
end;
procedure TForm1.Button6Click(Sender: TObject);
begin
  // Считать строку
  Memo1.Lines.Add(RegFile.ReadString(StrKey, 'Value', ''));
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // Удалить секцию
  RegFile.EraseSection(SubKey);
  // Освободить память
  RegFile.Free;
end;
end.

На приведенном ниже рисунке показано, как выглядит созданная нами подсекция в редакторе REGEDIT.

Статьи по Delphi (014)…

Отметим, что рассмотренных выше функций вполне достаточно для того чтобы обеспечить минимальную функциональность приложения. Если же вам требуется читать и записывать данные из других секций реестра, вы можете воспользоваться методами класса TRegistry или (что мене удобно) непосредственно функциями Win32 API.

Класс TRegistry

Прежде чем рассмотреть пример использования свойств и методов класса TRegistry, давайте кратко перечислим их.

В следующей таблице перечислены свойства класса TRegistry.

Свойство. Описание.
CurrentKey. Позволяет узнать текущую подсекцию, в которой проводятся операции по чтению и записи. Для изменения подсекции следует использовать методы OpenKey и OpenKeyReadOnly.
CurrentPath. Позволяет узнать полное название текущей подсекции.
LazyWrite. Задает способ обновления информации в реестре – непосредственно или после вызова метода CloseKey.
RootKey. Задает корневую секцию в реестре. По умолчанию установлено значение HKEY_CURRENT_USER.

В следующей таблице перечислены методы класса TRegistry.

Метод. Описание.
CloseKey. Записывает внесенные изменения и закрывает текущую подсекцию.
Create. Создает экземпляр класса TRegistry и задает значение корневой секции - HKEY_CURRENT_USER.
CreateKey. Создает подсекцию.
DeleteKey. Удаляет подсекцию.
DeleteValue. Удаляет значение элемента.
Destroy. Уничтожает ранее созданный экземпляр класса TRegistry.
GetDataInfo. Возвращает тип данных для указанного элемента.
GetDataSize. Возвращает размер данных для указанного элемента.
GetDataType. Возвращает тип данных для указанного элемента.
GetKeyInfo. Возвращает информацию о текущем элементе.
GetKeyNames. Возвращает имена подсекций для указанной секции.
GetValueNames. Возвращает названия элементов для указанной подсекции.
HasSubKeys. Позволяет узнать, имеются ли подсекции для указанной секции.
KeyExists. Позволяет узнать, существует ли элемент.
LoadKey. Создает новую подсекцию и загружает в нее информацию из указанного файла.
MoveKey. Перемещает указанную подсекцию и все вложенные подсекции в заданное место.
OpenKey. Открывает подсекцию.
OpenKeyReadOnly. Открывает подсекцию только для чтения.
ReadBinaryData. Считывает данные в бинарном формате.
ReadBool. Считывает данные в булевом формате.
ReadCurrency. Считывает данные в формате валюты.
ReadDate. Считывает данные в формате даты.
ReadDateTime. Считывает данные в формате “дата/время”.
ReadFloat. Считывает данные в формате с плавающей точкой.
ReadInteger. Считывает данные в целочисленном формате.
ReadString. Считывает данные в строчном формате.
ReadTime. Считывает данные в формате времени.
RegistryConnect. Устанавливает соединение с реестром на другом компьютере.
RenameValue. Переименовывает элемент.
ReplaceKey. Замещает значение элемента значениями из файла.
RestoreKey. Восстанавливает значение элемента из файла.
SaveKey. Сохраняет значение элемента в файле.
UnLoadKey. Удаляет подсекцию, загруженную методом LoadKey.
ValueExists. Позволяет узнать, существует ли значение у элемента.
WriteBinaryData. Записывает данные в бинарном формате.
WriteBool. Записывает данные в булевом формате.
WriteCurrency. Записывает данные в формате валюты.
WriteDate. Записывает данные в формате даты.
WriteDateTime. Записывает данные в формате “дата/время”.
WriteExpandString. Записывает данные в формате "расширенно" строки.
WriteFloat. Записывает данные в формате с плавающей точкой.
WriteInteger. Записывает данные в целочисленном формате.
WriteString. Записывает данные в строчном формате.
WriteTime. Записывает данные в формате времени.

После того как мы кратко познакомились со свойствами и методами класса TRegistry, давайте рассмотрим несколько примеров их использования.

Инициализация.

Перед использованием свойств и методов класса TRegistry, необходимо создать экземпляр этого класса. Например:

var
  R: TRegistry;
  ...R := TRegistry.Create;

Задание корневой секции.

Если вы собираетесь работать с секцией, отличной от HKEY_CURRENT_USER (это значение задается по умолчанию), то после инициализации вы должны изменить значение свойства RootKey. Возможны следующие значения:

HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS
HKEY_PERFORMANCE_DATA
HKEY_CURRENT_CONFIG
HKEY_DYN_DATA
Например:
with R do
begin
  RootKey := HKEY_LOCAL_MACHINE;
  //
  // Продолжаем работу с реестром
  //
end;

Доступ к реестру Win9x и ME без WinAPI.

Оформил: DeeCo

Автор: Илья Маляренко.

Думаю, каждый пользователь Windows хоть раз сталкивался с необходимостью чтения данных из неактивных файлов реестра. Существует, как минимум, 2 варианта решения данной задачи: воспользоваться стандартной утилитой regedit, которая может экспортировать и импортировать данные из файлов реестра. Второй вариант: использовать очень добротную и аккуратно написанную программу regview (для MSDOS), с отличным пользовательским интерфейсом (http://paullee.ru/ - там же описание формата файлов реестра Win9x на русском). К сожалению, для работы в пакетном режиме подходит лишь regedit. При этом, скорость оставляет желать лучшего... Хочется поделиться с Вами, уважаемые, небольшим компонентом TRawRegistry для доступа к информации неактивных и активных файлов реестра формата Win9x/ME. Сразу оговорюсь: я не программист, и не собираюсь таковым быть, поэтому качество реализации оставляет желать лучшего (за неимением гербовой пишут на почтовой =) TRawRegistry - реализует все методы и свойства класса TRegistry, необходимые для чтения ключей и значений параметров из реестра. На данный момент существуют, минимум, две основные проблемы: работа с бинарными данными и общая скорость доступа к файлам. Из-за использования динамических массивов (что легко исправимо), компилируется, начиная с D4. В качестве примера прилагаю программу, использующую данный класс. Это аналог утилиты regedit, но без возможности записи какой бы то ни было информации в реестр.

Скачать архив RawReg.zip (18K).

Запись строки в реестр без использования класса TRegistry.

Автор: Dimka Maslov WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Запись строки в реестр без использования класса TRegistry
Функция записывает в реестр информацию в виде строки.
Входные параметры:
RootKey - идентификатор корневого раздела реестра, например
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE и т.д.
Key - имя раздела реестра, если он не существует, то автоматически
создаётся
Name - имя параметра, для записи параметра "По умолчанию" ("Default"),
эта строка должна быть пустой
Value - значение параметра
В случае успеха функция возвращает True, или False при возникновении
ошибки записи в реестр
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        13 мая 2002 г.
***************************************************** }
function RegWriteStr(RootKey: HKEY; Key, Name, Value: string): Boolean;
var
  Handle: HKEY;
  Res: LongInt;
begin
  Result := False;
  Res := RegCreateKeyEx(RootKey, PChar(Key), 0, nil, REG_OPTION_NON_VOLATILE,
    KEY_ALL_ACCESS, nil, Handle, nil);
  if Res <> ERROR_SUCCESS then
    Exit;
  Res := RegSetValueEx(Handle, PChar(Name), 0, REG_SZ, PChar(Value),
    Length(Value) + 1);
  Result := Res = ERROR_SUCCESS;
  RegCloseKey(Handle);
end;

Относительный и полный пути файла.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
Question: 
Could anyone point me to any function that can receive 
an HTML style relative path as its input and return a full path as its 
output based on the exe's current location, e.g 
Input: '..\..\test2.dat' 
Output: C:\folder1\test2.dat 
(assuming that the executable is running from 
 C:\folder1\folder2\folder3\Project1.exe) 
}
 { 
Answer: 
PathCombine() Concatenates two strings that represent properly 
formed paths into one path, as well as any relative path pieces. 
}
 function PathCombine(lpszDest: PChar; const lpszDir, lpszFile: PChar):
 PChar; stdcall; external 'shlwapi.dll' name 'PathCombineA';
 function PathCombineA(lpszDest: PAnsiChar; const lpszDir, lpszFile:
 PAnsiChar): PAnsiChar; stdcall; external 'shlwapi.dll';
 function PathCombineW(lpszDest: PWideChar; const lpszDir, lpszFile:
 PWideChar): PWideChar; stdcall; external 'shlwapi.dll';
 { 
 Requires Windows 2000 (or Windows NT 4.0 with Internet Explorer 4.0 or later); 
 Requires Windows 98 (or Windows 95 with Internet Explorer 4.0 or later) 
}
 procedure TForm1.FormCreate(Sender: TObject);
 var
   dest: string;
   BaseFile, RelativePath: String;
 begin
   BaseFile := 'C:\folder1\folder2\folder3\';
   RelativePath := '..\..\test2.dat';
   SetLength(dest, MAX_PATH);
   PathCombine(@dest[1], PChar(ExtractFilePath(BaseFile)), PChar(RelativePath));
   SetLength(dest, StrLen(@Dest[1]));
   ShowMessage(dest);
 end;
 { 
  The ExtractRelativePath function takes a base directory but the 
  ExpandFilename function does the expansion based on the current 
  directory not a given base path. 
}
 RelativePath := ExtractRelativePath(ExtractFilePath(BaseFile), TargetFile);

Удаленная база данных.

Данные (файлы) удаленной базы данных находятся на удаленном компьютере. (Следует обратить внимание, что каталоги удаленного компьютера не могут рассматриваться как сетевые диски.) Программа работы с удаленной базой данных состоит из двух частей: клиентской и серверной. Клиентская часть программы, работающая на компьютере пользователя, обеспечивает взаимодействие с серверной программой: посредством запросов, передаваемых на удаленный компьютер, предоставляет доступ к данным. Серверная часть программы, работающая на удаленном компьютере, принимает запросы, выполняет их и пересылает данные клиентской программе. Запросы представляют собой команды, представленные на языке SQL (Structured Query Language) — языке структурированных запросов. Программа, работающая на удаленном сервере, проектируется таким образом, чтобы обеспечить одновременный доступ к информации нескольким пользователям. При этом для обеспечения доступа к данным вместо механизма блокировки файлов используют механизм транзакций. Транзакция — это некоторая последовательность действий, которая должна быть обязательно выполнена над данными перед тем, как они будут переданы. В случае обнаружения ошибки во время выполнения любого из действий вся последовательность действий, составляющая транзакцию, повторяется снова. Таким образом, механизм транзакций обеспечивает защиту от аппаратных сбоев. Он также обеспечивает возможность многопользовательского доступа к данным.

Разработка программы работы с удаленной базы данных — сложная и трудоемкая задача. Ее решение предполагает наличие у разработчика глубоких знаний и большого опыта разработки программного обеспечения. Поэтому в данной книге задача разработки удаленных баз данных не рассматривается.

Использование Remote Debugging.

Оформил: DeeCo

Автор: Андрей Бороздин.

Довольно часто в КГ в рубрике "Программирование" можно встретить советы для начинающих программистов в среде Delphi. Однако порой газету читают и законченные программеры. Именно для них, а также для тех, кто уже имеет определенный опыт программирования, и предназначена эта статья. Не часто, но бывает так, что программа на твоем компьютере, где стоит среда разработки, запускается и работает замечательно, а вот на другой машине, например, на машине клиента, возникают какие-нибудь непонятные ошибки. Существует несколько способов отладки таких программ. Во-первых, можно поставить среду разработки пользователю на машину и там попробовать отладить программу. Однако этот вариант вряд ли можно считать удачным и отличающимся изяществом. К тому же пропадет чистота эксперимента: инсталляция Delphi может заменить какие-нибудь старые системные библиотеки Windows более новыми, после чего ошибка в программе может уже и не возникнуть. К тому же может помешать отсутствие свободного пространства на диске или нежелание пользователя. Следующий наиболее простой и незатейливый вариант — это в той части программы, где, как вы предполагаете, находится ошибка, расставить функции MessageBox (или ShowMessage) с каким-нибудь сообщением и, таким образом, более четко определить место возникновения ошибки. Этот вариант прост, как все гениальное, но обладает рядом существенных недостатков: 1. Это довольно долго и утомительно. Каждый раз приходится убирать старые функции и расставлять новые, перекомпилировать программу и копировать ее на машину клиента. 2. Ошибка может возникать в том месте, где поставить свою функцию весьма непросто. Например, в одном из стандартных модулей Delphi. 3. Ну и, наконец, можно просто банально забыть убрать какой-нибудь из МеssаgеВох'ов. Сам так не раз прокалывался. Однако в ряде случаев этот вариант может оказаться единственно возможным. Еще один вариант отладки таких программ предназначен для тех, у кого рабочая машина и машина клиента находятся в локальной сети. В состав инсталляции Delphi входит небольшая, но очень полезная утилита — Remote Debugging. Ее инсталляция находится в папке rdebug. Назначение Remote Debugging, как следует из названия, — это удаленная отладка программ. Как раз наш случай, лучше и не придумаешь. Но прежде чем воспользоваться всеми прелестями удаленной отладки программы в среде Delphi, Remote Debugging надо скопировать на машину пользователю, проинсталлировать и запустить. После чего в System Тrау'е появится изображение зеленого жучка. Вернемся к нашему проекту. В опциях проекта (Project|Options) необходимо произвести следующие изменения: 1. На вкладке Linker включить опцию Include remote debug symbols. 2. На вкладке Directories/Conditionals в поле Output directory необходимо указать путь к общедоступной папке на машине клиента (с именем машины). Если сделать это затруднительно, то можно указать папку на своей машине, а полученные после компиляции файлы с расширениями exe и rsm каждый раз копировать на машину клиента. 3. Нажмите кнопку ОК. Далее перейдем к параметрам, с которыми будет запускаться программа (Run|Parameters). На вкладке Remote необходимо указать следующие данные: 1. В поле Remote Path указать путь и имя файла на удаленной машине, где находится исполняемый файл, с сетевым именем удаленной машины. 2. В поле Remote Host надо указать сетевое имя удаленной машины или ее IP адрес. 3. Далее можно сделать следующее: Включить опцию Debug Project on remote machine и нажать кнопку ОК. Тогда после выполнения команды Run (F9) вы запустите сессию удаленной отладки. Или так: Нажать кнопку Load, чтобы сразу начать сессию удаленной отладки. Разумеется, на удаленной машине уже должны быть исполняемый файл (.exe) и файл для удаленной отладки (.rsm). После всех этих нехитрых манипуляций с настройками проекта и параметрами запуска на удаленной машине должна запуститься программа, а Delphi — перейти в состояние отладки. Далее удаленная отладка программы ничем не отличается от обычной отладки любой программы. Ну, вот, пожалуй, и все. Успешной отладки, господа программисты!

Переименование каталога.

uses 
  ShellApi; 
procedure RenameDir(DirFrom, DirTo: string); 
var 
  shellinfo: TSHFileOpStruct; 
begin 
  with shellinfo do 
  begin 
    Wnd    := 0; 
    wFunc  := FO_RENAME; 
    pFrom  := PChar(DirFrom); 
    pTo    := PChar(DirTo); 
    fFlags := FOF_FILESONLY or FOF_ALLOWUNDO or 
              FOF_SILENT or FOF_NOCONFIRMATION; 
  end; 
  SHFileOperation(shellinfo); 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  RenameDir('C:\Dir1', 'C:\Dir2'); 
end;

Добавить файлы в меню Reopen.

Оформил: DeeCo

Чтобы поместить в конец меню "Файл" список недавно отрывавшихся файлов можно создать список с этими файлами, процедуру обновления меню и процедуру открывания файла. Список файлов нужно заполнить при запуске программы. Здесь список файлов сохраняется в ini-файл. Процедура открытия файлов помещает название открываемого файла в начало списка с файлами и вызывает процедуру обновления. При закрытии программы список нужно сохранить на диске.
...
public
mi: array[0..9] of TMenuItem;
FilesCount: integer;
Files: TStringList;
procedure UpdateMenu;
  procedure OpenFile(FileName: string);
    procedure OnReopen(Sender: TObject);
end;
...
uses IniFiles;
procedure TForm1.UpdateMenu;
var
  i: integer;
begin
  for i := 0 to FilesCount - 1 do
    mi[i].Destroy;
  FilesCount := Files.Count;
  for i := 0 to FilesCount - 1 do
  begin
    mi[i] := TMenuItem.Create(self);
    mi[i].Caption := Files.Strings[i];
    mi[i].OnClick := OnReopen;
    miFile.Add(mi[i]);
  end;
end;
procedure TForm1.OpenFile(FileName: string);
var
  index: integer;
begin
  Memo1.Lines.LoadFromFile(FileName);
  index := Files.IndexOf(FileName);
  if index <> 0 then
  begin
    if index < 0 then
      Files.Insert(0, FileName)
    else
      Files.Exchange(0, index);
    while Files.Count > 10 do
      Files.Delete(10);
    UpdateMenu;
  end;
end;
procedure TForm1.OnReopen(Sender: TObject);
begin
  OpenFile((Sender as TMenuItem).Caption);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
  ini: TIniFile;
  i: integer;
begin
  FilesCount := 0;
  Files := TStringList.Create;
  ini := TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'files.ini');
  ini.ReadSection('files', Files);
  for i := 0 to Files.Count - 1 do
    Files.Strings[i] := ini.ReadString('files',
      Files.Strings[i], '');
  ini.Destroy;
  UpdateMenu;
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
  ini: TIniFile;
  i: integer;
begin
  ini := TIniFile.Create(ExtractFilePath(ParamStr(0)) +
    'files.ini');
  ini.EraseSection('files');
  for i := 0 to Files.Count - 1 do
    ini.WriteString('files', IntToStr(i),
      Files.Strings[i]);
  ini.Destroy;
  Files.Destroy;
  for i := 0 to FilesCount - 1 do
    mi[i].Destroy;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    OpenFile(OpenDialog1.FileName);
end;

Изменение месторасположения .NET-файла во время работы.

Автор: Scott Frolich

Кто-нибудь знает как изменить месторасположение файла PDOXUSRS.NET во время выполнения программы?

DbiSetProp(hSessionHandle, sesNetFile, pchar('c:\newdir'));

Для получения дескриптора сеанса, если вы используете сессию по умолчанию, необходимо вызвать DbiGetCurrSession .

Как заменить или переместить файл, используемый другим приложением.

Автор: Marcus Neves.

В Windows NT 4/2000 есть такая API функция, имеющая очень интересный последний аргумент. Функция позволяет также переименовывать указанный файл или директорию.

MoveFileEx(ExistingFN, NewFN, MOVEFILE_REPLACE_EXISTING или 
MOVEFILE_DELAY_UNTIL_REBOOT 

Если задать MOVEFILE_DELAY_UNTIL_REBOOT в dwFlags (характерно для Windows NT 4/2000), то в реестр будет занесена специальная информация и после перезагрузки Windows сотрёт или перепишет указанный файл.

Параметры:

ExistingFN указатель на строку (null-terminated) , которая содержит имя нужного нам файла или директории. NewFN указатель на строку (null-terminated), содкржащую новое имя файла, указанного ExistingFN. Флаг MOVEFILE_REPLACE_EXISTING указывает функции заменить, если файл ExistingFN уже существует. Если указать в NewFN - nil, ExistingFN будет стёрт.

Пример:

Перемещение файла:
MoveFileEx('c:\winnt\system32\kernel32.dll', 'd:\winnt.bak\system32\kernel32.dll', 
MOVEFILE_REPLACE_EXISTING или MOVEFILE_DELAY_UNTIL_REBOOT 
Удаление существующего файла:
MoveFileEx('c:\winnt\system32\kernel32.dll', nil, 
MOVEFILE_REPLACE_EXISTING или MOVEFILE_DELAY_UNTIL_REBOOT

Заменить запущенную DLL.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function SystemErrorMessage: string;
 var
    P: PChar;
 begin
   if FormatMessage(Format_Message_Allocate_Buffer + Format_Message_From_System,
                    nil,
                    GetLastError,
                    0,
                    @P,
                    0,
                    nil) <> 0 then
   begin
     Result := P;
     LocalFree(Integer(P))
   end
    else
      Result := '';
 end;
 // Path to Original File 
procedure TForm1.Button2Click(Sender: TObject);
 begin
   if Opendialog1.Execute then
     edit1.Text := OpenDialog1.FileName;
 end;
 // Path to New File 
procedure TForm1.Button3Click(Sender: TObject);
 begin
   if Opendialog2.Execute then
     edit2.Text := OpenDialog2.FileName;
 end;
 // Replace the File. 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   if (Movefileex(PChar(Edit1.Text), PChar(Edit2.Text), MOVEFILE_DELAY_UNTIL_REBOOT) = False) then
     ShowMessage(SystemErrorMessage)
   else
   begin
     ShowMessage('Please Restart Windows to have these changes take effect');
     halt;
   end;
 end;

Замена подстрок.

Автор: Сергею Шамайтис.

function ReplaceSub(str, sub1, sub2: string): string;
var
  aPos: Integer;
  rslt: string;
begin
  aPos := Pos(sub1, str);
  rslt := '';
  while (aPos <> 0) do
  begin
    rslt := rslt + Copy(str, 1, aPos - 1) + sub2;
    Delete(str, 1, aPos + Length(sub1) - 1);
    aPos := Pos(sub1, str);
  end;
  Result := rslt + str;
end;

Заменяем текст в текстовом файле.

Идут два мужика: один - сисадмин, другой - тоже козел.
procedure FileReplaceString(const FileName, searchstring, replacestring: string); 
var 
  fs: TFileStream; 
  S: string; 
begin 
  fs := TFileStream.Create(FileName, fmOpenread or fmShareDenyNone); 
  try 
    SetLength(S, fs.Size); 
    fs.ReadBuffer(S[1], fs.Size); 
  finally 
    fs.Free; 
  end; 
  S  := StringReplace(S, SearchString, replaceString, [rfReplaceAll, rfIgnoreCase]); 
  fs := TFileStream.Create(FileName, fmCreate); 
  try 
    fs.WriteBuffer(S[1], Length(S)); 
  finally 
    fs.Free; 
  end; 
end;

Заменить текст в документе Word.

uses
  ComObj; 
// Replace Flags 
type 
  TWordReplaceFlags = set of (wrfReplaceAll, wrfMatchCase,
  wrfMatchWildcards);
function Word_StringReplace(ADocument: TFileName; SearchString,
ReplaceString: string; Flags: TWordReplaceFlags): Boolean;
const 
  wdFindContinue = 1; 
  wdReplaceOne = 1; 
  wdReplaceAll = 2; 
  wdDoNotSaveChanges = 0; 
var 
  WordApp: OLEVariant; 
begin 
  Result := False; 
  { Check if file exists } 
  if not FileExists(ADocument) then 
  begin 
    ShowMessage('Specified Document not found.'); 
    Exit; 
  end; 
  { Create the OLE Object } 
  try 
    WordApp := CreateOLEObject('Word.Application'); 
  except 
    on E: Exception do 
    begin 
      E.Message := 'Word is not available.'; 
      raise; 
    end; 
  end; 
  try 
    { Hide Word } 
    WordApp.Visible := False; 
    { Open the document } 
    WordApp.Documents.Open(ADocument); 
    { Initialize parameters} 
    WordApp.Selection.Find.ClearFormatting; 
    WordApp.Selection.Find.Text := SearchString; 
    WordApp.Selection.Find.Replacement.Text := ReplaceString; 
    WordApp.Selection.Find.Forward := True; 
    WordApp.Selection.Find.Wrap := wdFindContinue; 
    WordApp.Selection.Find.Format := False; 
    WordApp.Selection.Find.MatchCase := wrfMatchCase in Flags; 
    WordApp.Selection.Find.MatchWholeWord := False; 
    WordApp.Selection.Find.MatchWildcards := wrfMatchWildcards in Flags; 
    WordApp.Selection.Find.MatchSoundsLike := False; 
    WordApp.Selection.Find.MatchAllWordForms := False; 
    { Perform the search} 
    if wrfReplaceAll in Flags then 
      WordApp.Selection.Find.Execute(Replace := wdReplaceAll) 
    else 
      WordApp.Selection.Find.Execute(Replace := wdReplaceOne); 
    { Save word } 
    WordApp.ActiveDocument.SaveAs(ADocument); 
    { Assume that successful } 
    Result := True; 
    { Close the document } 
    WordApp.ActiveDocument.Close(wdDoNotSaveChanges); 
  finally 
    { Quit Word } 
    WordApp.Quit; 
    WordApp := Unassigned; 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  Word_StringReplace('C:\Test.doc','Old String','New String',
  [wrfReplaceAll]); 
end;

Word для генерации отчетов.

Работающая над программой группа питает отвращение к еженедельной отчетности о достигнутых результатах, поскольку она слишком явно свидетельствует об отсутствии таковых.
var
  Word: Variant;
begin
  Word := CreateOleObject('Word.Basic');
  with Word do
  begin {Затем следуют "чистые" команды WordBASIC...}
    FileNew('Normal');
    Insert('Это первая строчка'#13);
    Insert('Это вторая строчка'#13);
    FileSaveAs('c:\temp\test.txt', 3);
  end;
end;

Резидентная программа.

- Как бы вы поступили, если бы случайно оказались в одной камере с Гитлером, Саддамом Хусейном и Билли Гейтсом, и у вас, случайно, оказался с собой пистолет и два патрона? - Сначала два раза выстрелил бы в Гейтса, а потом рукояткой его, рукояткой.
Программу без использования VCL (Visual Component Library). Иначе это можно назвать "написанием программ на WinAPI". Один из способов создать такой проект в Delphi - в меню File | New... выбрать Console Application и удалить строку {$APPTYPE CONSOLE}.

Почти для любого действия нам понадобится окно. Но видеть нам его не нужно. Поэтому, создадим невидимое окно. Для этого нужно зарегистрировать класс окна и создать его, но не показывать. Эти два действия происходят в функции CreateMyWnd. Чтобы было возможно общение пользователя с программой, можно сделать TrayIcon (иконку справа на панели задач). Она создается в процедуре CreateTray. Иконку я взял, наверное, не самую подходящую, но это для примера. Точно так же можно взять собственную иконку. Для tray также нужно всплывающее меню. Здесь оно создается в функции CreateMyMenu и состоит всего из одного пункта. Резидентные программы обычно отслеживают что-то. Для этой цели бывает необходим таймер. Создается он при помощи SetTimer. Чтобы наша программа не "тормозила" компьютер, приоритет программы лучше всего установить в самый низкий. Конечно, это хорошо не во всех случаях, но иногда это весьма полезно. Эта программа занимается тем, что запускает ScreenSaver при сдвиге курсора в левый верхний угол (координаты курсора проверяются каждую секунду) и при нажатии клавиши Pause (реализуются через HotKey). Задача, конечно, не самая актуальная. Присылайте, пожалуйста, ваши идеи по поводу задач для резидентной программы.

program MyResident;
uses
  Windows,
  ShellAPI,
  Messages;
const
  ClassName = 'MyResident'; { Имя класса }
  WM_NOTIFYTRAYICON = WM_USER + 1; { Это сообщение будет
    генерироваться при событиях с tray }
var
  menu: hMenu; { Всплывающее меню }
  mywnd: hWnd; { Окно программы }
function MyWndProc(wnd: hWnd; msg, wParam,
  lParam: longint): longint; stdcall;
var
  p: TPoint;
  s: array [0..255] of char;
  tray: TNotifyIconData;
begin
  case msg of
    WM_TIMER: begin { Событие таймера }
      GetCursorPos(p);
      if (p.x = 0) and (p.y = 0) then begin { Проверка координат курсора }
        { Если ScreenSaver еще не запущен - запустить: }
        GetClassName(GetForegroundWindow, s, length(s));
        if s <> 'WindowsScreenSaverClass'
          then SendMessage(wnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0);
      end;
      result := 0;
    end;
    WM_NOTIFYTRAYICON: begin { Событие tray }
      { Если нажата правая кнопка, показать меню: }
      if lparam = WM_RBUTTONUP then begin
        GetCursorPos(p);
        TrackPopupMenu(menu, TPM_LEFTALIGN, p.x, p.x, 0, wnd, nil);
      end;
      result := 0;
    end;
    WM_COMMAND: begin { Выбран пункт меню }
      { Если выбран нулевой пункт (здесь - единственный) -
        закрыть программу: }
      if lo(lparam) = 0 then SendMessage(mywnd, WM_CLOSE, 0, 0);
      result := 0;
    end;
    WM_HOTKEY: begin { Нажата горячая клавиша }
      { Запуск хранителя экрана: }
      SendMessage(wnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0);
      result := 0;
    end;
    WM_DESTROY: begin { Закрытие программы }
       { Удаление tray: }
      with tray do begin
        cbSize := sizeof(TNotifyIconData);
        wnd := mywnd;
        uID := 0;
      end;
      Shell_NotifyIcon(NIM_DELETE, @tray);
      PostQuitMessage(0);
      result := 0;
    end;
    else Result := DefWindowProc(wnd, msg, WParam, LParam);
  end;
end;
function CreateMyWnd: hWnd;
var
  wc: WndClass;
begin
  { Гегистрация класса: }
  wc.style := 0;
  wc.lpfnWndProc := @MyWndProc;
  wc.cbClsExtra := 0;
  wc.cbWndExtra := 0;
  wc.hInstance := hInstance;
  wc.hIcon := 0;
  wc.hCursor := 0;
  wc.hbrBackground := COLOR_WINDOW;
  wc.lpszMenuName := nil;
  wc.lpszClassName := ClassName;
  if RegisterClass(wc) = 0 then halt(0);
  { Создание окна: }
  result := CreateWindowEx(WS_EX_APPWINDOW, ClassName,
    'My Window', WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
  if result = 0 then halt(0);
end;
procedure CreateTray;
var
  tray: TNotifyIconData;
begin
  { Создание tray: }
  with tray do begin
    cbSize := sizeof(TNotifyIconData);
    wnd := mywnd;
    uID := 0;
    uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
    uCallBackMessage := WM_NOTIFYTRAYICON;
    hIcon := LoadIcon(0, IDI_ASTERISK);
    szTip := ('My Resident');
  end;
  Shell_NotifyIcon(NIM_ADD, @tray);
end;
function CreateMyMenu: hMenu;
begin
  { Создание меню: }
  result := CreatePopupMenu;
  if result = 0 then halt(0);
  if not AppendMenu(result, MF_STRING, 0, 'Exit') then halt(0);
end;
var
  msg: TMsg;
begin
  mywnd := CreateMyWnd; // Создание окна
  CreateTray; // Создание tray
  menu := CreateMyMenu; // Создание меню
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_IDLE); { Установка
    низкого приоритета }
  RegisterHotKey(mywnd, 0, 0, VK_PAUSE); // Регистрация "горячей клавиши"
  SetTimer(mywnd, 0, 1000, nil); // Создание таймера
  while (GetMessage(msg, 0, 0, 0)) do begin
    TranslateMessage(msg);
    DispatchMessage(msg);
  end;
  KillTimer(mywnd, 0); // Уничтожение таймера
  UnregisterHotKey(mywnd, 0); // "Уничтожение" горячей клавиши
end.

Резидентная программа и записная книжка.

Автор: Даниил Карапетян WEB сайт: http://program.dax.ru
Занимаются любовью хакер и эпилептичка, тут у нее приступ эпилепсии ну а он думает о как ей хорошо, ну он закончил, а она все еще в приступе ну он говорит эй ты чего а она ноль внимания - у нее приступ. Тогда он звонит 03 и говорит у меня тут проблема , а его спрашивают в чем дело на что он отвечает Да по моему оргазм завис...
Речь сегодня опять пойдет о резидентных программах. В этот раз в программу будут добавлены новые функции, а именно: записная книжка, "усыпление" компьютера, вызов диалога "Завершение работы Windows". Для тех, кто не читал предыдущего выпуска: чтобы создать программу без модулей (а это здесь нужно) можно в меню File | New... выбрать Console Application. Начиная с этого выпуска, я буду выкладывать на сайт program.dax.ru все файлы проекта, необходимые для компиляции. Скачав их (в архиве они будут занимать 3-5 Кбайт), Вам не придется думать, что делать с этим текстом и какие компоненты с какими событиями создавать. Записная книжка - это окно с многострочным полем ввода, которое легко вызывается и которое сохраняет текст, вводимый пользователем. То есть, при открытии текст считывается из файла, а при закрытии сохраняется в файл. Поскольку поле ввода - окно, его можно создать без каких-либо родительских окон. В VCL аналогом этого было бы создание Memo вне формы. Чтобы объяснить Windows, что это поле ввода, в качестве имени класса окна нужно указать 'EDIT'. ES_MULTILINE делает его многострочным. Когда записная книжка закрывается, текст из нее нужно сохранить. Но сообщения WM_CLOSE, WM_DESTROY и другие попадают не ко мне, а в стандартную оконную процедуру поля ввода. Поэтому стандартную процедуру поля ввода нужно заменить на свою. А чтобы сохранить функциональность поля ввода, все сообщения кроме WM_DESTROY пересылаются в старую оконную процедуру.

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

program Project1;
uses
  Windows,
  ShellAPI,
  Messages;
const
  ClassName = 'MyResident'; // Имя класса
  WM_NOTIFYTRAYICON = WM_USER + 1; { Это сообщение будет
                      генерироваться при событиях с tray }
var
  menu: hMenu; // Всплывающее меню
  mywnd: hWnd; // Окно программы
  memo: hWnd = 0; // Окно записной книжки
  OldMemoProc: Pointer; // Стандартная оконная процедура Edit
// Оконная процедура записной книжки:
function MemoWndProc(wnd: hWnd; msg, wParam,
  lParam: longint): longint; stdcall;
var
  s: PChar;
  len: integer;
  F: File;
begin
  case msg of
    WM_DESTROY: begin // Окно закрывается
      // Сохранение текста:
      len := GetWindowTextLength(memo);
      GetMem(s, len + 1);
      GetWindowText(memo, s, len + 1);
      AssignFile(F, 'memo.txt');
      Rewrite(F, 1);
      BlockWrite(F, s^, len);
      CloseFile(F);
      FreeMem(s);
      result := 0;
      memo := 0;
    end;
    WM_KEYUP: begin // Нажата клавиша
      if wparam = VK_ESCAPE // Нажат Escape
        then result := SendMessage(memo, WM_CLOSE, 0, 0)
        else result := DefWindowProc(wnd, msg, wparam, lparam);
    end;
    // Иначе - вызвать старую оконную процедуру
    else result := CallWindowProc(OldMemoProc, wnd, msg, wparam, lparam);
  end;
end;
// Создание окна записной книжки:
procedure CreateMemo;
var
  len: cardinal;
  F: hFile;
  s: PChar;
  ReadBytes: cardinal;
begin
  // Если записная книжка уже открыта - выход из процедуры:
  if GetForegroundWindow = memo then Exit;
  // Создание окна:
  memo := CreateWindowEx(WS_EX_PALETTEWINDOW, 'EDIT', nil,
    WS_POPUP or WS_SIZEBOX or WS_VSCROLL or
    ES_MULTILINE or ES_AUTOVSCROLL,
    GetSystemMetrics(SM_CXFULLSCREEN) div 2 - 200,
    GetSystemMetrics(SM_CYFULLSCREEN) div 2 - 200,
    400, 400, 0, 0, hinstance, nil);
  // Установка шрифта:
  SendMessage(memo, WM_SETFONT, GetStockObject(SYSTEM_FIXED_FONT), 0);
  // Сохранение старой и установка новой оконной процедуры:
  OldMemoProc := Pointer(GetWindowLong(memo, GWL_WNDPROC));
  SetWindowLong(memo, GWL_WNDPROC, longint(@MemoWndProc));
  { Открытие файла (здесь удобнее воспользоваться функциями WinAPI): }
  try
    F := CreateFile('memo.txt', GENERIC_READ, FILE_SHARE_READ, nil,
      OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if F = INVALID_HANDLE_VALUE then Exit;
    len := GetFileSize(F, nil);
    if len = $FFFFFFFF then Exit;
    GetMem(s, len + 1);
    ReadFile(F, s^, len, ReadBytes, nil);
    SetWindowText(memo, s);
    CloseHandle(F);
    FreeMem(s);
  except SetWindowText(memo, 'Error') end;
  // Показать окно:
  ShowWindow(memo, SW_SHOW);
  UpdateWindow(memo);
end;
// Главная оконная процедура:
function MyWndProc(wnd: hWnd; msg, wParam,
  lParam: longint): longint; stdcall;
var
  p: TPoint;
  tray: TNotifyIconData;
  ProgmanWnd: hWnd;
begin
  case msg of
    WM_NOTIFYTRAYICON: begin // Событие tray
      // Если нажата правая кнопка, показать меню:
      if lparam = WM_RBUTTONUP then begin
        SetForegroundWindow(mywnd);
        GetCursorPos(p);
        TrackPopupMenu(menu, TPM_LEFTALIGN, p.x, p.x, 0, wnd, nil);
      end;
      result := 0;
    end;
    WM_COMMAND: begin // Выбран пункт меню
      { В зависимости от выбранного пункта меню открывается
      записная книжка, запускается ScreenSaver, "усыпляется"
      компьютер или закрывается программа: }
      case loword(wparam) of
        0: CreateMemo;
        1: SendMessage(mywnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0);
        2: SetSystemPowerState(true, true);
        4: SendMessage(mywnd, WM_CLOSE, 0, 0);
      end;
      result := 0;
    end;
    WM_HOTKEY: begin // Нажата горячая клавиша
      case loword(lparam) of
        // Нажата клавиша Pause:
        0: SendMessage(wnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0);
        // Нажаты клавиши Alt+Pause:
        MOD_ALT: begin
          ProgmanWnd := FindWindow('Progman', 'Program Manager');
          if ProgmanWnd <> 0
            then SendMessage(ProgmanWnd, WM_CLOSE, 0, 0);
        end;
        // Нажаты клавиши Alt+Shift+Pause:
        MOD_ALT or MOD_SHIFT: SetSystemPowerState(true, true);
        // Иначе:
        else CreateMemo;
            result := 0;
    end;
    WM_ACTIVATEAPP: begin // Изменение активности приложения
      { Если приложение потеряло активность - закрыть (если нужно)
      записную книжку: }
      if (memo <> 0) and (wparam = 0)
        then SendMessage(memo, WM_CLOSE, 0, 0);
      result := 0;
    end;
    WM_DESTROY: begin // Закрытие программы
      // Удаление tray:
      with tray do begin
        cbSize := sizeof(TNotifyIconData);
        wnd := mywnd;
        uID := 0;
      end;
      Shell_NotifyIcon(NIM_DELETE, @tray);
      PostQuitMessage(0);
      result := 0;
    end;
    else result := DefWindowProc(wnd, msg, WParam, LParam);
  end;
end;
// Создание окна:
function CreateMyWnd: hWnd;
var
  wc: WndClass;
begin
  // Регистрация класса:
  wc.style := CS_HREDRAW or CS_VREDRAW;
  wc.lpfnWndProc := @MyWndProc;
  wc.cbClsExtra := 0;
  wc.cbWndExtra := 0;
  wc.hInstance := hInstance;
  wc.hIcon := LoadIcon(hinstance, IDI_ASTERISK);
  wc.hCursor := LoadCursor(hinstance, IDC_ARROW);
  wc.hbrBackground := COLOR_INACTIVECAPTION;
  wc.lpszMenuName := nil;
  wc.lpszClassName := ClassName;
  if RegisterClass(wc) = 0 then halt(0);
  // Создание окна:
  result := CreateWindowEx(WS_EX_APPWINDOW, ClassName,
    'My Window', WS_POPUP, 100, 100, 200, 200, 0, 0, hInstance, nil);
  if result = 0 then halt(0);
end;
// Создание Tray:
procedure CreateTray;
var
  tray: TNotifyIconData;
begin
  with tray do begin
    cbSize := sizeof(TNotifyIconData);
    wnd := mywnd;
    uID := 0;
    uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
    uCallBackMessage := WM_NOTIFYTRAYICON;
    hIcon := LoadIcon(0, IDI_ASTERISK);
    szTip := ('My Resident');
  end;
  Shell_NotifyIcon(NIM_ADD, @tray);
end;
// Создание всплывающего меню:
function CreateMyMenu: hMenu;
begin
  result := CreatePopupMenu;
  if result = 0 then Exit;
  AppendMenu(result, MF_STRING, 0, 'Memo');
  AppendMenu(result, MF_STRING, 1, 'ScreenSaver');
  AppendMenu(result, MF_STRING, 2, 'Sleep');
  AppendMenu(result, MF_SEPARATOR, 3, 'Exit');
  AppendMenu(result, MF_STRING, 4, 'Exit');
end;
var
  msg: TMsg;
begin
  mywnd := CreateMyWnd; // Создание окна
  CreateTray; // Создание tray
  menu := CreateMyMenu; // Создание меню
  // Установка низкого приоритета:
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_IDLE);
  // Регистрация "горячих клавиш":
  RegisterHotKey(mywnd, 0, 0, VK_PAUSE);
  RegisterHotKey(mywnd, 1, MOD_ALT, VK_PAUSE);
  RegisterHotKey(mywnd, 2, MOD_SHIFT, VK_PAUSE);
  RegisterHotKey(mywnd, 3, MOD_ALT or MOD_SHIFT, VK_PAUSE);
  // Распределение сообщений:
  while (GetMessage(msg, 0, 0, 0)) do begin
    TranslateMessage(msg);
    DispatchMessage(msg);
  end;
  // "Уничтожение" горячих клавиш:
  UnregisterHotKey(mywnd, 0);
  UnregisterHotKey(mywnd, 1);
  UnregisterHotKey(mywnd, 2);
end.

Момент окончания изменения размера или перемещения окна.

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  public
    procedure WMEXITSIZEMOVE(var Message: TMessage);
      message WM_EXITSIZEMOVE;
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.WMEXITSIZEMOVE(var Message: TMessage);
begin
  Form1.Caption := 'Finished Moving and sizing';
end;

Изменение размеров колонок DBGrid.

Если у тебя есть постоянная девушка, то на всех вечеринках приходится использовать установки "default" (по умолчанию). Можно попробовать переустановить "систему" в режиме "custom", но получаться те же самые Windows-совские глюки. Если ты Unix-оид, то у тебя, скорей всего, нет постоянной девушки.
У меня есть форма. На ней расположены поле редактирования, компонент SQL Query, DBGrid и кнопка. Я заполняю поле редактирования и при нажатии на кнопку DBGrid отражает результат запроса. Как я могу изменить размер табличной сетки и ее колонок в зависимости от новых значений полей? Поля, возвращаемые запросом, не заполняют всей ширины сетки, а все мои попытки сделать это из кода терпят крах...

Вы можете изменить размер колонки во время выполнения программы, изменяя свойство DisplayWidth соответствующего поля компонента DBGrid...

MyTableMyField.DisplayWidth := Length(MyTableMyField.value); 

Если Вам действительно необходимо вычислить ширину всего DBGrid, используйте следующий код:

function NewTextWidth(fntFont: TFont; const sString: OpenString): integer;
var
  fntSave: TFont;
begin
  result := 0;
  fntSave := Application.MainForm.Font;
  Application.MainForm.Font := fntFont;
  try
    result := Application.MainForm.Canvas.TextWidth(sString);
  finally
    Application.MainForm.Font := fntSave;
  end;
end;
{ вычисляем ширину табличной сетки, которую необходимо отобразить без                 }
{ горизонтальной полосы прокрутки и без дополнительного пространства между последней  }
{ колонкой и вертикальной полосой прокрутки. Свойство Datasource у компонента DBGrid, }
{ как и свойство Dataset у Datasource должны быть назначены заранее,                  }
{ но таблица не должна быть открытой. Примечание: полученная ширина включает ширину   }
{ вертикальной полосы прокрутки, полученной на основе базового режима                 }
{ отображения. Вычисленная ширина полностью занимает рабочую область компонента.      }
function iCalcGridWidth(
  dbg: TDBGrid { корректируемый компонент }
  ): integer; { "точная" ширина }
const
  cMEASURE_CHAR = '0';
  iEXTRA_COL_PIX = 4;
  iINDICATOR_WIDE = 11;
var
  i, iColumns, iColWidth, iTitleWidth, iCharWidth: integer;
begin
  iColumns := 0;
  result := GetSystemMetrics(SM_CXVSCROLL);
  iCharWidth := NewTextWidth(dbg.Font, cMEASURE_CHAR);
  with dbg.dataSource.dataSet do
    for i := 0 to FieldCount - 1 do
      with Fields[i] do
        if visible then
        begin
          iColWidth := iCharWidth * DisplayWidth;
          if dgTitles in dbg.Options then
          begin
            iTitleWidth := NewTextWidth(dbg.TitleFont, DisplayLabel);
            if iColWidth < iTitleWidth then
              iColWidth := iTitleWidth;
          end;
          inc(iColumns, 1);
          inc(result, iColWidth + iEXTRA_COL_PIX);
        end;
  if dgIndicator in dbg.Options then
  begin
    inc(iColumns, 1);
    inc(result, iINDICATOR_WIDE);
  end;
  if dgColLines in dbg.Options then
    inc(result, iColumns)
  else
    inc(result, 1);
end;

Я должен использовать функцию NewTextWidth, а не Canvas.TextWith компонента DBGrid, так как Canvas еще не инициализирован во время вызова iCalcGridWidth.

При изменении размеров окна без заголовка сначала отрисовывается рамка будущих размеров.

Автор: i-s-v WEB-сайт: http://isv.iatp.org.ua

Вот пример для правого нижнего угла окна, в котором расположен Image1 (или еще что-нибудь).

...
const
  MinHeight = 200;
  MinWidth = 200; //Минимальная ширина и высота формы. При желании
  можна и впихнуть максимальную
var
  isResizing: boolean = false;
  oldPos: TPoint;
  WRect: TRect;
  ...
procedure TfrmMain.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  isResizing := true;
  oldPos := Mouse.CursorPos;
  GetWindowRect(Handle, WRect); //получаем прямоугольник окна
  DrawFocusRect(GetDC(0), WRect); //АПИ функция, рисующая рамку
end;
procedure TfrmMain.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  dx, dy: integer;
begin
  if isResizing then
  begin
    DrawFocusRect(GetDC(0), WRect); //стираем предыдущую рамку
    dx := Mouse.CursorPos.X - oldPos.X;
    dy := Mouse.CursorPos.Y - oldPos.Y;
    if (WRect.Right - WRect.Left + dx > MinWidth) and (WRect.Right + dx <
      Screen.Width) then
      WRect.Right := WRect.Right + dx;
    if (WRect.Bottom - WRect.Top + dy > MinHeight) and (WRect.Bottom + dy <
      Screen.Height) then
      WRect.Bottom := WRect.Bottom + dy;
  end;
  oldPos := Mouse.CursorPos;
  DrawFocusRect(GetDC(0), WRect);
end;
end;
procedure TfrmMain.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if isResizing then
  begin
    DrawFocusRect(GetDC(0), WRect);
    BoundsRect := WRect;
  end;
  isResizing := false;
end;

Изменить размер Jpeg и сохранить его в новый файл.

Статьи по Delphi (014)…

В Интернете прошел первый конкурс красоты. Звание Мисс Интернет завоевала девушка В_21399КS.JРG. Поздравляем победительницу!
procedure TForm1.Button1Click(Sender: TObject);
var
  bmp: TBItmap;
  jpg: TJpegImage;
  scale: Double;
begin
  if opendialog1.execute then
  begin
    jpg := TJpegImage.Create;
    try
      jpg.Loadfromfile( opendialog1.filename );
      if jpg.Height > jpg.Width then
        scale := 50 / jpg.Height
      else
        scale := 50 / jpg.Width;
      bmp:= Tbitmap.Create;
      try
        {Create thumbnail bitmap, keep pictures aspect ratio}
        bmp.Width := Round( jpg.Width * scale );
        bmp.Height:= Round( jpg.Height * scale );
        bmp.Canvas.StretchDraw( bmp.Canvas.Cliprect, jpg );
        {Draw thumbnail as control}
        Self.Canvas.Draw( 100, 10, bmp );
        {Convert back to JPEG and save to file}
        jpg.Assign( bmp );
        jpg.SaveToFile(ChangeFileext( opendialog1.filename, '_thumb.JPG' ));
      finally
        bmp.free;
      end;
    finally
      jpg.free;
    end;
  end;
end;

Панель с изменяющимися размерами.

Привожу исходный код для создания панелей с изменяющимися размерами. Воровняйте панель по области клиента свойством alClient, расположите на ней несколько элементов управления и посмотрите как меняются размеры во время изменения размера формы при выполнении программы. Можно расширить код и запретить изменения размеров во время проектирования, но это я оставляю для вас. Данная технология может и не идеальна, поскольку я накропал код за считанные минуты, но для выполнения моих задач этого достаточно.

unit Elastic;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls;
type
  TElasticPanel = class(TPanel)
  private
    FHorz, FVert: boolean;
    nOldWidth, nOldHeight: integer;
    bResized: boolean;
  protected
    procedure WMSize(var message: TWMSize); message WM_SIZE;
  public
    nCount: integer;
    constructor Create(AOwner: TComponent); override;
  published
    property ElasticHorizontal: boolean read FHorz write FHorz default
      TRUE;
    property ElasticVertical: boolean read FVert write FVert default
      TRUE;
  end;
procedure Register;
implementation
constructor TElasticPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FHorz := TRUE;
  FVert := TRUE;
  nOldWidth := Width;
  nOldHeight := Height;
  bResized := FALSE;
end;
procedure TElasticPanel.WMSize(var message: TWMSize);
var
  bResize: boolean;
  xRatio: real;
  i: integer;
  ctl: TWinControl;
begin
  Inc(nCount);
  if Align = alNone then
    bResize := TRUE
  else
    bResize := bResized;
  if not (csDesigning in ComponentState) and bResize then
  begin
    if FHorz then
    begin
      xRatio := Width / nOldWidth;
      for i := 0 to ControlCount - 1 do
      begin
        ctl := TWinControl(Controls[i]);
        ctl.Left := Round(ctl.Left * xRatio);
        ctl.Width := Round(ctl.Width * xRatio);
      end;
    end;
    if FVert then
    begin
      xRatio := Height / nOldHeight;
      for i := 0 to ControlCount - 1 do
      begin
        ctl := TWinControl(Controls[i]);
        ctl.Top := Round(ctl.Top * xRatio);
        ctl.Height := Round(ctl.Height * xRatio);
      end;
    end;
  end
  else
  begin
    nOldWidth := Width;
    nOldHeight := Height;
  end;
  bResized := TRUE;
  nOldWidth := Width;
  nOldHeight := Height;
end;
procedure Register;
begin
  RegisterComponents('Additional', [TElasticPanel]);
end;
end.

Ограничение на изменение размера формы по размеру панели на ней.

Автор: Xavier Pacheco

{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit BlueBackFrm;
interface
uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons, ExtCtrls;
type
  TBlueBackForm = class(TForm)
    pnlMain: TPanel;
    bbtnOK: TBitBtn;
    bbtnCancel: TBitBtn;
    procedure FormResize(Sender: TObject);
  private
    procedure CenterPanel;
    { Create a message handler for the WM_WINDOWPOSCHANGING message }
    procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
      message WM_WINDOWPOSCHANGING;
  end;
var
  BlueBackForm: TBlueBackForm;
implementation
uses Math;
{$R *.DFM}
procedure TBlueBackForm.CenterPanel;
{ This procedure centers the main panel horizontally and
  vertically inside the form's client area
}
begin
  { Center horizontally }
  if pnlMain.Width < ClientWidth then
    pnlMain.Left := (ClientWidth - pnlMain.Width) div 2
  else
    pnlMain.Left := 0;
  { Center vertically }
  if pnlMain.Height < ClientHeight then
    pnlMain.Top := (ClientHeight - pnlMain.Height) div 2
  else
    pnlMain.Top := 0;
end;
procedure TBlueBackForm.WMWindowPosChanging(var Msg: TWMWindowPosChanging);
var
  CaptionHeight: integer;
begin
  { Calculate the caption height }
  CaptionHeight := GetSystemMetrics(SM_CYCAPTION);
  { This procedure does not take into account the width and
    height of the form's frame. You can use
    GetSystemMetrics() to obtain these values. }
  // Prevent window from shrinking smaller then MainPanel's width
  Msg.WindowPos^.cx := Max(Msg.WindowPos^.cx, pnlMain.Width + 20);
  // Prevent window from shrinking smaller then MainPanel's width
  Msg.WindowPos^.cy := Max(Msg.WindowPos^.cy, pnlMain.Height + 20 +
    CaptionHeight);
  inherited;
end;
procedure TBlueBackForm.FormResize(Sender: TObject);
begin
  CenterPanel; // Center MainPanel when the form is resized.
end;
end.

Различные разрешения - различные размеры шрифтов.

Я также долго мучился над проблемой решения этого вопроса, и ниже я излагаю свои выводы на основании моих многочисленных экспериментов. Свойство, отвечающее за размер шрифта важно, но не менее важны в этом вопросе и другие характеристики. Я получал безобразные результаты при изменении резолюции, пока я не начал задавать размер шрифта в пикселях (pixels) вместо точек (points). Вы можете установить font.height, и вы можете установить font.size. Я обнаружил, что установка значения font.height дает значительно лучшие результаты, поскольку данное свойство определяет количество пикселей, и размер шрифта меняется пропорционально изменению размера пиксела. Также, вы можете обнаружить, что шрифт по-умолчанию не может быть ниже определенной высоты. Будет гораздо лучше, если вместо SYSTEM вы выберите шрифт MS sans-serif. У формы имеется свойство, названное "scaling" (взял из памяти, надеюсь верно). Я обнаружил, что лучше его иметь выключенным. Если свойство включено, Delphi или Windows пытаются при изменении размера формы все соответствено смаштабировать. Все это хорошо только для сохранения относительных позиций элементов, так что я выключил свойство, и больше о нем не вспоминал. Если свойство выключено, а форма ваша максимизирована, вы обнаружите, что все ваши компоненты устремились вслед за левым верхним углом формы. Где не желателен этот эффект, я получал разрешение экрана (Screen.Height и Screen.Width) и прислаивал свойствам компонентов Left и Тор скорректированные свойства прежде, чем форма успевала появиться (в методе OnCreate, во время выполения приложения).

В целом же, завершая рассказ, следует подчеркнуть, что выключенное свойство scaling и использование пикселей вместо точек для изменения размеров шрифта, дает вполне приемлимый результат.

Как из своего приложения определить загрузку ресурсов GDI и USER.

Автор: Nomadic
Подходит фидошник к ларьку с надписью "ПИРОЖКИ". - Мне два сабжа пожалуйста.
{$APPTYPE CONSOLE}
// индикатор ресурсов
program res;
function MyGetFreeSystemResources32(Id: integer): integer;
stdcall; external 'rsrc32' name '_MyGetFreeSystemResources32@4';
const
  rSystem = 0;
  rGDI = 1;
  rUSER = 2;
begin
  writeln('free resources');
  writeln('System:', MyGetFreeSystemResources32(rSystem), '%');
  writeln('GDI:', MyGetFreeSystemResources32(rGDI), '%');
  writeln('USER:', MyGetFreeSystemResources32(rUSER), '%');
end.

Пример ресурсной таблицы строк.

Автор: Ralph Friedman Как мне создать ресурсную таблицу строк (Resource String Table), про которую упоминается в функции FmtLoadStr, но не сказано как создать эту таблицу, про это вообще нигде не сказано! Создайте файл в приведенном ниже формате и обзовите его, скажем (подойдите к этому творчески), strings.rc:
 
  STRINGTABLE LOADONCALL MOVEABLE DISCARDABLE   
  {  
   1, "UNPACK.INI"  
   2, "AcrobatClass"  
   3, "ACROEXCH.EXE"  
   4, "^.PDF"  
   5, "Extensions"  
   6, "ACROEXCH.EXE"  
   7, "PDF"  
   8, "AABSETUP.EXE"  
   9, "DelFiles-"  
   10, "INI-"  
   11, "UNPACK.INI"  
   12, "ACROSRCH.DLL"  
   13, "Regedit"  
   14, "ACROREAD.EXE"  
   15, "ACRO_LE.EXE"  
  }
Затем, с помощью компилятора ресурсов Borland (BRCC.EXE в вашем каталоге Delphi\Bin), скомпилируйте это в файл ресурсов:
 
 BRCC strings.rc

Вы получите файл с именем strings.res. В вашем .DPR-файле после строки {$R *.RES} добавьте строку {$R STRINGS.RES}, после этого строковые ресурсы будут компилироваться с вашим exe-файлом.

Поимей Resource Scrutator version 1.21 by CitruSoft Laboratory нахаляву.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Автор: vallkor (vallkor@ukr.net) Эта программа просматривает ресурсы программ, но сохранять эти ресурсы можно только при регистрации. Программа довольно таки плохенькая, для 35 баксов (для сравнения Ресторатор стоит 20 баков, но выглядит удачнее). Ну да ладно, все равно у меня лишних 35 баксов не было, а сохранять ресурсы ой как хочется... И тут меня осенило: ведь если можно запустить программу, её можно сломать, как говорил орк! Приступим... Зайдём в Help->Register Resource Scrutator и что мы там видим:
имя/е-mail/RegID
всё как везде, исключая мыло. Может оно для понта? поживем увидим. Я ввожу vallkor/vallkor@ukr.net/123321 открываю SoftIce (далее айс) и ставлю брекпоинт
GetDlgItemTextA
GetWindowTextA
не работает...
bpx MessageboxA
не пашет - на верное написано на Делфи, подумал Штирлиц. Мда, ладно, тогда ставим брекпоинт bpx LoadIconA (это загрузка иконки в сообщении о неверном СН) Ура! работает, мда! Теперь поишем в памяти наш номер:
s ds:0 l ffffffff "123321"
у меня был найден по адресу 0167:00СЗСЧСЧ теперь проверим на наличие самой распространенной ошибки: когда сгенерен правильный СН он не затирается, а остается в памяти причем очень близко от неправильного (стек то не резиновый :) ). Так оно и получилось, в окне просмотра данных на две строчки вверх я увидел свой СН: 780420182321. Вот и всё, в принципе. Безболезненное излечение. ЗЫ: уважаемые, если хотите получать за программы по 35 баков, то делайте защиту получше.

Перезапустить свою программу.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure TForm1.Button1Click(Sender: TObject);
 var
   FullProgPath: PChar;
 begin
   FullProgPath := PChar(Application.ExeName);
   // ShowWindow(Form1.handle,SW_HIDE); 
  WinExec(FullProgPath, SW_SHOW); // Or better use the CreateProcess function 
  Application.Terminate; // or: Close; 
end;

Восстановление размера окна.

Автор: Mike Orriss Существует ли какой-либо способ получения координат формы, которые она должна иметь при восстановлении с максимально распахнутого состояния?

Используйте API Функцию GetPlacement. Следующая выдержка из кода моего компонента TBag демонстрирует это:

procedure TBag.SetFormPlace(AName: string; AForm: TForm);
var
  s: string[99];
  Place: TWindowPlacement;
begin
  Place.length := SizeOf(TWindowPlacement);
  if not GetWindowPlacement(AForm.Handle, @Place) then
    exit;
  with Place do
  begin
    s := IntToStr(Flags);
    s := AppendS(s, ShowCmd);
    s := AppendS(s, ptMinPosition.X);
    s := AppendS(s, ptMinPosition.Y);
    s := AppendS(s, ptMaxPosition.X);
    s := AppendS(s, ptMaxPosition.Y);
    s := AppendS(s, rcNormalPosition.Left);
    s := AppendS(s, rcNormalPosition.Top);
    s := AppendS(s, rcNormalPosition.Right);
    s := AppendS(s, rcNormalPosition.Bottom);
  end;
  SetString(AName, s);
end;

Восстанавление длинных имен файлов по известным коротким.

Автор: Boris
Мой Windows понима~1 длинны~1 имена, а твой?
//---------------------------------------------------------------------
// Восстанавливает длинные имена файлов по известным коротким (8.3)
// В качестве аргумента принимает полный или неполный (в т.ч. относительный)
// путь к файлу, например 'C:\WINDОWS\РАБОЧИ~1\ИТАКДА~1.LNK' или
// '..\..\COMMON~1\BORLAN~1\BDE\BDEREA~1.TXT'. Понимает сетевые имена.
// Возвращает полный(!) путь типа 'C:\Windоws\Рабочий стол\и так далее.lnk',
// 'C:\Program Files\Common Files\Borland Shared\BDE\bdereadme.txt',
// '\\Computer\resource\Folder with long name\File with long name.ext'
//---------------------------------------------------------------------
function RestoreLongName(fn: string): string;
  function LookupLongName(const filename: string): string;
  var
    sr: TSearchRec;
  begin
    if FindFirst(filename, faAnyFile, sr) = 0 then
      Result := sr.Name
    else
      Result := ExtractFileName(filename);
    SysUtils.FindClose(sr);
  end;
  function GetNextFN: string;
  var
    i: integer;
  begin
    Result := '';
    if Pos('\\', fn) = 1 then
    begin
      Result := '\\';
      fn := Copy(fn, 3, length(fn) - 2);
      i := Pos('\', fn);
      if i <> 0 then
      begin
        Result := Result + Copy(fn, 1, i);
        fn := Copy(fn, i + 1, length(fn) - i);
      end;
    end;
    i := Pos('\', fn);
    if i <> 0 then
    begin
      Result := Result + Copy(fn, 1, i - 1);
      fn := Copy(fn, i + 1, length(fn) - i);
    end
    else
    begin
      Result := Result + fn;
      fn := '';
    end;
  end;
var
  name: string;
begin
  fn := ExpandFileName(fn);
  Result := GetNextFN;
  repeat
    name := GetNextFN;
    Result := Result + '\' + LookupLongName(Result + '\' + name);
  until length(fn) = 0;
end;

Возврат курсора по умолчанию после выполнения запроса.

Почему мышиный курсор не возвращается обратно (не становится обычной стрелкой) после выполнения запроса?

При выполнении открытого запроса, Delphi изменяет для вас курсор, и произойти это может даже в середине события, как, например, при нажатии на кнопку. Приведенный ниже пример отобразит курсор в виде иконки песочных часов (SQL Hourglass Icon) после того, как вы закроете окно с сообщением. При этом мышь будет вести себя так, как будто находится в режиме "стрелки".

// Добавьте к обработчику события нажатия кнопки,
// использование запроса при этом не имеет значения
// Select * from Customer (в IBLocal)
with query1 do 
begin
  close;
  open;
  showmessage(IntToStr(RecordCount));
end; // with
При наступлении события, Delphi пробует обратно придать курсору тип стрелки (Arrow), при этом выводится новая форма (диалог showmessage), которая мешает автоматическому переводу курсора в режим стрелки.

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

// Добавьте к обработчику события нажатия кнопки,
// использование запроса при этом не имеет значения
// Select * from Customer (в IBLocal)
with query1 do 
begin
  close;
  open;
  application.ProcessMessages;       // Добавьте эту строку.
  showmessage(IntToStr(RecordCount));
end; // with

Перевернуть строку 3.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function ReverseString1(const s: string): string;
 var
   i, len: Integer;
 begin
   len := Length(s);
   SetLength(Result, len);
   for i := len downto 1 do
   begin
     Result[len - i + 1] := s[i];
   end;
 end;
 function ReverseString2(const Str: string): string;
 // by Ido Kanner 
var
   ch: Char;
   i, Size: Integer;
 begin
   Result := Str;
   Size   := Length(Result);
   if (Size >= 2) then
   // 2 or more chars 
  begin
     // For 1 to middle of the string 
    for i := 1 to (Size div 2) do
     begin
       // Lets get the charecter of the current place in the string 
      ch := Result[i];
       // Place the Current pos of the char 
      // with the char of it's mirror place... 
      Result[i] := Result[Size - (i - 1)];
       // In the mirror place we will put char of the 
      // Original place... And we switched places !!! 
      Result[Size - (i - 1)] := ch;
     end
   end;
 end;
 function ReverseString3(S: string): string;
 // by Rudy Velthuis 
var
   P, Q: PChar;
   C: Char;
 begin
   Result := S;
   if Length(Result) = 0 then Exit;
   P := PChar(Result);
   Q := P + Length(Result) - 1;
   while P < Q do
   begin
     C := P^;
     P^ := Q^;
     Q^ := C;
     Inc(P);
     Dec(Q);
   end;
 end;
 procedure ReverseString4(var S: string);
 // by Rudy Velthuis 
var
   P, Q: PChar;
   C: Char;
 begin
   if Length(S) = 0 then Exit;
   P := PChar(S);
   Q := P + Length(S) - 1;
   while P < Q do
   begin
     C := P^;
     P^ := Q^;
     Q^ := C;
     Inc(P);
     Dec(Q);
   end;
 end;

Перевернуть строку.

Автор: ___Nikolay WEB-сайт: http://delphiworld.narod.ru
// Перевернуть строку
function ReverseString(s: string): string;
var
  i: integer;
begin
  Result := '';
  if Trim(s) <> '' then
    for i := Length(s) downto 1 do
      Result := Result + s[i];
end;

Перевернуть строку 2.

Автор: Profit Manson.

function ReverseString(s: string): string;
var
  i: integer;
  c: char;
begin
  if s <> '' then
    for i := 1 to Length(s) div 2 do
    begin
      c := s[i];
      s[i] := s[Length(s) + 1 - i];
      s[Length(s) + 1 - i] := c;
    end;
  Result := s;
end;

Поместить более 64 KB в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  By default, a TRichedit's text is limited to 64 KB. 
  Setting the Maxlength property to $7FFFFFF0 allows you to have approx. 
  2GB of text. 
  Standardmassig ist der Text im Richedit auf 64 KB beschrankt. 
  Indem man die Maxlength auf $7FFFFFF0 setzt, kann man ungefahr 2 GB 
  Text im TRichEdit haben. 
}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   RichEdit11.MaxLength := $7FFFFFF0;
 end;
  //oder: 
//or: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   SendMessage(RichEdit1.Handle, EM_EXLIMITTEXT, 0, $7FFFFFF0);
 end;

Выполнить обратный поиск строки в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

 {+------------------------------------------------------------ 
 | Function FindTextBackwards 
 | 
 | Parameters : 
 |   findWhat: text to find 
 |   inString: string to find it in 
 |   startAt : character index to start at (1-based) 
 |   caseSensitive: determines whether search is case-sensitive 
 |   words   : if true the characters immediately surrounding 
 |             a found location must not be alphanumeric 
 | Returns    : 
 |   character index (1-based) of first character of a found 
 |   location, or 0, if the text was not found. 
 | Description: 
 |   Performs a simple sequential search for a string in a larger 
 |   string, starting at the specified position and working towards 
 |   the start of the string. 
 | Error Conditions: none 
 | Created: 27.02.99 by P. Below 
 +------------------------------------------------------------}
 function FindTextBackwards(findWhat, inString : string;
   startAt : integer;
   caseSensitive, words : boolean): integer;
 var
   i, patternlen, findpos : integer;
   lastchar, firstchar : char;
 begin
   Result := 0;  { assume failure }
   patternlen := Length(findWhat);
   { Do a few sanity checks on the parameters }
   if (patternlen = 0) or
     (startAt < patternlen) or
     (Length(inString) < patternlen) then
     Exit;
   if not caseSensitive then
    begin
     { convert both strings to lower case }
     findWhat := AnsiLowercase(findWhat);
     inString := AnsiLowercase(inString);
   end; { If }
   i := startAt;
   lastchar := findWhat[patternlen];
   firstchar := findWhat[1];
   while (Result = 0) and (i >= patternlen) do
    begin
     if inString[i] = lastchar then
      begin
       findPos := i - patternlen + 1;
       if inString[findPos] = firstchar then
        begin
         { We have a candidate. Compare the substring of length 
          patternlen starting at findPos with findWhat. With 
          AnsiStrLComp we can do that without having to copy 
          the substring to a temp string first. }
         if AnsiStrLComp(@findWhat[1], @inString[findPos],
           patternlen) = 0 then
          begin
           { We have a match! }
           Result := findPos;
           if words then
            begin
             { Check the characters surrounding the hit. For the hit 
              to constitute a word they must not be alphanumeric. }
             if (findPos > 1) and
               IsCharAlphanumeric(inString[findPos - 1]) then
              begin
               { Not a match after all, <sigh>. }
               Result := 0;
             end { If }
             else
              begin
               if (i < Length(inString)) and
                 IsCharAlphanumeric(inString[i + 1]) then
                begin
                 { Not a match after all, <sigh>. }
                 Result := 0;
               end; { If }
             end; { Else }
           end; { If }
         end; { If }
       end; { If }
     end; { If }
     Dec(i);
   end; { While }
 end; { FindTextBackwards }
 procedure TForm1.Button1Click(Sender : TObject);
 var
   findPos : integer;
 begin
   findPos := FindTextBackwards(findEdit.Text,
     richedit1.Text,
     richedit1.selstart + 1,
     caseCheckbox.Checked,
     wordsCheckbox.Checked);
   if findPos > 0 then
    begin
     with richedit1 do
      begin
       selstart := findPos - 1;
       sellength := findEdit.GetTextLen;
       Perform(em_scrollcaret, 0, 0);
       SetFocus;
     end;
   end
   else
     ShowMessage('Text not found');
 end;

Получить первую или последнюю видимую строку в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function RE_GetLastVisibleLine(RichEdit: TRichEdit): Integer;
 const
   EM_EXLINEFROMCHAR = WM_USER + 54;
 var
   r: TRect;
   i: Integer;
 begin
   { 
   The EM_GETRECT message retrieves the formatting rectangle 
   of an edit control. 
  }
   RichEdit.Perform(EM_GETRECT, 0, Longint(@r));
   r.Left := r.Left + 1;
   r.Top  := r.Bottom - 2;
   { 
    The EM_CHARFROMPOS message retrieves information about the character 
    closest to a specified point in the client area of an edit control 
  }
   i := RichEdit.Perform(EM_CHARFROMPOS, 0, Integer(@r.topleft));
   { 
    The EM_EXLINEFROMCHAR message determines which 
    line contains the specified character in a rich edit control 
  }
   Result := RichEdit.Perform(EM_EXLINEFROMCHAR, 0, i);
 end;
 { 
  Sending the EM_GETFIRSTVISIBLELINE message to a multi-line edit control 
  finds out which line is the first line visible. 
  This is the line that is currently displayed at the top of the control. 
}
 function RE_GetFirstVisibleLine(RichEdit: TRichEdit): Integer;
 begin
   Result := RichEdit.Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
 end;

Подсветить HTML теги в RichEdit.

procedure HTMLSyntax(RichEdit: TRichEdit; TextCol,
  TagCol, DopCol: TColor); 
var  
  i, iDop: Integer; 
  s: string; 
  Col: TColor; 
  isTag, isDop: Boolean; 
begin 
  iDop := 0; 
  isDop := False; 
  isTag := False; 
  Col := TextCol; 
  RichEdit.SetFocus; 
  for i := 0 to Length(RichEdit.Text) do 
  begin 
    RichEdit.SelStart := i; 
    RichEdit.SelLength := 1; 
    s := RichEdit.SelText; 
    if (s = '<') or (s = '{') then isTag := True; 
    if isTag then 
      if (s = '"') then 
        if not isDop then 
        begin 
          iDop  := 1; 
          isDop := True; 
        end  
        else 
          isDop := False; 
    if isTag then 
      if isDop then 
      begin 
        if iDop <> 1 then Col := DopCol; 
      end  
      else 
        Col := TagCol 
    else 
      Col := TextCol; 
    RichEdit.SelAttributes.Color := Col; 
    iDop := 0; 
    if (s = '>') or (s = '}') then isTag := False; 
  end; 
   
  RichEdit.SelLength := 0; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  RichEdit1.Lines.BeginUpdate; 
  HTMLSyntax(RichEdit1, clBlue, clRed, clGreen); 
  RichEdit1.Lines.EndUpdate; 
end;

Задать расстояние между строк для TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   RichEdit;
 procedure RE_SetLineSpacing(ARichEdit: TRichEdit; lineSpacing: Byte);
 var
   pf2: ParaFormat2;
 begin
   FillChar(pf2, SizeOf(pf2), 0);
   pf2.cbSize := SizeOf(PARAFORMAT2);
   pf2.dwMask := PFM_LINESPACING;
   pf2.bLineSpacingRule := lineSpacing;
   SendMessage(ARichEdit.Handle, EM_SETPARAFORMAT, 0, Longint(@pf2));
 end;
 //Example: Setlinespacing to 1: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   RE_SetLineSpacing(RichEdit1, 1);
 end;

Сделать предварительный просмотр для TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit RichEditPreview;
 interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, ExtCtrls, Printers, RichEdit, Menus, ComCtrls, ToolWin;
 type
   TPageOffset = record
     mStart, mEnd: Integer;
     rendRect: TRect;
   end;
   TPreviewForm = class(TForm)
     Panel1: TPanel;
     Panel2: TPanel;
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
     procedure FormResize(Sender: TObject);
   private
     { Private-Deklarationen }
   public
     { Public-Deklarationen }
     PreviewPanel: TPanel;
     procedure DrawRichEdit;
   end;
   TPreviewPanel = class(TPanel)
   private
   public
     constructor Create(Owner: TComponent); override;
     destructor Destroy; override;
     procedure Paint; override;
     property Canvas;
   end;
 var
   PreviewForm: TPreviewForm;
 implementation
 uses Unit1, RxRichEd;
 {$R *.dfm}
 procedure TPreviewForm.FormCreate(Sender: TObject);
 begin
   PreviewPanel := TPreviewPanel.Create(Self);
   PreviewPanel.Parent := Self;
   PreviewPanel.Color := clWhite;
 end;
 procedure TPreviewForm.FormDestroy(Sender: TObject);
 begin
   if PreviewPanel <> nil then PreviewPanel.Free
 end;
 // We want the TPreviewPanel to approximate the scaled dimensions of the printed page. 
// Whenever the parent 
// form is resized, we need to rescale and center the panel on the form. 
// To do this, add an OnResize event to 
// the form and add the following code: 
procedure TPreviewForm.FormResize(Sender: TObject);
 var
    wPage, hPage, wClient, hClient: integer;
 begin
   // get the printer dimensions 
  wPage := GetDeviceCaps(Printer.Handle, PHYSICALWIDTH);
   hPage := GetDeviceCaps(Printer.Handle, PHYSICALHEIGHT);
   // get the client window dimensions. 
  hClient := Panel2.ClientHeight;
   // initially adjust width to match height 
  wClient := MulDiv(Panel2.ClientHeight, wPage, hPage);
   // if that doesn't fit, then do it the other way 
  if wClient > Panel2.ClientWidth then
    begin
     wCLient := Panel2.ClientWidth;
     hClient := MulDiv(Panel2.ClientWidth, hPage, wPage);
     // center the page in the window 
    PreviewPanel.Top := ((Panel2.ClientHeight - hClient) div 2) - Panel1.Height;
   end
   else
    begin
     // center the page in the window 
    PreviewPanel.Left := (Panel2.ClientWidth - wClient) div 2;
     PreviewPanel.Top  := Panel1.Height;
   end;
   // now set size of panel 
  PreviewPanel.Width  := wClient;
   PreviewPanel.Height := hClient
 end;
 // The DrawRichEdit() method renders the contents of 
// the control on the preview panel. 
// Much of the code is 
// very close to the code used to print the control in Part 2. 
// The first part of the method is identical to 
// the printing code: 
procedure TPreviewForm.DrawRichEdit;
 var
    wPage, hPage, xPPI, yPPI, wTwips, hTwips, currPage: integer;
   pageRect, rendRect, frameRect: TRect;
   po: TPageOffset;
   fr: TFormatRange;
   lastOffset, xOffset, yOffset, xPrinterOffset, yPrinterOffset: integer;
   FPageOffsets: array of TPageOffset;
   TextLenEx: TGetTextLengthEx;
   hdcDesktop, hdcCanvas, hdcPrinter, xDesktopPPI, yDesktopPPI,
   xFactor, yFactor: integer;
 begin
   wPage := GetDeviceCaps(Printer.Handle, PHYSICALWIDTH);
   hPage := GetDeviceCaps(Printer.Handle, PHYSICALHEIGHT);
   xPPI := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
   yPPI := GetDeviceCaps(Printer.Handle, LOGPIXELSY);
   wTwips := MulDiv(wPage, 1440, xPPI);
   hTwips := MulDiv(hPage, 1440, yPPI);
   with pageRect do
    begin
     Left := 0;
     Top := 0;
     Right := wTwips;
     Bottom := hTwips
   end;
   with rendRect do
    begin
     Left := 0;
     Top := 0;
     Right := pageRect.Right - (1440 * 4);
     Bottom := pageRect.Bottom - (1440 * 4)
   end;
   po.mStart := 0;
   // We will be using several device contexts (DCs), 
  // so let's go ahead and create variables for them. 
  hdcDesktop := GetWindowDC(GetDesktopWindow);
   hdcCanvas  := TPreviewPanel(PreviewPanel).Canvas.Handle;
   hdcPrinter := Printer.Handle;
   // Next, define and initialize a FORMATRANGE structure. 
  fr.hdc        := hdcDesktop;
   fr.hdcTarget  := hdcPrinter;
   fr.chrg.cpMin := po.mStart;
   fr.chrg.cpMax := -1;
   // We will need the size of the text in the control. 
  if RichEditVersion >= 2 then
    begin
     with TextLenEx do
      begin
       flags    := GTL_DEFAULT;
       codepage := CP_ACP;
     end;
     lastOffset := SendMessage(Form1.Editor.Handle, EM_GETTEXTLENGTHEX,
       wParam(@TextLenEx), 0)
   end
   else
      lastOffset := SendMessage(Form1.Editor.Handle, WM_GETTEXTLENGTH, 0, 0);
   // Clear the control's formatting buffer before rendering. 
  SendMessage(Form1.Editor.Handle, EM_FORMATRANGE, 0, 0);
   // Here is the tricky part. 
  // We need to scale the rendering DC to match the size of the printed page in 
  // printer device units. 
  SaveDC(hdcCanvas);
   SetMapMode(hdcCanvas, MM_TEXT);
   SetMapMode(hdcCanvas, MM_ANISOTROPIC);
   SetMapMode(hdcPrinter, MM_TEXT);
   SetWindowExtEx(hdcCanvas, pageRect.Right, pageRect.Bottom, nil);
   xDesktopPPI := GetDeviceCaps(hdcDesktop, LOGPIXELSX);
   yDesktopPPI := GetDeviceCaps(hdcDesktop, LOGPIXELSY);
   ScaleWindowExtEx(hdcCanvas, xDesktopPPI, 1440, yDesktopPPI, 1440, nil);
   SetViewportExtEx(hdcCanvas, PreviewPanel.ClientWidth, PreviewPanel.ClientHeight, nil);
   // Apparently, the Rich Edit control reduces the width of the 
  // rendering area by the amount of the left 
  // offset to the printable portion of the page when printing. 
  // This is a little odd to me because none of 
  // the Windows API GDI functions care whether you are printing 
  // within the printable portion of the page. 
  // Further, this occurs even though the rendering rectangle is 
  // already within the printable portion of the 
  // page.  Anyway, this does not seem to happen when the rendering 
  // DC is the screen so we need to manually 
  // adjust the rectangle ourselves. 
  xPrinterOffset  := MulDiv(GetDeviceCaps(hdcPrinter, PHYSICALOFFSETX), 1440, xPPI);
   yPrinterOffset  := MulDiv(GetDeviceCaps(hdcPrinter, PHYSICALOFFSETY), 1440, yPPI);
   rendRect.Left   := rendRect.Left + (xPrinterOffset shr 1);
   rendRect.Right  := rendRect.Right - xPrinterOffset - (xPrinterOFfset shr 1);
   rendRect.Top    := rendRect.Top + (yPrinterOffset shr 1);
   rendRect.Bottom := rendRect.Bottom - yPrinterOffset - (yPrinterOFfset shr 1);
   // Remember that we are hardcoding two-inch margins. 
  xOffset := MulDiv(PreviewPanel.ClientWidth shl 1, 1440, pageRect.Right);
   yOffset := MulDiv(PreviewPanel.ClientHeight shl 1, 1440, pageRect.Bottom);
   SetViewportOrgEx(hdcCanvas, xOffset, yOffset, nil);
   // Now we build the table of offsets. 
  // Note that we save the rendering rectangle returned by the format 
  // call.  When the rendering and target devices are the same 
  // (or the target device is set to zero), the 
  // returned rectangle is not really needed. 
  // In that case, you can simply ask the control to print to the 
  // original rendering rectangle.  However, when the devices are different, 
  // the returned rendering rectangle 
  // is sometimes larger than the requested rectangle. 
  // This must be a bug in the Rich Edit control.  We deal 
  // with it by saving the returned value to use when 
  // we actually render the control to the screen. 
  while ((fr.chrg.cpMin <> -1) and (fr.chrg.cpMin < lastOffset)) do
    begin
     fr.rc         := rendRect;
     fr.rcPage     := pageRect;
     po.mStart     := fr.chrg.cpMin;
     fr.chrg.cpMin := SendMessage(Form1.Editor.Handle, EM_FORMATRANGE, 0, Longint(@fr));
     po.mEnd       := fr.chrg.cpMin - 1;
     po.rendRect   := fr.rc;
     if High(FPageOffsets) = -1 then SetLength(FPageOffsets, 1)
     else
        SetLength(FPageOffsets, Length(FPageOffsets) + 1);
     FPageOffsets[High(FPageOffsets)] := po
   end;
   // If we were writing a fully working preview function, 
  // we could use FPageOffsets.size() to determine how 
  // many pages had been formatted. 
  // We would then set currPage (below) to the page that we wanted to 
  // display. 
  // In this example, however, we are going to display only the first page. 
  currPage := 0;
   // Now we set the rendering device to the panel's canvas. 
  // Since we have not cleared the formatting buffer, 
  // the target device is not needed, so we set it to zero. 
  // Then we fill in the remaining parts of the 
  // FORMATRANGE structure with the values we saved in FPageOffsets. 
  // Finally, we render the text to the 
  // screen (WPARAM is non-zero). 
  fr.hdc := hdcCanvas;
   fr.hdcTarget  := 0;
   fr.rc := FPageOffsets[currPage].rendRect;
   fr.rcPage := pageRect;
   fr.chrg.cpMin := FPageOffsets[currPage].mStart;
   fr.chrg.cpMax := FPageOffsets[currPage].mEnd;
   fr.chrg.cpMin := SendMessage(Form1.Editor.Handle, EM_FORMATRANGE, 1, Longint(@fr));
   // As I mentioned, the text may be drawn outside of the rendering rectangle. 
  // To make that easier to see, 
  // let's draw a rectangle that shows where the rendering rectangle should be 
  SetMapMode(hdcCanvas, MM_TEXT);
   SetViewportOrgEx(hdcCanvas, 0, 0, nil);
   frameRect := rendRect;
   OffsetRect(frameRect, 1440 + 1440, 1440 + 1440);
   xFactor          := MulDiv(PreviewPanel.ClientWidth,
     (pageRect.Right - rendRect.Right) shr 1, pageRect.Right);
   yFactor          := MulDiv(PreviewPanel.ClientHeight,
     (pageRect.Bottom - rendRect.Bottom) shr 1, pageRect.Bottom);
   frameRect.Left   := xFactor;
   frameRect.Right  := PreviewPanel.ClientWidth - xFactor;
   frameRect.Top    := yFactor;
   frameRect.Bottom := PreviewPanel.ClientHeight - yFactor;
   Windows.FrameRect(hdcCanvas, frameRect, GetStockObject(BLACK_BRUSH));
   // To wrap up, we restore the panel's canvas to the original state, 
  // release the desktop DC, clear the Rich 
  // Edit control's formatting buffer, empty the page offset table, 
    and Close the DrawRichEdit() method.RestoreDC(hdcCanvas, - 1);
   ReleaseDC(GetDesktopWindow, hdcDesktop);
   SendMessage(Form1.Editor.Handle, EM_FORMATRANGE, 0, 0);
   Finalize(FPageOffsets);
 end;
 (*****************************************************)
 (* Alles uber den Nachfahren von TPanel              *)
 (*****************************************************)
 constructor TPreviewPanel.Create(Owner: TComponent);
 begin
   inherited Create(Owner);
 end;
 destructor TPreviewPanel.Destroy;
 begin
   inherited Destroy
 end;
 procedure TPreviewPanel.Paint;
 begin
   inherited Paint;
   PreviewForm.DrawRichEdit;
 end;
 end.

Изменить цвет выделения для TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   RichEdit;
 procedure RE_SetSelBgColor(RichEdit: TRichEdit; AColor: TColor);
 var
   Format: CHARFORMAT2;
 begin
   FillChar(Format, SizeOf(Format), 0);
   with Format do
   begin
     cbSize := SizeOf(Format);
     dwMask := CFM_BACKCOLOR;
     crBackColor := AColor;
     Richedit.Perform(EM_SETCHARFORMAT, SCF_SELECTION, Longint(@Format));
   end;
 end;
 // Example: Set clYellow background color for the selected text. 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   RE_SetSelBgColor(RichEdit1, clYellow);
 end;

Показывать значки элементов списка в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   RichEdit;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   fmt: TParaformat2;
 begin
   FillChar(fmt, SizeOf(fmt), 0);
   fmt.cbSize := SizeOf(fmt);
   // The PARAFORMAT2 structure is used to set the numbering style. 
  // This is done through the following structure members: 
  fmt.dwMask := PFM_NUMBERING or PFM_NUMBERINGSTART or PFM_NUMBERINGSTYLE or
                 PFM_NUMBERINGTAB;
       // Set the following values (bitwise-or them together) to identify 
      // which of the remaining structure members are valid: 
      // PFM_NUMBERING, PFM_NUMBERINGSTART, PFM_NUMBERINGSTYLE, and PFM_NUMBERINGTAB 
  fmt.wNumbering := 2;
       //0 no numbering or bullets 
      //1 (PFN_BULLET) uses bullet character 
      //2 Uses Arabic numbers (1, 2, 3, ...). 
      //3 Uses lowercase letters (a, b, c, ...). 
      //4 Uses uppercase letters (A, B, C, ...). 
      //5 Uses lowercase Roman numerals (i, ii, iii, ...). 
      //6 Uses uppercase Roman numerals (I, II, III, ...). 
      //7 Uses a sequence of characters beginning with the Unicode 
      //  character specified by the wNumberingStart member. 
  fmt.wNumberingStart := 1;
       //  Starting value for numbering. 
  fmt.wNumberingStyle := $200;
       // Styles for numbering: 
      // 0 : Follows the number with a right parenthesis.  1) 
      // $100 : Encloses the number in parentheses.       (1) 
      // $200 : Follows the number with a period.          1. 
      // $300 : Displays only the number.                  1 
      // $400 : Continues a numbered list without applying the next number or bullet. 
      // $8000 : Starts a new number with wNumberingStart. 
  fmt.wNumberingTab := 1440 div 4;
   // Minimum space between a paragraph number and the paragraph text, in twips 
  RichEdit1.Perform(EM_SETPARAFORMAT, 0, lParam(@fmt));
 end;

Использовать SubScript или SuperScript в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// yOffset values 
type
   TCharacterFormat = (CFM_Superscript, CFM_Subscript, CFM_Normal);
 procedure RE_SetCharFormat(RichEdit: TRichEdit; CharacterFormat: TCharacterFormat);
 var
   // The CHARFORMAT structure contains information about 
  // character formatting in a rich edit control. 
  Format: TCharFormat;
 begin
   FillChar(Format, SizeOf(Format), 0);
   with Format do
   begin
     cbSize := SizeOf(Format);
     dwMask := CFM_OFFSET;
     // Character offset, in twips, from the baseline. 
    // If the value of this member is positive, 
    // the character is a superscript; 
    // if it is negative, the character is a subscript. 
    case CharacterFormat of
       CFM_Superscript: yOffset := 60;
       CFM_Subscript: yOffset := -60;
       CFM_Normal: yOffset := 0;
     end;
   end;
   // The EM_SETCHARFORMAT message sets character formatting in a rich edit control. 
  // SCF_SELECTION: Applies the formatting to the current selection 
  Richedit.Perform(EM_SETCHARFORMAT, SCF_SELECTION, Longint(@Format));
 end;
 // Examples: 
// Beispiele: 
// Apply Superscript to the current selection 
// Markierte Zeichen hoch stellen 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   RE_SetCharFormat(RichEdit1, CFM_Superscript);
 end;
 // Apply Subscript to the current selection 
// Markierte Zeichen tief stellen 
procedure TForm1.Button2Click(Sender: TObject);
 begin
   RE_SetCharFormat(RichEdit1, CFM_Subscript);
 end;

Выравнивание текста в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   RichEdit;
 // Using the Paragraph property 
procedure RE_AlignText1(ARichEdit: TRichEdit; alignment: TAlignment);
 begin
   ARichEdit.Paragraph.Alignment := alignment;
 end;
 // Using PARAFORMAT2; nonVCL 
procedure RE_AlignText2(ARichEdit: TRichEdit; alignment: TAlignment);
 var
   pf2: PARAFORMAT2;
 begin
   FillChar(pf2, SizeOf(pf2), 0);
   pf2.cbSize := SizeOf(PARAFORMAT2);
   SendMessage(ARichEdit.Handle, EM_GETPARAFORMAT, 0, Longint(@pf2));
   pf2.dwMask := PFM_ALIGNMENT;
   case alignment of
     taLeftJustify: pf2.wAlignment := PFA_LEFT;
     taCenter: pf2.wAlignment := PFA_CENTER;
     taRightJustify: pf2.wAlignment := PFA_RIGHT;
   end;
   SendMessage(ARichEdit.Handle, EM_SETPARAFORMAT, 0, Longint(@pf2));
 end;
 // Example: Right align text 
// Beispiel: Text rechtsbundig ausrichten 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   RE_AlignText2(RichEdit1, taRightJustify);
 end;

Как скопировать содержимое одного RichEdit в другой.

TMemoryStream это самый простой инструмент взаимодействия между всеми VCL компонентами:

procedure TForm1.Button1Click(Sender: TObject);
var
  MemoryStream: TMemoryStream;
begin
  MemoryStream := TMemoryStream.Create;
  try
    RichEdit1.Lines.SaveToStream(MemoryStream);
    MemoryStream.Seek(0, soFromBeginning);
    RichEdit2.Lines.LoadFromStream(MemoryStream);
  finally
    MemoryStream.Free;
  end;
end;

Использовать различные стили подчеркивания для TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   RichEdit;
 // Underline styles 
const
   CFU_UNDERLINETHICK = 9;
   CFU_UNDERLINEWAVE = 8;
   CFU_UNDERLINEDASHDOTDOT = 7;
   CFU_UNDERLINEDASHDOT = 6;
   CFU_UNDERLINEDASH = 5;
   CFU_UNDERLINEDOTTED = 4;
   CFU_UNDERLINE = 1;
   CFU_UNDERLINENONE = 0;
 procedure RE_SetCharFormat(ARichEdit: TRichEdit; AUnderlineType: Byte; AColor: Word);
 var
   // The CHARFORMAT2 structure contains information about 
  // character formatting in a rich edit control. 
  Format: CHARFORMAT2;
 begin
   FillChar(Format, SizeOf(Format), 0);
   with Format do
   begin
     cbSize := SizeOf(Format);
     dwMask := CFM_UNDERLINETYPE;
     bUnderlineType := AUnderlineType or AColor;
     ARichEdit.Perform(EM_SETCHARFORMAT, SCF_SELECTION, Longint(@Format));
   end;
 end;
 // Underline the current selection with a CFU_UNDERLINEWAVE line style (color red); 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   RE_SetCharFormat(RichEdit1, CFU_UNDERLINEWAVE, $50);
 end;

Подсветить ссылки в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  So what we need: 
  1. drop on your form a RichEdit component from win32 page of component 
  palette 
  2. in OnCreate event of your form write the next code: 
}
 procedure TForm1.FormCreate(Sender: TObject);
 var
   mask: Word;
 begin
   mask := SendMessage(Handle, EM_GETEVENTMASK, 0, 0);
   SendMessage(RichEdit1.Handle, EM_SETEVENTMASK, 0, mask or ENM_LINK);
   SendMessage(RichEdit1.Handle, EM_AUTOURLDETECT, Integer(True), 0);
   RichEdit1.Text := 'SwissDelphiCenter.com: '#13#10 +
     ' Site is located at www.SwissDelphiCenter.com';
 end;
 { 
  After that your Richedit will convert automatically any URLs in highlighted 
  (blue color and underlined). Even if you'll start to enter any text directly 
  in Richedit, any begings for URL will be converted too (not only existing 
  text string but new too) 
}
 // 3. now we must detect mouse clicks in URL range. For this task we must 
//    override WndProc method of our form: 
type
   TForm1 = class(TForm)
   protected
     procedure WndProc(var Message: TMessage); override;
   end;
 // 4. the implementation looks like this: 
procedure TForm1.WndProc(var Message: TMessage);
 var
   p: TENLink;
   strURL: string;
 begin
   if (Message.Msg = WM_NOTIFY) then
   begin
     if (PNMHDR(Message.lParam).code = EN_LINK) then
     begin
       p := TENLink(Pointer(TWMNotify(Message).NMHdr)^);
       if (p.Msg = WM_LBUTTONDOWN) then
       begin
         SendMessage(RichEdit1.Handle, EM_EXSETSEL, 0, Longint(@(p.chrg)));
         strURL := RichEdit1.SelText;
         ShellExecute(Handle, 'open', PChar(strURL), 0, 0, SW_SHOWNORMAL);
       end
     end
   end;
   inherited;
 end;
 { 
 5. Now you can compile your project (don't forget to include Richedit and 
 ShellAPI units in uses clause). 
}

Как конвертировать RFC1123 в TDateTime.

Автор: http://www.swissdelphicenter.ch

function RFC1123ToDateTime(Date: string): TDateTime;
var
  day, month, year: Integer;
  strMonth: string;
  Hour, Minute, Second: Integer;
begin
  try
    day := StrToInt(Copy(Date, 6, 2));
    strMonth := Copy(Date, 9, 3);
    if strMonth = 'Jan' then
      month := 1
    else if strMonth = 'Feb' then
      month := 2
    else if strMonth = 'Mar' then
      month := 3
    else if strMonth = 'Apr' then
      month := 4
    else if strMonth = 'May' then
      month := 5
    else if strMonth = 'Jun' then
      month := 6
    else if strMonth = 'Jul' then
      month := 7
    else if strMonth = 'Aug' then
      month := 8
    else if strMonth = 'Sep' then
      month := 9
    else if strMonth = 'Oct' then
      month := 10
    else if strMonth = 'Nov' then
      month := 11
    else if strMonth = 'Dec' then
      month := 12;
    year := StrToInt(Copy(Date, 13, 4));
    hour := StrToInt(Copy(Date, 18, 2));
    minute := StrToInt(Copy(Date, 21, 2));
    second := StrToInt(Copy(Date, 24, 2));
    Result := 0;
    Result := EncodeTime(hour, minute, second, 0);
    Result := Result + EncodeDate(year, month, day);
  except
    Result := now;
  end;
end;
function DateTimeToRFC1123(aDate: TDateTime): string;
const
  StrWeekDay: string = 'MonTueWedThuFriSatSun';
  StrMonth: string = 'JanFebMarAprMayJunJulAugSepOctNovDec';
var
  Year, Month, Day: Word;
  Hour, Min, Sec, MSec: Word;
  DayOfWeek: Word;
begin
  DecodeDate(aDate, Year, Month, Day);
  DecodeTime(aDate, Hour, Min, Sec, MSec);
  DayOfWeek := ((Trunc(aDate) - 2) mod 7);
  Result := Copy(StrWeekDay, 1 + DayOfWeek * 3, 3) + ', ' +
    Format('%2.2d %s %4.4d %2.2d:%2.2d:%2.2d',
    [Day, Copy(StrMonth, 1 + 3 * (Month - 1), 3),
    Year, Hour, Min, Sec]);
end;

Преобразовать RGB в TColor и наоборот.

// RGB to TColor Values
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit1.Color := RGB(58, 110, 165);
end;
// TColor to RGB values
procedure TForm1.Button2Click(Sender: TObject); 
var 
  Color: Longint; 
  r, g, b: Byte; 
begin 
  Color := ColorToRGB(Edit1.Color); 
  r     := Color; 
  g     := Color shr 8; 
  b     := Color shr 16; 
  label1.Caption := ' Red  : ' + IntToStr(r) + 
    ' Green: ' + IntToStr(g) + 
    ' Blue : ' + IntToStr(b); 
end;

Преобразовать RGB в COLORREF (BGR).

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function RGB2BGR(R, G, B: Byte): COLORREF; overload;
 begin
   Result := (Integer(B) shl 16) + (Integer(G) shl 8) + R;
 end;
 function RGB2BGR(RGB: Integer): COLORREF; overload;
 var
   R, G, B: Integer;
 begin
   R      := RGB div $10000;
   G      := ((RGB mod $10000) div $100) shl 8;
   B      := (RGB mod $100) shl 16;
   Result := B + G + R;
 end;

Как преобразовать цвета RGB в CMYK и обратно.

Ниже представлены две функции RGBTOCMYK() и CMYKTORGB(), которые позволяют преобразовывать набор цветов RGB и CMYK.

Замечание: В цвете CMY чёрные тона достигаются путём одинаковых значений Циана, Магенты и Жёлтого чернил. Чёрные чернила используются только при чисто чёрных точках, для повышения контрастности и экономии цветных чернил. Как раз для этого предназначена третья функция ColorCorrectCMYK().

procedure RGBTOCMYK(R: byte;
  G: byte;
  B: byte;
  var C: byte;
  var M: byte;
  var Y: byte;
  var K: byte);
begin
  C := 255 - R;
  M := 255 - G;
  Y := 255 - B;
  if C < M then
    K := C
  else
    K := M;
  if Y < K then
    K := Y;
  if k > 0 then
  begin
    c := c - k;
    m := m - k;
    y := y - k;
  end;
end;
procedure CMYKTORGB(C: byte;
  M: byte;
  Y: byte;
  K: byte;
  var R: byte;
  var G: byte;
  var B: byte);
begin
  if (Integer(C) + Integer(K)) < 255 then
    R := 255 - (C + K)
  else
    R := 0;
  if (Integer(M) + Integer(K)) < 255 then
    G := 255 - (M + K)
  else
    G := 0;
  if (Integer(Y) + Integer(K)) < 255 then
    B := 255 - (Y + K)
  else
    B := 0;
end;
procedure ColorCorrectCMYK(var C: byte;
  var M: byte;
  var Y: byte;
  var K: byte);
var
  MinColor: byte;
begin
  if C < M then
    MinColor := C
  else
    MinColor := M;
  if Y < MinColor then
    MinColor := Y;
  if MinColor + K > 255 then
    MinColor := 255 - K;
  C := C - MinColor;
  M := M - MinColor;
  Y := Y - MinColor;
  K := K + MinColor;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  R: byte;
  G: byte;
  B: byte;
  C: byte;
  M: byte;
  Y: byte;
  K: byte;
begin
  R := 151;
  G := 81;
  B := 55;
  Memo1.Lines.Add('R = ' + IntToStr(R));
  Memo1.Lines.Add('G = ' + IntToStr(G));
  Memo1.Lines.Add('B = ' + IntToStr(B));
  Memo1.Lines.Add('-------------------');
  RGBTOCMYK(R, G, B, C, M, Y, K);
  Memo1.Lines.Add('C = ' + IntToStr(C));
  Memo1.Lines.Add('M = ' + IntToStr(M));
  Memo1.Lines.Add('Y = ' + IntToStr(Y));
  Memo1.Lines.Add('K = ' + IntToStr(K));
  Memo1.Lines.Add('-------------------');
  CMYKTORGB(C, M, Y, K, R, G, B);
  Memo1.Lines.Add('R = ' + IntToStr(R));
  Memo1.Lines.Add('G = ' + IntToStr(G));
  Memo1.Lines.Add('B = ' + IntToStr(B));
  Memo1.Lines.Add('-------------------');
  RGBTOCMYK(R, G, B, C, M, Y, K);
  c := c + 2;
  m := m + 2;
  y := y + 2;
  ColorCorrectCMYK(C, M, Y, K);
  Memo1.Lines.Add('C = ' + IntToStr(C));
  Memo1.Lines.Add('M = ' + IntToStr(M));
  Memo1.Lines.Add('Y = ' + IntToStr(Y));
  Memo1.Lines.Add('K = ' + IntToStr(K));
end;

Как преобразовать RGB-цвет в оттенки серого.

Статьи по Delphi (014)…
function RgbToGray(RGBColor: TColor): TColor;
var
  Gray: byte;
begin
  Gray := Round((0.30 * GetRValue(RGBColor)) +
  (0.59 * GetGValue(RGBColor)) +
  (0.11 * GetBValue(RGBColor )));
  Result := RGB(Gray, Gray, Gray);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  h, w: integer;
begin
  for w := 0 to Image1.Width-1 do
    for h := 0 to Image1.Height-1 do
      Image2.Canvas.Pixels[w, h] :=
      RgbToGray(Image2.Canvas.Pixels[w, h]);
end;

Как конвертировать RGB в TColor.

function RGBToColor(R, G, B: Byte): TColor;
begin
  Result := B shl 16 or G shl 8 or R;
end;

Получить доступ к элементам TRadioGroup.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  Die Eigenschaft Controls fuhrt alle untergeordneten Komponenten eines Steuerelements 
  (Hier z.B TRadioGroup) auf. 
  Controls ist hilfreich, wenn auf die untergeordneten Steuerelemente mit einer Zahl und 
  nicht mit ihrem Namen Bezug genommen werden soll. 
  So kann Controls beispielsweise verwendet werden, um in einer TRadioGroup Eigenschaften 
  der RadioButtons zu manipulieren (Hints anzeigen, verstecken,...) 
}
 { 
  The property Controls lists all child controls of a Control.(Here a TRadioGroup) 
  Controls is an array of all the child controls. 
  The Controls property is convenient for referring to the children of a control 
  by number rather than name. 
  For example, Controls may be used to change properties of the Radiobuttons in a 
  TRadioGroup (showing hints, hiding items,...) 
}
 {1. *******************************************************}
 // Deactivates/activates a specified item of a TRadioGroup 
// Deaktiviert/aktiviert in einer TRadioGroup ein bestimmtes Item 
procedure RGB_EnableItem(RadioGroup: TRadioGroup; ItemIndex: Byte; bEnabled: Boolean);
 begin
   RadioGroup.Controls[ItemIndex].Enabled := bEnabled;
 end;
 // Example: Deactivates the 2. Item  (Index starts at 0) 
// Beispiel: 2. Item deaktivieren (Index beginnt bei 0) 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   RGB_EnableItem(RadioGroup1, 1, False);
 end;
 {2. *******************************************************}
 // Hides/Shows a specified item of a TRadioGroup 
// Versteckt oder zeigt ein bestimmtes Item in einer TRadioGroup an. 
procedure RGB_ShowItem(RadioGroup: TRadioGroup; ItemIndex: Byte; bVisible: Boolean);
 begin
   RadioGroup.Controls[ItemIndex].Visible := bVisible;
 end;
 // Example: Hides the 2. Item  (Index starts at 0) 
// Beispiel: 2. Item verstecken (Index beginnt bei 0) 
procedure TForm1.Button2Click(Sender: TObject);
 begin
   RGB_ShowItem(RadioGroup1, 1, False);
 end;
 {3. *******************************************************}
 // Show Hints for TRadioGroup items 
// Hints fur die Items in der TRadioGroup anzeigen 
procedure TForm1.Button3Click(Sender: TObject);
 var
   i: Byte;
 begin
   for i := 0 to RadioGroup1.ControlCount - 1 do
   begin
     RadioGroup1.Controls[i].ShowHint := True;
     RadioGroup1.Controls[i].Hint := (Radiogroup1.Controls[i] as TRadiobutton).Caption;
   end;
 end;
 {4. *******************************************************}
 // Focus a specified Radiobutton in a TRadioGroup 
// Ein bestimmter Radiobutton einer TRadioGroup fokussieren 
procedure RGB_FocusItem(RadioGroup: TRadioGroup; ItemIndex: Byte);
 var
   RadiogroupClick: TNotifyEvent;
 begin
   if ItemIndex >= 0 then
   begin
     RadioGroup.OnClick := nil;
     (RadioGroup.Controls[1] as TRadiobutton).SetFocus;
     RadioGroup.OnClick := RadiogroupClick;
   end;
 end;
 // Example: Focus the 2. Radiobutton 
// Beispiel: Den 2. Radiobutton fokussieren 
procedure TForm1.Button4Click(Sender: TObject);
 begin
   RGB_FocusItem(RadioGroup1, 1);
 end;

Richedit сам меняет язык при перемещении.

Автор: Максим Гуменюк
Заходит негр в компьютерную фирму и говорит: - Здравствуйте, я ищу работу. У меня степень доктора наук по компьютерам. Ему отвечают: - Хорошо, мы вас возьмем. Для начала мы дадим вам 100 тысяч долларов в год, мы же вас пока плохо знаем. Согласны? - Согласен. - Да, мы вам дадим автомобиль, но покамест только "опель", потом что-нибудь лучше дадим. Согласны? - Согласен. - И еще мы вам дадим квартиру в хорошем районе, но пока только однокомнатную. Вас это устраивает? Тут негр не выдерживает: - Послушайте! Вы что, надо мной прикалываетесь?! - Но ты же первый начал.
Самое главное, что мне не нравится в Richedit - это то, что он сам меняет язык при перемещении по тексту, не смотря, что вы включили нужный язык, при переходе на участок текста, набраный другим языком. этот баг я обхожу так. я создаю потомка Richedit: меню Component\New Component указываю предком TRichedit Переписаю обработку события WM_INPUTLANGCHANGE, при этом я не вызываю обработчик предка, т.е. Richedit-а Заодно обрабатываю событие WM_INPUTLANGCHANGEREQUEST, которое сообщает, что пользователь изменил язык. Тут надо вызвать обработчик предка, а то не будет переключаться язык.

Вот что из этого получилось. Имеем исправленный компонент VCL с дополнительным событием.

unit RichEditEx;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls;
type
  TLangChangeEvent = procedure(Sender: TObject; Lang: HKL) of object;
  TRichEditEx = class(TRichEdit)
  private
    FOnLangChange: TLangChangeEvent;
    procedure WMLangRequest(var M: TMessage); message WM_INPUTLANGCHANGEREQUEST;
    procedure WMLangChange(var M: TMessage); message WM_INPUTLANGCHANGE;
    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    property OnLangChange: TLangChangeEvent read FOnLangChange write
      FOnLangChange;
    { Published declarations }
  end;
procedure Register;
implementation
procedure tricheditex.WMLangRequest(var M: TMessage);
begin
  if assigned(FOnLangChange) then
    FOnLangChange(self, m.LParam);
  inherited;
end;
procedure tricheditex.WMLangChange(var M: TMessage);
begin
  m.Result := 1;
end;
procedure Register;
begin
  RegisterComponents('Samples', [TRichEditEx]);
end;
end.

Перейти на строку в RichEdit.

Статьи по Delphi (014)…

with Richedit1 do
begin
     selstart := perform( EM_LINEINDEX, linenumber, 0 );
     perform( EM_SCROLLCARET, 0, 0 );
end;
{
The EM_LINEINDEX message returns the character index of the first character
on a given line, assigning that to selstart moves the caret to that position.
The control will only automatically scroll the caret into view if it has
the focus, thus the EM_SCROLLCARET.
}

Печать из RichEdit.

Автор: Nomadic.

procedure TForm1.Button1Click(Sender: TObject);
var 
  lst: TextFile;
  sc: Integer;
begin
  //Printer.Orientation:=poPortraite
  AssignFile(lst,'prn');
  Rewrite(lst);
  sc:=0;
  for sc:=0 to RichEdit1.Lines.Count-1 do
    writeln(lst,StrToOem(RichEdit1.Lines[sc]));
  System.CloseFile(lst);
end;

RichEdit замена текста.

Штирлиц считает зарплату в рейхсмарках: 1022, 1023, 1024... "KB MEMORY OK", - подумал Штирлиц.
// This example doesn't use TReplaceDialog 
// Ohne Benutzung von TReplaceDialog 
function Search_And_Replace(RichEdit: TRichEdit; 
  SearchText, ReplaceText: string): Boolean; 
var 
  startpos, Position, endpos: integer; 
begin 
  startpos := 0; 
  with RichEdit do 
  begin 
    endpos := Length(RichEdit.Text); 
    Lines.BeginUpdate; 
    while FindText(SearchText, startpos, endpos, [stMatchCase])<>-1 do 
    begin 
      endpos   := Length(RichEdit.Text) - startpos; 
      Position := FindText(SearchText, startpos, endpos, [stMatchCase]); 
      Inc(startpos, Length(SearchText)); 
      SetFocus; 
      SelStart  := Position; 
      SelLength := Length(SearchText); 
      richedit.clearselection; 
      SelText := ReplaceText; 
    end; 
    Lines.EndUpdate; 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  Search_And_Replace(Richedit1, 'OldText', 'NewText'); 
end;

RichEdit поиск текста.

Лев ходит по пустыне. В свое время физики предложили свою подборку методов решения задачи о поимке льва в пустыне и помещении его в клетку. А как решают ту же задачу различные деятели эпохи информационных технологий? Программист на Паскале Просматривает пустыню полным перебором. Обнаружив льва, строит вокруг него клетку. Продвинутый программист на Паскале Сортирует пустыню по возрастанию, после чего ищет льва двоичным поиском и строит вокруг него клетку. Если в процессе строительства лев уходит, бросает работу с криком "Range Check Error". Программист на Си Ищет в пустыне камень и помещает его в клетку. Присваивает камню значение "лев". Продвинутый программист на Си Присваивает пустыне значение "клетка". Программист на Си++ Проектирует клетку таким образом, чтобы лев был ее составной частью. При инициализации клетки лев автоматически генерируется внутри. Программист на Аде Говорит, что лев и клетка - это объекты разных типов, и нечего морочить ему голову некорректными задачами. Программист на Дельфи Пишет во все конференции: "Народ, где взять компонент, который ищет в пустыне льва и помещает его в клетку?" Железячник Покупает в зоопарке львицу, делает ей операцию по смене пола и долго пытается запихнуть ее в клетку для канарейки. Геймер-асtiоn'ер Вооружается супершотганом, плазмаганом, рэйлганом, нэйлганом, шестиствольным пулеметом и бензопилой. Прочесывает пустыню, разнося все на своем пути. Ищет среди убитых льва и пытается обнаружить у него в животе желтый ключ. Если находит, отпирает им клетку и ждет награды. Геймер-квестовик Ищет по всей пустыне льва, находит, кладет в карман. Затем ищет по всей пустыне клетку, попутно пытаясь засунуть льва в чайник, башмак, телевизор, ведро с краской и другие попадающиеся на пути емкости. Геймер-стратег Поднимает по всей пустыне налоги, чтобы получить деньги на строительство клетки и охотничьих юнитов. К моменту окончания строительства все львы дохнут от голода. Пользователь интернета Заходит в свой любимый поисковик, пишет в строке Search "пустыня", ищет в найденном "лев в клетке". Если не находит, говорит, что задача неразрешима. Вебмастер Заходит в свой любимый поисковик и пишет в строке Search "пустыня + лев". Создает документ клетка.html и прописывает в нем ссылку на найденное. Спамер Рассылает по всей пустыне множество клеток, к каждой из которых привязана бумажка: "Если вы лев, пожалуйста, зайдите внутрь и закройтесь изнутри". Троянщик Делает то же, что и спамер, но вместо бумажки снаружи вешает внутри клетки картинку с голой львицей. Админ Выкапывает вокруг клетки ров, заполняет его концентрированной кислотой, устанавливает вдоль берега противотанковые ежи и противопехотные мины, все это опутывает колючей проволокой. К проволоке и прутьям клетки подключает провода от генератора высокого напряжения. Вешает на клетку 10 кодовых и 12 амбарных замков. Заходит внутрь, запирается на все замки, пускает ток, ключи проглатывает, коды забывает и говорит, что теперь ему никакой лев не страшен. Хакер Нейтрализует кислоту щелочью, перекусывает проволоку, проползает под ежами, перепрыгивает с шестом через мины, отключает ток, взламывает замки и входит в клетку. Не обнаружив внутри льва, матерится с досады, дает пинка админу и уходит обратно в пустыню.
function SearchForText_AndSelect(RichEdit: TRichEdit; SearchText: string): Boolean; 
var 
  StartPos, Position, Endpos: Integer; 
begin 
  StartPos := 0; 
  with RichEdit do 
  begin 
    Endpos := Length(RichEdit.Text); 
    Lines.BeginUpdate; 
    while FindText(SearchText, StartPos, Endpos, [stMatchCase])<>-1 do 
    begin 
      Endpos   := Length(RichEdit.Text) - startpos; 
      Position := FindText(SearchText, StartPos, Endpos, [stMatchCase]); 
      Inc(StartPos, Length(SearchText)); 
      SetFocus; 
      SelStart  := Position; 
      SelLength := Length(SearchText); 
    end; 
    Lines.EndUpdate; 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  SearchForText_AndSelect(RichEdit1, 'Some Text'); 
end;

Предел на размер текста в RichEdit.

Автор: Richard Shotbolt Я разместил на форме компонент TRichEdit, присвоил ему большой (104Кб) текст, но все попытки его редактирования свелись только в сторону уменьшения размера текста. Это проблема распределения памяти? Есть решение этой проблемы?

Я думаю что ответ на этот вопрос нужно искать в WIN95 API:

SendMessage(MyRichEdit.Handle, EM_EXLIMITTEXT, 0, NewBigSize); 

Подсчет слов в Richedit.

function GetWord: boolean;
var
  s: string; {предположим что слова не содержат>255 символов}
  c: char;
begin
  result := false;
  s := ' ';
  while not eof(f) do
  begin
    read(f, c);
    if not (c in ['a'..'z', 'A'..'Z' {,... и т.д, и т.п.}]) then
      break;
    s := s + c;
  end;
  result := (s <> ' ');
end;
procedure GetWordCount(TextFile: string);
begin
  Count := 0;
  assignfile(f, TextFile);
  reset(f);
  while not eof(f) do
    if GetWord then
      inc(Count);
  closefile(f);
end;

Получить слово под курсором в RichEdit.

Статьи по Delphi (014)…

uses 
 RichEdit; 
  
procedure TForm1.RichEdit1MouseMove(Sender: TObject; Shift: TShiftState; 
  X, Y: Integer); 
var 
  iCharIndex, iLineIndex, iCharOffset, i, j: Integer; 
  Pt: TPoint; 
  s: string; 
begin 
  with TRichEdit(Sender) do 
  begin 
    Pt := Point(X, Y); 
    // Get Character Index from word under the cursor 
    iCharIndex := Perform(Messages.EM_CHARFROMPOS, 0, Integer(@Pt)); 
    if iCharIndex < 0 then Exit; 
    // Get line Index 
    iLineIndex  := Perform(EM_EXLINEFROMCHAR, 0, iCharIndex); 
    iCharOffset := iCharIndex - Perform(EM_LINEINDEX, iLineIndex, 0); 
    if Lines.Count - 1 < iLineIndex then Exit; 
    // store the current line in a variable 
    s := Lines[iLineIndex]; 
    // Search the beginning of the word 
    i := iCharOffset + 1; 
    while (i > 0) and (s[i] <> ' ') do Dec(i); 
    // Search the end of the word 
    j := iCharOffset + 1; 
    while (j <= Length(s)) and (s[j] <> ' ') do Inc(j); 
    // Display Text under Cursor 
    Caption := Copy(s, i, j - i); 
  end; 
end;  

Как отловить правый Enter (NumPad).

Статьи по Delphi (014)…
Автор: Full WEB-сайт: http://full.hotmail.ru
Заходит в лифт программист и вспоминает что ему надо попасть на 12-ый этаж. Что делать? Ну, нажимает он "1", потом нажимает "2" и начинает судорожно искать кнопку "ENTER"

Для этого можно воспользоваться функцией GetHeapStatus:

procedure TForm1.WMKeyDown(var message: TWMKeyDown);
begin
  inherited;
  case message.CharCode of
    VK_RETURN:
    begin
      if (message.KeyData and $1000000 <> 0) then
      begin
        { ENTER on numeric keypad }
      end
      else
      begin
        { ENTER on the standard keyboard }
      end;
    end;
  end;
end;

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

Автор: Nomadic
Как известно, смертоносный вирус "Я люблю тебя" видоизменился, приняв новые обличья. Он последовательно мутировал в вирусы: "Ты мне нравишься" "Ты, конечно, мне нравишься, но давай просто будем друзьями" "Послушай, я просто хотела познакомиться..." "Ладно, я думаю, нам нужно прервать всякие отношения!" "Да я была просто пьяная!" "Перестань мне звонить, идиот!" "Ну все! Ты козел, папа твой козел и мама твоя дура!" и, наконец, в вирус "Пошел на...!
Маленькое предисловие. Т.к. основная моя работа связана с написанием софта для института, обрабатывающего геоданные, то и в отделе, где работаю, так же мучаются проблемами печати (в одном случае - надо печатать карты, с изолиниями, заливкой, подписями и пр.; в другом случае - свои таблицы и сложные отрисовки по внешнему виду). В итоге, моим коллегой был написан кусок, в котором ему удалось добиться качественной печати в двух режимах : MetaFile, Bitmap. Работа с MetaFile у нас сложилась уже исторически - достаточно удобно описать ф-цию, которая что-то отрисовыват (хоть на экране, хоть где), которая принимает TCanvas, и подсовывать ей то канвас дисплея, то канвас метафайла, а потом этот Metafile выбрасывать на печать.Достаточно решить лишь проблемы масштабирования, после чего - вперед. Главная головная боль при таком методе - при отрисовке больших кусков, которые занимают весь лист или его большую часть, надо этот метафайл по размерам делать сразу же в пикселах на этот самый лист. Тогда при изменении размеров (просмотр перед печатью) - искажения при уменьшении не критичны, а вот при увеличении линии и шрифты не "поползут". Итак : Набор идей, которые были написаны (с) Андреем Аристовым, программистом отдела матобеспечения СибНИИНП, г. Тюмень. Моего здесь только - приделывание сверху надстроек для личного использования. Вся работа сводится к следующим шагам : Получить необходимые коэф-ты; Построить метафайл или bmp для последующего вывода на печать; Напечатать. Ниже приведенный кусок (прошу меня не пинать, но писал я и писал для достаточно кривой реализации с передачей параметров через глобальные переменные) я использую для того, чтобы получить коэф-ты пересчета. kScale - для пересчета размеров шрифта, а потом уже закладываюсь на его размеры и получаю два новых коэф-та для kW, kH - которые и позволяют мне с учетом высоты шрифта выводить графику и пр. У меня при работе kW <> kH, что приходится учитывать.

Решили пункт 1.

procedure SetKoeffMeta; // установить коэф-ты
var
  PrevMetafile: TMetafile;
  MetaCanvas: TMetafileCanvas;
begin
  PrevMetafile := nil;
  MetaCanvas := nil;
  try
    PrevMetaFile := TMetaFile.Create;
    try
      MetaCanvas := TMetafileCanvas.Create(PrevMetafile, 0);
      kScale := GetDeviceCaps(Printer.Handle, LOGPIXELSX) /
        Screen.PixelsPerInch;
      MetaCanvas.Font.Assign(oGrid.Font);
      MetaCanvas.Font.Size := Round(oGrid.Font.Size * kScale);
      kW := MetaCanvas.TextWidth('W') / oGrid.Canvas.TextWidth('W');
      kH := MetaCanvas.TextHeight('W') / oGrid.Canvas.TextHeight('W');
    finally
      MetaCanvas.Free;
    end;
  finally
    PrevMetafile.Free;
  end;
end;

Решаем 2.

...
var
  PrevMetafile: TMetafile;
  MetaCanvas: TMetafileCanvas;
begin
  PrevMetafile := nil;
  MetaCanvas := nil;
  try
    PrevMetaFile := TMetaFile.Create;
    PrevMetafile.Width := oWidth;
    PrevMetafile.Height := oHeight;
    try
      MetaCanvas := TMetafileCanvas.Create(PrevMetafile, 0);
      // здесь должен быть ваш код - с учетом масштабирования.
      // я эту вещь вынес в ассигнуемую процедуру, и данный блок
      // вызываю лишь для отрисовки целой страницы.
      см.PS1.
    finally
      MetaCanvas.Free;
    end;
    ...
      PS1.Код, который используется для отрисовки.oCanvas - TCanvas метафайла.
      ...
    var
    iHPage: integer; // высота страницы
    begin
      with oCanvas do
      begin
        iHPage := 3000;
        // залили область метайфайла белым - для дальнейшей работы
        Pen.Color := clBlack;
        Brush.Color := clWhite;
        FillRect(Rect(0, 0, 2000, iHPage));
        // установили шрифты - с учетом их дальнейшего масштабирования
        oCanvas.Font.Assign(oGrid.Font);
        oCanvas.Font.Size := Round(oGrid.Font.Size * kScale);
        ...
          xEnd := xBegin;
        iH := round(RowHeights[iRow] * kH);
        for iCol := 0 to ColCount - 1 do
        begin
          x := xEnd;
          xEnd := x + round(ColWidths[iCol] * kW);
          Rectangle(x, yBegin, xEnd, yBegin + iH);
          r := Rect(x + 1, yBegin + 1, xEnd - 1, yBegin + iH - 1);
          s := Cells[iCol, iRow];
          // выписали в полученный квадрат текст
          DrawText(oCanvas.Handle, PChar(s), Length(s), r, DT_WORDBREAK or
            DT_CENTER);

Главное, что важно помнить на этом этапе - это не забывать, что все выводимые объекты должны пользоваться описанными коэф-тами (как вы их получите - это уже ваше дело). В данном случае - я работаю с переделанным TStringGrid, который сделал для многостраничной печати. Последний пункт - надо сформированный метафайл или bmp напечатать.

...
var
  Info: PBitmapInfo;
  InfoSize: Integer;
  Image: Pointer;
  ImageSize: DWORD;
  Bits: HBITMAP;
  DIBWidth, DIBHeight: Longint;
  PrintWidth, PrintHeight: Longint;
begin
  ...
  case ImageType of
    itMetafile:
      begin
        if Picture.Metafile <> nil then
          Printer.Canvas.StretchDraw(Rect(aLeft, aTop, aLeft + fWidth, aTop +
            fHeight), Picture.Metafile);
      end;
    itBitmap:
      begin
        if Picture.Bitmap <> nil then
        begin
          with Printer, Canvas do
          begin
            Bits := Picture.Bitmap.Handle;
            GetDIBSizes(Bits, InfoSize, ImageSize);
            Info := AllocMem(InfoSize);
            try
              Image := AllocMem(ImageSize);
              try
                GetDIB(Bits, 0, Info^, Image^);
                with Info^.bmiHeader do
                begin
                  DIBWidth := biWidth;
                  DIBHeight := biHeight;
                end;
                PrintWidth := DIBWidth;
                PrintHeight := DIBHeight;
                StretchDIBits(Canvas.Handle, aLeft, aTop, PrintWidth,
                  PrintHeight, 0, 0, DIBWidth, DIBHeight, Image, Info^,
                  DIB_RGB_COLORS, SRCCOPY);
              finally
                FreeMem(Image, ImageSize);
              end;
            finally
              FreeMem(Info, InfoSize);
            end;
          end;
        end;
      end;
  end;
В чем заключается идея PreView ? Остается имея на руках Metafila, Bmp - отрисовать с пересчетом внешний вид изображения (надо высчитать левый верхний угол и размеры "предварительно просматриваемого" изображения. Для показа изображения достаточно использовать StretchDraw. После того, как удалось вывести объекты на печать, проблему создания PreView решили как "домашнее задание".

Кстати, когда мы работаем с Bmp, то для просмотра используем следующий хинт - записываем битовый образ через такую процедуру:

w:=MulDiv(Bmp.Width,GetDeviceCaps(Printer.Handle,LOGPIXELSX),Screen.PixelsPerInch);
h:=MulDiv(Bmp.Height,GetDeviceCaps(Printer.Handle,LOGPIXELSY),Screen.PixelsPerInch);
PrevBmp.Width:=w;
PrevBmp.Height:=h;
PrevBmp.Canvas.StretchDraw(Rect(0,0,w,h),Bmp);
aPicture.Assign(PrevBmp); 
При этом масштабируется битовый образ с минимальными искажениями, а вот при печати - приходится bmp печатать именно так, как описано выше. Итог - наша bmp при печати чуть меньше, чем печатать ее через WinWord, но при этом - внешне - без каких-либо искажений и пр. Imho, я для себя проблему печати решил. На основе вышесказанного, сделал PreView для myStringGrid, где вывожу сложные многострочные заголовки и пр. на несколько листов, осталось кое-что допилить, но с принтером у меня проблем не будет уже точно :) PS. Кстати, Андрей Аристов на основе своей наработки сделал сложные геокарты, которые по качеству не хуже, а может, и лучше, чем выдает Surfer (специалисты поймут). На ватмат.

PPS. Прошу прощения за возможные стилистические неточности - время вышло, охрана уже ругается. Но код - выдран из работающих исходников.

Как научить Delphi делать правильное округление дробных чисел.

Автор: Lonely Wanderer
Чем отличается законченный программист от простого пользователя? Пользователь думает, что в килобайте 1000 байт, а Программист думает, что в километре 1024 метра.
function RoundStr(Zn: Real; kol_zn: Integer): Real;
var
  snl, s, s0, s1, s2: string;
  n, n1: Real;
  nn, i: Integer;
begin
  s := FloatToStr(Zn);
  if (Pos(',', s) > 0) and (Zn > 0) and
  (Length(Copy(s, Pos(',', s) + 1, length(s))) > kol_zn) then
  begin
    s0 := Copy(s, 1, Pos(',', s) + kol_zn - 1);
    s1 := Copy(s, 1, Pos(',', s) + kol_zn + 2);
    s2 := Copy(s1, Pos(',', s1) + kol_zn, Length(s1));
    n := StrToInt(s2) / 100;
    nn := Round(n);
    if nn >= 10 then
    begin
      snl := '0,';
      for i := 1 to kol_zn - 1 do
        snl := snl + '0';
      snl := snl + '1';
      n1 := StrToFloat(Copy(s, 1, Pos(',', s) + kol_zn)) + StrToFloat(snl);
      s := FloatToStr(n1);
      if Pos(',', s) > 0 then
        s1 := Copy(s, 1, Pos(',', s) + kol_zn);
    end
    else
      s1 := s0 + IntToStr(nn);
    if s1[Length(s1)] = ',' then
      s1 := s1 + '0';
    Result := StrToFloat(s1);
  end
  else
    Result := Zn;
end;

Или.

function RoundEx(X: Double; Precision: Integer ): Double;
{
Precision :
1 - до целых
10 - до десятых
100 - до сотых
...
}
var
  ScaledFractPart, Temp: Double;
begin
  ScaledFractPart := Frac(X) * Precision;
  Temp := Frac(ScaledFractPart);
  ScaledFractPart := Int(ScaledFractPart);
  if Temp >= 0.5 then
    ScaledFractPart := ScaledFractPart + 1;
  if Temp then
    ScaledFractPart := ScaledFractPart - 1;
  RoundEx := Int(X) + ScaledFractPart / Precision;
end;
Еще более короткий способ:

Округление дробных чисел с точностью i - количество знаков после запятой, S - дробное число в строковом виде.

function FormatData(s: String; i: Integer): String;
begin
  Result:=FloatToStr(Round(StrToFloat(s)*exp(i*ln(10)))/(exp(i*ln(10))));
end;

Меню с правой стороны.

Статьи по Delphi (014)…
Для выхода в меню нажмите Reset.

Главное меню окна – до боли знакомая вещь. Какое же извращение придумать с ним?..

Весьма необычно будет, если какой-нибудь пункт меню будет обособленно располагаться с правой стороны окна! (или несколько пунктов меню). Как же это сделать? Для этого нужно иметь: компонент MainMenu – 1 штука, форма – 1 штука, клава – 2 штуки (одна для того, чтобы набить ту чушь, что расположена ниже, а другая, являющаяся особой женского пола – для одних только вам известных забав). Начнём с первой (тем более, что кончить на второй всегда успеем!).

Всё что нужно сделать для этого – это создать главное меню, например, показанное на рисунке, и по созданию окна (событие OnCreate) написать следующий код:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ModifyMenu(MainMenu1.Handle, 3, mf_ByPosition or mf_Popup or mf_Help,
  Help1.Handle, PChar(Help1.Caption));
end; 

Как в TEdit расположить текст справа.

Статьи по Delphi (014)…
Новая серия книг Издательства Piter-press: Windows 98: баг за багом.

Идея заключается в том, как сделать правое выравнивание текста в TEdit, не прибегая к написанию нового компонента.

Для этого можно воспользоваться канвасом. Так как TEdit не имеет канваса, то сперва мы создадим TControlCanvas а затем, прикрепим TEdit к этому канвасу.

Теперь нам доступны все свойства и методы TControlCanvas, поэтому мы спокойно можем настраивать в нём текст. Ниже приведёна процедура, реализующая всё вышесказанное.

procedure RJustifyEdit(var ThisEdit : TEdit);
var
  Left, Width: Integer;
  GString: string;
  Rgn: TRect;
  TheCanvas: TControlCanvas;
begin
  TheCanvas := TControlCanvas.Create;
  try
    TheCanvas.Control := ThisEdit;
    GString := ThisEdit.Text;
    Rgn := ThisEdit.ClientRect;
    TheCanvas.FillRect(Rgn);
    Width := TheCanvas.TextWidth(GString);
    Left := Rgn.Right - Width - 1;
    TheCanvas.TextRect(Rgn, Left, 0, GString);
  finally
    TheCanvas.Free;
  end;
end;

Как в TEdit расположить текст справа 2.

Женится один программист. Друг его спрашивает: - Ну, как невеста, хороша? - Как топ-модель! - Что, 90х60х90? - Обижаешь: 128х64х128!

TEdit не поддерживает выравниваение текста по центру и по правой стороне - лучше использовать компонент TMemo. Вам понадобится запретить пользователю нажимать Enter, Ctrl-Enter и всевозможные комбинации клавиш со стрелками, чтобы избежать появления нескольких сторк в Memo. Этого можно добиться и просматривая содержимое текста в TMemo в поисках кода возврата каретки (13) и перевода строки(10) на событиях TMemo Change и KeyPress. Можно также заменять код возврата каретки на пробел - для того чтобы позволять вставку из буфера обмена многострочного текста в виде одной строки.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Alignment := taRightJustify;
  Memo1.MaxLength := 24;
  Memo1.WantReturns := false;
  Memo1.WordWrap := false;
end;
procedure MultiLineMemoToSingleLine(Memo : TMemo);
var
  t: string;
begin
  t := Memo.Text;
  if Pos(#13, t) > 0 then
  begin
    while Pos(#13, t) > 0 do
      delete(t, Pos(#13, t), 1);
    while Pos(#10, t) > 0 do
      delete(t, Pos(#10, t), 1);
    Memo.Text := t;
  end;
end;
procedure TForm1.Memo1Change(Sender: TObject);
begin
  MultiLineMemoToSingleLine(Memo1);
end;
procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
  MultiLineMemoToSingleLine(Memo1);
end;

Окно в виде кольца.

Статьи по Delphi (014)…

Умея создавать окно эллипсовидной формы, для вас не составит большого труда слепить что-нибудь наподобие того, что показано на рисунке. Всё, что вам нужно сделать - это создать не один, а два региона и объединить их, используя функцию CombineRgn, т.е. теперь по созданию окна можно написать что-то вроде этого:

procedure TForm1.FormCreate(Sender: TObject);
var
  hsWindowRegion, hsWindowRegion2: Integer;
begin
  hsWindowRegion := CreateEllipticRgn(50, 50, 350, 200);
  hsWindowRegion2:=CreateEllipticRgn(80, 80, 200, 150);
  CombineRgn(hsWindowRegion, hsWindowRegion, hsWindowRegion2, RGN_DIFF);
  SetWindowRgn(Handle, hsWindowRegion, true);
end;

Мы уже знаем какую функцию нужно использовать для объединения регионов, но как же она действует и что же ей нужно указывать?

Вводятся следующие параметры:

Дескриптор региона назначения, Дескриптор первого региона источника, Дескриптор второго региона источника, Режим взаимодействия регионов источников.

В качестве режима мы указали константу RGN_DIFF, а использовать можем:

RGN_AND - Создает пересечение из двух смешанных областей, RGN_COPY - Создает копию области, идентифицированной дескриптором первой области источника, RGN_DIFF - Выводит части первой области источника, которые не пересекаются со второй, RGN_OR - Создает объединение двух смешанных областей, RGN_XOR - Создает объединение двух смешанных областей за исключением зоны перекрытия.

Римские в арабские и наоборот.

Автор: Gua WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Конвертация : Римские -> арабские ; Арабские->Римские
Зависимости:
Автор:       Gua, fbsdd@ukr.net, ICQ:141585495, Simferopol
Copyright:
Дата:        03 мая 2002 г.
***************************************************** }
const
  R: array[1..13] of string[2] =
  ('I', 'IV', 'V', 'IX', 'X', 'XL', 'L', 'XC', 'C', 'CD', 'D', 'CM', 'M');
  A: array[1..13] of Integer =
  (1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000);
  ..............
function ArabicToRoman(N: Integer): string; //Арабские в римские
var
  i: Integer;
begin
  Result := '';
  i := 13;
  while N > 0 do
  begin
    while A[i] > N do
      Dec(i);
    Result := Result + R[i];
    Dec(N, A[i]);
  end;
end;
function RomanToArabic(S: string): Integer; //Римские в арабские
var
  i, p: Integer;
begin
  Result := 0;
  i := 13;
  p := 1;
  while p <= Length(S) do
  begin
    while Copy(S, p, Length(R[i])) <> R[i] do
    begin
      Dec(i);
      if i = 0 then
        Exit;
    end;
    Result := Result + A[i];
    p := p + Length(R[i]);
  end;
end;

Определить корень слова (для поиска похожих слов).

Автор: ___Nikolay WEB-сайт: http://delphiworld.narod.ru
// Поиск по корню слова
function RootOfWord(s: string): string;
label
  start;
const
  sGlas = 'аеёиоуыэюяаеiои'; // With english letters
  sSoglas = 'бвгджзйклмнпрстфхцчшщъь';
  sCompletions1 = 'й ь s';
  sCompletions2 = 'ам ям ом ем ин ём ся ет ит ут ют ат ят ыв ив ев ан ян ов ев ог ег ир ер ых ок ющ ущ er ed';
  sCompletions3 = 'енн овл евл ённ анн ост ест';
  sAttachments1 = 'в с';
  sAttachments2 = 'на за ис из до по вы во со';
  sAttachments3 = 'при рас пре про под';
  sAttachments4 = 'пере';
var
  sResult: string;
  i, iCnt, iGlasCount, iCheckCount: integer;
begin
  sResult := AnsiLowerCase(Trim(s));
  iCheckCount := 0;
  start:
  // "ся"
  if Length(sResult) > 3 then
    if sResult[Length(sResult) - 1] + sResult[Length(sResult)] = 'ся' then
      Delete(sResult, Length(sResult) - 1, 2);
  (*  E N G L I S H  *)
  // "ing"
  if Length(sResult) > 4 then
    if sResult[Length(sResult) - 2] + sResult[Length(sResult) - 1] + sResult[Length(sResult)] = 'ing' then
      Delete(sResult, Length(sResult) - 2, 3);
  // --
  // Гласные
  if Length(sResult) > 3 then
  begin
    iGlasCount := 0;
    for i := Length(sResult) downto 1 do
      if Pos(sResult[i], sGlas) <> 0 then // Если последний символ - гласная
        inc(iGlasCount)
      else
        break;
    if iGlasCount <> 0 then
    begin
      iGlasCount := iGlasCount - 1;
      Delete(sResult, Length(sResult) - iGlasCount, iGlasCount + 1);
    end;
  end;
  // Окончания
  if Length(sResult) > 3 then
    if Pos(sResult[Length(sResult)], sCompletions1) <> 0 then
      Delete(sResult, Length(sResult), 1);
  // "ся"
  if Length(sResult) > 3 then
    if sResult[Length(sResult) - 1] + sResult[Length(sResult)] = 'ся' then
      Delete(sResult, Length(sResult) - 1, 2);
  if Length(sResult) > 3 then
    while Pos(sResult[Length(sResult) - 2] + sResult[Length(sResult) - 1] +
      sResult[Length(sResult)], sCompletions3) <> 0 do
    begin
      if Length(sResult) > 3 then
        Delete(sResult, Length(sResult) - 1, 3)
      else
        break;
    end;
  if Length(sResult) > 3 then
    while Pos(sResult[Length(sResult) - 1] + sResult[Length(sResult)], sCompletions2) <> 0 do
    begin
      if Length(sResult) > 3 then
        Delete(sResult, Length(sResult) - 1, 2)
      else
        break;
    end;
  // Гласные
  if Length(sResult) > 3 then
  begin
    iGlasCount := 0;
    for i := Length(sResult) downto 1 do
      if Pos(sResult[i], sGlas) <> 0 then // Если последний символ - гласная
        inc(iGlasCount)
      else
        break;
    if iGlasCount <> 0 then
    begin
      iGlasCount := iGlasCount - 1;
      Delete(sResult, Length(sResult) - iGlasCount, iGlasCount + 1);
    end;
  end;
  // Приставки
  iCnt := 4;
  if Length(sResult) > iCnt then
    if Pos(Copy(sResult, 1, iCnt), sAttachments4) <> 0 then
      Delete(sResult, 1, iCnt);
  iCnt := 3;
  if Length(sResult) > iCnt then
    if Pos(Copy(sResult, 1, iCnt), sAttachments3) <> 0 then
      Delete(sResult, 1, iCnt);
  iCnt := 2;
  if Length(sResult) > iCnt then
    if Pos(Copy(sResult, 1, iCnt), sAttachments2) <> 0 then
      Delete(sResult, 1, iCnt);
  iCnt := 1;
  if Length(sResult) > iCnt then
    if Pos(Copy(sResult, 1, iCnt), sAttachments1) <> 0 then
      Delete(sResult, 1, iCnt);
  inc(iCheckCount);
  if iCheckCount < 2 then
    goto start;
  Result := sResult;
end;

Повернуть 2D точку.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

const
   PIDiv180 = 0.017453292519943295769236907684886;
 procedure Rotate(RotAng: Double; x, y: Double; var Nx, Ny: Double);
 var
   SinVal: Double;
   CosVal: Double;
 begin
   RotAng := RotAng * PIDiv180;
   SinVal := Sin(RotAng);
   CosVal := Cos(RotAng);
   Nx := x * CosVal - y * SinVal;
   Ny := y * CosVal + x * SinVal;
 end;
 (* End Of Rotate Cartesian Point*)

Как повернуть Bitmap на любой угол.

Статьи по Delphi (014)…
const
  PixelMax = 32768;
type
  pPixelArray = ^TPixelArray;
  TPixelArray = array [0..PixelMax-1] of TRGBTriple;
procedure RotateBitmap_ads(SourceBitmap: TBitmap;
out DestBitmap: TBitmap; Center: TPoint; Angle: Double);
var
  cosRadians : Double;
  inX : Integer;
  inXOriginal : Integer;
  inXPrime : Integer;
  inXPrimeRotated : Integer;
  inY : Integer;
  inYOriginal : Integer;
  inYPrime : Integer;
  inYPrimeRotated : Integer;
  OriginalRow : pPixelArray;
  Radians : Double;
  RotatedRow : pPixelArray;
  sinRadians : Double;
begin
  DestBitmap.Width := SourceBitmap.Width;
  DestBitmap.Height := SourceBitmap.Height;
  DestBitmap.PixelFormat := pf24bit;
  Radians := -(Angle) * PI / 180;
  sinRadians := Sin(Radians);
  cosRadians := Cos(Radians);
  for inX := DestBitmap.Height-1 downto 0 do
  begin
    RotatedRow := DestBitmap.Scanline[inX];
    inXPrime := 2*(inX - Center.y) + 1;
    for inY := DestBitmap.Width-1 downto 0 do
    begin
      inYPrime := 2*(inY - Center.x) + 1;
      inYPrimeRotated := Round(inYPrime * CosRadians - inXPrime * sinRadians);
      inXPrimeRotated := Round(inYPrime * sinRadians + inXPrime * cosRadians);
      inYOriginal := (inYPrimeRotated - 1) div 2 + Center.x;
      inXOriginal := (inXPrimeRotated - 1) div 2 + Center.y;
      if (inYOriginal >= 0) and (inYOriginal <= SourceBitmap.Width-1) and
      (inXOriginal >= 0) and (inXOriginal <= SourceBitmap.Height-1) then
      begin
        OriginalRow := SourceBitmap.Scanline[inXOriginal];
        RotatedRow[inY] := OriginalRow[inYOriginal]
      end
      else
      begin
        RotatedRow[inY].rgbtBlue := 255;
        RotatedRow[inY].rgbtGreen := 0;
        RotatedRow[inY].rgbtRed := 0
      end;
    end;
  end;
end;
{Usage:}
procedure TForm1.Button1Click(Sender: TObject);
var
  Center : TPoint;
  Bitmap : TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Center.y := (Image.Height div 2)+20;
    Center.x := (Image.Width div 2)+0;
    RotateBitmap_ads(
    Image.Picture.Bitmap,
    Bitmap,
    Center,
    Angle);
    Angle := Angle + 15;
    Image2.Picture.Bitmap.Assign(Bitmap);
  finally
    Bitmap.Free;
  end;
end;

Как повернуть Bitmap на любой угол 2.

procedure RotateBitmap(Bitmap: TBitmap; Angle: Double; BackColor: TColor);
type TRGB = record
       B, G, R: Byte;
     end;
     pRGB = ^TRGB;
     pByteArray = ^TByteArray;
     TByteArray = array[0..32767] of Byte;
     TRectList = array [1..4] of TPoint;
var x, y, W, H, v1, v2: Integer;
    Dest, Src: pRGB;
    VertArray: array of pByteArray;
    Bmp: TBitmap;
  procedure SinCos(AngleRad: Double; var ASin, ACos: Double);
  begin
    ASin := Sin(AngleRad);
    ACos := Cos(AngleRad);
  end;
  function RotateRect(const Rect: TRect; const Center: TPoint; Angle: Double): TRectList;
  var DX, DY: Integer;
      SinAng, CosAng: Double;
    function RotPoint(PX, PY: Integer): TPoint;
    begin
      DX := PX - Center.x;
      DY := PY - Center.y;
      Result.x := Center.x + Round(DX * CosAng - DY * SinAng);
      Result.y := Center.y + Round(DX * SinAng + DY * CosAng);
    end;
  begin
    SinCos(Angle * (Pi / 180), SinAng, CosAng);
    Result[1] := RotPoint(Rect.Left, Rect.Top);
    Result[2] := RotPoint(Rect.Right, Rect.Top);
    Result[3] := RotPoint(Rect.Right, Rect.Bottom);
    Result[4] := RotPoint(Rect.Left, Rect.Bottom);
  end;
  function Min(A, B: Integer): Integer;
  begin
    if A < B then Result := A
             else Result := B;
  end;
  function Max(A, B: Integer): Integer;
  begin
    if A > B then Result := A
             else Result := B;
  end;
  function GetRLLimit(const RL: TRectList): TRect;
  begin
    Result.Left := Min(Min(RL[1].x, RL[2].x), Min(RL[3].x, RL[4].x));
    Result.Top := Min(Min(RL[1].y, RL[2].y), Min(RL[3].y, RL[4].y));
    Result.Right := Max(Max(RL[1].x, RL[2].x), Max(RL[3].x, RL[4].x));
    Result.Bottom := Max(Max(RL[1].y, RL[2].y), Max(RL[3].y, RL[4].y));
  end;
  procedure Rotate;
  var x, y, xr, yr, yp: Integer;
      ACos, ASin: Double;
      Lim: TRect;
  begin
    W := Bmp.Width;
    H := Bmp.Height;
    SinCos(-Angle * Pi/180, ASin, ACos);
    Lim := GetRLLimit(RotateRect(Rect(0, 0, Bmp.Width, Bmp.Height), Point(0, 0), Angle));
    Bitmap.Width := Lim.Right - Lim.Left;
    Bitmap.Height := Lim.Bottom - Lim.Top;
    Bitmap.Canvas.Brush.Color := BackColor;
    Bitmap.Canvas.FillRect(Rect(0, 0, Bitmap.Width, Bitmap.Height));
    for y := 0 to Bitmap.Height - 1 do begin
      Dest := Bitmap.ScanLine[y];
      yp := y + Lim.Top;
      for x := 0 to Bitmap.Width - 1 do begin
        xr := Round(((x + Lim.Left) * ACos) - (yp * ASin));
        yr := Round(((x + Lim.Left) * ASin) + (yp * ACos));
        if (xr > -1) and (xr < W) and (yr > -1) and (yr < H) then begin
          Src := Bmp.ScanLine[yr];
          Inc(Src, xr);
          Dest^ := Src^;
        end;
        Inc(Dest);
      end;
    end;
  end;
begin
  Bitmap.PixelFormat := pf24Bit;
  Bmp := TBitmap.Create;
  try
    Bmp.Assign(Bitmap);
    W := Bitmap.Width - 1;
    H := Bitmap.Height - 1;
    if Frac(Angle) <> 0.0
      then Rotate
      else
    case Trunc(Angle) of
      -360, 0, 360, 720: Exit;
      90, 270: begin
        Bitmap.Width := H + 1;
        Bitmap.Height := W + 1;
        SetLength(VertArray, H + 1);
        v1 := 0;
        v2 := 0;
        if Angle = 90.0 then v1 := H
                        else v2 := W;
        for y := 0 to H do VertArray[y] := Bmp.ScanLine[Abs(v1 - y)];
        for x := 0 to W do begin
          Dest := Bitmap.ScanLine[x];
          for y := 0 to H do begin
            v1 := Abs(v2 - x)*3;
            with Dest^ do begin
              B := VertArray[y, v1];
              G := VertArray[y, v1+1];
              R := VertArray[y, v1+2];
            end;
            Inc(Dest);
          end;
        end
      end;
      180: begin
        for y := 0 to H do begin
          Dest := Bitmap.ScanLine[y];
          Src := Bmp.ScanLine[H - y];
          Inc(Src, W);
          for x := 0 to W do begin
            Dest^ := Src^;
            Dec(Src);
            Inc(Dest);
          end;
        end;
      end;
      else Rotate;
    end;
  finally
    Bmp.Free;
  end;
end;
// Использование
RotateBitmap(Image1.Picture.Bitmap, StrToInt(Edit1.Text), clWhite);

Вращать Bitmap вокруг точки.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// Vektor von FromP nach ToP 
// Vector from FromP to ToP 
function TForm1.Vektor(FromP, Top: TPoint): TPoint;
 begin
   Result.x := Top.x - FromP.x;
   Result.y := Top.y - FromP.y;
 end;
 // neue x Komponente des Verktors 
// new x-component of the vector 
function TForm1.xComp(Vektor: TPoint; Angle: Extended): Integer;
 begin
   Result := Round(Vektor.x * cos(Angle) - (Vektor.y) * sin(Angle));
 end;
 // neue Y-Komponente des Vektors 
// new y-component of the vector 
function TForm1.yComp(Vektor: TPoint; Angle: Extended): Integer;
 begin
   Result := Round((Vektor.x) * (sin(Angle)) + (vektor.y) * cos(Angle));
 end;
 function TForm1.RotImage(srcbit: TBitmap; Angle: Extended; FPoint: TPoint;
   Background: TColor): TBitmap;
 { 
 srcbit: TBitmap; // Bitmap dass gedreht werden soll ; Bitmap to be rotated 
 Angle: extended; // Winkel in Bogenma?, angle 
 FPoint: TPoint;  // Punkt um den gedreht wird ; Point to be rotated around 
 Background: TColor): TBitmap;  // Hintergrundfarbe des neuen Bitmaps ; 
                               // Backgroundcolor of the new bitmap 
}
 var
    highest, lowest, mostleft, mostright: TPoint;
   topoverh, leftoverh: integer;
   x, y, newx, newy: integer;
 begin
   Result := TBitmap.Create;
   // Drehwinkel runterrechnen auf eine Umdrehung, wenn notig 
  // Calculate angle down on one rotation, if necessary 
  while Angle >= (2 * pi) do
   begin
     angle := Angle - (2 * pi);
   end;
   // neue Ausma?e festlegen 
  // specify new size 
  if (angle <= (pi / 2)) then
   begin
     highest := Point(0,0);                        //OL 
    Lowest := Point(Srcbit.Width, Srcbit.Height); //UR 
    mostleft := Point(0,Srcbit.Height);            //UL 
    mostright := Point(Srcbit.Width, 0);             //OR 
  end
    else if (angle <= pi) then
   begin
     highest := Point(0,Srcbit.Height);
     Lowest := Point(Srcbit.Width, 0);
     mostleft := Point(Srcbit.Width, Srcbit.Height);
     mostright := Point(0,0);
   end
    else if (Angle <= (pi * 3 / 2)) then
   begin
     highest := Point(Srcbit.Width, Srcbit.Height);
     Lowest := Point(0,0);
     mostleft := Point(Srcbit.Width, 0);
     mostright := Point(0,Srcbit.Height);
   end
    else
   begin
     highest := Point(Srcbit.Width, 0);
     Lowest := Point(0,Srcbit.Height);
     mostleft := Point(0,0);
     mostright := Point(Srcbit.Width, Srcbit.Height);
   end;
   topoverh := yComp(Vektor(FPoint, highest), Angle);
   leftoverh := xComp(Vektor(FPoint, mostleft), Angle);
   Result.Height := Abs(yComp(Vektor(FPoint, lowest), Angle)) + Abs(topOverh);
   Result.Width  := Abs(xComp(Vektor(FPoint, mostright), Angle)) + Abs(leftoverh);
   // Verschiebung des FPoint im neuen Bild gegenuber srcbit 
  // change of FPoint in the new picture in relation on srcbit 
  Topoverh := TopOverh + FPoint.y;
   Leftoverh := LeftOverh + FPoint.x;
   // erstmal mit Hintergrundfarbe fullen 
  // at first fill with background color 
  Result.Canvas.Brush.Color := Background;
   Result.Canvas.pen.Color   := background;
   Result.Canvas.Fillrect(Rect(0,0,Result.Width, Result.Height));
   // Start des eigentlichen Rotierens 
  // Start of actual rotation 
  for y := 0 to srcbit.Height - 1 do
   begin                       // Zeilen  ; Rows 
    for x := 0 to srcbit.Width - 1 do
     begin                    // Spalten ; Columns 
      newX := xComp(Vektor(FPoint, Point(x, y)), Angle);
       newY := yComp(Vektor(FPoint, Point(x, y)), Angle);
       newX := FPoint.x + newx - leftoverh;
       // Verschieben wegen der neuen Ausma?e 
      newy := FPoint.y + newy - topoverh;
       // Move beacause of new size 
      Result.Canvas.Pixels[newx, newy] := srcbit.Canvas.Pixels[x, y];
       // auch das Pixel daneben fullen um Leerpixel bei Drehungen zu verhindern 
      // also fil lthe pixel beside to prevent empty pixels 
      if ((angle < (pi / 2)) or
         ((angle > pi) and
         (angle < (pi * 3 / 2)))) then
       begin
         Result.Canvas.Pixels[newx, newy + 1] := srcbit.Canvas.Pixels[x, y];
       end
        else
        begin
         Result.Canvas.Pixels[newx + 1,newy] := srcbit.Canvas.Pixels[x, y];
       end;
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   mybitmap, newbit: TBitMap;
 begin
   if OpenDialog1.Execute then
   begin
     mybitmap := TBitmap.Create;
     mybitmap.LoadFromFile(OpenDialog1.FileName);
     newbit := RotImage(mybitmap, DegToRad(45),
       Point(mybitmap.Width div 2, mybitmap.Height div 2), clBlack);
     Image1.Canvas.Draw(0,0, newBit);
   end;
 end;
 end;

Переворачиваем рабочий стол.

Автор: William Egge

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

В примере используется TDesktopCanvas, который получить доступ к десктопу через объект TCanvas. Так же в примере используется TQuickPixel, который позволяет увеличить скорость доступа к пикселям.

Скачайте исходник, откомпилируйте его, и поместите программку в папку "Автозагрузка" на компьютере Вашего друга и смело идите по своим делам :-).

Для завершения работы программки достаточно кликнуть по перевёрнутому экрану.

А теперь давайте разберёмся с исходником:

Класс TQuickPixel был сделан для быстрого доступа к пикселям, чтобы не возиться со строками развёртки. Для увеличения производительности, данный класс кэширует строки развёртки. Единственный недостаток данного класса заключается в том, что он устанавливает Ваш Bitmap в 24 бита.

Ниже представлен собственно сам код TQuickPixel.

unit QuickPixel;
interface
uses
  Windows, Graphics;
type
  TQuickPixel = class
  private
    FBitmap: TBitmap;
    FScanLines: array of PRGBTriple;
    function GetPixel(X, Y: Integer): TColor;
    procedure SetPixel(X, Y: Integer; const Value: TColor);
    function GetHeight: Integer;
    function GetWidth: Integer;
  public
    constructor Create(const ABitmap: TBitmap);
    property Pixel[X, Y: Integer]: TColor read GetPixel write SetPixel;
    property Width: Integer read GetWidth;
    property Height: Integer read GetHeight;
  end;
implementation
{ TQuickPixel }
constructor TQuickPixel.Create(const ABitmap: TBitmap);
var
  I: Integer;
begin
  inherited Create;
  FBitmap:= ABitmap;
  FBitmap.PixelFormat:= pf24bit;
  SetLength(FScanLines, FBitmap.Height);
  for I:= 0 to FBitmap.Height-1 do
    FScanLines[I]:= FBitmap.ScanLine[I];
end;
function TQuickPixel.GetHeight: Integer;
begin
  Result:= FBitmap.Height;
end;
function TQuickPixel.GetPixel(X, Y: Integer): TColor;
var
  P: PRGBTriple;
begin
  P:= FScanLines[Y];
  Inc(P, X);
  Result:= (P^.rgbtBlue shl 16) or (P^.rgbtGreen shl 8) or P^.rgbtRed;
end;
function TQuickPixel.GetWidth: Integer;
begin
  Result:= FBitmap.Width;
end;
procedure TQuickPixel.SetPixel(X, Y: Integer; const Value: TColor);
var
  P: PRGBTriple;
begin
  P:= FScanLines[Y];
  Inc(P, X);
  P^.rgbtBlue:= (Value and $FF0000) shr 16;
  P^.rgbtGreen:= (Value and $00FF00) shr 8;
  P^.rgbtRed:= Value and $0000FF;
end;
end.

Ну, надеюсь, вы с ним разобрались, перейдём же к самому проекту. Свойство окна BorderStyle установите в bsNone, свойство FormStyle - в fsStayOnTop, а свойству WindowState задайте значение wsMaximized. Вынесите на форму компонент TImage, его свойство Align выставьте в alClient, по нажатию на TImage напишите:

Close;

Затем следующим образом опишите обработчик создания окна [событие OnCreate()]:

procedure TForm1.FormCreate(Sender: TObject);
var
  B: TBitmap;
  Desktop: TDesktopCanvas;
  QP: TQuickPixel;
  X, Y: Integer;
  EndCopyIndex: Integer;
  Temp: TColor;
begin
  Left:= 0;
  Top:= 0;
  Width:= Screen.Width;
  Height:= Screen.Height;
  B:= nil;
  Desktop:= nil;
  try
    Desktop:= TDesktopCanvas.Create;
    B:= TBitmap.Create;
    B.Width:= Screen.Width;
    B.Height:= Screen.Height;
    B.Canvas.CopyRect(Rect(0, 0, B.Width, B.Height),
    Desktop, Rect(0, 0, B.Width, B.Height));
    B.PixelFormat:= pf24bit;
    QP:= TQuickPixel.Create(B);
    try
      for Y:= 0 to (QP.Height div 2)-1 do
      begin
        EndCopyIndex:= (QP.Height-1)-Y;
        for X:= 0 to QP.Width-1 do
        begin
          Temp:= QP.Pixel[X, Y];
          QP.Pixel[X, Y]:= QP.Pixel[X, EndCopyIndex];
          QP.Pixel[X, EndCopyIndex]:= Temp;
        end;
      end;
    finally
      QP.Free;
    end;
    with Image1.Picture.Bitmap do
    begin
      Width:= Image1.Width;
      Height:= Image1.Height;
      Canvas.CopyRect(Rect(0, 0, Width, Height), B.Canvas,
      Rect(0, 0, Width, Height));
    end;
  finally
    B.Free;
    Desktop.Free;
  end;
end;

Проверьте, все ли модули у вас подключены. Раздел uses должен выглядеть так:

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, DesktopCanvas, QuickPixel;

Повернуть DIB-изображение.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function RotateBitmap(var hDIB: HGlobal; radang: Double; clrBack: TColor): Boolean;
 // (c) Copyright original C Code: Code Guru 
var
   lpDIBBits: Pointer;
   lpbi, hDIBResult: PBitmapInfoHeader;
   bpp, nColors, nWidth, nHeight, nRowBytes: Integer;
   cosine, sine: Double;
   x1, y1, x2, y2, x3, y3, minx, miny, maxx, maxy, ti, x, y, w, h: Integer;
   nResultRowBytes, nHeaderSize: Integer;
   i, len: longint;
   lpDIBBitsResult: Pointer;
   dwBackColor: DWORD;
   PtrClr: PRGBQuad;
   RbackClr, GBackClr, BBackClr: Word;
   sourcex, sourcey: Integer;
   mask: Byte;
   PtrByte: PByte;
   dwpixel: DWORD;
   PtrDWord: PDWord;
   hDIBResInfo: HGlobal;
 begin;
   // Get source bitmap info 
  lpbi := PBitmapInfoHeader(GlobalLock(hdIB));
   nHeaderSize := lpbi^.biSize + lpbi^.biClrUsed * SizeOf(TRGBQUAD);
   lpDIBBits := Pointer(Longint(lpbi) + nHeaderSize);
   bpp := lpbi^.biBitCount; // Bits per pixel 
  ncolors := lpbi^.biClrUsed; // Already computed when bitmap was loaded 
  nWidth := lpbi^.biWidth;
   nHeight := lpbi^.biHeight;
   nRowBytes := ((((nWidth * bpp) + 31) and (not 31)) shr 3);
   // Compute the cosine and sine only once 
  cosine := cos(radang);
   sine := sin(radang);
   // Compute dimensions of the resulting bitmap 
  // First get the coordinates of the 3 corners other than origin 
  x1 := ceil(-nHeight * sine); // Originally floor at all places 
  y1 := ceil(nHeight * cosine);
   x2 := ceil(nWidth * cosine - nHeight * sine);
   y2 := ceil(nHeight * cosine + nWidth * sine);
   x3 := ceil(nWidth * cosine);
   y3 := ceil(nWidth * sine);
   minx := min(0, min(x1, min(x2, x3)));
   miny := min(0, min(y1, min(y2, y3)));
   maxx := max(0, max(x1, max(x2, x3)));// added max(0, 
  maxy := max(0, max(y1, max(y2, y3)));// added max(0, 
  w := maxx - minx;
   h := maxy - miny;
   // Create a DIB to hold the result 
  nResultRowBytes := ((((w * bpp) + 31) and (not 31)) div 8);
   len := nResultRowBytes * h;
   hDIBResInfo := GlobalAlloc(GMEM_MOVEABLE, len + nHeaderSize);
   if hDIBResInfo = 0 then
   begin
     Result := False;
     Exit;
   end;
   hDIBResult := PBitmapInfoHeader(GlobalLock(hDIBResInfo));
   // Initialize the header information 
  CopyMemory(hDIBResult, lpbi, nHeaderSize);
   //BITMAPINFO &bmInfoResult = *(LPBITMAPINFO)hDIBResult ; 
  hDIBResult^.biWidth := w;
   hDIBResult^.biHeight := h;
   hDIBResult^.biSizeImage := len;
   lpDIBBitsResult := Pointer(Longint(hDIBResult) + nHeaderSize);
   // Get the back color value (index) 
  ZeroMemory(lpDIBBitsResult, len);
   case bpp of
     1:
       begin //Monochrome 
        if (clrBack = RGB(255, 255, 255)) then
           FillMemory(lpDIBBitsResult, len, $ff);
       end;
     4,
     8:
       begin //Search the color table 
        PtrClr := PRGBQuad(Longint(lpbi) + lpbi^.bisize);
         RBackClr := GetRValue(clrBack);
         GBackClr := GetGValue(clrBack);
         BBackClr := GetBValue(clrBack);
         for i := 0 to nColors - 1 do // Color table starts with index 0 
        begin
           if (PtrClr^.rgbBlue = BBackClr) and
             (PtrClr^.rgbGreen = GBackClr) and
             (PtrClr^.rgbRed = RBackClr) then
           begin
             if (bpp = 4) then //if(bpp==4) i = i | i<<4; 
              ti := i or (i shl 4)
             else
               ti := i;
             FillMemory(lpDIBBitsResult, ti, len);
             break;
           end;
           Inc(PtrClr);
         end;// If not match found the color remains black 
      end;
     16:
       begin
         (* When the Compression field is set to BI_BITFIELDS,
         Windows 95 supports
         only the following 16bpp color masks: A 5-5-5 16-bit image, where the blue mask
         is $001F, the green mask is $03E0, and the red mask is $7C00; and a 5-6-5
         16-bit image, where the blue mask is $001F, the green mask is $07E0,
         and the red mask is $F800. *)
         PtrClr := PRGBQuad(Longint(lpbi) + lpbi^.bisize);
         if (PtrClr^.rgbRed = $7c00) then // Check the Red mask 
        begin // Bitmap is RGB555 
          dwBackColor := ((GetRValue(clrBack) shr 3) shl 10) +
             ((GetRValue(clrBack) shr 3) shl 5) +
             (GetBValue(clrBack) shr 3);
         end
         else
         begin // Bitmap is RGB565 
          dwBackColor := ((GetRValue(clrBack) shr 3) shl 11) +
             ((GetRValue(clrBack) shr 2) shl 5) +
             (GetBValue(clrBack) shr 3);
         end;
       end;
     24,
     32:
       begin
         dwBackColor := ((GetRValue(clrBack)) shl 16) or
           ((GetGValue(clrBack)) shl 8) or
           ((GetBValue(clrBack)));
       end;
   end;
   // Now do the actual rotating - a pixel at a time 
  // Computing the destination point for each source point 
  // will leave a few pixels that do not get covered 
  // So we use a reverse transform - e.i. compute the source point 
  // for each destination point 
  for y := 0 to h - 1 do
   begin
     for x := 0 to w - 1 do
     begin
       sourcex := floor((x + minx) * cosine + (y + miny) * sine);
       sourcey := floor((y + miny) * cosine - (x + minx) * sine);
       if ((sourcex >= 0) and (sourcex < nWidth) and
         (sourcey >= 0) and (sourcey < nHeight)) then
       begin // Set the destination pixel 
        case bpp of
           1:
             begin //Monochrome 
              mask := PByte(Longint(lpDIBBits) +
                 nRowBytes * sourcey +
                 (sourcex div 8))^ and ($80 shr
                 (sourcex mod 8));
               if mask <> 0 then
                 mask := $80 shr (x mod 8);
               PtrByte  := PByte(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + (x div
                 8));
               PtrByte^ := PtrByte^ and (not ($80 shr (x mod
                 8)));
               PtrByte^ := PtrByte^ or mask;
             end;
           4:
             begin
               if ((sourcex and 1) <> 0) then
                 mask := $0f
               else
                 mask := $f0;
               mask := PByte(Longint(lpDIBBits) +
                 nRowBytes * sourcey +
                 (sourcex div 2))^ and mask;
               if ((sourcex and 1) <> (x and 1)) then
               begin
                 if (mask and $f0) <> 0 then
                   mask := (mask shr 4)
                 else
                   mask := (mask shl 4);
               end;
               PtrByte := PByte(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + (x div
                 2));
               if ((x and 1) <> 0) then
                 PtrByte^ := PtrByte^ and (not $0f)
               else
                 PtrByte^ := PtrByte^ and (not $f0);
               PtrByte^ := PtrByte^ or Mask;
             end;
           8:
             begin
               mask := PByte(Longint(lpDIBBits) +
                 nRowBytes * sourcey +
                 sourcex)^;
               PtrByte  := PByte(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + x);
               PtrByte^ := mask;
             end;
           16:
             begin
               dwPixel := PDWord(Longint(lpDIBBits) +
                 nRowBytes * sourcey +
                 sourcex * 2)^;
               PtrDword  := PDWord(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + x * 2);
               PtrDword^ := Word(dwpixel);
             end;
           24:
             begin
               dwPixel := PDWord(Longint(lpDIBBits) +
                 nRowBytes * sourcey +
                 sourcex * 3)^ and $ffffff;
               PtrDword  := PDWord(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + x * 3);
               PtrDword^ := PtrDword^ or dwPixel;
             end;
           32:
             begin
               dwPixel := PDWord(Longint(lpDIBBits) +
                 nRowBytes * sourcey +
                 sourcex * 4)^;
               PtrDword := PDWord(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + x * 4);
               PtrDword^ := dwpixel;
             end;
         end; // Case 
      end
       else
       begin
         // Draw the background color. The background color 
        // has already been drawn for 8 bits per pixel and less 
        case bpp of
           16:
             begin
               PtrDWord := PDWord(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + x * 2);
               PtrDword^ := Word(dwBackColor);
             end;
           24:
             begin
               PtrDWord := PDWord(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + x * 3);
               PtrDword^ := PtrDword^ or dwBackColor;
             end;
           32:
             begin
               PtrDWord := PDWord(Longint(lpDIBBitsResult) +
                 nResultRowBytes * y + x * 4);
               PtrDword^ := dwBackColor;
             end;
         end;
       end;
     end;
   end;
   GlobalUnLock(hDIBResInfo);
   GlobalUnLock(hDIB);
   GlobalFree(hDIB);
   hDIB := hDIBResInfo;
   Result := True;
 end;

Алгоритм поворота изображения.

Статьи по Delphi (014)…
Сколько в Гейтса тортом не кидай, Windows лучше не станет.

Вот алгоритм поворота изображения. Пусть:

O - это центр поворота, M - некая точка исходного изображения.

Для каждой точки M нужно найти угол alpha между отрезком OM и горизонталью и длину r отрезка OM. Теперь, чтобы повернуть изображение на угол beta, нужно каждой точке M присвоить цвет точки исходного изображения с координатами x,y, где.

x = xo + r * cos(alpha + beta)
y = yo + r * sin(alpha + beta)
xo,yo - центр поворота, r - длина отрезка OM

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

Эту программу можно сильно ускорить, если исходное изображение записать в массив и обращаться к реальной переменной, а не к свойству Canvas.Pixels.

uses
  Math;
procedure TForm1.Button1Click(Sender: TObject);
var
  bm, bm1: TBitMap;
  x, y: integer;
  r, a: single;
  xo, yo: integer;
  s, c: extended;
begin
  bm := TBitMap.Create;
  bm.LoadFromFile('ex.bmp');
  xo := bm.Width div 2;
  yo := bm.Height div 2;
  bm1 := TBitMap.Create;
  bm1.Width := bm.Width;
  bm1.Height := bm.Height;
  a := 0;
  repeat
    for y := 0 to bm.Height - 1 do
    begin
      for x := 0 to bm.Width - 1 do
      begin
        r := sqrt(sqr(x - xo) + sqr(y - yo));
        SinCos(a + arctan2((y - yo), (x - xo)), s, c);
        bm1.Canvas.Pixels[x,y] := bm.Canvas.Pixels[
        round(xo + r * c), round(yo + r * s)];
      end;
      Application.ProcessMessages;
    end;
    Form1.Canvas.Draw(xo, yo, bm1);
    a := a + 0.05;
    Application.ProcessMessages;
  until
    Form1.Tag <> 0;
  bm.Destroy;
  bm1.Destroy;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Form1.Tag := 1;
end;

Вращение объектов.

Автор: Айткулов Павел WEB-сайт: http://rax.ru/click?apg67108864.narod.ru/

Здесь я бы хотел рассказать не о том, как работать с DelphiX, OpenGL или Direct, а о том, как можно вращать многогранники с помощью простых действий: moveto и lineto.

Здесь рассмотрим пример вращения куба. Будем рисовать на Canvase (например Listbox). Сначала нарисуем врашающийся квадрат (точнее 2 квадрата и соединим их). Пусть q - угол поворота квадрата, который мы рисуем. Очевидно, что нам надо задать координаты вершин квадрата - a:array [1..5,1..2] of integer. 1..4+1 - количество вершин квадрата (почему +1 будет объяснено позже). 1..2 - координата по X и Y. Кто учился в школе, наверное помнит, что уравнение окружности: X^2+Y^2=R^2, кто хорошо учился в школе, возможно вспомнит уравнение эллипса: (X^2)/(a^2)+ (Y^2)/(b^2)=1. Но это нам не надо. Нам понадобится уравнение эллипса в полярных координатах: x=a*sin(t); y=a*cos(t);t=0..2*PI; (учащиеся университетов и институтов ликуют).

С помощью данного уравнения мы заполняем массив с координатами.

for i:=1 to 5 do
begin
  // координата по Х; q+i*pi/2 - угол поворота
  // i-той вершины квадрата.
  a[i,1]:=trunc(80*sin(q+i*pi/2));
  // координата по Y; знак минус - потому что координаты
  // считаются с верхнего левого угла
  a[i,1]:=trunc(-30*cos(q+i*pi/2));
end;

Сейчас будем рисовать квадрат:

for i:=1 to 4 do
begin
  moveto(100+a[i,1],50+a[i,2]); //Встаем на i-ую точку квадрата.
  lineto(100+a[i+1,1],50+a[i+1,2]); //Рисуем линию к i+1-ой точке.

Вот почему array[1..5,1..2], иначе - выход за границы. end;

Затем рисуем второй такой же квадрат, но пониже (или повыше). Соединяем линиями первый со вторым:

for i:=1 to 4 do
begin
  moveto(100+a[i,1],50+a[i,2]);
  lineto(100+a[i,1],130+a[i,2]);
end;

Осталось очистить Listbox, увеличить q и сделать сначала. Все!!!

Можно также скрывать невидимые линии - когда q находится в определенном интервале. Также можно поизвращаться: повернуть куб в другой плоскости - поворот осей(для тех, кто знает формулу).

Вот что у нас получилось:

Исходник Готовая программа

Такие же вращающиеся кубики (додекаэдр, икосаэдр и 3 других (не знаю названия)) находятся здесь.

Как вращать текст.

Статьи по Delphi (014)…
procedure TextOutAngle(x,y,aAngle,aSize: integer; txt: string);
var
  hFont, Fontold: integer;
  DC: hdc;
  Fontname: string;
begin
  if length(txt) = 0 then
    Exit;
  DC:= Screen.ActiveForm.Canvas.handle;
  SetBkMode(DC, transparent);
  Fontname:= Screen.ActiveForm.Canvas.Font.name;
  hFont:= CreateFont(-aSize,0, aAngle*10,0, fw_normal,0, 0,
  0,1,4,$10,2,4,PChar(Fontname));
  Fontold:= SelectObject(DC, hFont);
  TextOut(DC,x,y,PChar(txt), length(txt));
  SelectObject(DC, Fontold);
  DeleteObject(hFont);
end;

Как вращать текст 2.

{ Эта процедура устанавливает угол вывода текста
для указанного Canvas, угол в градусах
Шрифт должен быть TrueType }
procedure CanvasSetTextAngle(c: TCanvas; d: single);
var
  LogRec: TLOGFONT; { Информация о шрифте }
begin
  {Читаем текущюю инф. о шрифте }
  GetObject(c.Font.Handle,SizeOf(LogRec) ,Addr(LogRec) );
  { Изменяем угол }
  LogRec.lfEscapement := round(d*10);
  { Устанавливаем новые параметры }
  c.Font.Handle := CreateFontIndirect(LogRec);
end;

Как вращать текст 3.

Для того чтобы вывести текст под углом, вытянуть или сжать его нужно воспользоваться структурой LOGFONT. Здесь показаны не все ее возможности, но, на мой взгляд, самые интересные.

procedure TForm1.FormPaint(Sender: TObject);
var
  lf: TLogFont;
begin
  FillChar(lf, SizeOf(lf), 0);
  with lf do
  begin
    // Высота буквы
    lfHeight := 15;
    // Ширина буквы
    lfWidth := 20;
    // Угол наклона в десятых градуса
    lfEscapement := 100;
    // Жирность 0..1000, 0 - по умолчанию
    lfWeight := 1000;
    // Курсив
    lfItalic := 0;
    // Подчеркнут
    lfUnderline := 1;
    // Зачеркнут
    lfStrikeOut := 1;
    // CharSet
    lfCharSet := RUSSIAN_CharSet;
    // Название шрифта
    StrCopy(lfFaceName, 'Arial');
  end;
  with Form1.Canvas do
  begin
    FillRect(ClipRect);
    Font.Handle := CreateFontIndirect(lf);
    TextOut(0, 100, 'It is a text string');
  end;
end;

Рисование кнопок с кругленными краями.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit RVButton;
 interface
 uses
   SysUtils, Classes, Controls, Messages, Graphics, Windows;
 const
   iOffset = 3;
 type
   TRVButton = class(TGraphicControl)
   private
     FCaption : string;
     FButtonColor: TColor;
     FLButtonDown: boolean;
     FBtnPoints : array[1..2] of TPoint;
     FKRgn : HRgn;
     procedure SetCaption(Value: string);
     procedure SetButtonColor(Value: TColor);
     procedure FreeRegion;
   protected
     procedure Paint; override;
     procedure DrawCircle;
     procedure MoveButton;
     procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
     procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
   public
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
   published
     property ButtonColor: TColor read FButtonColor write SetButtonColor;
     property Caption: string read FCaption write SetCaption;
     property Enabled;
     property Font;
     property ParentFont;
     property ParentShowHint;
     property ShowHint;
     property Visible;
     property OnClick;
   end;
 procedure Register;
 implementation
 procedure Register;
 begin
   RegisterComponents('Samples', [TRVButton]);
 end;
 { TRVButton }
 constructor TRVButton.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   ControlStyle := [csClickEvents,csCaptureMouse];
   Width := 50;
   Height := 50;
   FButtonColor := clBtnFace;
   FKRgn := 0;
   FLButtonDown := False;
 end;
 destructor TRVButton.Destroy;
 begin
   if FKRgn <> 0 then FreeRegion;
   inherited Destroy;
 end;
 procedure TRVButton.DrawCircle;
 begin
   FBtnPoints[1] := Point(iOffset,iOffset);
   FBtnPoints[2] := Point(Width - iOffset,Height - iOffset);
   FKRgn := CreateEllipticRgn(FBtnPoints[1].x,FBtnPoints[1].y,FBtnPoints[2].x,FBtnPoints[2].y);
   Canvas.Brush.Color := FButtonColor;
   FillRgn(Canvas.Handle,FKRgn,Canvas.Brush.Handle);
   MoveButton;
 end;
 procedure TRVButton.FreeRegion;
 begin
   if FKRgn <> 0 then DeleteObject(FKRgn);
   FKRgn := 0;
 end;
 procedure TRVButton.MoveButton;
 var
   Color1: TColor;
   Color2: TColor;
 begin
   with Canvas do
     begin
     if not FLButtonDown then
       begin
       Color1 := clBtnHighlight;
       Color2 := clBtnShadow;
       end
     else
       begin
       Color1 := clBtnShadow;
       Color2 := clBtnHighLight;
       end;
       Pen.Width := 1;
       if FLButtonDown then Pen.Color := clBlack
       else                 Pen.Color := Color2;
       Ellipse(FBtnPoints[1].x - 2,FBtnPoints[1].y - 2,FBtnPoints[2].x + 2,FBtnPoints[2].y + 2);
       if not FLButtonDown then Pen.Width := 2
       else                     Pen.Width := 1;
       Pen.Color := Color1;
       Arc(FBtnPoints[1].x,FBtnPoints[1].y,FBtnPoints[2].x,FBtnPoints[2].y,
           FBtnPoints[2].x,FBtnPoints[1].y,FBtnPoints[1].x,FBtnPoints[2].y);
       Pen.Color := Color2;
       Arc(FBtnPoints[1].x,FBtnPoints[1].y,FBtnPoints[2].x,FBtnPoints[2].y,
           FBtnPoints[1].x,FBtnPoints[2].y,FBtnPoints[2].x,FBtnPoints[1].y);
       end;
   SetCaption('');
 end;
 procedure TRVButton.Paint;
 begin
   inherited Paint;
   FreeRegion;
   DrawCircle;
 end;
 procedure TRVButton.SetButtonColor(Value: TColor);
 begin
   if Value <> FButtonColor then
     begin
     FButtonColor := Value;
     Invalidate;
     end;
 end;
 procedure TRVButton.SetCaption(Value: string);
 var
   X: Integer;
   Y: Integer;
 begin
   if ((Value <> FCaption) and (Value <> '')) then
   begin
     FCaption := Value;
   end;
   with Canvas.Font do
   begin
     Name  := Font.Name;
     Size  := Font.Size;
     Style := Font.Style;
     if Self.Enabled then Color := Font.Color
     else
       Color := clDkGray;
   end;
   X := (Width div 2) - (Canvas.TextWidth(FCaption) div 2);
   Y := (Height div 2) - (Canvas.TextHeight(FCaption) div 2);
   Canvas.TextOut(X, Y, FCaption);
   //  Invalidate; 
end;
 procedure TRVButton.WMLButtonDown(var Message: TWMLButtonDown);
 begin
   if not PtInRegion(FKRgn,Message.xPos,Message.yPos) then exit;
   FLButtonDown := True;
   MoveButton;
   inherited;
 end;
 procedure TRVButton.WMLButtonUp(var Message: TWMLButtonUp);
 begin
   if not FLButtonDown then exit;
   FLButtonDown := False;
   MoveButton;
   inherited;
 end;
 end.

Элементы управления с закругленными краями.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure MakeRounded(Control: TWinControl);
 var
   R: TRect;
   Rgn: HRGN;
 begin
   with Control do
   begin
     R := ClientRect;
     rgn := CreateRoundRectRgn(R.Left, R.Top, R.Right, R.Bottom, 20, 20);
     Perform(EM_GETRECT, 0, lParam(@r));
     InflateRect(r, - 5, - 5);
     Perform(EM_SETRECTNP, 0, lParam(@r));
     SetWindowRgn(Handle, rgn, True);
     Invalidate;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   // TMemo: 
  Memo1.BorderStyle := bsNone;
   MakeRounded(Memo1);
   // TEdit: 
  Edit2.BorderStyle := bsNone;
   MakeRounded(Edit2);
   // TPanel: 
  MakeRounded(Panel1);
   // TStaticText: 
  MakeRounded(StaticText1);
   // TForm 
  Form1.BorderStyle := bsNone;
   MakeRounded(Form1);
 end;

Округление чисел до определенной степени.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function Rounder(var Value: Double; Decimals: Integer): Double;
 var
   j: Integer;
   A: Double;
 begin
   A := 1;
   case Decimals of
     0: A := 1;
     1: A := 10;
     else
       for j := 1 to Decimals do
         A := A * 10;
   end;
   Result := Int((Value * A) + 0.5) / A;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   Value: Double;
 begin
   Value := 23.56784;
   //Result is 23.57 
  label1.Caption := FloatToStr(Rounder(Value, 2));
 end;

Округление денежного значения.

Автор: Сергей WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Округление денежного значения
Округление денежного значения
Зависимости: ???
Автор:       Сергей, nfkazak@inbox.ru, Краснодар
Copyright:   VIP BANK
Дата:        11 сентября 2002 г.
***************************************************** }
function RoundCurrency(value: currency): currency;
var
  x, y: Double;
begin
  x := int(value * 100);
  y := Frac(value * 100);
  if y >= 0.5 then
    x := x + 1;
  result := x / 100;
end;

Округление дробного числа до N знаков после запятой.

Автор: Perceptron.

function RoundEx(chislo: double; Precision: Integer): string;
var
  ChisloInStr: string;
  ChisloInCurr: currency;
begin
  ChisloInCurr := chislo;
  Str(ChisloInCurr: 20: Precision, ChisloInStr);
  ChisloInStr[Pos('.', ChisloInStr)] := ',';
  RoundEx := Trim(ChisloInStr);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit1.Text := RoundEx(StrToFloat(Edit1.Text), 2);
end;

Округление до ближайшего кратного.

Автор: Dimka Maslov WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> «Округление» до ближайшего кратного
Функция возвращает ближайшее к Value число, которое без
остатка делится на N. Если Value находится посередине
между двумя кратными, функция вернёт большее значение.
Зависимости: нет
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        20 февраля 2003 г.
***************************************************** }
function RoundTo(Value, N: Integer): Integer;
asm
   push ebx
   mov ebx, eax
   mov ecx, edx
   cdq
   idiv ecx
   imul ecx
   add ecx, eax
   mov edx, ebx
   sub ebx, eax
   jg @@10
   neg ebx
@@10:
   sub edx, ecx
   jg @@20
   neg edx
@@20:
   cmp ebx, edx
   jl @@30
   mov eax, ecx
@@30:
   pop ebx
end;

Округление реальных типов.

Автор: Steve Schafer
Компьютер позволяет решать все те проблемы, которые до изобретения компьютера не существовали

Если вам необходимо округлить число, то наиболее общее решение выглядит следующим образом:

function RoundFloat(R: Extended; Decimals: Integer): Extended;
var
  Factor: Extended;
begin
  Factor := Int(Exp(Decimals * Ln(10)));
  Result := Round(Factor * R) / Factor;
end;

Округление времени.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function RoundTime(ADate: string; Rounding: Integer; bRound: Boolean): string;
 var
   Year, Month, Day, Hour, Min, Sec, MSec: Word;
   tmpDate: TDateTime;
   Res, Diff: string;
   M: integer;
 begin
   tmpDate := StrToDateTime(ADate);
   DecodeTime(tmpDate, Hour, Min, Sec, MSec);
   if (Rounding > 0) and (bRound = True) then
   begin
     if Min mod Rounding = 0 then
       Res := IntToStr(Min)
     else
       Res := IntToStr(Round(Min / Rounding) * Rounding);
     M := StrToInt(Copy(ADate, Length(ADate) - 1, 2));
     Diff := IntToStr(StrToInt(Res) - M);
     if Copy(Diff, 1, 1) = '-' then
     begin
       Diff   := Copy(Diff, 2, Length(Diff) - 1);
       Result := FormatDateTime('dd.mm.yy hh:mm', (tmpDate - StrToTime('00:00' + Diff)));
     end
     else
       Result := FormatDateTime('dd.mm.yy hh:mm', (tmpDate + StrToTime('00:00' + Diff)));
   end
   else
     Result := ADate;
 end;
 // Example: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   Edit1.Text := FormatDateTime('dd.mm.yy hh:mm', Now);
   Edit2.Text := RountTime(Edit1.Text, SpinEdit1.Value, Checkbox1.Checked);
   // Example: RoundTime('07.08.02 10:41', '15', True) -- > 07.08.02 10:45 
end;

Как показать округлённое окошко подсказки в Tray в Windows2000.

В Windows 2000, формат структуры NotifyIconData, которая используется для работы с иконками в Трее (которая, кстати, называется "The Taskbar Notification Area" :) значительно отличается от предыдущий версий Windows. Однако, эти изменения НЕ отражены в юните ShellAPI.pas в Delphi 5.

Итак, нам понадобится преобразованный SHELLAPI.H, в котором присутствуют все необходимые объявления:

uses Windows;
type
  NotifyIconData_50 = record // определенная в shellapi.h
    cbSize: DWORD;
    Wnd: HWND;
    uID: UINT;
    uFlags: UINT;
    uCallbackMessage: UINT;
    hIcon: HICON;
    szTip: array[0..MAXCHAR] of AnsiChar;
    dwState: DWORD;
    dwStateMask: DWORD;
    szInfo: array[0..MAXBYTE] of AnsiChar;
    uTimeout: UINT; // union with uVersion: UINT;
    szInfoTitle: array[0..63] of AnsiChar;
    dwInfoFlags: DWORD;
  end {record};
const
  NIF_INFO = $00000010;
  NIIF_NONE = $00000000;
  NIIF_INFO = $00000001;
  NIIF_WARNING = $00000002;
  NIIF_ERROR = $00000003;

А это набор вспомогательных типов:

type
  TBalloonTimeout = 10..30 {seconds};
  TBalloonIconType = (bitNone, // нет иконки
    bitInfo,    // информационная иконка (синяя)
    bitWarning, // иконка восклицания (ж¸лтая)
    bitError);  // иконка ошибки (краснаа)

Теперь мы готовы приступить к созданию округлённых подсказок! Для этого воспользуемся следующей функцией:

uses SysUtils, Windows, ShellAPI;
function DZBalloonTrayIcon(const Window: HWND; const IconID: Byte;
  const Timeout: TBalloonTimeout; const BalloonText, BalloonTitle:
  string; const BalloonIconType: TBalloonIconType): Boolean;
const
  aBalloonIconTypes: array[TBalloonIconType] of
    Byte = (NIIF_NONE, NIIF_INFO, NIIF_WARNING, NIIF_ERROR);
var
  NID_50: NotifyIconData_50;
begin
  FillChar(NID_50, SizeOf(NotifyIconData_50), 0);
  with NID_50 do begin
    cbSize := SizeOf(NotifyIconData_50);
    Wnd := Window;
    uID := IconID;
    uFlags := NIF_INFO;
    StrPCopy(szInfo, BalloonText);
    uTimeout := Timeout * 1000;
    StrPCopy(szInfoTitle, BalloonTitle);
    dwInfoFlags := aBalloonIconTypes[BalloonIconType];
  end; {with}
  Result := Shell_NotifyIcon(NIM_MODIFY, @NID_50);
end;
Вызывается она следующим образом:
DZBalloonTrayIcon(Form1.Handle, 1, 10, 
  'this is the balloon text', 'title', bitWarning);
Иконка, должна быть предварительно добавлена с темже дескриптором окна и IconID (в данном примере Form1.Handle и 1). Можете попробовать все три типа иконок внутри всплывающей подсказки.

P.S. На всякий случай, ниже представлены функции для добавление/удаления иконок в трее:

uses SysUtils, Windows, ShellAPI;
{добавление иконки}
function DZAddTrayIcon(const Window: HWND; const IconID: Byte; const Icon:
  HICON; const Hint: string = ''): Boolean;
var
  NID: NotifyIconData;
begin
  FillChar(NID, SizeOf(NotifyIconData), 0);
  with NID do begin
    cbSize := SizeOf(NotifyIconData);
    Wnd := Window;
    uID := IconID;
    if Hint = '' then begin
      uFlags := NIF_ICON;
    end {if} else begin
      uFlags := NIF_ICON or NIF_TIP;
      StrPCopy(szTip, Hint);
    end {else};
    hIcon := Icon;
  end {with};
  Result := Shell_NotifyIcon(NIM_ADD, @NID);
end;
{добавляет иконку с call-back сообщением}
function DZAddTrayIconMsg(const Window: HWND; const IconID: Byte;
  const Icon: HICON; const Msg: Cardinal; const Hint: string = ''): Boolean;
var
  NID: NotifyIconData;
begin
  FillChar(NID, SizeOf(NotifyIconData), 0);
  with NID do begin
    cbSize := SizeOf(NotifyIconData);
    Wnd := Window;
    uID := IconID;
    if Hint = '' then begin
      uFlags := NIF_ICON or NIF_MESSAGE;
    end {if} else begin
      uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
      StrPCopy(szTip, Hint);
    end {else};
    uCallbackMessage := Msg;
    hIcon := Icon;
  end {with};
  Result := Shell_NotifyIcon(NIM_ADD, @NID);
end;
{удаляет иконку}
function DZRemoveTrayIcon(const Window: HWND; const IconID: Byte): Boolean;
var
  NID: NotifyIconData;
begin
  FillChar(NID, SizeOf(NotifyIconData), 0);
  with NID do begin
    cbSize := SizeOf(NotifyIconData);
    Wnd := Window;
    uID := IconID;
  end {with};
  Result := Shell_NotifyIcon(NIM_DELETE, @NID);
end;

Несколько заключительных замечаний:

Нет необходимости использовать большую структуру NotifyIconData_50 для добавления или удаления иконок, старая добрая структура NotifyIconData прекрасно подойдёт для этого. Для callback сообщения можно использовать WM_APP + что-нибудь. Используя различные IconID, легко можно добавить несколько различных иконок из одного родительского окна и работать с ними по их IconID.

Как округлять до сотых в большую сторону.

Автор: Vit WEB-сайт: http://forum.vingrad.ru

uses Math;
// Прибавляешь 0.5 затем округляешь:
function RoundMax(Num: real; prec: integer): real;
begin
  result := roundto(num + Power(10, prec - 1) * 5, prec);
end;
// До сотых соответственно будет:
function RoundMax100(Num: real): real;
begin
  result := round(num * 100 + 0.5) / 100;
end;

Механизм вызова удаленных процедур - RPC.

Программист рассуждает: - Автостанция - место, где стоят автобусы. Лодочная станция - место, где стоят лодки. А у меня на столе - рабочая станция.

Программы, общающиеся через сеть, нуждаются в механизме связи. На нижнем уровне по поступлении пакетов подается сигнал, обрабатываемый сетевой программой обработки сигналов. На верхнем уровне работает механизм rendezvous (рандеву), принятый в языке Ада. В NFS используется механизм вызова удаленных процедур (RPC), в котором клиент взаимодействует с сервером (см. Рисунок 1). В соответствии с этим процессом клиент сначала обращается к процедуре, посылающей запрос на сервер. По прибытии пакета с запросом сервер вызывает процедуру его вскрытия, выполняет запрашиваемую услугу, посылает ответ, и управление возвращается клиенту.

Интерфейс RPC можно представить состоящим из трех уровней:

Верхний уровень полностью "прозрачен". Программа этого уровня может, например, содержать обращение к процедуре rnusers(), возвращающей число пользователей на удаленной машине. Вам не нужно знать об использовании механизма RPC, поскольку вы делаете обращение в программе. Средний уровень предназначен для наиболее общих приложений. RPC-вызовами на этом уровне занимаются подпрограммы registerrpc() и callrpc(): registerrpc() получает общесис темный код, а callrpc() исполняет вызов удаленной процедуры. Вызов rnusers() реализуется с помощью этих двух подпрограмм. Нижний уровень используется для более сложных задач, изменяющих умолчания на значения параметров процедур. На этом уровне вы можете явно манипулировать гнездами, используемыми для передачи RPC-сообщений.

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

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

RPC (Remote Procedure Call, Сервис вызова удаленных процедур) представляет собой интерфейс между удаленными пользователями и определенными программами хоста, которые запускаются по запросам этих пользователей. Сервис RPC какого-либо хоста, как правило, предоставляет клиентам комплекс программ. Каждая из таких программ состоит, в свою очередь, из одной или нескольких удаленных процедур. Например, сервис удаленной файловой системы NFS, который построен на вызовах RPC, может состоять только из двух программ: например, одна программа взаимодействует с высокоуровневыми пользовательскими интерфейсами, а другая - с низкоуровневыми функциями ввода-вывода.

В каждом вызове удаленной процедуры участвуют две стороны: активный клиент, который отправляет запрос вызова процедуры на сервер, и сервер, который отправляет клиенту ответ.

Примечание. Следует иметь в виду, что термины "клиент" и "сервер" в данном случае относятся к определенной транзакции Конкретный хост или программное обеспечение (процесс или программа) могут работать как в роли клиента, так и в роли сервера. Например, программа, которая обеспечивает работу сервиса удаленных процедур, в то же время может быть клиентом в работе с сетевой файловой системой.

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

В случае работы с удаленной процедурой, основное отличие состоит в том, что вызов удаленной функции обслуживают два процесса: клиентский процесс и серверный процесс.

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

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

Однако между вызовами локальных и удаленных процедур есть несколько важных отличий:

Обработка ошибок. Клиент в любом случае должен получать уведомление об ошибках, возникающих при вызовах удаленных процедур на сервере или в сети. Глобальные переменные. Поскольку сервер не имеет доступа к адресному пространству клиента, при вызовах удаленных процедур нельзя использовать скрытые параметры в виде глобальных переменных. Производительность. Скорость выполнения удаленных процедур, как правило на один или два порядка ниже скорости выполнения аналогичных локальных процедур. Аутентификация. Поскольку вызовы удаленных процедур происходят по сети, необходимо использовать механизмы аутентификации клиента.

Принципы построения протокола.

Протокол RPC может использовать несколько различных транспортных протоколов. В обязанности RPC-протокола входит только обеспечение стандартов и интерпретация передачи сообщений. Достоверность и надежность передачи сообщений целиком обеспечивается транспортным уровнем.

Однако RPC может контролировать выбор и некоторые функции транспортного протокола. В качестве примера взаимодействия между RPC и транспортным протоколом рассмотрим процедуру назначения RPC-порта работы прикладного процесса через RPC - Portmapper.

Эта функция динамически (по запросу) назначает соединению RPC определенный порт. Функция Portmapper используется довольно часто, поскольку набор зарезервированных для RPC транспортных портов ограничен, а количество процессов, которые потенциально могут одновременно работать очень высоко. Portmapper, например, вызывается при выборе портов взаимодействия клиента и сервера системы NFS.

Сервис Portmapper использует механизм широковещательных сообщений RPC на определенный порт - III. На этот порт клиент отправляет широковещательное сообщение запроса порта определенного сервиса RPC. Сервис Portmapper обрабатывает таксе сообщение, определяет адрес локального сервиса RPC и отправляет клиенту ответ. Сервис RPC Portmapper может работать как с TCP, так и с UDP-протоколами.

RPC может работать с различными транспортными протоколами, но никогда не дублирует их функции, т. е. если RPC работает поверх TCP, все заботы о надежности и достоверности соединения RPC возлагает на TCP. Однако, если протокол RPC установлен поверх UDP, он может обеспечивать дополнительные собственные функции обеспечения гарантированной доставки сообщений.

Примечание. Прикладные задачи могут рассматривать RPC-протокол как определенную процедуру вызова функции по сети JSR (Jump Subroutine Instruction).

Для работы RPC-протокола необходимо выполнение следующих условий:

Уникальная идентификации всех удаленно вызываемых процедур на данном хосте. RPC-запросы содержат три поля идентификаторов - номер удаленной программы (сервиса), номер версии удаленной программы и номер удаленной процедуры указанной программы. Номер программы назначается производителем сервиса, номер процедуры указывает на конкретную функцию данного сервиса Идентификация версии RPC-протокола. RPC-сообщения содержат поле версии RPC-протокола. Она используется для согласования форматов передаваемых параметров при работе клиента с различными версиями RPC. Предоставление механизмов аутентификации клиента на сервере. RPC-протокол обеспечивает процедуру аутентификации клиента в сервисе, и, в случае необходимости, при каждом запросе или отправке ответа клиенту. Кроме того, RPC позволяет использовать различные дополнительные механизмы безопасности.

RPC может использовать четыре типа механизмов аутентификации:

AUTH_NULL - без использования аутентификации AUTH_UNIX - аутентификация по стандарту UNIX AUTH_SHORT - аутентификация по стандарту UNIX с собственной структурой кодирования AUTH_DES - аутентификация по стандарту DES Идентификация сообщений ответа на соответствующие запросы. Ответные сообщения RPC содержат идентификатор запроса, на основании которого они были построены. Этот идентификатор можно назвать идентификатором транзакции вызова RPC. Данный механизм особенно необходим при работе в асинхронном режиме и при выполнении последовательности из нескольких RPC-вызовов. Идентификация ошибок работы протокола. Все сетевые или серверные ошибки имеют уникальные идентификаторы, по которым каждый из участников соединения может определить причину сбоя в работе.

Структуры сообщений протокола

При передаче RPC-сообщений поверх транспортного протокола, несколько RPC-сообщений могут располагаться внутри одного транспортного пакета. Для того чтобы отделять одно сообщение от другого, используется маркер записи (RM - Record Marker). Каждое RPC-сообщение "маркируется" ровно одним RM.

RPC-сообщение может состоять из нескольких фрагментов. Каждый фрагмент состоит из четырех байт заголовка и (от 0 до 2**31-1) данных. Первый бит заголовка указывает, является ли данный фрагмент последним, а остальные 31 бит указывают длину пакета данных.

Структура RPC формально описана на языке описания и представления форматов данных - XDR с дополнениями, касающимися описания процедур. Можно даже сказать, что язык описания RPC является расширением XDR, дополненным работой с процедурами.

Структура RPC-пакета выглядит следующим образом:

struct rpc_msg { 
unsigned int xid;
union switch (msg_type mtype) { 
case CALL:
call_body cbody; 
case REPLY:
reply body rbody; 
} body; 
};

Где.

xid - идентификатор текущей транзакции, call_body - пакет запроса, reply_body - пакет ответа.

Структура запроса выглядит примерно так:

struct call body { 
unsigned int rpcvers; 
unsigned int prog; 
unsigned int vers; 
unsigned int proc; 
opaque_auth cred; 
opaque_auth verf;
/* procedure parameters */
};

Структура ответа (reply_body) может содержать либо структуру, передаваемую в случае ошибки (тогда она содержит код ошибки), либо структуру успешной обработки запроса (тогда она содержит возвращаемые данные).

Программный интерфейс высокого уровня.

Использование подпрограмм в программе - традиционный способ структурировать задачу, сделать ее более ясной. Наиболее часто используемые подпрограммы собираются в библиотеки, где могут использоваться различными программами. В данном случае речь идет о локальном (местном) вызове, т. е. и вызывающий, и вызываемый объекты работают в рамках одной программы на одном компьютере.

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

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

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

Удаленный вызов процедуры включает следующие шаги:

Программа-клиент производит локальный вызов процедуры, называемой заглушкой (stub). При этом клиенту "кажется", что, вызывая заглушку, он производит собственно вызов процедуры-сервера. И действительно, клиент передает заглушке необходимые параметры, а она возвращает результат. Однако дело обстоит не совсем так, как это себе представляет клиент. Задача заглушки - принять аргументы, предназначаемые удаленной процедуре, возможно, преобразовать их в некий стандартный формат и сформировать сетевой запрос. Упаковка аргументов и создание сетевого запроса называется сборкой (marshalling). Сетевой запрос пересылается по сети на удаленную систему. Для этого в заглушке используются соответствующие вызовы, например, рассмотренные в предыдущих разделах. Заметим, что при этом могут быть использованы различные транспортные протоколы, причем не только семейства TCP/IP. На удаленном хосте все происходит в обратном порядке. Заглушка сервера ожидает запрос и при получении извлекает параметры - аргументы вызова процедуры. Извлечение (unmarshalling) может включать необходимые преобразования (например, изменения порядка расположения байтов). Заглушка выполняет вызов настоящей процедуры-сервера, которой адресован запрос клиента, передавая ей полученные по сети аргументы. После выполнения процедуры управление возвращается в заглушку сервера, передавая ей требуемые параметры. Как и заглушка клиента; заглушка сервера преобразует возвращенные процедурой значения, формируя сетевое сообщение-отклик, который передается по сети системе, от которой пришел запрос. Операционная система передает полученное сообщение заглушке клиента, которая, после необходимого преобразования, передает значения (являющиеся значениями, возвращенными удаленной процедурой) клиенту, воспринимающему это как нормальный возврат из процедуры.

Таким образом, с точки зрения клиента, он производит вызов удаленной процедуры, как он это сделал бы для локальной. То же самое можно сказать и о сервере: вызов процедуры происходит стандартным образом, некий объект (заглушка сервера) производит вызов локальной процедуры и получает возвращенные ею значения. Клиент воспринимает заглушку как вызываемую процедуру-сервер, а сервер принимает собственную заглушку за клиента.

Таким образом, заглушки составляют ядро системы RPC, отвечая за все аспекты формирования и передачи сообщений между клиентом и удаленным сервером (процедурой), хотя и клиент и сервер считают, что вызовы происходят локально. В этом-то и состоит основная концепция RPC - полностью спрятать распределенный (сетевой) характер взаимодействия в коде заглушек. Преимущества такого подхода очевидны: и клиент и сервер являются независимыми от сетевой реализации, оба они работают в рамках некой распределенной виртуальной машины, и вызовы процедур имеют стандартный интерфейс.

Передача параметров

Передача параметров-значений не вызывает особых трудностей. В этом случае заглушка клиента размещает значение параметра в сетевом запросе возможно, выполняя преобразования к стандартному виду (например, изменяя порядок следования байтов). Гораздо сложнее обстоит дело с передачей указателей, когда параметр представляет собой адрес данных, а не их значение. Передача в запросе адреса лишена смысла, так как удаленная процедура выполняется в совершенно другом адресном пространстве. Самым простым решением, применяемым в RPC, является запрет клиентам передавать параметры иначе, как по значению, хотя это, безусловно, накладывает серьезные ограничения.

Связывание (binding)

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

Нахождение удаленного хоста с требуемым сервером Нахождение требуемого серверного процесса на данном хосте

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

Каждая процедура RPC однозначно определяется номером программы и процедуры. Номер программы определяет группу удаленных процедур, каждая из которых имеет собственный номер. Каждой программе также присваивается номер версии, так что при внесении в программу незначительных изменений (например, при добавлении процедуры) отсутствует необходимость менять ее номер. Обычно несколько функционально сход-ных процедур реализуются в одном программном модуле, который при запуске становится сервером этих процедур, и который идентифицируется номером программы.

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

Для передачи запроса клиенту также необходимо знать сетевой адрес хоста и номер порта, связанный с программой-сервером, обеспечивающей требуемые процедуры. Для этого используется демон portmap(IM) (в некоторых системах он называется rpcbind(IM)). Демон запускается на хосте, который предоставляет сервис удаленных процедур, и использует общеизвестный номер порта. При инициализации процесса-сервера он регистрирует в portmap(IM) свои процедуры и номера портов. Теперь, когда клиенту требуется знать номер порта для вызова конкретной процедуры, он посылает запрос на сервер portmap(IM), который, в свою очередь, либо возвращает номер порта, либо перенаправляет запрос непосредственно серверу удаленной процедуры и после ее выполнения возвращает клиенту отклик. В любом случае, если требуемая процедура существует, клиент получает от сервера portmap(IM) номер порта процедуры, и дальнейшие запросы может делать уже непосредственно на этот порт.

Обработка особых ситуаций (exception)

Обработка особых ситуаций при вызове локальных процедур не представляет особой проблемы. UNIX обеспечивает обработку ошибок процессов таких как деление на ноль, обращение к недопустимой области памяти и т. д. В случае вызова удаленной процедуры вероятность возникновения ошибочных ситуаций увеличивается. К ошибкам сервера и заглушек добавляются ошибки, связанные, например, с получением ошибочного сетевого сообщения.

Например, при использовании UDP в качестве транспортного протокола производится повторная передача сообщений после определенного тайм-аута. Клиенту возвращается ошибка, если, спустя определенное число попыток, отклик от сервера так и не был получен. В случае, когда используется протокол TCP, клиенту возвращается ошибка, если сервер оборвал TCP-соединение.

Семантика вызова

Вызов локальной процедуры однозначно приводит к ее выполнению после чего управление возвращается в головную программу. Иначе дело обстоит при вызове удаленной процедуры. Невозможно установить, когда конкретно будет выполняться процедура, будет ли она выполнена вообще, а если будет, то какое число раз? Например, если запрос будет получен удаленной системой после аварийного завершения программы сервера, процедура не будет выполнена вообще. Если клиент при неполучении отклика после определенного промежутка времени (тайм-аута) повторно посылает запрос, то может создаться ситуация, когда отклик уже передается по сети, а повторный запрос вновь принимается на обработку удаленной процедурой. В этом случае процедура будет выполнена несколько раз.

Таким образом, выполнение удаленной процедуры можно характеризовать следующей семантикой:

Один и только один раз. Данного поведения (в некоторых случаях наиболее желательного) трудно требовать ввиду возможных аварий сервера. Максимум раз. Это означает, что процедура либо вообще не была выполнена, либо была выполнена только один раз. Подобное утверждение можно сделать при получении ошибки вместо нормального отклика. Хотя бы раз. Процедура наверняка была выполнена один раз, но возможно и больше. Для нормальной работы в такой ситуации удаленная процедура должна обладать свойством идемпотентности (от англ. idemponent). Этим свойством обладает процедура, многократное выполнение которой не вызывает кумулятивных изменений. Например, чтение файла идемпотентно, а добавление текста в файл - нет.

Представление данных

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

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

Например, формат представления данных в RPC фирмы Sun Microsystems следующий:

Порядок следования байтов - Старший - последний Представление значений с плавающей точкой - IEEE Представление символа - ASCII

Сеть

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

Программные реализации системы, как правило, поддерживают один или два протокола. Например, система RPC разработки фирмы Sun Microsystems поддерживает передачу сообщений с использованием протоколов TCP и UDP. Выбор того или иного протокола зависит от требований приложения. Выбор протокола UDP оправдан для приложений, обладающих следующими характеристиками:

Вызываемые процедуры идемпотентны Размер передаваемых аргументов и возвращаемого результата меньше размера пакета UDP - 8 Кбайт. Сервер обеспечивает работу с несколькими сотнями клиентов. Поскольку при работе с протоколами TCP сервер вынужден поддерживать соединение с каждым из активных клиентов, это занимает значительную часть его ресурсов. Протокол UDP в этом отношении является менее ресурсоемким

С другой стороны, TCP обеспечивает эффективную работу приложений со следующими характеристиками:

Приложению требуется надежный протокол передачи Вызываемые процедуры неидемпонентны Размер аргументов или возвращаемого результата превышает 8 Кбайт

Выбор протокола обычно остается за клиентом, и система по-разному организует формирование и передачу сообщений. Так, при использовании протокола TCP, для которого передаваемые данные представляют собой поток байтов, необходимо отделить сообщения друг от друга. Для этого например, применяется протокол маркировки записей, описанный в RFC1057 "RPC: Remote Procedure Call Protocol specification version 2", при котором в начале каждого сообщения помещается 32-разрядное целое число, определяющее размер сообщения в байтах.

По-разному обстоит дело и с семантикой вызова. Например, если RPC выполняется с использованием ненадежного транспортного протокола (UDP), система выполняет повторную передачу сообщения через короткие промежутки времени (тайм-ауты). Если приложение-клиент не получает отклик, то с уверенностью можно сказать, что процедура была выполнена ноль или большее число раз. Если отклик был получен, приложение может сделать вывод, что процедура была выполнена хотя бы однажды. При использовании надежного транспортного протокола (TCP) в случае получения отклика можно сказать, что процедура была выполнена один раз. Если же отклик не получен, определенно сказать, что процедура выполнена не была, нельзяЗ.

Как это работает?

По существу, собственно система RPC является встроенной в программу-клиент и программу-сервер. Отрадно, что при разработке распределенных приложений, не придется вникать в подробности протокола RPC или программировать обработку сообщений. Система предполагает существование соответствующей среды разработки, которая значительно облегчает жизнь создателям прикладного программного обеспечения. Одним из ключевых моментов в RPC является то, что разработка распределенного приложения начинается с определения интерфейса объекта - формального описания функций сервера, сделанного на специальном языке. На основании этого интерфейса затем автоматически создаются заглушки клиента и сервера. Единственное, что необходимо сделать после этого, - написать фактический код процедуры.

В качестве примера рассмотрим RPC фирмы Sun Microsystems. Система состоит из трех основных частей:

rpcgen(1) - RPC-компилятор, который на основании описания интерфейса удаленной процедуры генерирует заглушки клиента и сервера в виде программ на языке С. Библиотека XDR (eXternal Data Representation), которая содержит функции для преобразования различных типов данных в машинно-независимый вид, позволяющий производить обмен информацией между разнородными системами. Библиотека модулей, обеспечивающих работу системы в целом.

Рассмотрим пример простейшего распределенного приложения для ведения журнала событий. Клиент при запуске вызывает удаленную процедуру записи сообщения в файл журнала удаленного компьютера.

Для этого придется создать как минимум три файла: спецификацию интерфейсов удаленных процедур log.x (на языке описания интерфейса), собственно текст удаленных процедур log.c и текст головной программы клиента main ( ) - client.c (на языке С) .

Компилятор rpcgen(l) на основании спецификации log.x создает три файла: текст заглушек клиента и сервера на языке С (log clnt.c и log svc.c) и файл описаний log.h, используемый обеими заглушками.

Итак, рассмотрим исходные тексты программ.

log.x

В этом файле указываются регистрационные параметры удаленной процедуры - номера программы, версии и процедуры, а также определяется интерфейс вызова - входные аргументы и возвращаемые значения. Таким образом, определена процедура RLOG, в качестве аргумента принимающая строку (которая будет записана в журнал), а возвращаемое значение стандартно указывает на успешное или неудачное выполнение заказанной операции.

program LOG_PROG {
version LOG_VER {
int RLOG (string) = 1;
} = 1;
} = 0хЗ1234567;

Компилятор rpcgen(l) создает файл заголовков log.h, где, в частности, определены процедуры:

log.h

/*
Please do not edit this file. 
It was generated using rpcgen. 
*/
#ifndef _LOG_H_RPCGEN 
#define _LOG_H_RPCGEN 
#include <rpc/rpc.>
/* Номер программы*/
#define LOG_PROG ((unsigned long) (0хЗ1234567)) 
#define LOG_VER ((unsigned long) (1)) /*Номер версии*/ 
#define RLOG ((unsigned long) (1)) /*Номер процедуры*/ 
extern int *rlog_l () ;
/*Внутренняя процедура - нам ее использовать не придется*/ 
extern int log_prog_l_freeresult(); 
#endif /* !_LOG_H_RPCGEN */

Рассмотрим этот файл внимательно. Компилятор транслирует имя RLOG определенное в файле описания интерфейса, в rlog_1, заменяя прописные символы на строчные и добавляя номер версии программы с подчеркиванием. Тип возвращаемого значения изменился с int на int *. Таково правило - RPC позволяет передавать и получать только адреса объявленных при описании интерфейса параметров. Это же правило касается и передаваемой в качестве аргумента строки. Хотя из файла print.h это не следует, на самом деле в качестве аргумента функции rlog_l ( ) также передается адрес строки.

Помимо файла заголовков компилятор rpcgen(l) создает модули заглушки клиента и заглушки сервера. По существу, в тексте этих файлов заключен весь код удаленного вызова.

Заглушка сервера является головной программой, обрабатывающей все сетевое взаимодействие с клиентом (точнее, с его заглушкой). Для выполнения операции заглушка сервера производит локальный вызов функции, текст которой необходимо написать:

log.c

#include <rpc/rpc.>
#include <sys/types.>
#include <sys/stat.>
#include "log.h" 
int *rlog_1 (char **arg) 
{
/*Возвращаемое значение должно определяться как static*/ 
static int result;
int fd; /*Файловый дескриптор журнала*/ 
int len;
result = 1;
/*0ткроем файл журнала (создадим, если он не существует), 
в случае неудачи вернем код ошибки result == 1.*/ 
if ((fd=open( "./server .log", 
O_CREAT | O_RDWR | O_APPEND)) return (&result);
len = strlen(*arg); 
if (write(fd, *arg, strlen(*arg)) != len)
result = 1; 
else
result = 0; 
close (fd); 
return(&result); /*Возвращаем результат - адрес result*/
}

Заглушка клиента принимает аргумент, передаваемый удаленной процедуре, делает необходимые преобразования, формирует запрос на сервер portmap(1M), обменивается данными с сервером удаленной процедуры и, наконец, передает возвращаемое значение клиенту. Для клиента вызов удаленной процедуры сводится к вызову заглушки и ничем не отличается от обычного локального вызова.

Client.c.

#include <rpc/rpc.>
#include "log.h" 
main(int argc, char *argv[])
{
CLIENT *cl;
char *server, *mystring, *clnttime; 
time_t bintime; 
int *result;
if (argc != 2) { 
fprintf(stderr, "Формат вызова: %s Адрес_хоста\n",
argv [0]); 
exit (1) ;
}
server = argv [1];
/*Получим дескриптор клиента. В случае неудачи - сообщим о 
невозможности установления связи с сервером*/
if ( (с1 = clnt_create (server, 
LOG_PROG, LOG_VER, "udp")) == NULL) {
clnt_pcreateerror (server); 
exit (2); 
}
/*Выделим буфер для строки*/
mystring = (char * )malloc (100); 
/*Определим время события*/ 
bintime = time ((time_t * ) NULL); 
clnttime = ctime(&bintime);
sprintf (mystring, "%s - Клиент запущен", clnttime);
/*Передадим сообщение для журнала - время начала работы клиента. 
В случае неудачи - сообщим об ошибке*/ 
if ( (result = rlog_l(&mystring, cl)) == NULL) {
fprintf(stderr, "error2\n"); 
clnt_perror(cl, server); 
exit(3); 
}
/*B случае неудачи на удаленном компьютере сообщим об ошибке*/ 
if (*result !=0 )
fprintf(stderr, "Ошибка записи в журнал\n"); 
/*0свободим дескриптор*/ 
cint destroy(cl);
exit (0);
}

Заглушка клиента log_clnt.c компилируется с модулем client.c для получения исполняемой программы клиента.

cc -о rlog client.c log_clnt.c -Insl

Заглушка сервера log_svc.c и процедура log.c компилируются для получения исполняемой программы сервера.

cc -о logger log_svc.c log.c -Insl

Теперь на некотором хосте server.nowhere.ru необходимо запустить серверный процесс:

$ logger

После чего при запуске клиента rlog на другой машине сервер добавит соответствующую запись в файл журнала.

Схема работы RPC в этом случае приведена на рис. 1. Модули взаимодействуют следующим образом:

Когда запускается серверный процесс, он создает сокет UDP и связывает любой локальный порт с этим сокетом. Далее сервер вызывает библиотечную функцию svc_register(3N) для регистрации номеров программы и ее версии. Для этого функция обращается к процессу portmap(IM) и передает требуемые значения. Сервер portmap(IM) обычно запускается при инициализации системы и связывается с некоторым общеизвестным портом. Теперь portmap(3N) знает номер порта для нашей программы и версии. Сервер же ожидает получения запроса. Заметим, что все описанные действия производятся заглушкой сервера, созданной компилятором rpcgen(IM). Когда запускается программа rlog, первое, что она делает, - вызывает библиотечную функцию clnt_create(3N), указывая ей адрес удаленной системы, номера программы и версии, а также транспортный протокол. Функция направляет запрос к серверу portmap(IM) удаленной системы server.nowhere.m и получает номер удаленного порта для сервера журнала. Клиент вызывает процедуру rlog_1 ( ) , определенную в заглушке клиента, и передает управление заглушке. Та, в свою очередь, формирует запрос (преобразуя аргументы в формат XDR) в виде пакета UDP и направляет его на удаленный порт, полученный от сервера portmap(IM). Затем она некоторое время ожидает отклика и в случае неполучения повторно отправляет запрос. При благоприятных обстоятельствах запрос принимается сервером logger (модулем заглушки сервера). Заглушка определяет, какая именно функция была вызвана (по номеру процедуры), и вызывает функцию rlog_1 ( ) модуля log.c. После возврата управления обратно в заглушку последняя преобразует возвращенное функцией rlog_1 ( ) значение в формат XDR, и формирует отклик также в виде пакета UDP. После получения отклика заглушка клиента извлекает возвращенное значение, преобразует его и возвращает в головную программу клиента

COM технология.

Оформил: DeeCo

Из цикла "COM vs. CORBA" Автор: Чистяков В.Ю. Источник: «Технология Клиент-Сервер»
Что такое COM COM-объект CoClass СОМ-интерфейс Экземпляр СОМ-объекта Инициализация и сохранение объектов Сервер и клиент Подсчет ссылок Библиотеки Создание простого COM-объекта Установление связи с удаленным объектом Создание экземпляра объекта и вызов метода Регистрация компонентов в распределенном приложении Управление объектами Агрегация Делегация Управление временем жизни экземпляров COM-объектов Количество экземпляров объектов Экземпляры и EXE-серверы Использование нестандартных типов данных в методах удаленного объекта Два пути Основные возможности низкоуровневого МIDL/С++ варианта Высокоуровневый вариант (Automation/VB) Статическое и динамическое связывание Обработка ошибок HRESULT Возврат информации об ошибках Получение информации об ошибках Метаданные объектов Управление потоками Масштабируемость Организация пула объектов. Just-in-Time (JIT) -активация Shared Property Manager Обеспечение безопасности Безопасность в COM+ Маскировка (Cloaking) Безопасность в Windows 95 и Windows 98 Устойчивость к сбоям Context Транзакции Compensating Resource Manager Использование кластеров серверов Асинхронное взаимодействие между объектами Queued Components COM+ Events Асинхронные вызовы

CORBA – это концепция, разрабатываемая от стандарта к реализации большим количеством организаций. СОМ – частная концепция Microsoft, исходящая из потребностей прикладного программиста и оформляемая в законченный стандарт. По существу, COM является стандартом "де-факто", изложенным корпорацией Microsoft в нескольких спецификациях.

CORBA – это технология, базирующаяся на спецификациях, создаваемых путем всенародного обсуждения и выливающихся в формальный бумажный стандарт, на базе которого делаются физические реализации. CORBA предназначена для стандартизации одной области программирования – общих принципов построения брокеров объектных запросов, что подразумевает наличие неких серверов и технологий взаимодействия с ними.

СОМ – общая технология взаимодействия объектов стандартизующая как сами объекты, так и методы их взаимодействия. Это спецификация, строящаяся на базе эталонных реализаций.

И то, и другое – стандарты. Оба они, как стандарты, переносимы и независимы от платформы. Преимущество стандарта Microsoft в том, что он создается одной мощной компанией, являющейся крупнейшим игроком на конкурентном рынке программного обеспечения. Мiсгоsоft старается сделать универсальной и переносимой реализацию, выполненную для конкретных нужд. Для этого Microsoft использует другие открытые стандарты и участвует в работе стандартообразующих организаций, в том числе OMG. Следствием является то, что эталонная реализация сама по себе становится спецификацией. OMG принципиально не располагает такой возможностью. Отсутствие эталонных реализаций CORBA приводит к тому, что для физической совместимости ORB разных производителей необходимо производить отладку и вносить специфический код для каждого из них. СОМ же, позволяя производить отладку на эталонной реализации, гарантирует совместимость приложений разных разработчиков. Если бы OMG кроме стандарта создавала эталонную реализацию, Microsoft проигрывал бы по всем статьям. Но поскольку это не так, подход Microsoft производительнее и создает меньше конфликтных ситуаций.

При сравнении CORBA и COM на память приходит ситуация с IBM PC образца первой половины восьмидесятых. Большая и известная IBM, решив выйти на новый рынок, создала реализацию персонального компьютера. Это не было стандартом, это даже не воспринималось всерьез. Многие конкуренты в то время считали эту реализацию избыточной и обвиняли IBM в переносе своих суперкомпьютерных привычек на устройство, предназначенной для решения примитивных задач. В сравнении с серьезными по тем временам (например, UNIX-совместимыми) компьютерными платформами, IBM РС не представляла интереса в силу явной маломощности. Фирмы производители таких систем громко заявляли, что РС никогда не сможет решать серьезных задач. Но, поскольку IBM располагала огромными ресурсами и авторитетом, эта модель персонального компьютера быстро завоевала рынок, а открытость архитектуры породила огромное количество клонов и привела к тому, что IBM РС стала стандартом "де-факто". Забавно, что теперь появляются стандарты на физическую реализацию РС, например, РС-99, не имеющие никакого отношения к IBM, а выдвигаемые Microsoft, монополистом в области ОС.

Очень похожая ситуация складывается с COM. COM был создан в процессе разработки технологии OLE 2. Но замах при создании COM был куда больше, чем требовалось для спецификации OLE 2. COM разрабатывался, как архитектура взаиможействия между объектами, причем заранее предполагалось, что взаимодействующие объекты могут находиться как в одном процессе, так и в разных. Для первой стадии, OLE 2, достаточно было взаимодействия процессов на одном компьютере. Но COM изначально не был ограничен рамками одного компьютера. Идеология с самого начала строилась так, чтобы в дальнейшем гладко перейти к сетевому взаимодействию. Именно в этом, то есть в исходно локальной природе COM, и пытаются упрекнуть Microsoft его конкуренты. Эти заявления основаны на реальных фактах, но аппелируют к фактам, не относящимся к обсуждаемой проблеме. По большому счету, это демагогия. Посмотрите, насколько эти заявления похожи на обвинения, звучавшие в адрес IBM.

Очень часто в споре о превосходстве той или иной технологии начинаются безосновательные заявления, а зачастую и попыткам необоснованно унизить конкурирующую технологию и их создателей. Основным недостатком, приписываемым COM, обычно является его Мiсrоsоft'овское происхождение (в стиле Маздай95). Это простительно для компьютерной прессы, но неприемлемо для технологического диспута, который должен демонстрировать преимущества и недостатки каждой из технологий, как концептуальные, так и технические. Что же касается Microsoft, то, конечно, Microsoft близок к положению монополиста – но на уровне ОС и офисных приложений, а не в объектных технологиях. Здесь у него есть несколько серьезных конкурентов, в число которых, кстати, не входит OMG, но входят некоторые его участники (например, Sun).

То, что на рынке компонентных технологий распределенных вычислений имеется конкуренция, в нашем случае – COM и CORBA, приводит к повышению качества продуктов, что нам с вами исключительно на руку.

Конкуренция производителей CORBA-систем никак не влияет на качество самой спецификации. В это же время конкуренция COM, EJB и CORBA через рыночные механизмы приводит к тому, что Microsoft и Sun, как игроки конкурентного рынка, вынуждены совершенствовать свои технологии и форсировать выпуск своих продуктов на рынок. Если еще раз вспомнить ситуацию с IBM РС, то сейчас мы находимся где-то на уровне поколения 386 процессора. Именно тогда Compaq выпустил компьютер на базе этого процессора раньше, чем IBM. Соответственно, именно сейчас у Microsoft есть возможность либо сделать COM открытым стандартом, либо монополизировать его окончательно и бесповоротно. К счастью, сейчас события развиваются по первому сценарию. Создана организация OpenGroup, занимающаяся вопросами стандартизации COM и переносом его на другие платформы. Стоит заметить, что многие компании одновременно входят и в OMG, и в Open Group.

Многие (например, автор CORBA-раздела этой публикации) глубоко убеждены, что COM и CORBA – диаметрально противоположные вещи. Простой взгляд на реализации этих технологий показывает, что они очень похожи, предназначены для решения одних и тех же задач, и, более того, иногда для реализации поддержки одной технологии используется другая. Так, например, поддержка CORBA в Delphi реализована на базе COM, то есть, базовый CORBA-класс реализует интерфейс IDispatch со всеми вытекающими из этого последствиями. При этом CORBA-интерфейс по существу становится и COM-интерфейсом тоже. В тех же Delphi и C++ Builder существует замечательное средство Midas, позволяющее работать с БД в мноугоровневой среде. Хотя это средство и базируется на COM, транспортом для передачи курсоров по сети может служить метод любого CORBA-интерфейса. При этом данные из SAFEARRAY помещаются в тип Any из CORBA.

Стандарт CORBA 3 во многом описывает вещи, уже присутствующие в COM, например, поддержку нескольких интерфейсов одним объектом. Судя по произведению Inprise (VisiBrocker), одинаковы у них и скоростные характеристики. Так что в COM и CORBA больше похожего, чем различий.

Что такое COM

COM является платформно-независимой, объектно-ориентированной технологией, позволяющей создавать бинарные компоненты. Эти компоненты можно использовать как локально, так и в распределенном сетевом окружении. COM служит основой для: OLE (технология составных документов), ActiveX-объектов и элементов управления ActiveX, DCOM, COM+.

На базе COM создано большинство новейших продуктов (MS Office, MTS, …) и технологий Windows (Automation, Drag & Drop, ...).

Целью этой статьи является сравнение COM и Corba, и, так как Corba на сегодня является технологией используемой, в основном, для организации распределенных вычислений, то и COM мы будем рассматривать с тех же позиций. Поэтому по умолчанию под «COM» будет подразумеваться DCOM и COM+.

DCOM (Distributed COM) – это расширение COM, делающее эту модель распределенной, то есть позволяющей вызывать COM-объекты, находящиеся на другом компьютере в сети.

MTS (Microsoft Transaction Server) – это сервер приложений позволяющий создавать распределенные приложения, поддерживающие транзакции. Вопреки распространенному мнению, хотя слово «транзакция» входит в название этого сервера, приложения создаваемые на базе MTS совершенно не обязаны использовать предоставляемый механизм транзакций.

COM+ – это эволюция COM и MTS. COM+ полностью встроен в Windows 2000. Он существенно расширяет возможности своих предшественников. COM+ обратно совместим с DCOM, MTS и COM, и позволяет создавать распределенные приложения, клиентские части которых можно запускать на старых ОС (Windows 9x и Windows NT).

COM – это технология, позволяющая объектам взаимодействовать, несмотря на границы процесса или машины, так же легко, как и объектам внутри одного процесса. COM обеспечивает такое взаимодействие, определяя, что единственный путь управления данными, ассоциированными с объектом, лежит через интерфейс объекта. Термин «интерфейс», о котором речь пойдет чуть ниже, означает реализацию в коде COM-совместимого двоичного интерфейса, ассоциированного с объектом.

COM-объект

COM-объект можно сравнить с объектом в понимании С++, VB или Java. Объект СОМ – это некоторая сущность, имеющая состояние и методы доступа, позволяющие изменять это состояние. СОМ-объекты можно создавать прямым вызовом специальных функций, но напрямую уничтожить его невозможно. Вместо прямого уничтожения используется механизм самоуничтожения, основанный на подсчете ссылок. Самым близким аналогом в объектно-ориентированных языках программирования является понятие объекта в языке Java.

Так, в COM присутствует понятие класса. Класс в COM носит название CoClass.

CoClass

CoClass – это класс, поддерживающий набор методов и свойств (один или более), с помощью которых можно взаимодействовать с объектами этого класса. Такой набор методов и свойств называется интерфейсом (Interface).

Каждый CoClass имеет два идентификатора – один из них, текстовый, называется ProgID и предназначен для человека, а второй, бинарный, называется CLSID.

CLSID является глобально уникальным идентификатором (GUID). GUID имеет размер 128 бит и уникален в пространстве и времени. Его уникальность достигается путем внедрения в него информации об уникальных частях компьютера, на котором он был создан, таких, как номер сетевой карты, и времени создания с точностью до миллисекунд. Эта технология, как и большинство других базовых концепций в СОМ, позаимствована из OSF DCE RPC. С помощью CLSID можно точно указать, какой именно объект требуется. Тип данных GUID применяется и для идентификации COM-интерфейсов. В этом случае он называется IID. Сгенерировать новое значение типа GUID можно с помощью API-функции Win32 CoCreateGuid. На практике использовать эту функцию приходится не часто, так как современные средства разработки полностью автоматизируют эту задачу, а VB вообще скрывает от программиста такие тонкости, как работу с CLSID и IID.

Для создания экземпляра объекта используется CLSID. Если имеется только ProgID CoClass’а или CLSID в строковом виде ("{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} ", где X – шестнадцатеричная цифра), то CLSID можно получить, вызвав функцию CLSIDFromString. Для случая с ProgID информация о CoClass’е должна содержаться в реестре машины, на которой производится вызов функции. В реестр информация заносится автоматически при регистрации объекта (во время процедуры инсталляции или при компиляции).

Перевести CLSID, IID или любой другой GUID в строку можно с помощью функции StringFromGUID2. Как уже говорилось выше, практически все необходимые GUID генерируются автоматически, но при необходимости можно сгенерировать GUID вручную, с помощью утилиты guidgen.

Программист никогда не взаимодействует с объектом и его данными напрямую. Для этого используются интерфейсы объектов.

СОМ-интерфейс

COM проводит фундаментальное различие между определением интерфейса и его реализацией. Это свойство СОМ аналогично подходам, принятым в OSF DCE RPC и CORBA. По степени абстракции СОМ ближе к первому из них, так как CORBA менее требовательна к неизменности и уникальности интерфейсов во времени и пространстве.

В понимании СОМ интерфейс – это контракт, состоящий из списка связанных прототипов функций, чье назначение определено, а реализация – нет. Эти прототипы функций эквивалентны абстрактным базовым классам С++, то есть классам, имеющим только виртуальные методы, описания без реализации. Определение интерфейса описывает функции-члены интерфейса, называемые методами, типы их возвращаемого значения, число и типы их параметров, а также описывает, что они, собственно, должны делать. Напрямую с интерфейсом не ассоциировано никакой реализации.

Реализация интерфейса (interface implementation) – это код, который программист создает для выполнения действий, оговоренных в определении интерфейса. Реализации интерфейсов, помещенные в COM-библиотеки или exe-модули, могут использоваться при создании объектно-ориентированных приложений. Разумеется, программист может игнорировать эти реализации и создать собственные. Интерфейсы ассоциируются с CoClass’ами. Чтобы воспользоваться реализацией функциональности интерфейса, нужно создать экземпляр объекта соответствующего класса, и запросить у этого объекта ссылку на соответствующий интерфейс.

Например, для описания взаимодействия с некоторым абстрактным стеком можно определить интерфейс IStack (в COM стало доброй традицией начинать названия интерфейсов с «I»). Этот интерфейс может содержать два метода, скажем, Push и Pop. Вызов метода Pop возвращает значения, заложенные до этого методом Push в стек, но в обратном порядке. Это определение интерфейса не говорит, как функции будут реализованы в коде. Один программист может реализовать стек как массив, а методы Push и Pop – как методы доступа к этому массиву. Другому же взбредет в голову использовать связанный список и соответствующую реализацию методов. Независимо от конкретной реализации методов, представление в памяти указателя на интерфейс IStack, и, соответственно, его использование клиентом полностью специфицируется определением интерфейса.

Простые объекты могут поддерживать только один интерфейс. Более сложные объекты, как правило, поддерживают несколько интерфейсов. Это свойство позволяет реализовать полиморфизм на уровне компонентной модели.

Слово «интерфейс» используется в COM не в том смысле, что в С++. Интерфейс в С++ ссылается на все функции, поддерживаемые классом. COM-интерфейс ссылается на предварительно оговоренную группу связанных функций, реализуемых COM-классом, но не обязательно на ВСЕ функции, поддерживаемые классом.

В CORBA на сегодня не реализована поддержка множества интерфейсов одним объектом. Это приводит к тому, что CORBA-интерфейс практически определяет класс объекта. СОМ же, наоборот, поддерживает реализацию нескольких интерфейсов в одном объекте, и поэтому требует отдельного определения класса объекта (см. "CoClass"). В третьей версии спецификации CORBA должна появиться поддержка множества интерфейсов для одного объекта, что должно еще больше сблизить эти технологии.

Java-программистам концепция СОМ-интерфейсов будет понятна сразу, без объяснений – в Java интерфейсы выглядят точно так же, как в COM.

Для описания интерфейсов в COM, как и в CORBA, применяется язык IDL. В отличие от CORBA использование IDL не обязательно (СОМ является практически бинарным стандартом).

MIDL

Для описания COM-интерфейсов используется MIDL (MS Interface Definition Language). При создании COM-объектов на C++ использование MIDL является стандартной практикой. Некоторые среды программирования (Delphi, VB) обходятся без MIDL. Delphi имеет свой, Паскале-подобный синтаксис, а в VB любой класс априори является COM-объектом и дополнительное описание не требуется. MIDL является развитием OSF DCE IDL и имеет обратную совместимость с ним.

Как видно из примера, MIDL-описание очень похоже на C++. Главные отличия MIDL от C++ в том, что MIDL позволяет задать только описание интерфейса, и в том, что MIDL содержит дополнительные атрибуты, помещаемые в квадратные скобки. Самым главным атрибутом интерфейса является uuid. Он задает IID интерфейса.

Ниже приведен пример описания интерфейса на MIDL.

[
   uuid(F3792A83-69C9-11D2-AC8C-525400DDA17A),
   helpstring("Этот интерфейс определяет методы работы со стеком.")
]
interface IStack : IUnknown
{
   HRESULT Push([in] VARIANT Val);
   HRESULT Pop([out, retval] VARIANT *pVal);
}

Интерфейс и указатели на интерфейс

Экземпляр «реализации интерфейса» на самом деле является указателем на массив указателей на методы (таблицу функций, ссылающуюся на реализации всех методов, определенных в интерфейсе, также называемую виртуальной таблицей – VTbl). Объекты с несколькими интерфейсами могут предоставлять указатели на несколько таблиц функций. Любой код, содержащий указатель, через который он имеет доступ к массиву, может вызывать методы этого интерфейса.

Все это очень длинно и косноязычно, так что указатель на таблицу функций интерфейса, который должен иметь другой объект для вызовов методов этого интерфейса, называют просто указателем на интерфейс. Можно создать таблицы функций на С вручную, или почти автоматически на С++ и других объектно-ориентированных языках. Некоторые языки программирования (например, VB) имеют специальные синтаксические расширения, позволяющие сделать этот процесс простым и наглядным.

Если компилятор позволяет (что типично для С/С++), клиент может вызвать метод интерфейса по имени, а не позиции в массиве. Поскольку интерфейс – это тип, зная имена методов, компилятор может проверить типы параметров и возвращаемого значения каждого из методов интерфейса. Напротив, такая проверка типов недоступна даже в С/С++, если клиент использует схему вызова по позиции. К счастью, все современные средства разработки (C++, VB, Delphi, Java, ...) позволяют вызывать методы интерфейсов по имени. Вызов методов интерфейсов по позиции может понадобиться только при формировании COM-вызова, что называется, на лету. Естественно, в повседневной работе прикладного программиста такое практически не случается. На практике для реализации динамического связывания используется специальный интерфейс IDispatch, но о нем речь пойдет позже (в разделе «Динамическое связывание»).

Интерфейс имеет свое имя. Как мы уже говорили, каждый интерфейс – это неизменяемый контракт, во время исполнения определяемый по глобально уникальному идентификатору интерфейса, IID. IID, это особая разновидность GUID, позволяет клиентам точно и без ненужных сложностей, узнать у объекта, поддерживает ли он некоторый интерфейс. Идентификация интерфейсов по IID решает проблему неуникальности имен интерфейсов. Это необходимо, поскольку разработка COM-объектов ведется одновременно во всех частях света, и никто не гарантирует, что объекты с одноименными, но разными интерфейсами не встретятся в один прекрасный момент на каком-нибудь компьютере.

В COM нет нужды в том, что в Corba называется строковым связыванием или объектной ссылкой, записанной в строку. В отличие от CORBA-объектов, объекты COM не живут после смерти. Поэтому ссылки нужно получать только на загруженный в память экземпляр объекта (вернее на один из его интерфейсов). Получить ссылку на объект можно, попросту создав его, или запросив эту ссылку у другого объекта. Есть некоторые особенности при передаче указателей на интерфейс другому потоку того же приложения. Эта тема рассматривается в разделе "Управление потоками".

Интересной задачей является определение того, указывают ли два указателя на интерфейсы разных объектов или одного и того же . Сделать это очень просто. Надо запросить от каждого из них указатель на IUnknown (см. ниже) и просто сравнить их. Если они равны, значит, они получены от одного и того же объекта. Это верно даже в случае, если указатели получались через дюжину компьютеров, а сам объект находится на другой стороне земного шара. Не меняет этого правила даже то, что объект был получен агрегированием и на самом деле создан из нескольких других. Главное, чтобы указатели на интерфейс были получены легальными путями.

IUnknown и наследование интерфейсов

Наследование в COM не означает повторного использования кода, как это обычно бывает в объектно-ориентированных языках программирования. Оно означает только, что ассоциированный с интерфейсом контракт наследуется в манере чисто виртуального базового класса С++ и изменяется добавлением новых методов или дальнейшим уточнением допустимого использования методов. В COM отсутствует избирательное наследование. Если один интерфейс наследует от другого, он включает все методы, определяемые другим интерфейсом.

Наследование в стандартных COM-интерфейсах используется довольно скупо. Все интерфейсы должны быть унаследованы от интерфейса IUnknown. IUnknown содержит три жизненно важных для COM метода: QueryInterface, AddRef и Release. Все COM-объекты должны реализовать интерфейс IUnknown, поскольку он обеспечивает возможность получать поддерживаемые объектом интерфейсы с помощью QueryInterface, и управлять сроком жизни объекта через AddRef и Release.

Высокоуровневые средства разработки (такие, как VB) полностью скрывают реализацию IUnknown, облегчая тем самым работу программиста. Низкоуровневые средства разработки (например, VC++) напротив, дают полный доступ к исходным текстам, реализующим IUnknown. На C++ или Delphi можно самостоятельно реализовать IUnknown. Но проще воспользоваться стандартными реализациями. Для C++ самый удобный, но в тоже время гибкий и очень компактный способ реализации IUnknown – воспользоваться библиотекой ATL (Active Template Library). ATL – это библиотека, главным образом состоящая из набора шаблонов C++. ATL упрощает работу с COM, предоставляя реализации для многих стандартных интерфейсов. Эта библиотека входит в поставку MS VC++ и Borland C++ Builder. В сочетании с визуальными средствами разработки этих сред ATL позволяет очень быстро создавать сложные приложения, базирующиеся на COM.

Экземпляр СОМ-объекта

Чтобы воспользоваться функциональностью COM-объекта, нужно создать его экземпляр (instance).

Экземпляр COM-объекта аналогичен объекту в C++ или VB, за тем исключением, что он может быть создан в другом процессе или на другом компьютере.

Для создания экземпляра COM-объекта на C++ применяются функции CoCreateInstance, CoCreateInstanceEx, CoGetObject, CoGetClassObject, CoGetInstanceFromFile, CoGetInstanceFromIStorage, OleLoadFromStream и некоторые другие, всех и не упомнишь. На VB все немного проще. Там есть две универсальные функции, CreateObject и GetObject, и оператор new. Все объекты VB являются COM-объектами.

Чаще всего применяются функции CoCreateInstance, CoCreateInstanceEx, CreateObject и оператор new .

Чтобы создать экземпляр объекта с помощью CoCreateInstance, этой функции необходимо передать: CLSID требуемого объекта, контекст создаваемого объекта, IID требующегося интерфейса, и указатель, в который и будет возвращен указатель на интерфейс созданного объекта.

Контекст создаваемого объекта – это информация о том, где должен создаваться объект: в том же процессе (адресном пространстве EXE-модуля), в другом процессе того же компьютера, на удаленном сервере, или как локальная заглушка для удаленного объекта.

У этой функции есть еще один параметр, pUnkOuter, предназначенный для агрегации объектов, но об этом речь пойдет в разделе "Агрегация".

Функция CoCreateInstance была создана на заре существования COM-модели и предназначена для локального COM. Она не позволяет создавать объекты на конкретном удаленном сервере, вместо этого она берет информацию о сервере из реестра. Информацию о местонахождении сервера и атрибуты защиты можно настроить с помощью утилиты DCOMCNFG или средств администрирования MTS или COM+. К недостаткам CoCreateInstance можно отнести также то, что при ее использовании нет возможности задать учетную запись (и пароль), от имени которой будет создаваться объект, и то, что она позволяет получить указатель только на один интерфейс создаваемого объекта. Для локально создаваемого объекта эти ограничения не существенны – атрибуты защиты при этом не работают (имеется прямой доступ к объекту), а указатель на другой интерфейс можно молниеносно получить с помощью вызова метода IUnknown::QueryInterface (ведь по принципам COM любой COM-интерфейс должен быть унаследован от IUnknown). Вызов же QueryInterface у удаленного объекта приводит к передаче данных по сети, что значительно медленней локального вызова.

Всех этих недостатков лишена новая версия этой функции – CoCreateInstanceEx.

Остальные функции позволяют создать объект, загрузив его состояние из разных источников (OleLoadFromStream, CoGetInstanceFromFile, CoGetInstanceFromIStorage), или создать объект с помощью моникера и строки параметра (CoGetObject).

Моникер – это COM-объект, умеющий создавать другой объект. ОС сама (по префиксу в строковом параметре) находит необходимый моникер, тот в свою очередь создает необходимый объект там, где ему необходимо, и возвращает указатель на интерфейс созданного объекта.

С некоторой натяжкой можно сказать, что моникер – это аналог Службы имен в CORBA, по существу, он позволяет получать указатель на интерфейс конкретного экземпляра объекта по строковому параметру.

Создание моникера – задача сложная, и по плечу она не каждому. Спасает то, что имеется много стандартных реализаций моникеров. Так, через них делается связывание документов в OLE, создание Queued-объектов (асинхронно работающих объектов повышенной надежности, о них речь пойдет дальше), и получение указателей на WMI-объекты, интерфейс новой технологии Windows 2000 – Active Directory. Существует интересная разновидность моникеров – URL-моникеры, которые позволяют получить указатель на объект, основываясь на URL.

Экземпляр СОМ-объекта можно сравнить с экземпляром CORBA-объекта, сопоставленного с конкретным сервантом. Его, однако, нельзя сравнивать с экземпляром CORBA-объекта, так как последний может виртуально существовать после смерти его серванта и воскреснуть в новом облике с другим сервантом.

Инициализация и сохранение объектов

COM-объекты не имеют конструкторов, привычных для объектно-ориентированных языков программирования. Вместо этого можно инициализировать объекты (или загружать в них заранее сохраненное состояние) с помощью стандартных IPersistXXX COM-интерфейсов. Вот список этих интерфейсов: IPersistStream, IPersistStreamInit, IPersistStorage, IPersistFile, IPersistMemory, IPersistPropertyBag. Всех их объединяет то, что они унаследованы от интерфейса IPersist, а назначение каждого понятно из его названия. Хотелось бы обратить внимание на два интерфейса – IPersistStreamInit и IPersistPropertyBag. Первый имеет метод InitNew, предназначенный для инициализации новых объектов, а второй позволяет сохранять информацию в виде читабельного текста. IE и VB используют эти интерфейсы для сохранения и загрузки состояния элементов управления ActiveX, встроенных в html-странички и в VB-формы. Эти интерфейсы можно использовать и для сохранения/загрузки объектов в БД на удаленном сервере или для передачи COM-объектов через параметры методов удаленных объектов по значению (в CORBA аналогичная возможность носит название ValueType).

Сервер и клиент

В отличие от интерфейса, COM-объект – это не абстракция, и его код должен где-то храниться. Можно, конечно, создавать и использовать COM-объекты в одном исполняемом файле, но значительно интересней помещать COM-объекты в независимые модули. COM позволяет помещать COM-объекты в DLL- или EXE-модули и подгружать их при необходимости. Причем программисту даже не надо следить за подгрузкой необходимых модулей. За него это делает COM. Программист просто говорит, что ему надо загрузить некоторый объект, а COM лезет в реестр той машины, на которой надо создать объект, берет оттуда нужную информацию, загружает необходимый модуль (если он еще не загружен), создает объект и возвращает ссылку на него.

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

Здесь есть одна тонкость. Как известно, новый процесс создается только при загрузке EXE-модуля, а DLL грузится в адресное пространство того процесса, в котором требуется его функциональность. Значит, компоненты, помещенные в DLL-модули, не могут создаваться удаленно. Это именно так, но не все так плохо... Если создать COM-объект в одном процессе, то ничто ни мешает передать указатель на его интерфейс в другой процесс. Это достигается реализацией одного COM-объекта как EXE-сервера. У этого объекта создается метод, который получает на вход CLSID объекта (объект должен быть зарегистрирован на той же машине, где создан первый COM-объект) и IID интерфейса (который изначально требуется от второго объекта). Ему же нужно передать указатель, через который будет возвращен интерфейс создаваемого объекта. В общем, метод первого объекта должен походить на функцию CoCreateInstance. Вызывая этот метод, можно удаленно создавать объекты, расположенные в DLL-модулях на другом компьютере. Процесс, в который загружается DLL на удаленном компьютере, называется суррогатным процессом. Можно даже скрыть все эти ухищрения от программиста, специальным образом зарегистрировав объект на вызывающей машине.

Именно это и сделано в MTS и COM+, причем так искусно, что создание объекта таким образом ничем не отличается от создания объектов традиционным для DCOM образом.

Более того, MTS и, особенно, COM+ предоставляют множество дополнительных функций и упрощают создание и внедрение распределенных приложений.

Подсчет ссылок

Для каждой реализации интерфейса ведется подсчет ссылок (Reference counting). Любой код, который хочет хранить указатель (ссылку) на интерфейс, должен увеличить счетчик ссылок этого интерфейса. Сделать это можно, вызвав у интерфейса метод AddRef (Помните? Каждый COM-интерфейс унаследован от IUnknown.). Пока у объекта счетчик ссылок не равен нулю, этот объект не будет уничтожен. Чтобы уменьшить счетчик, надо вызвать метод Release того же интерфейса. COM-объект жив, пока хотя бы у одного интерфейса счетчик ссылок больше нуля. Обычно сразу после создания объекта имеется только один указатель на его интерфейс со счетчиком ссылок, установленным в единицу. Так что, если в этот момент вызвать у его интерфейса метод Release, объект автоматически уничтожится.

Есть строгие правила, описывающие, когда и кому надо вызывать AddRef и Release. Эти правила описаны в спецификации COM. Самым главным правилом является то, что если какая-нибудь функция возвращает указатель на интерфейс, то она должна увеличивать счетчик ссылок этого интерфейса. Этому правилу подчиняются даже CoCreateInstance и QueryInterface. Если же указатель передается в функцию, и ей не надо сохранять его для использования после выхода из этой функции, то она может не увеличивать счетчик ссылок на этот интерфейс.

Подсчет ссылок позволяет эффективно управлять временем жизни COM-объекта в распределенной среде. Счетчик ссылок необходим еще и потому, что СОМ является компонентной моделью, в которой компоненты могут параллельно использоваться любым количеством других компонентов. Если в такой среде применять физическое удаление, такая система не бужет жизнеспособна из-за невозможности обеспечения правильного срока жизни объектов. Единственная альтернатива – сохранение объекта в живых до завершения работы сервера. При таком подходе, во-первых, пришлось бы управлять сроком жизни самого сервера, а во-вторых, это привело бы к перерасходу бесценных системных ресурсов.

В распределенных системах DCOM берет на себя управление подсчетом ссылок на удаленные объекты. Даже если клиент потерпит крах и не сможет самостоятельно освободить сделанные им ссылки, DCOM через некоторое время сделает это за него. Чтобы избежать чрезмерной услужливости DCOM, proxy объектов, создаваемые тем же DCOM, регулярно сообщают о своем существовании, производя так называемый ping. Однако есть две проблемы, которые портят радужную картину.

Первая – это то, что если следить за ссылками вручную, довольно-таки просто ошибиться и забыть сделать AddRef или Release. Лишний AddRef приведет к тому, что объект не освободится до закрытия клиентского приложения. Лишний Release приведет к появлению неверных указателей в клиентском приложении, что, в свою очередь, приводит к его краху. Как уже говорилось, сетевым подсчетом ссылок занимается сам DCOM, и проблема со ссылками в клиентском приложении не может повлиять на работоспособность сервера. В некоторых языках проблема подсчета ссылок в клиентском приложении решена путем встраивания механизма автоматического подсчета ссылок в сам язык. Так сделано в VB и Delphi. А в C++ эта проблема может быть решена средствами самого языка. Для ее решения используются три свойства языка.

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

В сочетании с перегружаемыми операторами классы-обертки практически полностью снимают проблему подсчета ссылок в C++. Лучшей реализацией такого класса-обертки является шаблон CComPtr<*> из библиотеки ATL. Переменная, созданная на базе такого класса-обертки, называется Smart Pointer, то есть «умный указатель». Объявив вместо простого указателя на интерфейс (например, IStak * pIStak) Smart Pointer, (CComPtr<IStak> * spIStak), можно не заботится о правильности подсчета ссылок.

Вторая проблема – это то, что если два объекта делают ссылки друг на друга, то они попадают в deadlock, взаимно блокируя друг друга. Эта ситуация возникает нечасто, но если уж возникает, ее решение отнимает много нервов и времени. Есть два пути решения этой проблемы. Один – нарушить соглашения COM, и не делать увеличения счетчика ссылок одному из объектов. При этом время жизни объекта, которому не делается увеличение счетчика, должно быть заведомо больше, чем время, на которое требуется указатель на его интерфейс. Этот путь опасен и не всегда работает в сетевом окружении. Второй путь – создать промежуточный (третий) объект, который и подсунуть объекту, приводящему к взаимоблокировке. При этом на третий объект делается две ссылки, а на первый ссылок не делается вообще. В момент, когда первый объект уничтожается, он отпускает первый и третий. Далее уничтожается второй, и, в свою очередь, отпускает третий, что приводит к уничтожению третьего объекта. Второй способ избежания взаимоблокировок является более предпочтительным, но требует дополнительного кода. Очень плохо, что COM не взял на себя решение проблем с взаимоблокировкой.

Библиотеки

COM-объекты помещаются в библиотеки. Чаще всего это файл с расширением DLL, но иногда библиотеки помещаются в EXE- или OCX-модули. Расширение OCX всего лишь говорит о том, что в библиотеке находится элементы управления ActiveX, по существу это тоже DLL. EXE-формат (исполняемый файл) используется в двух случаях: 1) когда надо создать отдельный COM-сервер, не требующий при своей работе наличия MTS или COM+, 2) когда создается настольное Windows-приложение, которое должно поддерживать Automation (выставлять некоторую объектную модель, доступную из других приложений).

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

[
   uuid(96479D03-B2D5-11D3-AE78-004095E1F072),
   version(1.0),
   helpstring("Stack Type Library v 1.0")
]
library StackLib
{
   [
      uuid(95F3208B-F9AC-11d3-A6AD-0050BAC0EF0F),
      helpstring("Iauaeo Stack")
   ]
   coclass Stack
   {
      [default] interface IStack;
   };
}

В теле библиотеки содержится описание входящих в нее объектов. На основании такого описания MIDL генерирует библиотеку типов. Как и другие объекты в COM, библиотеки должны иметь свой уникальный идентификатор (GUID). В применении к библиотеке он называется LibID. В случае DLL библиотека типов прилинковывается к DLL, а в случае с EXE обычно поставляется отдельно в файле с расширением TLB или OLB. Как бы там ни было, библиотека типов регистрируется в системе, и с ее помощью можно получать информацию о любом элементе библиотеки. С помощью библиотеки типов можно даже сгенерировать MIDL-описание для всех элементов библиотеки. В поставку MS Visual Studio входит утилита «OLE View», позволяющая просматривать библиотеки типов и получать MIDL-описание.

Создание простого COM-объекта

Создавать COM-объекты очень просто. Однако для создания проекта COM-библиотеки на C++ нужно написать довольно много одинакового (повторяющегося из проекта в проект и занудного) кода. Чтобы не делать этого каждый раз, лучше всего воспользоваться библиотекой ATL (о ней уже говорилось ранее) и соответствующими визардами (речь идет про Microsoft VC и Borland C++ Builder). На VB вообще надо только выбрать тип проекта «ActiveX DLL». При этом VB создаст класс. Любой метод или свойство этого класса, не помеченный как Private, будет являться методом или свойством COM-объекта. Пример, который далее будет реализован на C++, на VB будет выглядеть так:

Public Function Summa(ByVal P1 As Long, ByVal P2 As Long)
    Summa = P1 + P2
End Function

В C++ (и на VC и на C++ Builder) надо создать проект. В VC надо выбрать меню «File\New» и в появившемся визарде на закладке «Projects» вбить имя проекта, путь к нему, и нажать Enter. Назовем проект «VCTest1». На следующей странице достаточно еще раз нажать Enter, так как ее установки полностью нас удовлетворяют. Появится окно, перечисляющее, какие файлы будут созданы. Это последний шанс отменить создание проекта. Еще раз нажмем Enter.

Рис. 1

Итак, что же создает этот визард?

VCTest1.dsp – DLL-проект VC. VCTest1.cpp и VCTest1.h – файлы библиотеки. Они отвечают за саморегистрацию библиотеки и за создание объектов. VCTest1.idl – IDL-описание нашей библиотеки. Пока что этот файл содержит пустое описание библиотеки типов. VCTest1ps.mk – make-файл, создающий DLL, содержащую proxy и stub для интерфейсов, входящих в нашу библиотеку. Если библиотека будет отвечать требованиям Automation, то DLL, создаваемая этим make-файлом, да и сам make-файл нам никогда не понадобятся. Иначе для маршалинга придется использовать proxy/stub DLL. При создании проекта можно заставить ATL-визард объединять proxy/stub с основной DLL-библиотекой.

Если скомпилировать проект, получится VCTest1.dll, не содержащая ни одного объекта. Поэтому вместо компиляции добавим COM-объект.

Для этого из меню Insert нужно выбрать пункт «New ATL Object». Появится «ATL Object Wizard». Надо выбрать «Objects\Simple Objects» и нажать кнопку «Next >» (см. Рис.1).

На этом шаге визард попросит задать имя объекта. Сделать это можно в поле «Short Name». Введем в него строку «VCTestObj» (см. рис.2).

Рис. 2

Остальные параметры можно оставить по умолчанию, так что нажмем Enter. При этом к проекту добавятся три файла:

VCTestObj.h – содержащий описание C++-объекта (CVCTestObj), являющегося реализацией нашего COM-объекта; VCTestObj.cpp – сейчас он пуст (в будущем в него будет добавляться код для объекта CVCTestObj); VCTestObj.rgs – файл, содержащий описание – информацию, которую надо занести в реестр.

В IDL-файл при этом добавилось описание нового объекта и нового интерфейса.

Осталось добавить метод, и можно компилировать проект.

Для этого следует переключиться на закладку «ClassView» в окне «Workspace», щелкнуть правой кнопкой мыши по имени интерфейса (IVCTestObj) и в контекстном меню выбрать пункт «Add Method» (см. Рис.3).

В появившемся диалоге надо ввести имя метода и описание его параметров, как это показано на Рис.4.

После нажатия Enter в описании интерфейса (файл VCTest1.idl) и в описании объекта C++ (файлы VCTestObj.h и VCTestObj.cpp) добавится описание нового метода. Осталось перейти к новому методу и внести необходимый код:

*piResult = iP1 + iP2;

Теперь можно собрать проект, нажав кнопку F7. При сборке проекта происходит автоматическая регистрация библиотеки. Так что после завершения можно сразу приступать к тестированию проекта.

Единственное, что необычно в приведенном примере – это то, что результат возвращается через параметр, а не как возвращаемое значение функции. Это вызвано тем, что возвращаемое значение в методах COM-объекта зарезервировано для возврата кода ошибки (см. «Обработка ошибок»).

Установление связи с удаленным объектом Создание экземпляра объекта и вызов метода

В отличие от CORBA, в СОМ не предусмотрено специальных средств получения указателей на интерфейсы конкретных объектов. Поэтому стандартным способом установления связи с COM-объектом является его создание. Как уже говорилось, есть несколько методик, позволяющих имитировать разные подходы, в том числе и привычные для CORBA. Ниже приведен C++ код консольного приложения, создающий COM-объект и вызывающий его метод.

#include "stdafx.h"
#include <stdio.h>
#include <conio.h>
#include "objbase.h"
#include "..\VCTest1.h"
#include "..\VCTest1_i.c"
int main(int argc, char* argv[])
{
   // Инициализируем COM
   CoInitialize(NULL);
   IVCTestObj * pIVCTestObj;
   // Создаем объект - VCTestObj
   HRESULT hr = CoCreateInstance(CLSID_VCTestObj,
      NULL, CLSCTX_SERVER,
      IID_IVCTestObj, (void**)&pIVCTestObj);
   if(SUCCEEDED(hr))
   {
      // Если объект создан...
      int iP1 = 1, iP2 = 2, iResult = 0;
  
      // Вызываем метод COM-объекта...  
      hr = pIVCTestObj->Summa(iP1, iP2, &iResult);
      // Освобождаем ссылку на COM-объект...  
      pIVCTestObj->Release();
      printf("Call 'Summa(%d, %d)' return %d.\n", iP1, iP2, iResult);
   }
   printf( "\n\nError code: 0x%08x\n\n"
      "Press eny key to continue...\n", hr);
   _getch(); // Ожидаем ввода символа (для удобства отладки)
  
   CoUninitialize(); // Деинициализируем COM
   return 0;
}

В этом примере, CLSID объекта, IID и описание интерфейса берутся из сгенерированных компилятором MIDL для первого проекта (см. «Создание простого COM-объекта») файлов (VCTest1.h и VCTest1_i.c).

Такой способ удобно применять, если и объект и вызывающее его приложение созданы на C++ и доступны описания в текстовом виде. Если же вам доступна только бинарная библиотека, то можно воспользоваться расширением C++ – директивой «#import». Это расширение сделано Microsoft. Если я не ошибаюсь, оно доступно начиная с пятой версии VC. Чтобы получить результат, аналогичный включению файлов VCTest1.h и VCTest1_i.c, надо использовать следующий синтаксис:

#import "..\Debug\VCTest1.dll" \
   no_implementation no_namespace \
   named_guids raw_interfaces_only

С помощью директивы «#import» также можно создать классы-обертки, позволяющие упростить работу с включенными в библиотеку компонентами. Подробнее об этой директиве можно узнать в документации по VC.

В C++ Builder тоже реализована поддержка «#import», но, к сожалению, криво. В документации предлагается для взаимодействия с COM-объектами пользоваться импортом библиотек типов. Для этого нужно в меню «Project» выбрать пункт «Import Type Library», найти в списке необходимую библиотеку типов и нажать кнопку «Install». При этом создаются файлы описания и классы-обертки. После перекомпиляции пакеджа (в который происходит импорт) в палитре компонентов, на закладке «ActiveX» появятся новые компоненты, в нашем случае один «VCTestObj». Если поместить один из них на форму, то с его помощью можно будет производить вызовы методов COM-объекта. Вот как выглядит вызов метода нашего тестового объекта:

VCTestObj1->Connect();
ShowMessage("Summa(1, 2) = " + String(VCTestObj1->Summa(1, 2)));
VCTestObj1->Disconnect();

Метод Connect создает экземпляр объекта, а Disconnect освобождает ссылку (вызовом Release). Таким образом, объект создается и, после вызова метода Summa, уничтожается. Если надо обеспечить жизнь объекта на протяжении всей жизни формы, то можно вместо вызова Connect установить свойство AutoConnect в True во время разработки. Это приведет к тому, что COM-объект создастся при загрузке формы. Метод Disconnect в этом случае тоже можно проигнорировать, при уничтожении формы ссылка на COM-объект будет освобождена автоматически.

Этот механизм C++ Builder позаимствовал у Delphi. Сравните код Delphi с приведенным ранее.

VCTestObj1.Connect();
ShowMessage('Summa(1, 2) = ' + IntToStr(VCTestObj1.Summa(1, 2)));
VCTestObj1.Disconnect();

Еще одна полезная черта «оберток» заключается в том, что они преобразуют обработку ошибок «в стиле COM» в схему, принятую для базового языка.

Еще меньше приходится делать в VB. В нем надо только зарегистрировать ссылку, объявить переменную и вызвать метод. Если впоследствии в библиотеке, содержащей вызываемый объект, произойдут изменения, VB сам их отследит. Вот код вызова метода нашего тестового компонента:

Dim obj As New VCTestObj
MsgBox "Summa(1, 2) = " & obj.Summa(1, 2)

Регистрация компонентов в распределенном приложении

В приведенных выше примерах предполагается, что сервер соответствующим образом зарегистрирован. По умолчанию DLL-библиотека, в которую помещен COM-объект, регистрируется как внутрипроцессный (in-process) сервер. Это приводит к тому, что DLL будет грузиться внутрь каждого процесса, который попытается создать находящийся в ней объект. Чтобы с объектами из этой DLL-библиотеки можно было работать по сети, необходимо зарегистрировать ее в COM+ или MTS. Вот как выглядит эта операция.

Первое что необходимо сделать, это открыть COM+ Component Services (MTS Explorer) и добраться до папки «My Computer». В ней необходимо выбрать папку «COM+ Applications» и создать (или открыть, если оно уже существует) новое приложение COM+. Создать новое приложение можно, выбрав из меню «Action» (или из контекстного меню) пункт «New\Applications».

В приложение COM+ необходимо добавить компоненты. Это делается аналогично созданию приложения, с той лишь разницей, что в этот раз надо выбрать папку «Components» приложения. Результат регистрации должен быть похож на Рис. 6.

В одно приложение можно помещать компоненты из разных библиотек. Приложение олицетворяет процесс, в котором будут загружаться и исполняться компоненты. Если есть необходимость загружать компоненты в разных процессах, то нужно помещать их в разные приложения COM+.

COM+ Component Services (для Windows NT 4.0 – MTS Explorer) позволяют не только производить первичную регистрацию компонентов, но и дают возможность настраивать их характеристики, производить отладку компонентов, осуществлять их мониторинг и даже создавать инсталляторы для распределенных приложений.

Чтобы зарегистрировать приложение COM+ на клиентской машине, необходимо с помощью COM+ Component Services (MTS Explorer) создать proxy-инсталлятор. Затем нужно прогнать этот инсталлятор на каждой клиентской машине. Деинсталляция COM+-приложения, как и любого другого Windows-приложения, доступна через «Control Panel» – «Add\Remove Programs».

Не-СОМ+-приложения можно зарегистрировать и администрировать с помощью утилиты DCOMCNFG.

Управление объектами Агрегация

С помощью COM можно создавать объекты, реализация которых состоит из нескольких других объектов. Такой составной объект выглядит как единый. Агрегация – лучший в COM-модели способ повторного использования бинарных компонентов, не приводящий к потерям производительности. Его слабой стороной является то, что нельзя перехватывать и по-своему обрабатывать отдельные методы интерфейсов агрегируемых объектов. Можно только подставлять свою реализацию целого интерфейса.

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

Делегация

Это простое включение объекта внутрь и передача (делегация) ему для обработки некоторых вызовов другого объекта. Этот способ обеспечивает наибольшую гибкость, но проигрывает по эффективности агрегации, так как производятся лишние вызовы.

Управление временем жизни экземпляров COM-объектов

Время жизни COM-объекта ограничено временем жизни серверного приложения, в котором создан этот объект. К тому же объект самоуничтожается при освобождении (вызовом метода Release) последней ссылки на него. Тем не менее, на практике это не приводит к каким-нибудь ограничениям. Если логическое время жизни объекта должно превышать время жизни экземпляра объекта в оперативной памяти, можно просто сохранить в файл или в поле БД его состояние перед уничтожением, а когда он понадобится, загрузить его вновь. Сохраненный объект может путешествовать между компьютерами, например, через параметр удаленного объекта или в электронном письме. На нужном компьютере надо просто создать новый экземпляр нужного класса и инициализировать его состояние пришедшими данными.

Сохранение и загрузку объектов можно инкапсулировать в методах другого удаленного объекта. Например, можно создать объект с методами Save и Load, передавая им в качестве параметров идентификатор БД или имя файла и указатель на интерфейс сохраняемого (загружаемого) объекта. Часто для этого применяются моникеры. OLE DB и ADO позволяют записывать в БД объекты, поддерживающие стандартные интерфейсы IPersistXXX (см. «Инициализация и сохранение объектов»).

COM+ даже позволяет вызывать методы объектов на выключенном компьютере. Эти возможности освещается в разделе «Асинхронное взаимодействие между объектами».

Количество экземпляров объектов

При вызове CoCreateInstance вы не обязательно получаете указатель на новый объект. Вполне возможно, что вы получите указатель на уже созданный, разделяемый объект. Такой объект называется singleton. За то, как выполняется операция создания экземпляра COM-объекта, отвечает так называемая фабрика классов. Ее реализация находится в том же модуле, что и реализация объектов создаваемых этой фабрикой.

Если для создания COM-объектов вы используете ATL, то для реализации singleton-объекта надо добавить одну строчку в код вашего объекта:

DECLARE_CLASSFACTORY_SINGLETON(CMyClass)

Где CMyClass это имя вашего класса.

Singleton-объекты удобны, когда необходимо организовать управление общими ресурсами. Такие объекты похожи на Corba-объекты, использующие сервант по умолчанию.

В COM не реализован сервис имен (Naming Services), аналогичный CORBA. Этот Corba-сервис позволяет получать ссылку на конкретный экземпляр объекта, созданный на сервере. Идентификация объекта при этом происходит по имени.

Обычно необходимость такой функциональности встречается редко, так как COM придерживается отличных от Corba принципов взаимодействия с объектами. Но если такая функциональность вам необходима, то можно создать свой сервис имен. Для этого удобно воспользоваться функциональностью Singleton-объектов. Singleton-объект создает другие объекты и помещает их в массив, статический в простом случае, и динамический, или мап (объект, позволяющий ассоциировать одни значения с другими – в нашем случае имена объектов с указателями на их интерфейс) в более сложном случае. Один из методов этого Singleton-объекта принимает в качестве входного параметра имя и возвращает ассоциированный с ним объект. Простой пример, того, как это можно сделать с помощью ATL, находится на нашем сайте в электронной версии этой статьи. Там же приведен и Corba-вариант, использующий сервис имен.

Экземпляры и EXE-серверы

Если объект реализован в DLL, то, в принципе, его экземпляры можно создавать внутри нескольких EXE-серверов, но регистрировать в COM+ или MTS их можно только один раз (на одной машине). Если есть необходимость создавать такие COM-объекты в разных EXE-модулях, то их или вовсе не надо регистрировать в COM+/MTS, или нужно регистрировать как библиотечное приложение (библиотечный пакет в MTS). При этом создание экземпляров и передачу указателей на их интерфейсы необходимо делегировать другим объектам, зарегистрированным в COM+/MTS.

Если создается монолитный EXE-сервер, то тем, будет или нет загружаться новая копия EXE-модуля при создании нового объекта (и некоторыми другими аспектами), можно управлять через один из параметров функции CoRegisterClassObject. ATL использует эту функцию при загрузке EXE-модуля для регистрации реализуемых им компонентов.

Использование нестандартных типов данных в методах удаленного объекта Два пути

В COM есть два варианта организации обработки нестандартных (user-defined или сконструированных) типов данных. Первый – это уровень компилятора MIDL и C/++. Второй – использование стандартных для COM и совместимых с Automation универсальных типов данных и соответствующей техники.

Оба варианта позволяют использовать базовые типы (long, byte, ...), перечисления (enum) и структуры. Разница заключается в требованиях и ограничениях, предъявляемых обоими вариантами.

Первый вариант является более низкоуровневым и обычно приводит к тому, что методы, описанные с использованием хитрых MIDL-атрибутов, начинают неправильно восприниматься (или вообще становятся недоступными) в высокоуровневых языках типа VB. Еще одной особенностью первого варианта является то, что при его использовании не задействуются библиотеки типов. Маршалинг (запаковка параметров, передача их по сети и распаковка на другом компьютере) при этом выполняется создаваемой MIDL-компилятором заглушкой – специальной DLL-библиотекой, которая определенным образом регистрируется на взаимодействующих компьютерах.

Все универсальные типы данных, применяемые во втором варианте, созданы с помощью низкоуровневых средств. Но, поскольку эти типы стандартны, о них знают и умеют с ними работать высокоуровневые языки типа VB. А так, как поддержка этих типов осуществляется с ОС, заботиться о создании и регистрации заглушек не надо, они устанавливается при установке ОС. Второй вариант обеспечивает поддержку метаописаний объектов в библиотеке типов, и совместим со всеми средствами разработки. К тому же он спасает от возни с заглушками, что ускоряет время разработки и бережет нервы.

Главное преимущество первого варианта заключается в том, что он позволяет «протащить через проволоку» практически любые типы данных, но если есть возможность, лучше пользоваться вторым подходом.

Если создавать приложения на VB, просто нет никакого выбора, кроме использования второго подхода. Собственно, VB все делает сам, и полностью обходится без MIDL-компилятора, да впрочем, и без самого IDL. Зато VB генерирует библиотеку типов, по которой впоследствии может быть создано описание для любого языка.

Второй вариант можно использовать и при создании приложений на С++. Надо только выполнять некоторые требования и не использовать низкоуровневые MIDL-конструкции.

Основные возможности низкоуровневого МIDL/С++ варианта Массивы

Для передачи по сети массивов MIDL использует атрибуты size_is() и length_is(). При этом метод, передающий массив, будет выглядеть так:

HRESULT Method1(
    [in] short iLength,
    [in, length_is(iLength)] short asNumbers[10]);
HRESULT Method2(
    [in] short iLength,
    [in] short iSize,
    [in, length_is(iLength), size_is(iSize)] short * asNumbers);

Атрибут size_is определяет, сколько памяти будет занято RPC-заглушкой, а length_is определяет, сколько данных надо реально передавать. Так, в первом методе на вызываемой (так как все параметры типа "in") стороне всегда будет заниматься память для десяти элементов (10 * sizeof(short) = 20 байт) массива, но передаваться по сети будет iLength элементов. Во втором случае динамически будут определяться оба параметра. Если объем занимаемой памяти равен передаваемому, то можно пользоваться только атрибутом size_is. В работе с чисто входными (in) и чисто выходными параметрами (out) есть несколько нюансов, но рассказ об этом может занять слишком много места. За подробностями можно обратиться к MSDN. В одном из номеров нашего журнала (3 квартал 1998 года, статья «Visual C++ 6.0 в составе Microsoft Visual Studio» ) была статья, посвященная этой теме.

Работа со строками

MIDL позволяет использовать заканчивающиеся нулем строки. Правила работы со строками те же, что и в C/C++. Поддерживаются как обычные, так и двухбайтные строки. Чтобы сообщить MIDL, что некоторый параметр рассматривается, как строка, нужно пометить его специальным атрибутом – string. VB не поддерживает этот атрибут. Для работы со строками в нем применяется специальный тип – BSTR. Используя атрибуты size_is() и length_is(), можно передавать строки, содержащие в своем теле несколько NULL-символов.

Union

Еще одной интересной особенностью MIDL является поддержка им объединений (union), аналогичных объединениям в С++. Расширенный атрибут switch_is позволяет задать переменную, используемую для определения элемента объединения, имеющего смысл в данный момент времени. Обычно объединение (вернее, его описание и переменную, созданную на базе этого объединения) помещают в структуру. Одно из полей этой структуры и используется в атрибуте switch_is.

Ручная запаковка и распаковка

Самым радикальным случаем при работе с MIDL является так называемый wire marshaling. Этот тип маршалинга преобразует сложный тип в простой с помощью пользовательских функций. Суть его вкратце можно описать так. У вас есть тип данных, который по каким-то причинам нельзя переслать по сети в качестве параметра функции или в составе другого типа данных. Примером может служить указатель на объект C++ или тип данных, содержащий ссылки на локальные ресурсы. В этом случае вы объявляете этот тип в IDL с помощью атрибута wire_marshal или user_marshal. При этом вы задаете исходный тип и так называемый wire-тип. Wire-тип – это некоторый базовый тип, который априори может передаваться по сети, например, массив байтов. Далее надо реализовать четыре функции:

_UserSize – Вычисляет размер, необходимый для буфера данных RPC. Этот метод вызывается перед передачей данных на другой компьютер. _UserMarshal – Запаковывает данные в Wire-тип. _UserUnmarshal – Распаковывает данные из Wire-типа. _UserFree – Освобождает память, занятую при распаковке. Вызывается RPC-заглушкой на стороне клиента, позволяя тем самым освободить данные, на которые ссылается исходный тип.

На основе IDL-описания и кода этих функций MIDL-компилятор создает специальную заглушку, используемую RPC-подсистемой ОС для передачи по сети того, что иначе не передается.

Листинг 1. IDL-файл примера «работа со структурами и массивами»

// RemTest1.idl : IDL source for RemTest1.dll
//
// Этот файл будет обработан MIDL для
// создания библиотеки типов (RemTest1.tlb) и кода маршаллинга.
import "oaidl.idl";
import "ocidl.idl";
[
   uuid(421D531D-A54F-4137-93E8-7006448611B7),
   version(1.0),
   helpstring("RemTest1 1.0 Type Library")
]
library REMTEST1Lib
{
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");
   typedef [uuid(7EF6E5F1-ACF8-49a5-B49B-A92349F27157), version(1.0)] 
   enum SomeEnum
   {
      Some,
      Some1,
      Some2,
      Some3
   }SomeEnum;
   typedef [uuid(080C357D-EE8D-40b0-AD0A-5667995BB186), version(1.0)] 
   struct SomeStruct
   {
      int i;
      BSTR str;
      SomeEnum se;
   }SomeStruct;
   [
      object,
      uuid(153450BE-9F71-4193-9A00-95C58175FD8F),
      version(1.0),
      dual,
      nonextensible,
      pointer_default(unique),
      oleautomation
   ]
   interface IO : IDispatch
   {
      [id(1)] HRESULT M1([in,out] SomeStruct * ss);
      [id(2)] HRESULT M2([in,out] SAFEARRAY(SomeStruct) * ssbs);
      [id(3)] HRESULT M3([in] long len, [in, size_is(len)] SomeStruct * ssa);
   };
   [uuid(51ABF99E-4583-4DF1-85F5-51DE0EB57CDC)]
   coclass O
   {
      [default] interface IO;
   };
};

Листинг 2. Заголовочный фал примера «работа со структурами и массивами»

// O.h : Declaration of the CO
#ifndef __O_H_
#define __O_H_
#include "resource.h" // main symbols
/////////////////////////////////////////////////////////////////////////////
// CO
class ATL_NO_VTABLE CO : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CO, &CLSID_O>,
    public ISupportErrorInfo,
    public IDispatchImpl<IO, &IID_IO, &LIBID_REMTEST1Lib>
{
public:
    CO(){}
DECLARE_REGISTRY_RESOURCEID(IDR_O)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CO)
    COM_INTERFACE_ENTRY(IO)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
// ISupportsErrorInfo
    STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
// IO
public:
    STDMETHOD(M3)(/*[in]*/ long len, /*[in, size_is(len)]*/ SomeStruct * ssa);
    STDMETHOD(M2)(/*[in,out]*/ SAFEARRAY ** ssbs);
    STDMETHOD(M1)(/*[in,out]*/ SomeStruct * ss);
};
#endif //__O_H_

Высокоуровневый вариант (Automation/VB)

Концепция этого варианта очень проста: вы не можете использовать низкоуровневые средства MIDL. Вместо этого вы используете стандартные (их еще называют Automation-совместимыми) типы данных, структуры и перечисления.

VB делает это автоматически.

Ниже приведено краткое описание Automation-совместимых типов:

VARIANT_BOOL – Логический тип. Может иметь значение True (-1) или False (0). BYTE – 8-битное целое число без знака. int – Целое число со знаком. На 32-хбитных платформах имеет размер 32-бита. long – 32-битное целое число со знаком. short – 16-битное целое число со знаком. double – 64-битное число с плавающей точкой. float – 32-битное число с плавающей точкой. CURRENCY – 64-битное число, с фиксированной точкой. DECIMAL – 96-битный (12 байтов) тип, предназначенный для хранения чисел со знаком и переменной плавающей точкой (от 0 до 28 разрядов). DATE – 64-битное число с плавающей точкой, используемое для хранения даты. IDispatch * – Указатель на disp- или dual-интерфейс. IUnknown * – Указатель на IUnknown. Используется для передачи обыкновенных интерфейсов (не disp- или dual-интерфейсов). BSTR – basic-строка или бинарная срока, на Win32-платформах указатель на строку Unicode-символов. Переменная этого типа хранит не только саму строку, но и ее длину. Это позволяет помещать в тело строки любые символы, в том числе неограниченное количество NULL-символов. VARIANT – тип, позволяющий создать переменную, тип которой определяется во время выполнения программы и может изменяться с течением времени. Поддерживаются все типы, доступные для Automation, за исключением самого VARIANT’а. По существу, VARIANT – это структура, содержащая поле vt, определяющее тип, и поле, созданное на базе объединения, содержащего доступные типы данных. VARIANT позволяет хранить в себе даже структуру (VT_RECORD), массив (VT_ARRAY) или указатель на интерфейс. Использование массивов VARIANT’ов и массивов структур позволяет создавать модели данных любой сложности. Единственная проблема типа VARIANT – размер. VARIANT занимает 16 байт. Впрочем, по сравнению с типом данных Any (из CORBA), он просто малютка. SAFEARRAY – так называемый безопасный массив. С его помощью можно создавать динамические много- и одномерные массивы. Как и в случае с типом VARIANT, в качестве типа ячейки SAFEARRAY’я можно использовать любой Automation-тип, включая сам SAFEARRAY. В отличие от типа VARIANT, SAFEARRAY не приводит к перерасходу оперативной памяти, даже когда хранит в себе структуры (тип VT_RECORD). При объявлении массивов типа SAFEARRAY в MIDL используется синтаксис – SAFEARRAY(elementtype) *arrayname. Здесь elementtype – это тип элемента массива. SAFEARRAY может быть многомерным, но все его размерности должны быть одного типа. Типа «структура» как такового не существует. Структуры являются чисто сконструированным типом данных, и, похожи на структуры в языке С, за тем исключением, что здесь в структуры нельзя включать поля, являющиеся указателями на функции. Структуры могут содержать любые перечисленные типы данных, перечисления и другие структуры, определенные в этой же или импортированной (оператором importlib) библиотеке.

Не обязательно, но очень рекомендуется добавлять к определенным пользователям типам (структурам и перечислениям) уникальный идентификатор (в атрибуте uuid). Эти идентификаторы сделают описания типов уникальными (по крайней мере, в пределах нашей планеты ;-)) и позволят получать их описание с помощью таких API-функций, как GetRecordInfoFromGuids.

Эта функция возвращает указатель на интерфейс IRecordInfo. С его помощью можно создавать новые экземпляры пользовательских типов данных, получать их описание и производить над ними другие операции.

Структуры в массивах и с переменной типа VARIANT

С хранением структур в VARIANT или SAFEARRAY есть некоторая тонкость. Хотя в библиотеке типов можно указать, что в поле структуры или параметре функции может содержаться массив (SAFEARRAY) конкретных структур, во время работы приложения все равно приходится иметь дело с VARIANT’ом и SAFEARRAY’ем. Даже то, что у них можно узнать, что в них лежит структура (VT_RECORD), не дает никакого представления о том, что это за структура. А раз неизвестно, какая структура лежит, то и взаимодействовать с ней затруднительно. Как уже говорилось ранее, COM придерживается стратегии идентификации объектов, с которыми он взаимодействует, при помощи GUID. И если с некоторым объектом, в данном случае со структурой, не ассоциирован GUID, то COM не может поместить ее в VARIANT или SAFEARRAY. Поэтому при описании структуры, которую необходимо поместить в SAFEARRAY, надо задавать GUID. Зато после выполнения этого нехитрого требования вам становится доступна вся информация о структуре, хранящейся в ячейке массива SAFEARRAY или переменной типа VARIANT. Вы можете проверить, какого типа структура лежит в массиве, динамически получить информацию о полях структуры, считать или установить значение отдельного поля (по имени) и многое другое. Вы также можете просто узнать количество записей в массиве, получить указатель на данные, привести его к указателю на нужную структуру и заняться обработкой данных.

Листинг 3. cpp-файл примера «работа со структурами и массивами».

// O.cpp : Implementation of CO
#include "stdafx.h"
#include "RemTest1.h"
#include "O.h"
/////////////////////////////////////////////////////////////////////////////
// CO
STDMETHODIMP CO::InterfaceSupportsErrorInfo(REFIID riid)
{
    static const IID* arr[] = 
    {
        &IID_IO
    };
    for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        if (InlineIsEqualGUID(*arr[i],riid))
            return S_OK;
    }
    return S_FALSE;
}
STDMETHODIMP CO::M1(SomeStruct *ss)
{
    if(!ss)
        return E_INVALIDARG;
    ss->i = 111;
    ss->se = Some3;
    return S_OK;
}
STDMETHODIMP CO::M2(SAFEARRAY ** ssbs)
{
    HRESULT hr = S_OK;
    // Проверяем, что в масиве лежат структуры...
    if(FADF_RECORD != (*ssbs)->fFeatures)
        return E_INVALIDARG;
    // С помощью IRecordInfo можно получать различную информацию о структуре
    CComPtr<IRecordInfo> spIRecordInfo;
    hr = SafeArrayGetRecordInfo(*ssbs, &spIRecordInfo);
    if(FAILED(hr))
        return hr;
    // Проверяем, та ли структура лежит в массиве...
    GUID RecordGuid;
    spIRecordInfo->GetGuid(&RecordGuid);
    GUID SomeStructGuid;
    // Переводим взятый из IDL-файла строковый GUID в бинарный GUID
    CLSIDFromString(OLESTR("{080C357D-EE8D-40b0-AD0A-5667995BB186}"), 
                  &SomeStructGuid);
    // Если GUID-ы не совпали, значит, нам подсунули массив не тех структур
    if(RecordGuid != SomeStructGuid)
        return E_INVALIDARG;
    // Если вы уверены, что получаете правильный массив, то предыдущий код 
    // не нужен. Дальше начинается код, меняющий первый элемент в массиве
    SomeStruct * pss;
    // Блокируем массив
    if(SUCCEEDED(SafeArrayLock(*ssbs)))
    {// Теперь можно производить доступ к его элементам
        // Получаем доступ к элементу 1
        LONG iIndex = 1;
        hr = SafeArrayPtrOfIndex(*ssbs, &iIndex, (void**)&pss);
        if(FAILED(hr))
            return hr;
        // Изменяем значения полей...
        pss->i = 987654321;
        SysFreeString(pss->str);
        pss->str = SysAllocString(OLESTR("Изменено на C++"));
        pss->se = Some3;
        // Отпускаем массив
        return SafeArrayUnlock(*ssbs);
    }
    return S_OK;
}
STDMETHODIMP CO::M3(long len, SomeStruct * ssa)
{
    if(len < 2)
        return S_FALSE;
    ssa[1].i = 555;
    ssa[1].se = Some2;
    return S_OK;
}

В Листингах 1, 2 и 3 приведена реализация COM-объекта, демонстрирующего работу с отдельной структурой (метод M1), с массивом структур через SAFEARRAY (метод M2), и работа с массивом, описанным средствами MIDL (метод M3). Под Windows 2000 этот пример не требует создания и регистрации заглушки, но так как метод M3 содержит прямое обращение к не-Automation типу, на других платформах этот пример может потребовать компиляции и регистрации заглушки. Чтобы избежать этого, можно изменить описания метода M3 так, чтобы использовались только типы VARIANT и SAFEARRAY.

Код метода M2 перегружен. Но, во-первых, основную его часть занимают проверки, которые на практике не нужны. Во-вторых, можно сделать шаблонный класс, который принимал бы в качестве параметра тип структуры и эмулировал простой массив. Сделать это можно, скрыв блокировку и ее снятие в конструкторе и деструкторе, а эмулировать массив – перегрузив оператор [ ].

Клиентское приложение, взаимодействующее с объектом из примера, можно создать на C++ (например, выбрав проект MFC EXE) или на VB. В Листинге 4 приведен пример на VB.

Листинг 4. VB-клиент примера «работа со структурами и массивами»

'Не забудьте подключить ссылку на эту библиотеку
Dim rem1 As New REMTEST1Lib.O
Private Sub Command1_Click()
    Dim SS As SomeStruct
    SS.i = 12345
    SS.se = Some1
    SS.Str = "Изменено на VB"
    MsgBox "i = " & SS.i & " SS.se = " & SS.se & " str = " & SS.Str
    rem1.M1 SS
    MsgBox "i = " & SS.i & " SS.se = " & SS.se & " str = " & SS.Str
End Sub
Private Sub Command2_Click()
    Dim ssa(10) As SomeStruct
    Dim ssa1() As SomeStruct
    For i = 0 To 10
        ssa(i).i = i
        ssa(i).se = Some1
        ssa(i).Str = "Str " & 1
    Next
    ssa1 = ssa
    MsgBox " ssa1(1).se = " & ssa1(1).se & " ssa1(1).Str = " & ssa1(1).Str
    rem1.M2 ssa1
    MsgBox " ssa1(1).se = " & ssa1(1).se & " ssa1(1).Str = " & ssa1(1).Str
End Sub

Статическое и динамическое связывание

Интерфейсы COM по умолчанию поддерживают статическое связывание. Для динамического связывания используется специальный интерфейс IDispatch. В принципе объект может поддерживать только IDispatch-интерфейс. Такой интерфейс называется disp-интерфейсом. Этот подход применялся во времена VB третьей версии. Сейчас чаще всего применяются так называемые dual-интерфейсы, но об этом позже.

Disp-интерфейсы и IDispatch

Как уже говорилось, IDispatch применяется в тех случаях, где нужна интерпретация, то есть при применении OLE Automation или скриптовых языков, таких, как VBScript и JScript. Хотя один объект и может поддерживать более одного disp-интерфейса, скриптовые языки могут воспользоваться только одним из них.

Вызовы методов disp-интерфейса производятся по номеру называемому DISPID. Отображением имен методов в DISPID занимается GetIDsOfNames.

Вот полный перечень методов, входящих в IDispatch:

GetTypeInfoCount – Возвращает число поддерживаемых TypeInfo (всегда равен 0 или 1, практического интереса не представляет). GetTypeInfo – возвращает информацию о типах для текущего интерфейса (см. «Метаданные объектов»). GetIDsOfNames – Позволяет получить список DISPID по заданному списку имен. Invoke – это главный метод в IDispatch. С его помощью и производится вызов. Ему передаются DISPID (номер) метода или свойства и список параметров. Параметры помещаются в массив вариантов. После окончания вызова в этот же массив помещаются значения [out]-параметров. Если вызываемый метод является функцией или производится считывание значения свойства, то значение возвращается через параметр pVarResult.

Производительность Invoke ниже, чем прямого вызова интерфейса с теми же параметрами. Главным образом разница в скорости объясняется необходимостью запаковки и последующей распаковки параметров в VARIANT. При сетевом вызове разница в скорости вызова метода disp-интерфейса и метода простого интерфейса становится менее значимой. Однако большинство высокоуровневых средств (типа VB и Delphi) при вызове удаленного метода кроме Invoke вызывают GetIdsOfNames, причем норовят сделать это при каждом вызове, без какой бы ни было оптимизации. Так как GetIdsOfNames выливается в отдельный сетевой вызов, на методах с небольшим числом маленьких по размеру параметров производительность вызовов методов через IDispatch может быть медленнее статического вызова в 4 раза.

Для вызова методов disp-интерфейсов в VB, Delphi и Java используется стандартный синтаксис этих языков. На C++ для этого применяются dispatch-драйверы, специальные классы-обертки.

На C++ более удобно и производительно использовать VTBL-часть dual-интерфейсов.

dual-интерфейсы

Это интерфейсы, поддерживающие как IDispatch, так и прямой вызов через VTBL.

Dual-интерфейсы совмещают скорость обыкновенных интерфейсов с гибкостью disp-интерфейсов. Именно поэтому Microsoft рекомендует использовать их везде, где это возможно.

Практически все среды разработки позволяют легко создавать dual-интерфейсы. VB позволяет создавать только dual-интерфейсы, хотя использовать может и обыкновенные, и disp-интерфейсы. Если нормальный интерфейс описан вне VB (например, в библиотеке типов, созданной с помощью MIDL), то на VB можно создать реализацию этого интерфейса, воспользовавшись ключевым словом Implements.

Вызов Idispatch-части при вызове методов dual-интерфейсов еще больше снижает производительность, так как на стороне сервера этот вызов превращается в нормальный VTBL-вызов, что приводит к ненужным расходам на работу с библиотекой типов. Dual-интерфейсы защищены гораздо лучше, чем чистые disp-интерфейсы, поскольку при конвертации, о которой говорилось выше, производится контроль и приведение типов параметров вызванного метода.

Обработка ошибок HRESULT

По спецификации COM каждый метод COM-объекта должен возвращать специальное 32-х битное число, называемое HRESULT. В нем вызывающей стороне возвращается код ошибки (отрицательное значение), информация об успехе (константа S_OK, равная нулю) или предупреждение (значение, большее нуля).

Обычно COM-объект имеет определенный набор кодов возврата, но так как объекты могут транслировать сообщения об ошибке, полученные от вызываемых ими других методов, то не всегда можно гарантировать, что список значений неизменен. Чтобы определить, был ли вызов успешен или произошла ошибка, можно воспользоваться макросами: SUCCEEDED() – успех и FAILED() – неудача.

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

Возврат информации об ошибках

Объект, содержащий дополнительную информацию об ошибках, может быть ассоциирован с потоком, в котором происходит цепочка СОМ-вызовов. Любой метод в этой цепочке может считать информацию из этого объекта или записать в него свою. Этот объект беспрепятственно преодолевает границы процесса и границы компьютеров. Если ошибка не была обработана на сервере, то информация о ней становится доступна клиентскому компьютеру.

Чтобы записать в этот объект собственную информацию, необходимо выполнить следующие шаги:

Для объекта, в котором инициируется ошибка, реализовать интерфейс ISupportErrorInfo. Для создания объекта, описывающего ошибку, вызвать функцию CreateErrorInfo. Для задания атрибутов этого объекта воспользоваться методами ICreateErrorInfo. Для ассоциации объекта, сообщающего об ошибке, с текущим логическим потоком, вызвать функцию SetErrorInfo. Получение информации об ошибках

Чтобы получить информацию об ошибках, необходимо выполнить следующие шаги:

Следует проверить, может ли объект обработать ошибку, соответствующую возвращенному значению. Для этого через IUnknown::QueryInterface запрашивается указатель на интерфейс ISupportErrorInfo, а затем – InterfaceSupportsErrorInfo, для проверки того, что ошибка случилась именно с тем объектом, который возвратил ее, и что объект, сообщающий об ошибке, относится именно к этой ошибке, а не к предыдущему вызову. Затем нужно вызвать функцию GetErrorInfo, чтобы получить указатель на объект, описывающий ошибку. В принципе, если вас не интересует, кто записал информацию об ошибке, шаг 2 можно пропустить. Чтобы получить информацию от объекта, вызываются методы IErrorInfo.

Если объект не готов обработать ошибку, а нуждается в передаче информации о ней дальше по цепи вызова, он должен просто возвратить полученный код ошибки. Так как функция GetErrorInfo стирает информацию об ошибке, ассоциированную с потоком, то её должен вызывать только объект, обрабатывающий ошибку.

В многоуровневой архитектуре сообщение об ошибке может проходить через большое количество методов различных объектов. Можно вынимать сообщение об ошибке, дописывать его к собственному и закладывать обратно, но со своим кодом ошибки. На практике такая техника выглядит, например, так:

В объекте доступа к данным (например, OLE DB) происходит ошибка «нарушение ссылочной целостности». Объект доступа к данным закладывает соответствующее сообщение и возвращает код ошибки. Если это сообщение в неизменном виде дойдет до клиента, тот ни за что не поймет, что же случилось... Промежуточный компонент, управляющий бизнес-логикой, вынимает это сообщение, закладывает сообщение о том, что «Операция добавления данных о клиенте не удалась, так как:» и дописывает сообщение, присланное объектом доступа к данным. Далее компонент, управляющий бизнес-логикой, закладывает свой код ошибки и возвращает управление вызвавшему его методу. Пункт три может повторяться до тех пор, пока управление не получит клиентское приложение, инициировавшее всю цепочку вызовов. Клиентское приложение вынимает сообщение и показывает его пользователю.

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

Прямую работу с IErrorInfo можно встретить только в коде, написанном на C/C++. Да и то обычно его пытаются «завернуть» в обработку исключений C++. VB, например, берет на себя обработку COM-ошибок, скрывая от программиста работу с HRESULT и IErrorInfo в специальном объекте «Err».

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

Метаданные объектов

Каждый COM-интерфейс, каждый COM-класс и, в принципе, любой элемент COM-библиотеки могут иметь бинарное описание их параметров и атрибутов. Такое описание хранится в библиотеке типов, которая (как уже говорилось) обычно прилинковывается к DLL-модулю или поставляется в отдельном TLB-файле.

Библиотеки типов используются для получения информации об объектах, интерфейсах и пользовательских типах данных. Они также позволяют избавиться от proxy/stub при маршалинге. Создавать библиотеки типов можно с помощью MIDL или программно. VB тоже создает библиотеки типов, но при этом они неразрывно связаны с создаваемыми на нем компонентами. Естественно, VB позволяет использовать библиотеки типов, созданные другими средствами.

Для создания библиотек типов «вручную» используется специальный объект, реализующий интерфейс ICreateTypeLib[2]. Для создания такого объекта используется API-функция CreateTypeLib[2].

Прочитать содержимое библиотеки типов можно с помощью интерфейса ITypeLib. Получить указатель на этот интерфейс можно, вызвав API-функции LoadTypeLib или LoadRegTypeLib.

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

Disp-интерфейсы (как и dual-интерфейсы) позволяют получить их описание через свой метод IDispatch::GetTypeInfo. На нашем сайте в электронной версии этой статьи можно найти пример, показывающий, как получить информацию о типе содержимого VARIANT’а. Хотя в этом примере речь описывается техника работы с типом VARIANT, часть этого примера можно использовать как общее описание работы с библиотеками типов и интерфейсом ITypeInfo. Там же можно найти и CORBA-аналог этого проекта.

Управление потоками

Многопоточные приложения имеют два основных подводных камня: взаимоблокировки (deadlocks) и гонки (races). Взаимоблокировка происходит, когда один поток ожидает от другого каких-то действий, а тот, в свою очередь, ждет некоторых действий от первого. Состояние гонок возникает, если один поток заканчивает свою работу раньше, чем другой, от которого он зависит. Из-за этого первый поток получает мусор вместо результатов, ибо второй еще не предоставил верных.

COM помогает предотвратить взаимоблокировки вызовов объектов. Существуют и некоторые функции, созданные для предотвращения состояния гонок во внепроцессных серверах.

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

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

Самый простой путь понять архитектуру потоков, принятую в COM – это представить, что все COM-объекты в процессе разделены на группы, называемые апартаментами (apartments). COM-объекты всегда живут в одном апартаменте, в том смысле, что их методы могут быть легально вызваны только потоком, принадлежащим этому апартаменту. Любой другой поток, желающий вызвать объект, должен делать это через proxy.

Существует два типа апартаментов: однопоточные апартаменты (single-threaded apartments, STA) и многопоточные апартаменты (multi-threaded apartments, MTA).

Однопоточный апартамент (STA) – это апартамент, содержащий только один поток. COM синхронизирует все обращения к методам COM-объекта, находящегося в STA. Делается это через очередь сообщений Windows. Каждый поток клиентского процесса или внепроцессного (EXE) сервера, желающий работать в отдельном апартаменте, должен вызвать CoInitialize или CoInitializeEx с параметром dwCoInit, равным COINIT_APARTMENTTHREADED (эти вызовы и приводят к инициализации потока и выделению ему синхронизирующей очереди сообщений). Внутрипроцессные объекты, располагающиеся в DLL (в том числе COM+/MTS-объекты), регистрируют свою поточную модель в реестре с помощью именованного значения ThreadingModel внутри ключа InprocServer32. При передаче указателей на интерфейсы между разными STA, или между STA и MTA, должен обязательно производиться маршалинг. При передаче указателя на интерфейс как параметра метода какого-нибудь интерфейса, или при создании нового COM-объекта, маршалинг делается автоматически. Если необходимо вручную (без использования COM, например, через общую область памяти) передать указатель на интерфейс другому потоку, необходимо произвести «ручной» маршалинг.

Для «ручного» маршалинга используется функция CoMarshalInterThreadInterfaceInStream (пожалуй, эта функция – рекордсмен среди Win32 API-функций по длине имени). В качестве первого параметра ей передается IID интерфейса. В качестве второго – указатель на этот интерфейс. В качестве третьего – указатель на интерфейс IStream, в который и будет записана информация о передаваемом интерфейсе. Перед вызовом этой функции необходимо создать объект, реализующий интерфейс IStream. Самый простой способ сделать это – воспользоваться API-функцией CreateStreamOnHGlobal, создающей Stream, базирующийся на оперативной памяти. Последнее, что необходимо сделать – это из другого потока выполнить обратный маршалинг. Для этого необходимо вызвать CoGetInterfaceAndReleaseStream. Ручной маршалинг очень редко применяется на практике. Если же возникнет необходимость делать его часто, то можно создать простой класс-обертку, или воспользоваться глобальной (для процесса) таблицей интерфейсов. Она похожа на AOM (Active Object Map) в Corba. Управление глобальной таблицей интерфейсов производится через стандартный интерфейс IGlobalInterfaceTable. Указатель на него можно получить, создав COM-объект с CLSID CLSID_StdGlobalInterfaceTable. Еще один способ избавиться от «ручного маршалинга» – агрегировать специальный объект FreeThreadedMarshaler. Для создания такого объекта используется функция CoCreateFreeThreadedMarshaler. ATL Wizard позволяет автоматизировать всю работу по созданию и агрегированию ThreadedMarshaler.

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

Процесс может содержать любое количество STA.

Многопоточный апартамент (MTA) – это апартамент, содержащий несколько потоков. MTA еще называют свободно-поточным. Маршалинг указателей на интерфейсы в MTA-апартаменте не производится, а любой компонент может получать вызовы своего метода из любого другого потока, входящего в этот же MTA. При этом вызовы методов компонента могут осуществляться параллельно. Параллелизм многопоточных объектов обеспечивает высочайшую производительность и полностью использует преимущества многопроцессорного аппаратного обеспечения. Он позволяет пользователям работать, не дожидаясь окончания длительных операций, выполняемых другими пользователями. Однако синхронизация, и вообще все, что связано с потокобезопасностью, возлагается на код вызываемого компонента. Для синхронизации при этом можно использовать стандартные средства Windows (Мьютексы, Семафоры, События и, конечно, оконные очереди). Создание корректно работающего многопоточного ПО – задача не из простых. Многопоточная отладка, пожалуй, самая неудобная сторона современного программирования, но получаемые преимущества зачастую перевешивают трудности.

Процесс может содержать только один MTA.

Все процессы имеют апартаментную модель, просто некоторые апартаменты содержат один поток, а некоторые – несколько. Поточная модель применима к апартаменту, а не к процессу. Она также может быть применима к CoClass’у но не к DLL-модулю. Разные классы в одной DLL могут иметь разные модели потоков. Но, все же, ATL позволяет определять так называемую глобальную потоковую модель, влияющую на потоковые характеристики всех входящих в модуль объектов.

Процесс, содержащий два или более STA, и не содержащий MTA, называется процессом с апартаментной моделью (apartment model process).

Процесс с одним потоком является частным случаем STA-модели. Такой процесс называется однопоточным (single-threaded process).

Процесс, содержащий MTA и один или более STA, называется процессом со смешанной моделью (mixed model process).

Процесс, содержащий MTA и не содержащий STA, называется свободно-поточным процессом (free-threaded process).

Главным апартаментом процесса считается тот, который был первым проинициализирован. В однопоточном процессе он остается единственным.

Модели потоков в COM предоставляют механизм совместной работы клиентов и серверов, использующие различные архитектуры потоков. Поддерживаются вызовы между объектами с разными моделями потоков как в разных процессах, так и внутри одного. С точки зрения вызывающего объекта все вызовы объектов вне процесса выглядят одинаково, неважно, какие потоки использует вызываемый объект, и наоборот.

В СОМ не существует аналогичной CORBA сессионной идеологии работы с потокками, когда все вызовы от одного клиента выполняются в одном потоке, а другого – в другом. Но если каждый клиент будет создавать отдельную копию STA-компонента, то внешне это будет похоже на сессионный метод CORBA. Единственное отличие заключается в том, что если двум таким компонентам взбредет в голову обратиться друг к другу, или к общему STA-компоненту, то в COM эти методы будут автоматически синхронизированы.

Выбор поточной модели для объекта зависит от назначения этого объекта. Объекту, управляющему некоторым общим ресурсом, (объекту, к которому производятся частые параллельные обращения), лучше поддерживать многопоточность. Это обеспечит максимальную эффективность при обслуживании клиентов. С другой стороны, объект, взаимодействующий с одним пользователем, может поддерживать STA для синхронизации вызовов.

Межпроцессная и межпоточная модели похожи. Если нужно передать указатель на интерфейс объекту в другом апартаменте (или потоке) в этом же процессе, используется такая же модель маршалинга, что и для объектов в разных процессах. С помощью указателя на стандартный объект маршалинга можно произвести маршалинг указателей на интерфейсы через границы потока (апартамента) так же, как и между процессами.

На сегодня большинство языков программирования позволяют создавать COM-объекты в STA-архитектуре, но только C++ позволяет создавать многопоточные компоненты. Библиотека ATL позволяют существенно облегчить эту непростую задачу. Скоро должна появиться седьмая версия VB. Разработчики обещают ввести в эту версию поддержку режима свободных потоков.

Масштабируемость Организация пула объектов.

Организация пула объектов – сервис COM+, позволяющий создавать пул загруженных в память экземпляров объектов, готовых к использованию любым клиентом. Пул для компонента можно настраивать, задавая такие характеристики, как размер пула и значение таймаута. Пока приложение работает, COM+ управляет этим пулом, деталями активации и повторного использования объектов согласно заданным вами критериям. Пул объектов может существенно повысить производительность распределенного приложения. Когда клиент пытается создать новый объект, COM+, вместо того, чтобы лазить по реестру и загружать что-то в память, выделяет клиенту готовый объект из пула. После завершения клиентом работы с объектом и освобождения ссылки, COM+ не уничтожает объект, а снова возвращает его в пул.

Just-in-Time (JIT) -активация

Just-in-Time (JIT)-активация – предоставляемый COM+ сервис, помогающий более эффективно использовать ресурсы сервера, особенно при использовании большого количества транзакций. Если компонент сконфигурирован, как JIT-активируемый, COM+ будет порой деактивировать его экземпляр, несмотря на то, что клиент по-прежнему имеет активную ссылку на объект. При следующем вызове метода объекта клиентом, считающим объект активным, COM+ снова активирует объект прозрачно для клиента.

Преимущество такого подхода состоит в том, что клиент держит ссылку на объект столько времени, сколько ему нужно, не тратя ресурсов сервера, например, памяти.

Shared Property Manager

С помощью Shared Property Manager (SPM) можно в пределах одного COM+-приложения организовать набор свойств, хранящих общие данные для нескольких компонентов. Это позволяет получить простой способ передачи данных между компонентами, реализованными в разных DLL-библиотеках. При доступе к этим свойствам SPM автоматически осуществляет синхронизацию.

Обеспечение безопасности

Безопасность в COM основывается на службах безопасности Windows NT и Windows 2000, и механизме безопасности RPC. Базовыми понятиями являются аутентификация (процесс определения прав пользователя) и авторизация (процесс определения, имеет ли пользовать право делать то, что ему требуется). Два основных типа безопасности в COM – безопасность активации (activation security) и безопасность вызова (call security). Безопасность активации определяет, имеет ли клиент право запустить сервер. После запуска сервера вы можете использовать безопасность вызова для контроля доступа к серверным объектам.

Начиная с Windows NT 4.0, средства безопасности COM были значительно расширены. В новой модели серверы управляют объектами и защищают их, клиенты получают доступ к объектам через серверы, а серверы могут получать доступ от лица клиента.

В Windows 2000 реализованы протокол аутентификации Kerberos v5 и пакет средств безопасности SChannel. Появились такие возможности, как имперсонация на уровне делегирования (delegate-level impersonation), взаимная аутентификация (для протокола Kerberos), возможность устанавливать уровни аутентификации для AppID реестра, и маскировка (cloacking). Используя средства безопасности COM, можно реализовать объекты, способные выполнять привилегированные операции без ущерба для безопасности.

Два главных интерфейса, IClientSecurity и IServerSecurity, позволяют программно управлять безопасностью на уровне вызова.

Безопасность в COM+

COM+ предоставляет средства безопасности, которые можно использовать для защиты приложений COM+. COM+ позволяет управлять защитой как с помощью визуального задания через Component Services атрибутов защиты, так и программно, вызывая в коде специальные API-функции. Вот краткое перечисление расширений стандартных механизмов защиты NT, появившихся в COM+:

Декларативная ролевая (основанная на ролях) безопасность. Программно реализуемая ролевая безопасность. Службы аутентификации. Заимствование прав/делегирование (Impersonation/delegation). Автоматически предоставляемые COM+ службы – ролевая безопасность и аутентификация – позволяют вынести всю функциональность, связанную с безопасностью, за пределы ваших компонентов. Достаточно включить и настроить соответствующие сервисы, и COM+ возьмет на себя детали обеспечения указанной политики безопасности.

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

Ролевая безопасность – главное средство безопасности приложений COM+. Используя роли, можно автоматически создать политику авторизации, указывая (если нужно, вплоть до уровня метода), кому какие ресурсы доступны. Кроме этого, ролевую безопасность можно применять программно, если приложение требует более тщательного контроля доступа.

Аутентификация – прежде, чем авторизовать клиентов для доступа к ресурсам, следует убедиться, что они – именно те, за кого себя выдают. Это и делают службы аутентификации COM+. Вы просто административно включаете сервис аутентификации, работающий прозрачно для приложения.

Заимствование прав/делегирование (Impersonation/Delegation) – иногда приложению нужно выполнить некие действия от имени клиента, используя права клиента, например, при доступе к БД, аутентифицирующей клиента. Это называется заимствованием прав, или имперсонацией. Возможны разные уровни имперсонации. Чтобы задействовать ее, следует, во-первых, административно ее настроить, и, во-вторых, прописать её поддержку в коде компонентов приложения.

Сегодня реально существуют три уровня имперсонации: идентификация (identify), имперсонация (impersonate), и делегация (delegate). До появления W2K их было два – идентификация и имперсонация. В W2K появился третий, делегация.

На уровне идентификации (RPC_C_IMP_LEVEL_IDENTIFY), используемом системой по умолчанию, сервер может получить атрибуты защиты клиента. Сервер может использовать эти атрибуты защиты для проверок ACL. На уровне имперсонации (RPC_C_IMP_LEVEL_IMPERSONATE), сервер может имитировать контекст безопасности клиента, действуя от его лица. Сервер может работать с локальными ресурсами как клиент. Если это локальный сервер, он может от имени клиента обращаться к сетевым ресурсам. Если же удаленный, то он может работать только с ресурсами той машины, где он находится. Уровень делегации – наиболее мощный уровень имперсонации. При выборе этого уровня сервер (локальный или удаленный) также имитирует контекст безопасности клиента, действуя от его лица. В процессе имперсонации и локальные, и сетевые права клиента могут быть переданы любому количеству машин.

Для работы на уровне делегации нужно выполнять следующие требования:

Клиент должен установить уровень имперсонации RPC_C_IMP_LEVEL_DELEGATE. Учетная запись клиента не должна быть помечена как "Account is sensitive and cannot be delegated" в Active Directory Service. Учетная запись сервера должна быть помечена в Active Directory Service атрибутом "Trusted for delegation". Все задействованные компьютеры должны работать под W2K и в домене W2K (поскольку требуется протокола Kerberos).

Выбором уровня имперсонации клиент сообщает серверу, как далеко тот может зайти. Клиент задает уровень имперсонации через proxy, используемую для связи с сервером.

Контрольный журнал (Audit Trails) – ролевая безопасность позволяет отслеживать обращения к вашему компоненту, вплоть до ведения детальных логов.

Безопасность активации

Безопасность активации (называемая также безопасностью запуска) контролирует, кто имеет право запускать сервер. Эту службу автоматически исполняет Service Control Manager (SCM) конкретной машины. При получении запроса на активацию объекта от клиента, SCM проверяет запрос на основе информации, хранящейся в его реестре (в том числе и для вызовов, поступающих с этой же машины).

W2K, определяя права клиента при активации, проверяет, установлен ли клиентом флаг маскировки. Клиент может сделать это, вызвав CoInitializeSecurity. Если да, то для идентификации клиента используется маркер потока (если он имеется). Если маскировка выключена, вместо маркера потока используется маркер процесса.

Windows 9х и MacOS не поддерживают безопасности активации.

Маскировка (Cloaking)

Маскировка определяет, от чьего имени клиент обращается к серверу в процессе имперсонации. Если включить маскировку, промежуточный сервер будет предъявлять вызываемому серверу права пользователя, от имени которого делается вызов. В сущности, личность клиента, видимая серверу – это клиент, ассоциированный с proxy. Права proxy определяются несколькими факторами, один из которых – заданный тип маскировки.

Безопасность в Windows 95 и Windows 98

Если сеть построена из машин под Windows 9х, важно, имеется ли в сети домен Windows NT/W2K. Если домен присутствует, то Windows 9х может обеспечить аутентификацию и авторизацию через сквозной механизм безопасности. Если же нет, то проверка безопасности при вызовах невозможна.

Поскольку по умолчанию COM пытается выполнить проверку безопасности, то для успешного вызова COM-сервера клиентом в сети Win9х нужно изменить настройки COM, отключив проверку безопасности.

Устойчивость к сбоям Context

Сам по себе контекст не имеет отношения ни к устойчивости к сбоям, ни к другим возможностям COM+, но именно через него производится управление сервисами, ответственными за это.

Context – новинка MTS, COM+ и Windows 2000. Каждый COM-объект создается с ассоциированным контекстом. В случае объектов, не использующих служб COM+, контекст в значительной степени игнорируется. Но для приложений, сконфигурированных в COM+, контексты – это основа, на которой обеспечивается сервис COM+.

Контекст – это, в сущности, набор runtime-свойств одного или нескольких объектов. Каждый COM-объект во время исполнения (то есть, между его активацией и деактивацией) ассоциирован с единственным контекстом. Каждый контекст существует в единственном апартаменте COM, но в одном апартаменте могут сосуществовать несколько контекстов. В одном контексте могут исполняться несколько объектов. (Следует пояснить, что объекты, агрегирующие Free Threaded Marshaller, являются исключением – они исполняются в любом контексте).

COM+ использует контекстные свойства как основу предоставляемых run-time служб. Эти свойства содержат состояние, которое определяет, как среда исполнения будет осуществлять сервис для объектов в контексте. Изменяя эти свойства, можно влиять на состояние и поведение run-time служб СОМ+.

Каждому контексту соответствует ассоциированный объект, ObjectContext, следящий за его свойствами. Вы можете получить доступ к ObjectContext, вызвав CoGetObjectContext. Получив ObjectContext, вы можете воспользоваться его интерфейсом IObjectContext для работы с контекстными свойствами.

Кроме IObjectContext есть еще несколько специализированных интерфейсов для доступа к контекстным свойствам: IObjectContextInfo, IContextState и IObjectContextActivity.

Транзакции

MTS и COM+ поддерживают так называемый «механизм автоматических транзакций». Транзакции выполняются на уровне компонентов, т.е. каждый компонент, участвующий в транзакции необходимо специальным образом пометить. Это можно сделать как визуально, так и программно. Run-time система COM+ считывает эту информацию и исходя из нее, создает для компонента новую транзакцию или помещает компонент в уже существующую. Компонент может управлять транзакцией с помощью контекста компонента (см. «Context»).

Compensating Resource Manager

Compensating Resource Managers (CRM) – эта особенность Component Services позволяет просто интегрировать ресурсы приложений с транзакциями Microsoft Distributed Transaction Coordinator (MS DTC). Это альтернатива разработке полного менеджера ресурсов Microsoft Transaction Services. С помощью пары COM-компонентов и реализации специфичных интерфейсов ресурсы вашего приложения могут влиять на результат транзакции и получать оповещение о ее результате.

CRM – это пара компонентов COM – CRM Worker и CRM Compensator. CRM Worker отвечает за основную часть работы CRM и реализует COM-интерфейс, характерный для выполняемой им задачи. Инфраструктура CRM предоставляет интерфейс к CRM Worker, через который CRM Worker может помещать записи в лог-файл. Эти записи применяются для правильного восстановления данных в случае сбоя.

CRM Compensator – компонент, создаваемый инфраструктурой CRM после завершения транзакции. Он реализует определенный интерфейс, через который инфраструктура CRM может передать уведомления о завершении транзакции и записи в лог-файле, сделанные CRM Worker.

Использование кластеров серверов

W2K Advanced Server и Data Center позволяют организовать кластеры из нескольких серверов. Это обеспечивает дополнительную устойчивость серверных COM+-приложений за счет автоматического переключения на другой сервер в случае сбоя одного из них. Использование продукта Microsoft AppCenter Server позволяет распределять нагрузку между серверами с помощью CLB (COM+ Load Balancing), что дает возможность запуска компонентов на наименее загруженном сервере. Это повышает производительность и доступность приложения.

Асинхронное взаимодействие между объектами Queued Components QC-компоненты являются модификацией COM+/MTS-компонентов. QC-компоненты отвечают требованиям механизма Store-and-Forward. Клиентское приложение может использовать QC-компоненты, так же, как любой другой COM-объект. Они основаны на COM, но в качестве транспортного протокола вместо RPC используют MSMQ-сервис (сервис асинхронной работы с очередями сообщений от Microsoft). Классический DCOM использует синхронный RPC для передачи информации от одного компьютера другому посредством заместителей (proxy) и заглушек (stub).

QC взаимодействует между клиентским proxy и stub сервера с помощью MSMQ.

Во время активизации QC-объекта, клиент подключается не к COM-объекту, а к Са11 Recorder’у. Клиентское приложение производит вызовы как обычно, но они не отправляются немедленно через RPC. Вместо этого информация о них записывается в очередь MSMQ. Когда клиент деактивирует компонент (вызывает Release()), QC использует MSMQ для асинхронной посылки последовательности вызовов серверу, на котором создается реальный компонент. Сервер читает сообщения из MSMQ-очереди, содержащей эту последовательность, активизирует компонент Player (исполнитель) и передает ему информацию о вызовах. Player создает реальный объект и воспроизводит на нем вызовы.

Достоинства QC:

QC является высокоуровневой технологией. С его помощью проще программировать, чем при прямом использовании MSMQ. Не нужно учитывать мелкие технологические детали. Создаваемые разработчиком COM-компоненты могут быть использованы асинхронно как через QC сервис, так и синхронно, через DCOM.

С помощью QC-компонентов можно реализовать режим работы off-line и добиться полной независимости клиента от сервера. Их единственным недостатком является то, что они доступны только на W2K.

COM+ Events

Распределенные приложения, в которых сервер должен оповещать клиентов об изменениях важной для них информации, встречаются довольно часто. В таком приложении трудно понять, кто является сервером, а кто – клиентом. Клиенты в момент получения уведомлений сами становятся серверами. Чтобы избежать путаницы, следуя примеру Microsoft, назовем программу, рассылающую сообщения «Издателем» (Publisher), а приложения, заинтересованные в получении уведомлений – «Подписчиками» (Subscriber).

Система поддержки событий COM+ – это служба операционной системы, предоставляющая базовые сервисы поддержки событийного механизма. Событие в этой системе описывается как метод некоторого COM-интерфейса. Интерфейс необходимо поместить в «COM+»-объект, который в свою очередь специальным образом зарегистрировать в системе поддержки событий. Не правда ли, очень похоже на сказку про Василису прекрасную – «Смерть кощеева на конце иглы, игла в яйце, яйцо в утке, утка в зайце...». Рассылка событий – процесс, инициируемый издателем и осуществляющий уведомление подписчиков о случившемся событии. Издатель – любая программа, инициирующая событие посредством вызова метода интерфейса, принадлежащего событийному объекту. Подписчик – «COM+»-объект, реализующий тот же самый событийный интерфейс, что и событийный класс. Подписчик должен быть зарегистрирован как обычный «COM+»-компонент. Для того чтобы получать уведомления о событиях необходимо, чтобы для этого подписчика была оформлена (создана) подписка. Подписка – формальная регистрация производящаяся на компьютере где установлен класс событий. Сама подписка производится в ветке subscriptions на зарегистрированном здесь же подписчике. Класс событий (или Событийный класс) – «COM+»-объект, содержащий событийный интерфейс. Этот объект должен быть специальным образом зарегистрирован в системе поддержки событий. Ни одного экземпляра этого класса никогда не создается. При попытке создания экземпляра этого класса система поддержки событий создает специальный событийный объект, указатель на который и передается Издателю. Событийный объект – квази-компонент создаваемый на базе описания событийного интерфейса, взятого из событийного класса. Событийный интерфейс – интерфейс, который должны реализовать все подписчики, которые хотят получать уведомления о событиях. Вызов методов этого событийного интерфейса и инициирует рассылку сообщений. Заметьте, что подписка может быть осуществлена на конкретный метод этого интерфейса, но реализовать интерфейс придется полностью. Это не проблема, так как VB скрывает сложности реализации интерфейсов, а в VC есть визард, автоматизирующий эту операцию.

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

Издатель создает событийный объект вызовом функции CreateObject (CoCreateInstance – для C++).

Через вызов необходимого метода событийного интерфейса издатель инициирует рассылку уведомлений о событии (на языке американских программистов – производит Firing).

Система поддержки событий просматривает базу данных (COM+ каталог) в поисках зарегистрированных подписчиков (на соответствующий интерфейс или метод). Она связывается с подписчиками (создавая их напрямую, через моникер или Queued-компонент, как указано в базе данных подписки («COM+»-каталоге)) и уведомляет их о событиях, вызывая у них соответствующий метод событийного интерфейса.

Регистрируя класс событий, мы сообщаем системе поддержки событий, какие интерфейсы и методы содержит класс событий. Описание класса событий хранится в COM+-каталоге. Произвести регистрацию можно либо с помощью средств администрирования COM+, либо программным способом.

В системе поддержки событий подписку производит наделенное администраторскими правами лицо (через консоль Component Services) или программа. Подписка – это структура данных, снабжающая сервис событий информацией о подписчике. Она определяет, кто и от кого будет получать уведомления. Естественно, в терминах COM «кто и от кого» определяются через CLSID соответствующих объектов. Кроме CLSID подписчика можно вдобавок указывать и имя компьютера, на котором необходимо создавать объект-подписчик.

Подписка может быть постоянной (persistent) или временной (transient). В случае постоянной подписки COM+ сам создает объект подписчика (на машине подписчика) так же, как и в случае с QC. Transient-объект должен быть создан вручную и подключен через соответствующий API. Вышесказанное означает, что постоянный объект можно регистрировать визуально, но он не может загружаться в адресное пространство клиентского приложения. Постоянный объект может переживать перезагрузку компьютера. Transient-объект не переживает перезагрузки, но может быть создан внутри любого приложения. Как издатель, так и подписчик могут быть Queued-компонентами и, соответственно, работать асинхронно.

Подробнее о COM+ Events можно прочесть в номере нашего журнала за 4 квартал 1999 года.

Асинхронные вызовы

В W2K появилась возможность осуществлять асинхронные вызовы компонентов без использования Queued-подсистемы. При этом асинхронность достигается за счет вызовов в отдельных потоках. Подробнее об этой технологии вы можете прочесть в статье «Асинхронные вызовы методов COM-компонентов в Windows 2000» в этом номере журнала.

Впервые статья была опубликована в журнале <Технология Клиент-Сервер>. Эту и множество других статей по программированию, разработке БД, многоуровневым технологиям (COM, CORBA, .Net, J2EE) и CASE-средствам вы можете найти на сайте http://www.optim.ru/ и на страницах журнала. Ваши предложения и замечания вы можете посылать по адресу tcs@optim.ru.

Загрузка нескольких RTF файлов в RichEdit.

Автор: McAndrews
//Кидаем на форму RichEdit1, Button1, OpenDialog1
//и по клику кнопки создаём следующую процедуру:
procedure TForm1.Button1Click(Sender: TObject);
var
  i, nFiles: integer;
  FileNames, UnitedText, Separator, Tmp: string;
  TextStream: TStringStream;
begin
  TextStream := TStringStream.Create('');
  Separator := '\par \par \par '; // это разеделитель между выводимыми
  // файлами, в данном случае - 3 параграфа
  UnitedText := '';
  OpenDialog1.Options := OpenDialog1.Options
    // что бы можно было выбрать несколько
  + [ofAllowMultiSelect]; // файлов
  RichEdit1.MaxLength := $7FFFFFF0; // предусмотрительно увеличиваем максимальный
  // объём загружаемых данных в RichEdit1
  try // а вдруг что..
    if OpenDialog1.Execute then
    begin
      nFiles := OpenDialog1.Files.count - 1;
      for i := 0 to nFiles do
      begin
        FileNames := OpenDialog1.Files.Strings[i];
        RichEdit1.Lines.LoadFromFile(FileNames);
        // открываем каждый файл поочереди
        // в RichEdit1
        RichEdit1.Lines.SaveToStream(TextStream);
        // и записываем данные уже от туда в
        // поток TextStream,
        Tmp := TextStream.DataString; // а из потока во временную
        // переменную Tmp типа String,
        TextStream.Position := 0; // ставим указатель в потоке на 0,
        if i = 0 then // проверяем - является ли данный
          Tmp := copy(Tmp, 0, length(Tmp) - 5) // файл первый, последним, или
        else if i = nFiles then // или между ними, в зависимости
          Tmp := Separator + copy(Tmp, 2, length(Tmp))
            // от этого удаляем управляющие
        else // символы начала или окончания
          Tmp := Separator + copy(Tmp, 2, length(Tmp) - 5);
            //  RTF-файла и всталяем разделитель.
        // дело в том, что каждый RTF-файл
        // начинаетсz c символа '{' и заканчивается '}'.
        UnitedText := UnitedText + Tmp; // записываем всё в переменную UnitedText
      end;
      TextStream.WriteString(UnitedText);
      // помещаем уже объеденённый текст в поток
      TextStream.Position := 0; // ставим указатель на 0
      RichEdit1.Lines.LoadFromStream(TextStream);
      // и записываем потом в RichEdit1
    end;
  finally
    TextStream.Free;
  end;
end;

Формат Rich Text (RTF).

Встречаются вирус и FAT. - Привет, ты кто? - Я? Вирус. - A я AFT, то есть TAF, то есть FTA, черт, совсем запутался...

Данный формат был определен фирмой Microsoft как стандартный формат для обмена текстовыми документами. Следовательно, по назначению этот формат подобен SYLK-формату для электронных таблиц. RTF поддерживается многими продуктами фирмы Microsoft. Так, например, начиная с версии 2.0, он введен в Windows в качестве Clipboard-формата, благодаря чему возможен обмен данными между различными прикладными программами Windows. Кроме того, RT-формат поддерживается программами WORD для Macintosh, начиная с версии 3.X, и WORD для PC, начиная с версии 4. X.

В RTF для обмена документами используются только представимые символами коды из ASCII-, MAC- и PC-символьного набора. Кроме текста, файл в RT-формате в читаемой форме содержит команды управления.

Документ состоит преимущественно из команд управления настройки программы чтения файлов в RTF-формате. Эти команды можно разделить на управляющие слова (control words) и управляющие символы (control symbols).

Управляющее слово представляет собой последовательность символов с разделителем (delimiter) в конце:

\lettersequence <delimiter>

Перед управляющим словом вводится обратная косая черта "\" (backslash). В качестве разделяющих могут использоваться следующие символы:

Ш Пробел (space), причем этот символ относится к управляющему слову; Цифра или знак "-". После этих символов должен следовать параметр с разделителем. В качестве разделителя может быть использован пробел или другие символы, кроме цифр и букв;

Все символы, отличные от цифр и букв. Эти символы не относятся к управляющему слову.

В RT-формате для задания управляющей последовательности используются буквы от "А" до "Z" и от "а" до "z", а также цифры от "0" до "9". Национальные символы к управляющей информации не относятся.

В качестве, управляющих символов используются отдельные буквы. Перед каждым управляющим символом вводится обратная косая черта "\":

\control symbol

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

В RT-формате существует возможность объединять отдельные последовательности в группы при помощи скобок:

{ начало группы } конец группы

Такие группы создаются, например, при описании сносок, колонтитулов и т.п. Если необходимо разместить символы "\", "{" или "}" внутри обычного текста, то перед ними надлежит поставить обратную косую черту:

\\ \{ \}.

Это позволяет программе, читающей RTF-последовательность, распознать, что символ не следует интерпретировать как управляющий.

В RT-формате используются также некоторые символьные коды для управления печатью.

Код Значение
09Н Табулятор
0АН Символ CR
0СН Символ LF

Символы CR и LF, расположенные внутри текста, будут пропущены. Microsoft использует эти символы для большей наглядности при представлении RTF-файла. Внутри управляющих слов символы CR и LF могут иметь особое значение, подробнее об этом будет сказано при описании команд.

Специальные управляющие слова (special control words).

В RTF имеются управляющие слова специального назначения. Ниже приведены эти слова и их назначение.

\chpgn Управляющее слово change page number выводит текущий номер страницы. \chftn Управляющее слово change footnote активизирует автоматическую нумерацию сносок. \chdate С помощью change date можно вывести текущую дату. \chtime С помощью change time можно вывести текущее время. \chatn Ссылка на примечание (текст следует в группе). \chftnsep Свидетельствует об изменении линии, отделяющей сноски от текста. \/ С помощью данной последовательности вводится текст формулы. \: Обозначает подпункт индекса. \* Текст может быть пропущен при чтении RTF-формата. \~ Задает жесткий (неразрываемый) пробел между двумя словами, т.е. в этой позиции предложение не может быть разделено при переносе на следующую строку. \- Символ обозначает мягкий перенос (nonrequired hyphen). \_ Символ обозначает неразрываемый перенос (nonbreaking hyphen), на месте которого слово не может быть разделено. \'hh Последовательность позволяет прямой ввод в тексте шестнадцатеричных чисел. Они задаются вместо символов hh. \page Данная последовательность задает переход на новую страницу. \line Эта последовательность вызывает переход на новую строку в тексте. \раг С помощью данной последовательности в тексте отмечается конец абзаца. Последовательность \раг может заменяться последовательностью \10 или \13. При этом \10 соответствует ASCII-символу с кодом 10 (carriage return). Непосредственно ввести ASCII-код 10 (CR) нельзя, т.к. он игнорируется программой чтения. \sect Эта последовательность обозначает конец текстового фрагмента или текстового абзаца. \tab Данная последовательность заменяет табулятор. Можно также непосредственно задавать ASCII-код 09Н. \се11 Конец таблицы (столбец). \row Конец таблицы (строка).

Операторы настройки (destination control words).

С помощью описанных ниже управляющих слов можно произвести основную настройку программы чтения RTF. Они могут встречаться только в начале документа или в начале группы. Все операторы вместе с параметрами должны быть заключены в скобки, например:

{\rtf0\pc.......}

Ниже описан формат некоторых операторов.

\rtf <параметр>

Задает метку начала файла. В качестве параметра программа, при помощи которой создается файл, может указывать номер версии. Например:

{\rtf0.......}

Метка должна стоять в начале файла. После этого оператора могут следовать другие операторы или закрывающая скобка.

Установка вида кодировки для сохраняемого текста осуществляется с помощью следующих операторов:

\ansi: текст хранится в стандартном ASCII-формате. Этот формат используется, например, в Windows. \mac:. текст хранится в кодировке Macintosh \рс: для вывода текста используется кодировка символов IBM-PC. \рса: IBM-PC Code Page 850 (модель PS\2).

Для передачи текстов между различными системами должна использоваться кодировка \ansi.

\colortbl

Windows и другие программы пользуются палитрами для определения используемых цветов. Чаще всего палитра состоит из 16 различных цветов, получаемых путем комбинации основных цветов - красного, зеленого и синего. Команда \colortbl позволяет изменить отдельные определения в палитре. Для каждого цвета в таблице указывается величина красной, зеленой и синей составляющей. Каждый цвет описывается тремя параметрами:

\red000 \green000 \blue000

Вместо 000 указывается величина доли цвета в диапазоне от 0 до 255. Пример: в таблице, состоящей из 16 цветов, должны быть заново определены цвета 0-й и 2-й. Для переопределения цветов может быть задана следующая последовательность:

{ \colortbl \redl28\green64\bluel28\;;\red0\green64\bluel2 8; }

Определение цвета завершается точкой с запятой. Поскольку 1-й цвет остается без изменений, в последовательности два символа ";" следуют один за другим. Последовательность завершается закрывающей скобкой.

Оператор \cfn определяет цвет фона (при стандартной настройке n=0), а оператор \cbn - цвет символа (при стандартной настройке n=0).

\fonttbl

Данный оператор используется для построения таблицы шрифтов и соотносит имя шрифта (группы шрифтов) с номером шрифта. При определении шрифта можно использовать следующие операторы:

\fnil: имя группы шрифтов неизвестно. При выводе должен использоваться стандартный шрифт. \froman: должны использоваться шрифты группы Roman (например, Times Roman) \fswiss: этот оператор предписывает использование шрифтов из группы Swiss (Helvetica, Swiss и т.д.) \fmodern: могут использоваться шрифты Pica, Elite и Courier. \fscript: используются курсивные шрифты группы Script. \fdecor: при указании этого оператора шрифты должны выбираться из группы Decor (Old English и т.д.). \ftech: выбор группы шрифтов с техническими и математическими символами (SYMBOL и т.д.).

Команда для создания группы шрифтов может состоять из таких операторов:

{\fonttbl\f0\fnil default;}
{\fl\froman roman h;}
{\f2\fswiss helvetica;}

После ключевого слова \fonttbl следует первый номер шрифта \f0. Далее идет определение группы шрифтов. Здесь после номера 0 указано \fnil, т.е. имя группы неизвестно. Следующий параметр обозначает имя выбираемого шрифта (например, roman h). Указание default говорит считывающей программе о том, что следует использовать стандартный шрифт. После имени шрифта стоит точка с запятой. В приведенном примере определены только шрифты с номерами 1 и 2. Вся последовательность может быть объединена фигурными скобками в группу.

Таблица шрифтов должна быть заполнена значениями до того, как встретится оператор \stylesheet или текст. Стандартный шрифт вводится оператором \deffn.

\stylesheet

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

\sbasedon000: вместо символов 000 указывается номер вида шрифта, который используется в качестве текущего. \snext000: назначает номер следующего вида шрифта (style), который - станет текущим.

Оператор может иметь, например, следующий вид:

{\stylesheet\s0\f3\fs20\qj Normal;}
{\sl\f3\fs30\b\qc Heading Level 3;}

Шрифты нумеруются числами от 0 до n (\s0...). В первой строке стилю абзаца 0 (\s0) предписывается имя Normal. Вывод текста выполняется шрифтом 3 (\f3), размером 10 пунктов (\fs20). Параметр \qj говорит о том, что надпись должна выравниваться по формату. Вторая строка определяет стиль шрифта 1, который получает имя Heading Level 3. Шрифт имеет размер 15 пунктов, жирное начертание (\Ь = bold) и при выводе надпись центрируется (\qc).

\pict

Этот оператор используется при описании рисунка в формате битовой карты (bitmap). Следующие параметры содержат описание рисунка в виде шестнадцатеричных значений. Параметры могут быть выборочно использованы для определения геометрии рисунка или его формата:

\pich000: оператор указывает высоту рисунка в пикселях. Вместо трех нулей подставляется числовое значение. Если оператор отсутствует, высота рисунка определяется из текущих графических данных. С помощью параметра можно задать для рисунка определенную высоту. \piw000: оператор задает ширину рисунка в пикселях. Справедливо все, сказанное о предыдущем операторе. \picscaled: с помощью этого оператора рисунок масштабируется таким образом, чтобы он точно заполнял отведенную для него область. \ wmetafilen: параметр указывает, что данные хранятся в формате Windows-метафайла. Параметр n определяет тип метафайла (1 = ММ-текст). \macpict: параметр указывает, что данные хранятся в Macintosh-формате Quick-Draw. \bin000:. параметр определяет количество байтов. Вместо трех нулей записывается шестнадцатеричное число. \wbitmapn: параметр определяет формат bitmap-рисунка. Параметр n задает тип (0 соответствует логической битовой карте). \brdrs: определяет простую рамку для рисунка \brdrdb: определяет двойную рамку для рисунка. \brdrth: определяет жирную линию для рамки рисунка. \brdrsh: определяет затененную рамку для рисунка \brdrdot: определяет для рисунка пунктирную линию. \brdrhair: определяет тонкую линию для рамки рисунка. \picwGoaln: задает желаемую ширину рисунка в twips \pichGoaln: задает желаемую высоту рисунка в twips. \picscalexn: задает горизонтальное масштабирование (n может принимать значения от 1 до 100, по умолчанию -10). \picscaleyn: задает вертикальное масштабирование (n может принимать значения от 1 до 100, по умолчанию - 10). \piccroptn: описывает верхнее поле рисунка в twips. При положительных значениях параметра n верхняя часть рисунка обрезается. Отрицательные значения n дают возможность построить вокруг рисунка рамку. Стандартное значение n равно 0. \рiссгорьп: описывает нижнее поле рисунка в twips. При положительных значениях параметра n нижняя часть рисунка обрезается. Отрицательные значения n дают возможность построить вокруг рисунка рамку. Стандартное значение n равно 0. \рiссгоргn: описывает правое поле рисунка в twips. При положительных значениях параметра n рисунок обрезается справа. Отрицательные значения n дают возможность построить вокруг рисунка рамку. Стандартное значение n равно 0. \piccropln: описывает левое поле рисунка в twips. При положительных значениях параметра п рисунок обрезается слева. Отрицательные значения п дают возможность построить вокруг рисунка рамку. Стандартное значение п равно 0. \wbmbitspixeln: определяет количество битов, приходящихся на пиксел, для bitmap-рисунка (стандартное значение равно 1). \ wbmplanesn: определяет количество битовых плоскостей (стандартное значение равно 1). \wbmwidtbbytesn: определяет длину битовой карты в байтах. \footnote Оператор должен следовать непосредственно после отметки сноски и относиться к тексту. \header Оператор определяет верхний колонтитул текущего фрагмента текста и поэтому должен указываться в начале текста. \headerl Оператор определяет верхний колонтитул четной страницы (left hand header), на которой колонтитул выводится с выравниванием влево. \headerr Оператор устанавливает верхний колонтитул нечетной страницы, на которой колонтитул выводится с выравниванием вправо (right hand header). \headerf Оператор устанавливает верхний колонтитул первой страницы (first page header). \footer Оператор устанавливает нижний колонтитул текущего фрагмента и должен указываться перед началом текста. \footerl Оператор устанавливает нижний колонтитул четной страницы (left hand footer), на которой колонтитул выводится с выравниванием влево. \footerr Оператор устанавливает нижний колонтитул нечетной страницы, на которой он выводится с левосторонним выравниванием (right hand footer). \footerf Оператор устанавливает нижний колонтитул первой страницы (first page footer). \ftnsep Оператор определяет символ-разделитель для сносок. \ftnsepc Оператор определяет разделитель для сноски, которая продолжается на следующей странице (continued footnote). \ftncn Этот оператор применяется для обозначения продолжения текста сноски. \info С помощью данного ключевого слова в начале документа открывается информационный блок. Он может содержать информацию о заголовке документа, дате его составления и т.д. Внутри информационного блока, который начинается оператором \info, могут встречаться следующие операторы: \title \subject \operator \author \keywords \doccomm \version \nextfile Перечисленные команды позволяют передавать вспомогательную информацию о документе, например, имя автора, версию текста, заголовок и т.д. Программа чтения RTF-данных не обязана обрабатывать команды информационного блока. \vern000 Вместо 000 в операторе записывается номер версии программы, в которой составлен документ. \creatim С помощью данного ключевого слова определяется дата и время создания документа. Эти данные кодируются с помощью таких операторов: \угО00: год создания документа \тоО00: месяц создания документа \dy000: день создания документа \hr000: время создания документа в часах \min000: время создания документа в минутах \sec000: время создания документа в секундах \revtim Определяет время и дату последней корректировки документа. Оба параметра задаются так, как указано в описании оператора \creatim. \printtim Определяет время и дату последней распечатки документа. Оба параметра задаются так, как указано в описании оператора \creatim. \buptim Определяет время и дату последнего сохранения документа (backup). Оба параметра задаются так, как указано в описании оператора \creatim. \edmins000 Хранит длительность (в минутах) последнего сеанса редактирования. \nofpages000 \nofwords000 \nofchars000 \id000 Вышеуказанные операторы содержат уточняющую информацию о документе (количество страниц, количество слов и количество символов), а также внутренний идентификационный номер. \comment В заголовке может встретиться текст, помеченный ключевым словом \comment. В этом случае речь идет о комментарии в тексте, который пропускается программой чтения данных в RTF-формате. \field Имеется группа операторов, при помощи которых описываются поля, вставляемые в WORD. Группа начинается ключевым словом \field, за которым могут следовать такие операторы: \flddirty: поле было изменено во время последней актуализации \fldedit: текст был отредактирован во время последней актуализации \fldlock: поле закрыто для доступа и не может быть актуализировано \fldpriv: результат не сообщается (например, рисунок) \xe Эта группа операторов начинается последовательностью символов \xe и может содержать следующие ключевые слова: \ьхе: для номеров страниц задается жирное начертание (bold). \ixe: для номеров страниц задается курсивное начертание (italic). \txe text: вместо номера страницы используется текст. \гхе bookmark: генерация номеров страниц для области, содержащей закладку (bookmark). \tc Группа содержит элементы данных для таблиц и может состоять из следующих ключевых слов: \tcfn: передается тип таблицы (n может иметь значения от А до Z, значение по умолчанию - С). \tcln: номер уровня (стандартное значение n = 1). В группу операторов, определяющих закладки, входят только два оператора: \*\bkmkstart: начало области \*\bkmkend: конец области

Операторы форматирования (document formatting).

В следующую группу входят команды для форматирования текста. Эти команды могут быть разделены на подгруппы:

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

В этом разделе описываются операторы форматирования всего документа.

\paperw000 Команда задает ширину листа в twips (1 twip равен 1/20 пункта или 1/1440 дюйма). Значение ширины указывается вместо 000. Если команда отсутствует, то ширина листа устанавливается равной 12240 twips. \рарегhО00 Команда задает высоту листа в twips. Значение высоты указывается вместо 000. Если команда отсутствует, то высота листа устанавливается разной 15840 twips. \margl000 С помощью этой команды можно установить ширину левого поля при печати в twips. Стандартная ширина поля равна 1800 twips. \margr000 С помощью этой команды можно установить ширину правого поля при печати в twips. Стандартная ширина поля равна 1800 twips. \margt000 С помощью этой команды можно установить ширину верхнего поля (margin top) при печати в twips. Стандартная ширина поля равна 1440 twips. \margb000 С помощью этой команды можно установить ширину нижнего поля (margin bottom) при печати в twips. Стандартная ширина поля равна 1440 twips. \facingp Оператор facing page определяет внешний вид страницы. Он устанавливает например, печатается колонтитул на четных или на нечетных страницах. Если з; оператором следует параметр 0, то вывод подавляется. \gutter000 С помощью этого оператора можно установить ширину поля подшивки (gutter внутри facing page. \deftab000 Команда определяет величину табулятора. Стандартное значение составляет 72 twips. \widowctrl Оператор включает wido-контроль. Если за оператором следует параметр 0, то контроль снова отключается. \endnotes Если в документе указан этот параметр, то тексты сносок размещаются в конце фрагмента. \ftobj По умолчанию тексты сносок выводятся в конце страницы (footnotes bottom justified). Данный оператор позволяет переключиться с установки, заданной опер тором \endnotes, на стандартную установку. \ftntj Тексты сносок выводятся в тексте (footnotes top justified). \ftnstart000 Вместо 000 команда содержит начальный номер первой сноски. Стандартное значение номера равно 1. \ftnrestart При указании этого оператора нумерация сносок на каждой странице начинается с 1. Параметр 0 отключает эту установку. \pgnstart000 Вместо 000 команда содержит номер начальной страницы. Стандартное значение номера равно 1. \linestart000 Значение, указанное вместо 000, задает номер начальной строки. Стандартное значение номера равно 1. \landscape Команда позволяет задать альбомную ориентацию (landscape). При указании параметра 0 восстанавливается книжная ориентация. \byphhotz Определяет область, которая обозначается как - Hyphenation hot zone. \ftnsep Определяет разделитель, который отделяет сноски от текста. \ftnsepc Определяет разделитель, который отделяет сноски, продолжающиеся на следующую страницу, от текста. \ftncn Отметка для следующей сноски. \enddoc Сноска в конце документа. \*\nextfile Передает (в скобках {}) имя файла вывода или индексного файла. \*\template После оператора в скобках {} указано имя файла шаблона (template), если оно отличается от принятого по умолчанию. \makeback Автоматическое создание Backup-файла, если документ защищен. \defformat Сообщает программе чтения RTF-данных о том, что документ должен быть защищен. \revison Включает отметку об изменениях. \margmirror Указывает на необходимость зеркально отобразить описания для левого и правого полей. \геvргорn Значение параметра n задает начертание и способ выделения символов при создании редакторских примечаний. Значение n, равное 0, соответствует нормальному начертанию, 1 - жирному, 2 - курсиву, 3 - подчеркиванию (стандартная настройка), 4 - двойному подчеркиванию. \геvьагn В зависимости от значения параметра n, задается способ выделения вертикальными линиями текста редакторских примечаний: 0 - не отмечается, 1 - отмечается левый край текста, 2 - отмечается правый край текста, 3 - отметка располагается за пределами текста (стандартная настройка) по краю страницы.

Форматирование секции (section formatting).

Вторая группа операторов управляет форматом секции (section) и состоит из следующих команд.

\sectd Оператор устанавливает стандартную настройку параметров секции. \sbknone Оператор подавляет переход на новую страницу перед началом секции (section break continuous). \sbkcol При указании этой команды в начале секции осуществляется переход на новую колонку (section break new column). \sbkpage При указании этой команды (section break new page) в начале секции происходит переход к новой странице. \sbkeven При указании этой команды в начале секции выполняется переход к новой странице, если номер страницы четный (section break even). \sbkodd При указании этой команды в начале секции выполняется переход к новой странице, если номер страницы нечетный (section break odd). \pgostartsn Нумерация страниц начинается с номера n. \pgnrestart Команда устанавливает временную нумерацию страниц, начиная со значения 1. Параметр 0 восстанавливает прежний порядок нумерации. \pgndec Номера страниц выводятся в десятичном формате. \pgnucrm Номера страниц выводятся большими римскими цифрами (upper case roman). \pgnlcrm Номера страниц выводятся маленькими римскими цифрами (lower case roman). \pgncont Сквозная нумерация страниц (стандартная настройка). \pgnucltr Номера страниц выводятся прописными символами (upper case letter). \pgnlcltr Номера страниц выводятся строчными символами (lower case letter). \pgnx000 С помощью этой команды задается X-координата номера страницы при автоматической нумерации. Значение указывается относительно верхнего левого угла страницы и по умолчанию устанавливается равным 720 twips. \pgny000 С помощью этой команды задается Y-координата номера страницы при автоматической нумерации. Значение указывается относительно верхнего левого угла страницы и по умолчанию устанавливается равным 720 twips. \linemod000 Оператор устанавливает величину интервала при нумерации строк. \linex000 Оператор устанавливает расстояние между строками. По умолчанию оно равно 360 twips. \linestartn Нумерация строк начинается со значения n. \linerestart Возвращает номер строки к значению 1. Команда устанавливает, что нумерация строк начинается с 1. \linepage На каждой странице нумерация строк начинается со значения 1. \linecont Нумерация строк продолжается с предыдущей секции (предыдущей главы). \headery000 Команда задает Y-координату верхнего колонтитула. Значение указывается относительно верхнего края страницы и по умолчанию принимается равным 720 twips. \footery000 Команда задает Y-координату нижнего колонтитула. Значение указывается относительно нижнего края страницы и по умолчанию принимается равным 720 twips. \vertalt Текст по вертикали выравнивается к верхнему краю страницы. Текст располагается в верхней части страницы (vertically align at top of page). \vertalc Текст размещается посредине страницы. Вертикальное выравнивание текста выполняется по центру страницы (vertical align centered). \vertalj Текст по вертикали размещается так, чтобы заполнить всю страницу (vertical align justified). \vertalb Вертикальное выравнивание текста выполняется по нижнему краю страницы (vertical align bottom). \cols000 С помощью этой команды можно задать количество столбцов в строке. Стандартное значение равно 1. \colsx000 С помощью этой команды устанавливается расстояние между двумя столбцами в twips. Стандартное значение равно 720 twips. \endnhere При указании этого оператора послетекстовые сноски выводятся в конце секции. Такой вывод можно подавить путем задания параметра 0. \titlepg Оператор задает вывод титульной страницы. Параметр 0 подавляет вывод титульной страницы.

Форматирование абзаца (paragraph formatting).

Следующие команды управляют форматированием абзаца (paragraph).

\pard Оператор устанавливает стандартную настройку для абзаца. \s000 Команда определяет шрифт для данного абзаца. С помощью подставляемого вместо 000 значения осуществляется выбор в таблице шрифтов. \ql При задании команды quad left текст выравнивается по левому краю страницы. \qr При задании команды quad right текст выравнивается по правому краю страницы. \qc При задании команды quad centered текст размещается по центру страницы. \qj При задании команды quad justified текст выравнивается по формату страницы. \fi000 Оператор определяет величину отступа первой строки абзаца (first line indent). Стандартное значение равно 0. \li000 Оператор определяет величину втяжки абзаца слева (left indent). Стандартное значение равно 0. \ri000 Оператор определяет величину втяжки абзаца справа (right indent). Стандартное значение равно 0. \sb000 Оператор задает количество пустых строк, вставляемых перед абзацем (spaces before). Стандартное значение равно 0. \sa000 Оператор задает количество пустых строк, вставляемых после абзаца (spaces after). Стандартное значение равно 0. \sl000 Команда устанавливает расстояние между строками в пунктах. Стандартное значение равно одной строке (12 пунктам). Команда \sl000 включает auto line mode. \intbl Абзац является частью некоторой таблицы. \keep При указании этой команды фиксируется принадлежность текста к текущему абзацу. Команда отключается указанием параметра 0. \кеерn При указании этой команды текст ставится в соответствие следующему (next) абзацу. Команда отключается указанием параметра 0. \sbys Указанием этого оператора может быть включен режим side by side. При задании параметра 0 режим отключается. \pagebb Указанием этого оператора может быть включена команда page break before. При задании параметра 0 команда отключается. \nо1inе При задании этой команды отключается нумерация строк. Указание параметра 0 подавляет команду. \brdrt Задает линию над абзацем (boarder top). \brdrb Задает линию под абзацем (boarder bottom).. \brdrl Задает линию слева от абзаца (boarder left). \brdrr Задает линию справа от абзаца (boarder right). \box Задает рамку вокруг абзаца. \brdrs Задает нормальную линию (single thickness) для рамки. \brdrtb Задается толстую (thick) линию для рамки. \brdsu Задает оттененную рамку (boarder). \brdrdb Задается двойную рамку (boarder). \brdrdot Рамка обозначается пунктирной линией. \brdrbair Рамка обозначается тонкой линией. \brspn Расстояние в twips между рамкой и объектом. \tqr Обозначает табулятор, относительно которого текст выравнивается вправо (right flush tab). \tgc Обозначает табулятор, относительно которого текст выравнивается по центру (centered tab). \tqdec Обозначает десятичный табулятор (decimal aligned tab). \tldot Обозначает табулятор, для которого пустое пространство заполняется точками (tab leader dot). \tibypben Обозначает табулятор, для которого пустое пространство заполняется дефисами (tab leader hyphen). \tlul Обозначает табулятор, для которого пустое пространство заполняется символами подчеркивания (tab leader underline). \tith Обозначает табулятор, для которого пустое пространство заполняется жирной чертой (tab leader thick line). \ \tx000 С помощью этой команды задается положение табулятора. \tb000 С помощью команды задается положение вертикального табулятора. При этом по всей длине текущего абзаца проводится вертикальная линия.

Следующие команды позволяют разместить абзац в определенной позиции листа.

\posxn Позиционирует абзац на расстоянии n twips от левого края. \posxc Абзац по горизонтали размещается по центру относительно указанного элемента (страницы, колонки). \posxi Абзац по горизонтали размещается внутри указанного элемента. \posxl Абзац по горизонтали размещается слева относительно указанного элемента. \posxo Абзац по горизонтали размещается вне указанного элемента. \posxr Абзац по горизонтали размещается справа относительно указанного элемента. \posysi Позиционирует абзац на расстоянии "n" twips от верхнего края. \posyil Позиционирует абзац в текущей позиции (inline). \posyt Абзац по вертикали размещается в верхней части указанного элемента (страницы, колонки). \posyc Абзац по вертикали размещается в центре указанного элемента. \posyb Абзац по вертикали размещается в нижней части указанного элемента. \abswn Абсолютная ширина текста абзаца в twips. \dxfrtextn Расстояние в twips по горизонтали между основным текстом и абсолютно позиционированным объектом (рамкой с текстом или рисунком). \pvmrg Позиция по вертикали относительно полей. \pvpg Позиция по вертикали относительно страницы. \phmrg Позиция по горизонтали относительно полей. \рhрg Позиция по горизонтали относительно страницы. \рhсо1 Позиция по горизонтали относительно столбца.

Для форматирования таблиц определены следующие команды.

\clbrdrb Нижний край таблицы. \clbrdrt Верхний край таблицы. \clbrdrl Левый край таблицы. \clbrdrr Правый край таблицы. \trowd Устанавливает стандартные значения строк таблицы. \trql Левостороннее выравнивание при размещении строк таблицы (с учетом столбцов). \trqr Правостороннее выравнивание при размещении строк таблицы (с учетом столбцов). \trqc Центрирование строк таблицы (с учетом столбцов). \trgapho Половина расстояния между ячейками таблицы (в twips). \trrbn Высота строки в twips. \trleftm Расположение левого угла таблицы. \cellxn Смещение правой границы таблицы. \clmgf Объединение (merge) первой ячейки с остальными ячейками таблицы. \clmrg Объединение ячейки с предыдущей ячейкой.

Форматирование символов (character formatting).

Данная группа операторов используется для форматирования символов (жирное начертание, подчеркивание и т.д.).

\plain С помощью этой команды устанавливаются стандартные параметры форматирования. \b Установка жирного начертания (bold) для печатаемого текста. Для отмены необходимо указать параметр 0. \i Задание курсивного начертания (italic) для печатаемого текста. Установка отменяется указанием параметра 0. \strike Печатаемый текст будет зачеркнутым (strikethrough). Для отмены следует выбрать параметр 0. \outl Установка контурного шрифта (outline) для печатаемого текста. Для отмены надлежит выбрать параметр 0. \shad Печатаемый текст будет оттенен. Для отмены необходимо установить параметр 0. \scaps Установка печати текста строчными буквами (small caps). Для отмены нужно выбрать параметр 0. \caps Установка печати текста прописными и строчными буквами. Для отмены необходимо выбрать параметр 0. \v Печатаемый текст будет заменен пробелами (invisible). Для отмены следует установить параметр 0. \f000 Установка настройки шрифта с указанием в качестве параметра номера фонта. \fs000 Оператор в качестве параметра содержит высоту символов, заданную в единицах по 1/2 пункта. Стандартное значение высоты равно 24 единицам (12 пунктам). \expnd000 Параметр позволяет изменить расстояние между символами путем указания количества интервалов величиной по 1/4 пункта каждый. \ul Установка режима подчеркивания текста. Для отмены необходимо выбрать параметр 0. \ulw Оператор позволяет задать подчеркивание только слов. \uld Задание пунктирного подчеркивания текста (dotted underline). \uldb Установка режима двойного подчеркивания текста (double underline). \ulnone Отмена подчеркивания текста. \up000 Установка режима надстрочного написания символов (superscript). Размер задается в единицах 1/2 пункта. \dn000 Установка режима подстрочного написания символов (subscript). Размер задается в единицах 1/2 пункта. \cf000 Указание номера цвета в палитре. \сьО00 Задание цвета фона путем указания номера в палитре. \revised

Последующие версии RT-формата могут быть дополнены новыми командами. Если программа не распознает команду, то она может быть пропущена. Конец каждой команды отмечается одним из описанных выше разделяющих символов (чаще всего -точкой с запятой).

Загрузить RTF текст из файла ресурса в TRichEdit.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch


(*
   Load RTF file from resource:
   You can store any kind of file as a RCDATA resource.
   The following example shows this with an RTF file.
   Create a text file called textres.rc and put the
   following line in it:
   TESTDOC RCDATA "textdoc.rtf"
   Next, compile that using the Borland Resource Compiler,
   which is provided with Delphi.
   brcc32.exe textres.rc
      Your next step is to include the compiled resource (.RES) file into
   your executable, which can be done with the {$R} compiler directive.
 *)
 (*
   Man kann eine beliebige Datei als RCDATA Ressource in eine
   Exe-Datei einbinden.
   Das folgende Beispiel zeigt, wie man einen RTF-Text aus
   einer Ressource ladt und in einem TRichEdit anzeigt.
   Erstelle zuerst eine Datei "textres.rc" mit folgendem Inhalt:
   TESTDOC RCDATA "textdoc.rtf"
   Kompiliere diese mit brcc32.exe:
   brcc32.exe textres.rc
      Es wurde nun eine textres.res Datei erzeugt.
 *)

 implementation
 {$R *.dfm}
 {$R textres.res}  // <---- your resource file! 
procedure TForm1.Button1Click(Sender: TObject);
 var
   rs: TResourceStream;
 begin
   rs := TResourceStream.Create(hinstance, 'TESTDOC', RT_RCDATA);
   try
     Richedit1.PlainText := False;
     TempStream.Position := 0;
     Richedit1.Lines.LoadFromStream(rs);
   finally
     rs.Free;
   end;
 end;

Распечатать RTF-файл и определить диапазон страницы для печати.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, RichEdit, RxRichEd, ExtCtrls, Printers;
 type
   TPageOffset = record
     mStart,
     mEnd: Integer;
     rendRect: TRect;
   end;
   TForm1 = class(TForm)
     Panel1: TPanel;
     Editor: TRxRichEdit;
     PrintBtn: TButton;
     PreviewBtn: TButton;
     CloseBtn: TButton;
     procedure PrintBtnClick(Sender: TObject);
     procedure PreviewBtnClick(Sender: TObject);
     procedure CloseBtnClick(Sender: TObject);
     procedure FormShow(Sender: TObject);
   private
     { Private-Deklarationen }
   public
     { Public-Deklarationen }
   end;
 var
   Form1: TForm1;
 implementation
 uses Unit2;
 {$R *.dfm}
 procedure TForm1.PrintBtnClick(Sender: TObject);
 var
    wPage, hPage, xPPI, yPPI, wTwips, hTwips: integer;
   pageRect, rendRect: TRect;
   po: TPageOffset;
   fr: TFormatRange;
   lastOffset, currPage, pageCount: integer;
   xOffset, yOffset: integer;
   FPageOffsets: array of TPageOffset;
   TextLenEx: TGetTextLengthEx;
   firstPage: boolean;
 begin
   //First, get the size of a printed page in printer device units 
  wPage := GetDeviceCaps(Printer.Handle, PHYSICALWIDTH);
   hPage := GetDeviceCaps(Printer.Handle, PHYSICALHEIGHT);
   //Next, get the device units per inch for the printer 
  xPPI := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
   yPPI := GetDeviceCaps(Printer.Handle, LOGPIXELSY);
   //Convert the page size from device units to twips 
  wTwips := MulDiv(wPage, 1440, xPPI);
   hTwips := MulDiv(hPage, 1440, yPPI);
   //Save the page size in twips 
  with pageRect do
    begin
     Left := 0;
     Top := 0;
     Right := wTwips;
     Bottom := hTwips
   end;
   //Next, calculate the size of the rendering rectangle in twips 
  //Rememeber - two inch margins are hardcoded, so the below code 
  //reduces the width of the output by four inches 
  with rendRect do
    begin
     Left := 0;
     Top := 0;
     Right := pageRect.Right - (1440 * 4);
     Bottom := pageRect.Bottom - (1440 * 4)
   end;
   //Define a single page and set starting offset to zero 
  po.mStart := 0;
   //Define and initialize a TFormatRange structure. This structure is passed 
  //to the TRichEdit with a request to format as much text as will fit on a 
  //page starting with the chrg.cpMin offset and ending with the chrg.cpMax. 
  //Initially, we tell the RichEdit control to start at the beginning 
  //(cpMin = 0) and print as much as possible (cpMax = -1). We also tell it 
  //to render to the printer 
  with fr do
    begin
     hdc := Printer.Handle;
     hdcTarget  := Printer.Handle;
     chrg.cpMin := po.mStart;
     chrg.cpMax := -1;
   end;
   //In order to recognize when the last page is rendered, we need to know how 
  //much text is in the control. 
  if RichEditVersion >= 2 then
    begin
     with TextLenEx do
      begin
       flags := GTL_DEFAULT;
       codepage := CP_ACP;
     end;
     lastOffset := SendMessage(Editor.Handle, EM_GETTEXTLENGTHEX, wParam(@TextLenEx), 0)
   end
   else
      lastOffset := SendMessage(Editor.Handle, WM_GETTEXTLENGTH, 0, 0);
   //As a precaution, clear the formatting buffer 
  SendMessage(Editor.Handle, EM_FORMATRANGE, 0, 0);
   //Printers frequently cannot print at the absolute top-left position on the 
  //page. In other words, there is usually a minimum margin on each edge of the 
  //page. When rendering to the printer, RichEdit controls adjust the top-left 
  //corner of the rendering rectangle for the amount of the page that is 
  //unprintable. Since we are printing with two-inch margins, we are presumably 
  //already within the printable portion of the physical page. 
  SaveDC(fr.hdc);
   SetMapMode(fr.hdc, MM_TEXT);
   xOffset := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX);
   yOffset := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY);
   xOffset := xOffset + MulDiv(1440 + 1440, xPPI, 1440);
   yOffset := yOffset + MulDiv(1440 + 1440, yPPI, 1440);
   SetViewportOrgEx(fr.hdc, xOffset, yOffset, nil);
   //Now we build a table of page entries, one entry for each page that would be 
  //printed. 
  while ((fr.chrg.cpMin <> -1) and (fr.chrg.cpMin < lastOffset)) do
    begin
     fr.rc := rendRect;
     fr.rcPage := pageRect;
     po.mStart := fr.chrg.cpMin;
     fr.chrg.cpMin := SendMessage(Editor.Handle, EM_FORMATRANGE, 0, Longint(@fr));
     po.mEnd := fr.chrg.cpMin - 1;
     po.rendRect := fr.rc;
     if High(FPageOffsets) = -1 then SetLength(FPageOffsets, 1)
     else
        SetLength(FPageOffsets, Length(FPageOffsets) + 1);
     FPageOffsets[High(FPageOffsets)] := po
   end;
   pageCount := Length(FPageOffsets);
   ShowMessage(Format('Es wurde %d Seiten ermittelt', [pageCount]));
   SendMessage(Editor.Handle, EM_FORMATRANGE, 0, 0);
   RestoreDC(fr.hdc, - 1);
   //Now, we are almost ready to actually print. 
  Printer.BeginDoc;
   fr.hdc := Printer.Handle;
   fr.hdcTarget := Printer.Handle;
   SaveDC(fr.hdc);
   SetViewportOrgEx(fr.hdc, xOffset, yOffset, nil);
   //Ok, here we go to print 
  firstPage := True;
   //At this point you can select from page and to page 
  currPage := 0;  //Print from the first page 
  pageCount := 1;  //Only One page for testing 
  while (currPage < pageCount) do
    begin
     if firstPage then firstPage := False
     else
        Printer.NewPage;
     SetViewportOrgEx(fr.hdc, xOffset, yOffset, nil);
     fr.rc := FPageOffsets[currPage].rendRect;
     fr.rcPage := pageRect;
     fr.chrg.cpMin := FPageOffsets[currPage].mStart;
     fr.chrg.cpMax := FPageOffsets[currPage].mEnd;
     fr.chrg.cpMin := SendMessage(Editor.Handle, EM_FORMATRANGE, 1, Longint(@fr));
     Inc(currPage);
   end;
   //At this point, we have finished rendering the contents of the RichEdit 
  //control. Now we restore the printer's HDC settings and tell Windows that 
  //we are through printing this document 
  RestoreDC(fr.hdc, - 1);
   Printer.EndDoc;
   //Finally, we clear the RichEdit control's formatting buffer and delete 
  //the saved page table information 
  fr.chrg.cpMin := SendMessage(Editor.Handle, EM_FORMATRANGE, 0, 0);
   Finalize(FPageOffsets);
   //That's it 
end;
 procedure TForm1.PreviewBtnClick(Sender: TObject);
 begin
   PreviewForm.ShowModal
 end;
 procedure TForm1.CloseBtnClick(Sender: TObject);
 begin
   Close
 end;
 procedure TForm1.FormShow(Sender: TObject);
 begin
   Editor.Lines.LoadFromFile('Exceltabelle.rtf');
 end;
 end.

Исправление загрузки RTF текста через поток.

Автор: Лагонский Сергей Николаевич
Я после дня игры в X-COM стал думать как мне поменьше ходов потратить, идя по комнате, чтобы потом хватило дверь открыть!
В версии Borland Delphi 3 Client/Server я обнаружил, что при загрузке текста формата RTF методом "LoadFromStream" в компонент TRichEdit он не интерпретируется как RTF, а отображается полностью (со всеми управляющими символами). Разбираясь в исходном тексте модуля COMCTRLS.PAS (дата создания: 4 августа 1997 года, размер: 391728 байт) я нашел причину, из-за которой вышеуказанный метод не работал как надо. Ниже я привожу исходный и исправленный тексты реализации метода "TRichEditStrings.LoadFromStream" (измененные строки отмечены символом "!"):

1. Исходный текст.

procedure TRichEditStrings.LoadFromStream(Stream: TStream);
var
  EditStream: TEditStream;
  Position: Longint;
  TextType: Longint;
  StreamInfo: TRichEditStreamInfo;
  Converter: TConversion;
begin
  StreamInfo.Stream := Stream;
  if FConverter <> nil then
    Converter := FConverter
  else
    Converter := RichEdit.DefaultConverter.Create;
  StreamInfo.Converter := Converter;
  try
    with EditStream do
    begin
      dwCookie := LongInt(Pointer(@StreamInfo));
      pfnCallBack := @StreamLoad;
      dwError := 0;
    end;
    Position := Stream.Position;
    if PlainText then
      TextType := SF_TEXT
    else
      TextType := SF_RTF;
    SendMessage(RichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));
    if (TextType = SF_RTF) and (EditStream.dwError <> 0) then
    begin
      Stream.Position := Position;
      ! if PlainText then
        TextType := SF_RTF
          !
        else
        TextType := SF_TEXT;
      SendMessage(RichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));
      if EditStream.dwError <> 0 then
        raise EOutOfResources.Create(sRichEditLoadFail);
    end;
  finally
    if FConverter = nil then
      Converter.Free;
  end;
end;

2. Текст с исправлением:

procedure TRichEditStrings.LoadFromStream(Stream: TStream);
var
  EditStream: TEditStream;
  Position: Longint;
  TextType: Longint;
  StreamInfo: TRichEditStreamInfo;
  Converter: TConversion;
begin
  StreamInfo.Stream := Stream;
  if FConverter <> nil then
    Converter := FConverter
  else
    Converter := RichEdit.DefaultConverter.Create;
  StreamInfo.Converter := Converter;
  try
    with EditStream do
    begin
      dwCookie := LongInt(Pointer(@StreamInfo));
      pfnCallBack := @StreamLoad;
      dwError := 0;
    end;
    Position := Stream.Position;
    if PlainText then
      TextType := SF_TEXT
    else
      TextType := SF_RTF;
    SendMessage(RichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));
    if (TextType = SF_RTF) and (EditStream.dwError <> 0) then
    begin
      Stream.Position := Position;
      ! if PlainText then
        TextType := SF_TEXT
          !
        else
        TextType := SF_RTF;
      SendMessage(RichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));
      if EditStream.dwError <> 0 then
        raise EOutOfResources.Create(sRichEditLoadFail);
    end;
  finally
    if FConverter = nil then
      Converter.Free;
  end;
end;

Как сохранить RTF в TBlobField.

Иисус изменил твою жизнь. Сохранить? (Да/Нет)

В этом примере поле 'Table1Memo' это paradox 'formatted memo'. Оно так же может быть полем blob.

Через TBlobStream содержимое контрола RichEdit можно загружать или сохранять в базу данных:

procedure TForm1.BtnGetClick(Sender: TObject);
var
  bs: TBlobStream;
begin
  bs := nil;
  with Table1 do
    try
      open;
      first;
      bs := TBlobStream.Create(table1memo, bmread);
      Richedit1.plaintext := false;
      Richedit1.Lines.Loadfromstream(bs);
    finally
      bs.free;
      close;
    end;
end;
procedure TForm1.BtnPutClick(Sender: TObject);
var
  bs: TBlobStream;
begin
  bs := nil;
  with Table1 do
    try
      open;
      first;
      edit;
      bs := TBlobStream.Create(table1memo, bmwrite);
      Richedit1.plaintext := false;
      Richedit1.Lines.Savetostream(bs);
      post;
    finally
      bs.free;
      close;
    end;
end;

Копировать RTF-текст в буфер обмена.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

How can I copy a text formatted as RTF ({\rtf1\ansi ....) on the Clipboard?

{ 
You have to dig into the Rich Text Format if you want to copy text to the 
clipboard that has format information embedded. The application you paste 
this text into has to understand RTF, or the formatting will not show up. 
OK, the first step is to register a clipboard format for RTF, since this is 
not a predefined format: 
}
 Var
   CF_RTF : Word;
   CF_RTF := RegisterClipboardFormat('Rich Text Format');
 { 
The format name has to appear as typed above, this is the name used by MS 
Word for Windows and similar MS products. 
NOTE: The Richedit Unit declares a constant CF_RTF, which is NOT the 
clipboard format handle but the string you need to pass to RegisterClipboard 
format! So you can place Richedit into your uses clause and change the line 
above to 
}
   CF_RTF := RegisterClipboardFormat(Richedit.CF_RTF);
 { 
The next step is to build a RTF string with the embedded format information. 
You will get a shock if you inspect the mess of RTF stuff W4W will put into 
the clipboard if you copy just a few characters (the app below allows you to 
inspect the clipboard), but you can get away with a lot less. The bare 
minimum would be something like this (inserts an underlined 44444): 
}
 const
   testtext: PChar = '{\rtf1\ansi\pard\plain 12{\ul 44444}}';
 { 
The correct balance of opening and closing braces is extremely important, one 
mismatch and the target app will not be able to interpret the text 
correctly. If you want to control the font used for the pasted text you need 
to add a fonttable (the default font is Tms Rmn, not the active font in the 
target app!). See example app below, testtext2. If you want more info, the 
full RTF specs can be found on www.microsoft.com, a subset is also described 
in the Windows help compiler docs (hcw.hlp, comes with Delphi). 
}
 unit Clipfmt1;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls;
 type
   TForm1 = class(TForm)
     MemFormats: TMemo;
     label1: TLabel;
     BtnShowFormats: TButton;
     BtnGetRTF: TButton;
     BtnSetRTF: TButton;
     MemExample: TMemo;
     procedure FormCreate(Sender: TObject);
     procedure BtnShowFormatsClick(Sender: TObject);
     procedure BtnGetRTFClick(Sender: TObject);
     procedure BtnSetRTFClick(Sender: TObject);
   private
   public
     CF_RTF: Word;
   end;
 var
   Form1: TForm1;
 implementation
 uses Clipbrd;
 {$R *.DFM}
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   // register clipboard format rtf 
  CF_RTF := RegisterClipboardFormat('Rich Text Format');
   if CF_RTF = 0 then
   begin
     ShowMessage('Unable to register the Rich Text clipboard format!');
     Application.Terminate;
   end;
   BtnShowFormats.Click;
 end;
 // show clipboard formats 
procedure TForm1.BtnShowFormatsClick(Sender: TObject);
 var
   buf: array [0..60] of Char;
   n: Integer;
   fmt: Word;
   Name: string[30];
 begin
   MemFormats.Clear;
   for n := 0 to Clipboard.FormatCount - 1 do
   begin
     fmt := Clipboard.Formats[n];
     if GetClipboardFormatName(fmt, buf, Pred(SizeOf(buf))) <> 0 then
       MemFormats.Lines.Add(StrPas(buf))
     else
     begin
       case fmt of
         1: Name := 'CF_TEXT';
         2: Name := 'CF_BITMAP';
         3: Name := 'CF_METAFILEPICT';
         4: Name := 'CF_SYLK';
         5: Name := 'CF_DIF';
         6: Name := 'CF_TIFF';
         7: Name := 'CF_OEMTEXT';
         8: Name := 'CF_DIB';
         9: Name := 'CF_PALETTE';
         10: Name := 'CF_PENDATA';
         11: Name := 'CF_RIFF';
         12: Name := 'CF_WAVE';
         13: Name := 'CF_UNICODETEXT';
         14: Name := 'CF_ENHMETAFILE';
         15: Name := 'CF_HDROP (Win 95)';
         16: Name := 'CF_LOCALE (Win 95)';
         17: Name := 'CF_MAX (Win 95)';
         $0080: Name := 'CF_OWNERDISPLAY';
         $0081: Name := 'CF_DSPTEXT';
         $0082: Name := 'CF_DSPBITMAP';
         $0083: Name := 'CF_DSPMETAFILEPICT';
         $008E: Name := 'CF_DSPENHMETAFILE';
         $0200..$02FF: Name := 'private format';
         $0300..$03FF: Name := 'GDI object';
         else
           Name := 'unknown format';
       end;
       MemFormats.Lines.Add(Name);
     end;
   end;
 end;
 // get rtf code from clipboard 
procedure TForm1.BtnGetRTFClick(Sender: TObject);
 var
   MemHandle: THandle;
 begin
   with Clipboard do
   begin
     Open;
     try
       if HasFormat(CF_RTF) then
       begin
         MemHandle := GetAsHandle(CF_RTF);
         MemExample.SetTextBuf(GlobalLock(MemHandle));
         GlobalUnlock(MemHandle);
       end
       else
         MessageDlg('The clipboard contains no RTF text!',
           mtError, [mbOK], 0);
     finally
       Close;
     end;
   end;
 end;
 // set rtf code to the clipboard 
procedure TForm1.BtnSetRTFClick(Sender: TObject);
 const
   testtext: PChar = '{\rtf1\ansi\pard\plain 12{\ul 44444}}';
   testtext2: PChar = '{\rtf1\ansi' +
     '\deff4\deflang1033{\fonttbl{\f4\froman\fcharset0\fprq2 Times New Roman;}}' +
     '\pard\plain 12{\ul 44444}}';
   flap: Boolean = False;
 var
   MemHandle: THandle;
   rtfstring: PChar;
 begin
   with Clipboard do
   begin
     if flap then
       rtfstring := testtext2
     else
       rtfstring := testtext;
     flap := not flap;
     MemHandle := GlobalAlloc(GHND or GMEM_SHARE, StrLen(rtfstring) + 1);
     if MemHandle <> 0 then
     begin
       StrCopy(GlobalLock(MemHandle), rtfstring);
       GlobalUnlock(MemHandle);
       Open;
       try
         AsText := '1244444';
         SetAsHandle(CF_RTF, MemHandle);
       finally
         Close;
       end;
     end
     else
       MessageDlg('Global Alloc failed!',
         mtError, [mbOK], 0);
   end;
 end;
 end.

Массив констант во время выполнения приложения.

Автор: Peter Below

...хорошо, непосредственно это синтаксис не поддерживает, поскольку массив констант Array of Const подобен открытым массивам, главным образом в части характеристик времени компиляции. Но вы можете обойти этот неприятный момент, обладая хотя бы начальными знаниями того, как реализован открытый массив. Что нам для этого необходимо: динамически размещенный массив array of TVarRec, который "принимает" ваши параметры, и "псевдоним" (alias) функции Format, позволяющий работать с таким массивом без "ругани" со стороны компилятора.

type
  { объявляем тип для динамического массива array of TVarRecs }
  TVarArray = array[0..High(Word) div Sizeof(TVarRec) - 1] of TVarRec;
  PVarArray = ^TVarArray;
  { Объявляем alias-тип для функции Format. Передаваемые параметры будут иметь
  в стеке тот же самый порядок вызова, что и при нормальном вызове Format }
  FormatProxy = function(const aFormatStr: string; var aVarRec: TVarRec;
    highIndex: Integer): string;
  { AddVarRecs копирует параметры, передаваемые в массиве A в pRecs^, начиная
  с pRecs^[atIndex]. highIndex - самый большой доступный индекс pRecs, число
  распределенных элементов - 1. }
procedure AddVarRecs(pRecs: PVarArray; atIndex, highIndex: Integer; const A:
  array of const);
var
  i: Integer;
begin
  if pRecs <> nil then
    for i := 0 to High(A) do
    begin
      if atIndex <= highIndex then
      begin
        pRecs^[atIndex] := A[i];
        Inc(atIndex);
      end { If }
      else
        Break;
    end; { For }
end; { AddVarRecs }
procedure TScratchMain.SpeedButton2Click(Sender: TObject);
var
  p: PVarArray;
  S: string;
  Proxy: FormatProxy;
begin
  { распределяем массив для четырех параметров, индексы - 0..3 }
  GetMem(p, 4 * Sizeof(TVarRec));
  try
    { добавляем параметры последующими вызовами AddVarRecs }
    AddVarRecs(p, 0, 3, [12, 0.5, 'Шаблон']);
    AddVarRecs(p, 3, 3, ['Тест']);
    { получаем полномочия Format }
    @Proxy := @SysUtils.Format;
    { Вызов с динамически сгенерированным массивом параметров.
    Естественно, строка формата может также быть сформирована
    и во время выполнения программы. }
    S := Proxy('Целое: %d, Реальное: %4.2f, Строки: %s, %s', p^[0], 3);
    { выводим результат }
    ShowMessage(S);
  finally
    FreeMem(p, 4 * Sizeof(TVarRec));
  end;
end;
Я надеюсь вы поняли принцип. Естественно, имеются ограничения. Вы можете передавать в AddVarRecs числовые величины, строковые переменные и литералы, но не в коем случае не строковые выражения! В этом случае компилятор должен для хранения результата сформировать в стеке временную строку, передать ее в AddVarRecs (или лучше по адресу в TVarRec), и она может прекратить свое существование или может быть перезаписана в стеке другими данными, если в конечном счете вы передадите в Proxy целый массив!

Тестировалось только в Delphi 1.0!

Как присвоить событие в run-time.

Автор: Vit WEB-сайт: http://forum.vingrad.ru

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
  private
    procedure Click(Sender: TObject);
  end;
var
  Form1: TForm1;
implementation
procedure TForm1.Click(Sender: TObject);
begin
  // do something
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  button1.OnClick := Click;
end;
end.
А как сделать чтобы "procedure Click" была не методом класса, а отдельно стоящей функцией

Дополнил: Le Taon

procedure Click(Self: TObject; Sender: TObject);
begin
  ...
end;
var
  evhandler: TNotifyEvent;
  TMethod(evhandler).Code := @Click;
  TMethod(evhandler).Data := nil;
  Button1.OnClick := evhandler;
// Без извращений можно так:
  TDummy = class
    class procedure Click(Sender: TObject);
  end;
  Button1.OnClick := TDummy.Click;
По идее, при вызове OnClick первым параметром будет запихнут указатель на экземпляр того класса который в этом OnClick хранится . Я в низкоуровневой реализации не силен, но кажись, так как параметры в процедурах в Delphi передаются через регистры, то ничего страшного не произойдет.

Еще дополнение от: Fantasist

procedure C(Self: pointer; Sender: TObject);
begin
  TButton(Sender).Caption := 'ee';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  @Button1.OnClick := @c;
end;

Self тут у нас будет равен nil, а Sender как раз и получается Sеndеr'ом.

Дублирование компонентов и их потомков во время выполнения приложения.

Учитывая пожелания пользователей, фирма Microsoft объявила, что она внесёт изменение в название Windows 2000, теперь эта операционная система будет называться Windows 2000 beta ДВАДЦАТЬ ПЯТЬ. P.S. (А чё? Ведь существует-же W2000 beta 3 RELEASE CANDIDATE 1)
Приведенный ниже код содержит функцию DuplicateComponents, позволяющую проводить клонирование любых компонентов и их потомков во время выполнения приложения. Действия ее напоминают операцию копирования/вставки (copy/paste) во время разработки приложения. Новые компоненты при создании получают тех же родителей, владельцев (в случае применения контейнеров) и имена (естественно, несколько отличающихся), что и оригиналы. В данной функции есть вероятность багов, но я пока их не обнаружил. Ошибки и недочеты могут возникнуть из-за редко применяемых специфических методов, которые, вместе с тем, могут помочь программистам, столкнувшимися с аналогичными проблемами.

Данная функция может оказаться весьма полезной в случае наличия нескольких одинаковых областей на форме с необходимостью синхронизации изменений в течение некоторого промежутка времени. Процедура создания дубликата проста до безобразия: разместите на TPanel или на другом родительском компоненте необходимые элементы управления и сделайте: "newpanel := DuplicateComponents(designedpanel)".

uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls, IniFiles, TypInfo, Debug;
type
  TUniqueReader = class(TReader)
    LastRead: TComponent;
    procedure ComponentRead(Component: TComponent);
    procedure SetNameUnique(
      Reader: TReader;
      Component: TComponent;
      var Name: string
      );
  end;
implementation
procedure TUniqueReader.ComponentRead(Component: TComponent);
begin
  LastRead := Component;
end;
// Задаем уникальное имя считываемому компоненту, например,
// "Panel2", если "Panel1" уже существует
procedure TUniqueReader.SetNameUnique(
  Reader: TReader;
  Component: TComponent; // Считываемый компонент
  var Name: string // Имя компонента для дальнейшей модификации
  );
var
  i: Integer;
  tempname: string;
begin
  i := 0;
  tempname := Name;
  while Component.Owner.FindComponent(Name) <> nil do
  begin
    Inc(i);
    Name := Format('%s%d', [tempname, i]);
  end;
end;
function DuplicateComponents(
  AComponent: TComponent // исходный компонент
  ): TComponent; // возвращаемся к созданию нового компонента
  procedure RegisterComponentClasses(
    AComponent: TComponent
    );
  var
    i: integer;
  begin
    RegisterClass(TPersistentClass(AComponent.ClassType));
    if AComponent is TWinControl then
      if TWinControl(AComponent).ControlCount > 0 then
        for i := 0 to
          (TWinControl(AComponent).ControlCount - 1) do
          RegisterComponentClasses(TWinControl(AComponent).Controls[i]);
  end;
var
  Stream: TMemoryStream;
  UniqueReader: TUniqueReader;
  Writer: TWriter;
begin
  result := nil;
  UniqueReader := nil;
  Writer := nil;
  try
    Stream := TMemoryStream.Create;
    RegisterComponentClasses(AComponent);
    try
      Writer := TWriter.Create(Stream, 4096);
      Writer.Root := AComponent.Owner;
      Writer.WriteSignature;
      Writer.WriteComponent(AComponent);
      Writer.WriteListEnd;
    finally
      Writer.Free;
    end;
    Stream.Position := 0;
    try
      // создаем поток, перемещающий данные о компоненте в конструктор
      UniqueReader := TUniqueReader.Create(Stream, 4096);
      UniqueReader.OnSetName := UniqueReader.SetNameUnique;
      UniqueReader.LastRead := nil;
      if AComponent is TWinControl then
        UniqueReader.ReadComponents(
          // считываем компоненты и суб-компоненты
          TWinControl(AComponent).Owner,
          TWinControl(AComponent).Parent,
          UniqueReader.ComponentRead
          )
      else
        UniqueReader.ReadComponents(
          // читаем компоненты
          AComponent.Owner,
          nil,
          UniqueReader.ComponentRead
          );
      result := UniqueReader.LastRead;
    finally
      UniqueReader.Free;
    end;
  finally
    Stream.Free;
  end;
end;
Содержание.