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

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

CPU not found. Press any key to continue
Используйте событие OnDataChange объекта Datasource, соединенного с DBGrid. Если параметр State в обработчике событие равен dsBrowse, значит вы перешли в новую строку (или только что открыли таблицу). Почему сетка не поддерживает такое событие? Поскольку сетка может быть не единственным элементом управления, оторбажающим данные из текущей строки и может быть не единственным элементом, позволяющим осуществлять перемещение от строки к строке. С помощью Datasource обработка события осуществляется централизованно. Я не уверен в том, что проблему можно решить, обрабатывая событие одинарного щелчка, для отслеживания события изменения строк я рекомендую использовать событие TDatasource.OnDataChange, а для колонок - TDBGrid.OnColEnter/Exit. Лично я пользуюсь следующей рабочей технологией: Для того, чтобы обнаружить изменения текущей строки, воспользуйтесь событием TDataSource OnDataChange. OnDataChange возникает при прокрутке или щелчке на другой строке. Обработчик события может выглядеть приблизительно так:
procedure Form1.DSrc1DataChange(Sender: TObject; Field: TField);
где Field является колонкой, где произошло изменение.

Поля TTable могут использоваться для сравнения текущих выбранных строк полей (ключ) с вашими требованиями. С той же целью может быть использовано и свойство TDBGrid Fields. Для примера:

if tbl1.Fields[0].AsString = 'BlaBlaBla' then ...

Или.

if dbGrid1.Fields[I].IsNull then ... 
Для отслеживания изменения колонки, используйте события TDBGrid OnColExit & OnColEnter. Для определения выбранной к настоящему времени колонки воспользуйтесь свойствами TDBGrid SelectedField и SelectedIndex. Когда выбирается другая колонка другой строки, вы получаете события OnColExit, OnColEnter и OnDataChange. Можно пойти и "кривым" путем, взявшись за обработку события TDBGrid OnDrawDataCell, которое возникает когда ячейка выбирается, или когда сетка скроллируется. Обработчик события может выглядеть примерно так:
procedure Form1.dbGrid1DrawDataCell(Sender: TObject; Rect: TRect;
Field: TField; State: TGridDrawState);
При изменении ячейки вы получаете поток событий, поэтому вам нужно каким-то образом их фильтровать. Если у вас нет проблем в создании "101 изменения" стандартных компонентов - что является проблемой для меня 8-), то попробуйте это. Это легко.

Чтобы иметь доступ к индексу строки или колонки выбранной ячейки, вы должны унаследовать ваш класс от TCustomGrid и опубликать свойства времени выполнения Row и Col (текущие строка и колонка сетки, не таблицы!!):

type
TSampleDBGrid = class(TCustomGrid)
public
property Col;
property Row;
end;

В соответствующей процедуре или обработчике события осуществите приведение типа:

var
G: TSampleDBGrid;
begin
G := TSampleDBGrid(myDBGrid1);
if G.Row = I then ...
if G.Col = J then ...

Дело в том, что TDBGrid является потомком TCustomGrid, который имеет несколько свойств, содержащих координаты сетки, но это не опубликовано в TDBGrid.

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

grid.colcount := dbGrid.fieldcount;
table.first;
row := 0;
while not table.eof do begin
grid.rowcount := row + 1;
for i := 0 to grid.colcount-1 do
grid.cells[i,row] := dbGrid.fields[i].asString;
table.next;
inc (row);
end;
Могут быть ошибки, но это должно помочь.

Посмотрите на следующий код, он может вам помочь. Он берет у элемента управления свойсто 'Name' и помещает его в свойство 'Caption' метки.

unit Unit1;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Label1: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Edit1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Edit2MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  close;
end;
procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Label1.Caption := TEdit(Sender).Name;
end;
procedure TForm1.Edit2MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Label1.Caption := TEdit(Sender).Name;
end;
end.

Определить номер строки в TDBGrid.

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

{ 
 I did not found property to know the rownumber of a TDBGrid, 
 so I will try this in the OnDrawColumnCell of a DBGrid: 
}
 procedure TformX.DBGridXDrawColumnCell(Sender: TObject; const
   Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
 var
   RowNo : Integer;
 begin
   RowNo := Rect.Bottom div TStringGrid(DBGridX).DefaultRowHeight
   //so, RowNo has the Row to be draw in the moment 
  ...
 end;

DBGrid - сохранение конфигурации.

Заходит програмер в свой темный подъезд и слышит, что в темноте кто-то шебуршится. - Бї@дь надо-было сохраниться! - запоздало подумал он.

Нижеописанный код создает, сохраняет и загружает конфигурационный файл и изменяет размеры столбцов таблицы DBGRID.

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Grids, DBGrids, Db, DBTables, StdCtrls, IniFiles;
...
procedure TMainForm.NewIni(const NomeIni: string);
var
  F: System.Text;
  i: Byte;
begin
  System.Assign(F, NomeIni);
  System.ReWrite(F);
  System.WriteLn(F, '[Campi_Ordine]');
  for i:=1 to Table1.FieldCount do
    System.WriteLn(F, 'Campo',i,'=',Table1.Fields[i-1].FieldName);
  System.WriteLn(F, '');
  System.WriteLn(F, '[Campi_Size]');
  for i:=1 to Table1.FieldCount do
    System.WriteLn(F, 'Campo',i,'=',Table1.Fields[i-1].DisplayWidth);
  System.Close(F);
end;
procedure TMainForm.SaveIni(const FN: string);
var
  Ini: TIniFile;
  i: Integer;
  S : string;
begin
  NewIni(FN);
  Ini := TIniFile.Create(FN);
  with Ini do begin
    for i:=1 to Table1.FieldCount do
    begin
      S:= Table1.Fields[i-1].FieldName;
      WriteString('Campi_Ordine', 'Campo'+IntToStr(i),
      Table1.Fields[i-1].FieldName);
      WriteInteger('Campi_Size', 'Campo'+IntToStr(i),
      Table1.Fields[i-1].DisplayWidth);
    end;
  end;
  Ini.Free;
end;
procedure TMainForm.LoadIni(const FN: string);
var
  Ini: TIniFile;
  i: Integer;
  j: Longint;
  S: string;
  function MyReadInteger(const Section, Ident: string): Longint;
  begin
    result := Ini.ReadInteger(Section, Ident, -1);
    if result=-1 then
      raise Exception.Create('Errore nel file di configurazione.');
  end;
  function MyReadString(const Section, Ident: string): string;
  begin
    result := Ini.ReadString(Section, Ident, '');
    if result='' then
      raise Exception.Create('Errore nel file di configurazione.');
  end;
begin
  Ini := TIniFile.Create(FN);
  try
    with Ini do
    begin
      for i:=1 to Table1.FieldCount do
      begin
        S:= MyReadString('Campi_Ordine', 'Campo'+IntToStr(i));
        j:= MyReadInteger('Campi_Size', 'Campo'+IntToStr(i));
        Table1.FieldByName(S).index := i-1;
        Table1.FieldByName(S).DisplayWidth := j;
      end;
    end;
  finally
    Ini.Free;
  end;
end;

DBGrid - выбранные строки.

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

Автор: Nancy Reid
На собрании перед выпуском Windows'98 в компании Micro$oft участникам был задан странный вопрос: - Если вы зашли на авиалайнер и узнали, что ваша группа программистов была ответственна за составление программы, управляющей полетом вашего авиалайнера, сколько бы из вас немедленно выскочило из самолета? Все подняли руки, кроме одного программиста. Когда его спросили, что бы он сделал, тот ответил, что он бы с удовольствием остался на борту. Когда его спросили почему, он ответил: - А чего мне бояться полета? С нашей программой этот самолет даже не доехал бы до взлетной полосы.

Существует одно свойство, не упомянутое в файлах помощи (оплошность, господа программисты из Borland), имеющее имя SelectedRows, и вот как его можно использовать:

procedure TfrmGrid.Button1Click(Sender: TObject);
var
  i: integer;
begin
  For i := 0 to DBGrid1.SelectedRows.Count - 1 do
  begin
    Table1.GoToBookmark(TBookmark(DBGrid1.SelectedRows[i]));
    Table1.Delete;
  end;
end;

Выделить все строки в DBGrid.

Мужик заходит в лавочку, торгующую легальным софтом. Подходит к одному из продавцов и тихо-тихо говорит: - Мне Windows'2000, пожалуйста. Тут все, и покупатели и продавцы, замирают и пристально смотрят на мужика. Мужик не выдерживает такой паузы, оборачивается ко всем и кричит: - Да! Да! Тр@хаться я иду!
function GridSelectAll(Grid: TDBGrid): Longint;
begin
  Result := 0;
  Grid.SelectedRows.Clear;
  with Grid.Datasource.DataSet do
  begin
    First;
    DisableControls;
    try
      while not EOF do
      begin
        Grid.SelectedRows.CurrentRowSelected := True;
        inc(Result);
        Next;
      end;
    finally
      EnableControls;
    end;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  GridSelectAll(DBGrid1);
end;

Как выделить окошко DBGrid другим цветом.

Оформил: DeeCo

Необходимо обработать событие "OnDrawCellData". Например для того, чтобы пометить выбранное окошко красным фоном, необходимо сделать следующее:
procedure TForm1.DBGridDrawDataCell(Sender: TObject; const Rect: TRect;
  Field: TField; State: TGridDrawState);
begin
  if gdFocused in State then
    with (Sender as TDBGrid).Canvas do
    begin
      Brush.Color := clRed;
      FillRect(Rect);
      TextOut(Rect.Left, Rect.Top, Field.AsString);
    end;
end;

Решение проблемы передачи фокуса TDBGrid.

- Что для геймера Жизнь? - Два компакта с "Half-life"!
В данном документе содержится решение проблемы невозможности получения DBGrid-ом фокуса после щелчка на каком-либо элементе управления родительской формы, в то время, как DBGrid находится на ее дочерней MDI-форме. Относится ко всем версиям Delphi

Очевидно, DBGrid имеет некоторые проблемы с управлением фокусом, если он находится на дочерней MDI-форме. Эта проблема решена в приведенном ниже наследнике TDBGrid, в котором обрабатываются мышиные сообщения и выясняется когда фокус должен быть передан сетке. Наследник создан в виде компонента, который легко устанавливается в Палитру Компонентов. Примечание: код адаптирован для всех версий Delphi. Проблемы могут быть в Delphi 2 и 3, если вы забудете заменить устаревшие в этих версиях модули "winprocs" и "wintypes" на "windows.".

unit FixedDBGrid;
interface
uses
  Winprocs, wintypes, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs, Grids, DBGrids;
type
  TFixedDBGrid = class(TDBGrid)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }
    procedure WMRButtonDown(var Message: TWMRButtonDown); message
      WM_RBUTTONDOWN;
    procedure WMLButtonDown(var Message: TWMLButtonDown); message
      WM_LBUTTONDOWN;
  published
    { Published declarations }
  end;
procedure Register;
implementation
procedure TFixedDBGrid.WMRButtonDown(var Message: TWMRButtonDown);
begin
  winprocs.SetFocus(handle); {помните, что winprocs относится только к Delphi 1!}
  inherited;
end;
procedure TFixedDBGrid.WMLButtonDown(var Message: TWMLButtonDown);
begin
  winprocs.SetFocus(handle); {помните, что winprocs относится только к Delphi 1!}
  inherited;
end;
procedure Register;
begin
  RegisterComponents('Samples', [TFixedDBGrid]);
end;
end.

Сортировка колонок в DBGrid.

Звонок Пользователя в службу поддержки. - Алло, это служба поддержки??? - Да. - У меня проблема, мой модем не хочет работать!!! - Давайте пропишем Вам строчку инициализации. - Давайте. - AT&F1&M5 - Спасибо!!!Через 5 минут. - У меня все еще не работает!!! - Да??? Ну давайте запишите другую - Я слушаю - ATS10=100 - Спасибо! Проходит еще 5 минут... - У меня Модем все еще плохо работает!!! - Ну что, пишите еще одну строчку... - Слушаю. - AT&F&B1&A0&S0=90 - Спасибо. Прошло еще 5 минут... - Алло!!! У меня сгорел модем!!! - Жаль, а то у меня еще столько в запасе строчек инициализаций осталось...
Многие профессиональные приложения отображают данные в полях табличной сетки и позволяют Вам сортировать любую колонку, просто щелкая по ее заголовку. То, что здесь изложено - не наилучший путь для решения задачи, данная технология ничто иное, как простая имитация такого поведения компонента. Главное препятствие в решении задачи - сам DBGrid. Проблема в отсутствии событий OnClick или OnMouseDown, позволяющие реагировать на элементарные манипуляции с заголовком. Правда, существует событие OnDoubleClick, но для данной цели оно не слишком изящно. Все что нам нужно - сделать заголовок, реагирующий на однократный щелчок мышью. Обратимся к компоненту THeaderControl. THeaderControl - компонент, введенный в палитру еще в Delphi 2.0 и обеспечивающий необходимые нам функции. Главное достоинство - реакция компонента при щелчке на отдельных панелях, панели также обеспечивают визульное отображение подобно кнопке (могут вдавливаться и отжиматься). Нам необходимо "прикрутить" THeaderControl к DBGrid. Вот как это сделать: Во-первых, создайте новое приложение. Положите THeaderControl на форму. Он автоматически выровняется по верхнему краю формы. Затем поместите на форму DBGrid и присвойте свойству Align значение alClient. Затем добавьте компоненты TTable и TDataSource. В компоненте TTable присвойте свойству DatabaseName значение DBDEMOS, а свойству TableName значение EVENTS.DB. В TDataSource укажите в свойстве DataSet на компонент Table1, а в TDBGrid в свойстве DataSource на DataSource1. Если свойство Active компонента TTable было неактивно, включите его (значение True). Теперь немного поколдуем!

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

procedure TForm1.FormCreate(Sender: TObject);
var
  TheCap: string;
  TheWidth, a: Integer;
begin
  DBGrid1.Options := DBGrid1.Options - [dgTitles];
  HeaderControl1.Sections.Add;
  HeaderControl1.Sections.Items[0].Width := 12;
  Table1.Exclusive := True;
  Table1.Active := True;
  for a := 1 to DBGrid1.Columns.Count do
  begin
    with DBGrid1.Columns.Items[a - 1] do
    begin
      TheCap := Title.Caption;
      TheWidth := Width;
    end;
    with HeaderControl1.Sections do
    begin
      Add;
      Items[a].Text := TheCap;
      Items[a].Width := TheWidth + 1;
      Items[a].MinWidth := TheWidth + 1;
      Items[a].MaxWidth := TheWidth + 1;
    end;
    try
      Table1.AddIndex(TheCap, TheCap, []);
    except
      HeaderControl1.Sections.Items[a].AllowClick := False;
    end;
  end;
  Table1.Active := False;
  Table1.Exclusive := False;
  Table1.Active := True;
end;
После того как THeaderControl заменил стандартный заголовок DBGrid, в первую очередь мы сбрасываем (устанавливаем в False) флаг dgTitles в свойстве Options компонента DBGrid. Затем мы добавляем колонку в HeaderControl и устанавливаем ее ширину, равную 12. Это будет пустой колонкой, которая имеет ту же ширину, что и левая колонка статуса в DBGrid. Затем нужно убедиться что таблица открыта для эксклюзивного доступа (никакие другие пользователи использовать ее не смогут). Причину я объясню немного позже. Теперь добавляем секции в HeaderControl. Для каждой добавленной колонки мы создаем в заголовке тот же текст, что и в соответствующей колонке DBGrid. В цикле мы проходим по всем колонкам DBGrid и повторяем текст заголовка колонки и его высоту. Мы также устанавливаем для HeaderControl значения свойств MinWidth и MaxWidth, равными ширине соответствующей колонки в DBGrid. Это предохранит колонки от изменения их ширины. Для изменяющих размер колонок нужно дополнительное кодирование, и я решил не лишать Вас этого удовольствия. Теперь самое интересное. Мы собираемся создать индекс для каждой колонки в DBGrid. Имя индекса будет таким же, как и название колонки. Данный код мы должны заключить в конструкцию try..finally, поскольку существуют некоторые поля, которые не могут быть проиндексированы (например, Blob- и Memo-поля). При попытке индексации этих полей генерится исключительная ситуация. Мы перехватываем это исключение и недопускаем возможности щелчка на данной колонке. Это означает, что колонки, содержащие неиндексированные поля, не будут реагировать на щелчок мышью. Создание этих индексов служит объяснением тому, почему таблица должна быть открыта в режиме эксклюзивного доступа. И в заключение мы закрываем таблицу, сбрасываем флаг эксклюзивности и снова делаем таблицу активной.

Последний шаг. При щелчке на HeaderControl нам необходимо включить правильный индекс таблицы. Создадим обработчик события OnSectionClick компонента HeaderControl как показано ниже:

procedure TForm1.HeaderControl1SectionClick(
HeaderControl: THeaderControl; Section: THeaderSection);
begin
  Table1.IndexName := Section.Text;
end;
Это все! После щелчка на заголовке колонки значение свойства таблицы IndexName становится равным заголовку компонента HeaderControl. Просто и красиво, да? Тем не менее есть масса мест, требующих улучшения. К примеру, вторичный щелчок должен возобновлять порядок сортировки. Или возможность изменения размера самих колонок. Попробуйте сами, это не сложно! Улучшения Здесь приведен улучшенный код по сравнению с предыдущей версией "Совета", он заключается в использовании в качестве имени индекса имя поля вместо заголовка.

Это улучшает гибкость. Изменения указаны наклонным курсивом.

procedure TfrmDoc.FormCreate(Sender: TObject);
var
  TheCap: string;
  TheFn: string;
  TheWidth: Integer;
  a: Integer;
begin
  Dbgrid1.Options := DBGrid1.Options - [DGTitles];
  Headercontrol1.sections.Add;
  Headercontrol1.Sections.Items[0].Width := 12;
  for a := 1 to DBGRID1.Columns.Count do
  begin
    with DBGrid1.Columns.Items[a - 1] do
    begin
      TheFn := FieldName;
      TheCap := Title.Caption;
      TheWidth := Width;
    end;
    with Headercontrol1.Sections do
    begin
      Add;
      Items[a].Text := TheCap;
      Items[a].Width := TheWidth + 1;
      Items[a].MinWidth := TheWidth + 1;
      Items[a].MaxWidth := TheWidth + 1;
    end; (* WITH Headercontrol1.Sections *)
    try (* except *)
      { Используем индексы с тем же именем, что и имя поля }
      (DataSource1.Dataset as TTable).IndexName := TheFn;
        { Пробуем задать имя индекса }
    except
      HeaderControl1.Sections.Items[a].AllowClick := False; { Индекс недоступен }
    end; (* EXCEPT *)
  end; (* FOR *)
end; (* PROCEDURE *)

Используйте свойство FieldName компонента DBGrid для задания индекса с тем же именем, что и имя поля.

procedure TfrmDoc.HeaderControl1SectionClick(HeaderControl:
THeaderControl; Section: THeaderSection);
begin
  (DataSource1.Dataset as TTable).IndexName :=
  DBGrid1.Columns.Items[ Section.Index - 1 ].FieldName;
end;

Копирование информации из DBGrid-а в Clipboard.

Автор: Беличенко Б.
Утро. 7.30. Звонит будильник. Программер медленно отрывает голову от подушки. Кидает будильник об стену и нервно шепчет: - I'm is bypassing my startup files! И опять засыпает.

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

unit UnGridToClb;
interface
uses
  Windows, SysUtils, Classes, Dialogs,
  Grids, DBGrids, Db, DBTables, ClipBrd;
procedure CopyGRDToClb(dbg: TDBGrid);
// Копирует DBGrid в буфер обмена,
// после чего данные отлично переносятся
// как в простой текстовый редактор, так и в Excell
implementation
procedure CopyGRDToClb(dbg: TDBGrid);
var
  bm: TBookMark;
  pch, pch1: PChar;
  s, s2: string;
  i, j: integer;
begin
  s := '';
  for j := 0 to dbg.Columns.Count - 1 do
    s := s + dbg.Columns.Items[j].Title.Caption + #9;
  s := s + #13 + #10;
  if not dbg.DataSource.DataSet.active then
  begin
    ShowMessage('Нет выборки!!!');
    Exit;
  end;
  try
    dbg.Visible := False; //Делаем грид невидимым, чтобы не тратилось время
    //на его перерисовку при прокрутке DataSet - просто и
    //эффективно
    bm := dbg.DataSource.DataSet.GetBookmark; // для того чтобы не
    // потерять текущую запись
    dbg.DataSource.DataSet.First;
    while not dbg.DataSource.DataSet.EOF do
    begin
      s2 := '';
      for j := 0 to dbg.Columns.Count - 1 do
      begin
        s2 := s2 + dbg.Columns.Items[j].Field.AsString + #9;
      end;
      s := s + s2 + #13 + #10;
      dbg.DataSource.DataSet.Next;
    end;
    //Переключаем клавиатуру "в русский режим",
    //иначе - проблемы с кодировкой
    GetMem(pch, 100);
    GetMem(pch1, 100);
    GetKeyboardLayoutName(pch);
    StrCopy(pch1, pch);
    while pch <> '00000419' do
    begin
      ActivateKeyboardLayout(HKL_NEXT, 0);
      GetKeyboardLayoutName(pch);
      if strComp(pch, pch1) = 0 then
        //Круг замкнулся - нет такого языка '00000419'
        StrCopy(pch, '00000419');
    end;
    clipboard.AsText := s; //Данные - в буфер!!!
    //Возвращаем режим клавиатуры
    while strComp(pch, pch1) <> 0 do
    begin
      ActivateKeyboardLayout(HKL_NEXT, 0);
      GetKeyboardLayoutName(pch);
    end;
    FreeMem(pch);
    FreeMem(pch1);
    dbg.DataSource.DataSet.GotoBookmark(bm);
    //ShowMessage('Данные успешно скопированы в буфер обмена.');
  finally
    dbg.Visible := True;
  end;
end;
end.

Как из DBGrid перенести данные в существующий Excel файл.

Автор: xUSSR
Во время раскопок в Америке были найдены кусочки медной проволоки. После долгих исследований было выяснено, что это - остатки древней кабельной сети, которой пользовались индейцы. Во время раскопок в Германии были найдены кусочки стекла. После долгих исследований было выяснено, что это - остатки оптоволоконной сети древних нибелунгов. В России долго копали, копали, копали... и не нашли ничего. Так было выяснено, что древние славяне пользовались спутниковой связью.
//Если не обезательно из DBGrid-а... то вот пример из Query ...
// Если есть вопросы то ¹ ACQ 134087719
// имя файла шаблона и откуда (можно TTable)
Procedure SendtoExcel(ShFile: String; Querys: TQuery);
Var
   ExcelApp, Workbook, Range, Cell1, Cell2, ArrayData: Variant;
   BeginCol, BeginRow, j: integer;
   RowCount, ColCount: Integer;
Begin
   BeginCol := 1;
   BeginRow := 3;
 // Размеры выводимого массива данных
   RowCount := Querys.RecordCount;
   ColCount := Querys.FieldDefs.Count;
 // Создание Excel
   ExcelApp := CreateOleObject('Excel.Application');
 // Отключаем реакцию Excel на события, чтобы ускорить вывод информации
   ExcelApp.Application.EnableEvents := false;
 //  Создаем Книгу (Workbook)
 //  Если заполняем шаблон, то
   Workbook := ExcelApp.WorkBooks.Add(ShFile);
   ArrayData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant);
 // Заполняем массив
   Querys.DisableControls;
   Querys.First;
   While Not Querys.eof Do
   Begin
       For J := 1 To Querys.FieldDefs.Count Do
       Begin
           ArrayData[Querys.RecNo, J] :=
            Querys.FieldbyName(Querys.FieldDefs.Items[j - 1].DisplayName).value;
       End;
       Querys.Next;
   End;
   Querys.EnableControls;
//     Левая верхняя ячейка области, в которую будем выводить данные
   Cell1 := WorkBook.WorkSheets[1].Cells[BeginRow, BeginCol];
 // Правая нижняя ячейка области, в которую будем выводить данные
   Cell2 := WorkBook.WorkSheets[1].Cells[BeginRow + RowCount - 1,
    BeginCol + ColCount - 1];
 // Область, в которую будем выводить данные
   Range := WorkBook.WorkSheets[1].Range[Cell1, Cell2];
 // А вот и сам вывод данных
 // Намного быстрее поячеечного присвоения
   Range.Value := ArrayData;
 // Делаем Excel видимым
   ExcelApp.Visible := True;
End;

Экспорт из TDBGrid в Excel без OLE.

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

{
  Exporting a DBGrid to excel without OLE
  I develop software and about 95% of my work deals with databases.
  I enjoied the advantages of using Microsoft Excel in my projects
  in order to make reports but recently I decided to convert myself
  to the free OpenOffice suite.
  I faced with the problem of exporting data to Excel without having
  Office installed on my computer.
  The first solution was to create directly an Excel format compatible file:
  this solution is about 50 times faster than the OLE solution but there
  is a problem: the output file is not compatible with OpenOffice.
  I wanted a solution which was compatible with each "DataSet";
  at the same time I wanted to export only the dataset data present in
  a DBGrid and not all the "DataSet".
  Finally I obtained this solution which satisfied my requirements.
  I hope that it will be usefull for you too.
  First of all you must import the ADOX type library
  which will be used to create the Excel file and its
  internal structure: in the Delphi IDE:
  1)Project->Import Type Library:
  2)Select "Microsoft ADO Ext. for DDL and Security"
  3)Uncheck "Generate component wrapper" at the bottom
  4)Rename the class names (TTable, TColumn, TIndex, TKey, TGroup, TUser, TCatalog) in
    (TXTable, TXColumn, TXIndex, TXKey, TXGroup, TXUser, TXCatalog)
    in order to avoid conflicts with the already present TTable component.
  5)Select the Unit dir name and press "Create Unit".
    It will be created a file named AOX_TLB.
    Include ADOX_TLB in the "uses" directive inside the file in which you want
    to use ADOX functionality.
  That is all. Let's go now with the implementation:
 }
unit DBGridExportToExcel;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, ComCtrls, DB, IniFiles, Buttons, dbgrids, ADOX_TLB, ADODB;
type
  TScrollEvents = class
    BeforeScroll_Event: TDataSetNotifyEvent;
    AfterScroll_Event: TDataSetNotifyEvent;
    AutoCalcFields_Property: Boolean;
  end;
procedure DisableDependencies(DataSet: TDataSet; var ScrollEvents:
  TScrollEvents);
procedure EnableDependencies(DataSet: TDataSet; ScrollEvents: TScrollEvents);
procedure DBGridToExcelADO(DBGrid: TDBGrid; FileName: string; SheetName:
  string);
implementation
//Support procedures: I made that in order to increase speed in
//the process of scanning large amounts
//of records in a dataset
//we make a call to the "DisableControls" procedure and then disable the "BeforeScroll" and
//"AfterScroll" events and the "AutoCalcFields" property.
procedure DisableDependencies(DataSet: TDataSet; var ScrollEvents:
  TScrollEvents);
begin
  with DataSet do
  begin
    DisableControls;
    ScrollEvents := TScrollEvents.Create();
    with ScrollEvents do
    begin
      BeforeScroll_Event := BeforeScroll;
      AfterScroll_Event := AfterScroll;
      AutoCalcFields_Property := AutoCalcFields;
      BeforeScroll := nil;
      AfterScroll := nil;
      AutoCalcFields := False;
    end;
  end;
end;
//we make a call to the "EnableControls" procedure and then restore
// the "BeforeScroll" and "AfterScroll" events and the "AutoCalcFields" property.
procedure EnableDependencies(DataSet: TDataSet; ScrollEvents: TScrollEvents);
begin
  with DataSet do
  begin
    EnableControls;
    with ScrollEvents do
    begin
      BeforeScroll := BeforeScroll_Event;
      AfterScroll := AfterScroll_Event;
      AutoCalcFields := AutoCalcFields_Property;
    end;
  end;
end;
//This is the procedure which make the work:
procedure DBGridToExcelADO(DBGrid: TDBGrid; FileName: string; SheetName:
  string);
var
  cat: _Catalog;
  tbl: _Table;
  col: _Column;
  i: integer;
  ADOConnection: TADOConnection;
  ADOQuery: TADOQuery;
  ScrollEvents: TScrollEvents;
  SavePlace: TBookmark;
begin
  //
  //WorkBook creation (database)
  cat := CoCatalog.Create;
  cat._Set_ActiveConnection('Provider=Microsoft.Jet.OLEDB.4.0; Data Source=' +
    FileName + ';Extended Properties=Excel 8.0');
  //WorkSheet creation (table)
  tbl := CoTable.Create;
  tbl.Set_Name(SheetName);
  //Columns creation (fields)
  DBGrid.DataSource.DataSet.First;
  with DBGrid.Columns do
  begin
    for i := 0 to Count - 1 do
      if Items[i].Visible then
      begin
        col := nil;
        col := CoColumn.Create;
        with col do
        begin
          Set_Name(Items[i].Title.Caption);
          Set_Type_(adVarWChar);
        end;
        //add column to table
        tbl.Columns.Append(col, adVarWChar, 20);
      end;
  end;
  //add table to database
  cat.Tables.Append(tbl);
  col := nil;
  tbl := nil;
  cat := nil;
  //exporting
  ADOConnection := TADOConnection.Create(nil);
  ADOConnection.LoginPrompt := False;
  ADOConnection.ConnectionString :=
    'Provider=Microsoft.Jet.OLEDB.4.0; Data Source=' + FileName +
    ';Extended Properties=Excel 8.0';
  ADOQuery := TADOQuery.Create(nil);
  ADOQuery.Connection := ADOConnection;
  ADOQuery.SQL.Text := 'Select * from [' + SheetName + '$]';
  ADOQuery.Open;
  DisableDependencies(DBGrid.DataSource.DataSet, ScrollEvents);
  SavePlace := DBGrid.DataSource.DataSet.GetBookmark;
  try
    with DBGrid.DataSource.DataSet do
    begin
      First;
      while not Eof do
      begin
        ADOQuery.Append;
        with DBGrid.Columns do
        begin
          ADOQuery.Edit;
          for i := 0 to Count - 1 do
            if Items[i].Visible then
            begin
              ADOQuery.FieldByName(Items[i].Title.Caption).AsString :=
                FieldByName(Items[i].FieldName).AsString;
            end;
          ADOQuery.Post;
        end;
        Next;
      end;
    end;
  finally
    DBGrid.DataSource.DataSet.GotoBookmark(SavePlace);
    DBGrid.DataSource.DataSet.FreeBookmark(SavePlace);
    EnableDependencies(DBGrid.DataSource.DataSet, ScrollEvents);
    ADOQuery.Close;
    ADOConnection.Close;
    ADOQuery.Free;
    ADOConnection.Free;
  end;
end;
end.

Экспортировать DBGrid в HTML.

Автор: Тенцер А.Л.
Звонит юзер оператору АТС: - Девушка! Мне тут какой-то совершенно дикий счет пришел за услуги связи... - Ваш номер? - 555-55-55 - Так... Все верно. Это счет за секс-услуги по телефону. - Да ты че! Я такой гадостью сроду не занимался! - Как? А неделю назад вы звонили Васе Пупкину по телефону 444-44-44 и пытались настроить полуось?
type
 TGridToHTMLOption = (ghWithHeaders);
 TGridToHTMLOptions = set of TGridToHTMLOption;
function DBGridToHTML(Grid : TDBGrid;
  ExportOptions: TGridToHTMLOptions): String;
const
  HTMLStart =
   '< !DOCTYPE HTML PUBLIC " -//W3C//DTD HTML 4.0 Transitional//EN" > '#13
+
   '< HTML> '#13 +
   '< HEAD> < META http-equiv=Content-Type content=" text/html;
charset=windows-1251" > '#13 +
   '< STYLE> '#13 +
   'BODY {'#13 +
   ' BACKGROUND: white;'#13 +
   ' COLOR: black;'#13 +
   ' FONT-FAMILY: arial;'#13 +
   ' FONT-SIZE: 8pt;'#13 +
   ' VERTICAL-ALIGN: top'#13 +
   '}'#13 +
   'TABLE {'#13 +
   ' BACKGROUND: white;'#13 +
   ' BORDER-BOTTOM: silver 0px solid;'#13 +
   ' BORDER-LEFT: silver 1px solid;'#13 +
   ' BORDER-RIGHT: silver 0px solid;'#13 +
   ' BORDER-TOP: silver 1px solid;'#13 +
   ' FONT-FAMILY: arial;'#13 +
   ' FONT-SIZE: 8pt;'#13 +
   ' FONT-WEIGHT: normal;'#13 +
   '}'#13 +
   'TD {'#13 +
   ' BORDER-BOTTOM: silver 1px solid;'#13 +
   ' BORDER-LEFT: silver 0px solid;'#13 +
   ' BORDER-RIGHT: silver 1px solid;'#13 +
   ' BORDER-TOP: silver 0px solid;'#13 +
   ' VERTICAL-ALIGN: top;'#13 +
   ' TEXT-ALIGN: left;'#13 +
   '}'#13 +
   'TD.grid {'#13 +
   ' TEXT-ALIGN: left;'#13 +
   '}'#13 +
   'TD.gridr {'#13 +
   ' TEXT-ALIGN: right;'#13 +
   '}'#13 +
   'TD.gridc {'#13 +
   ' TEXT-ALIGN: center;'#13 +
   '}'#13 +
   'TH {'#13 +
   ' BACKGROUND: silver;'#13 +
   ' BORDER-BOTTOM: gray 1px solid;'#13 +
   ' BORDER-LEFT: gray 0px solid;'#13 +
   ' BORDER-RIGHT: gray 1px solid;'#13 +
   ' BORDER-TOP: gray 0px solid;'#13 +
   ' FONT-WEIGHT: bold;'#13 +
   '}'#13 +
   'TH.grid {'#13 +
   ' TEXT-ALIGN: left;'#13 +
   '}'#13 +
   'TH.gridr {'#13 +
   ' TEXT-ALIGN: right;'#13 +
   '}'#13 +
   'TH.gridc {'#13 +
   ' TEXT-ALIGN: center;'#13 +
   '}'#13 +
   '< /STYLE> '#13 +
   '< TITLE> Печать таблицы< /TITLE> '#13 +
   '< /HEAD> '#13 +
   '< BODY> '#13;
  HTMLEnd = '< /BODY> < /HTML> ';
  TableStart = '< TABLE WIDTH=" 100%"  CELLSPACING=0 CELLPADDING=1> '#13;
  TableEnd = '< /TABLE> '#13;
  HeaderRowStart = '< TR> '#13;
  HeaderRowEnd = '< /TR> '#13;
  BodyRowStart = '< TR> '#13;
  BodyRowEnd = '< /TR> '#13;
const
  StyleNames: array [TAlignment] of String = ('grid', 'gridr',
'gridc');
  function TD(Column: TColumn; IsTitle: Boolean; Widht: Integer):
String;
  var
    S: String;
    Align: TAlignment;
    Tag: String;
  begin
    if IsTitle then begin
      Tag := 'TH';
      Align := Column.Title.Alignment;
      S := StyleNames[Align];
    end else begin
      Tag := 'TD';
      Align := Column.Alignment;
      if Align = taLeftJustify then begin
        if (Column.Field is TBCDField) or
           (Column.Field is TCurrencyField) then
          Align := taRightJustify;
        if (Column.Field is TBooleanField) then
          Align := taCenter;
      end;
      S := StyleNames[Align];
      if (Column.Field is TBCDField) or (Column.Field is
TIntegerField) then
        S := S + ' NOWRAP'
    end;
    if Widht >  0 then
      S := S + Format(' WIDTH=" %d%%" ', [Widht]);
    Result := '< ' + Tag + ' class=' + S + '> ';
    if IsTitle then begin
      S := Column.Title.Caption
    end else begin
      if Column.Field is TBooleanField then
      with TBooleanField(Column.Field) do begin
        if Length(DisplayValues) = 0 then begin
          if AsBoolean then
            S := 'да'
          else
            S := 'нет';
        end else
          S := Column.Field.DisplayText;
      end else
        S := Column.Field.DisplayText;
    end;
    if Length(Trim(S)) = 0 then
      S := ' ';
    Result := Result + S + '< /' + Tag + '> '#13;
  end;
var
 BM : String;
 I : Integer;
 Widhts: array of Integer;
 TotalWidht: Integer;
begin
  Result := '';
  with Grid  do begin
    if Assigned(DataSource) and
       Assigned(DataSource.DataSet) and
       DataSource.DataSet.Active then
    with DataSource.DataSet do begin
      DisableControls;
      BM := BookMark;
      SetLength(Widhts, Columns.Count);
      TotalWidht := 0;
      for I := 0 to Pred(Columns.Count) do begin
        if Assigned(Columns[I].Field) then begin
          Widhts[I] := Columns[I].Width;
          Inc(TotalWidht, Widhts[I]);
        end;
      end;
      for I := 0 to High(Widhts) do begin
        Widhts[I] := Widhts[I] * 100 div TotalWidht;
      end;
      Result := HTMLStart;
      Result := Result + TableStart;
      if (ghWithHeaders in ExportOptions) then begin
        Result := Result + HeaderRowStart;
        for I := 0 to Pred(Columns.Count) do begin
          if Assigned(Columns.Items[I].Field) then begin
            Result := Result + TD(Columns.Items[I], TRUE, Widhts[I]);
          end;
        end;
        Result := Result + HeaderRowEnd;
      end;
      First;
      while not Eof do begin
        Result := Result + BodyRowStart;
        for I := 0 to Pred( Columns.Count ) do begin
          if Assigned(Columns.Items[I].Field) then begin
            Result := Result + TD(Columns.Items[I], FALSE,
-1{Integer(Widhts[Index]});
          end;
        end;
        Result := Result + BodyRowEnd;
        Next;
      end;
      Result := Result + TableEnd + HTMLEnd;
      BookMark := BM;
      EnableControls;
    end;
  end;
end;

Перемещение по таблице с помощью вертикальной полосы прокрутки.

Автор: Reinhard Kalinke
Компьютерный магазин. Заходит покупатель - толстый упакованный армянин. АРМЯНИН: День добрый! ПРОДАВЕЦ: Здравствуйте! А: Компьютеры есть хорошие? П: Есть, вот модель - Аквариус. А: А сколько у него памяти? П: 4,3 гига винт, 32 метра димм, 4 метра видео, 512 килов кэш. А: И сколько это вместе?
Это небольшое исправление к исходному коду VCL, позволяющее поддерживать перемещение по таблице с помощью изменения позиции движка вертикальной полосы прокрутки. (Примечание: это работает только с таблицами Paradox и BDE. Для использования этого кода с другими таблицами/движками вам необходимо заменить DBIGetSeqNo на функцию, надежно возвращающую текущую позицию записи вне зависимости от того, использует ли таблица индекс или нет.)

В DBGRID.PAS измените две следующих процедуры:

procedure TCustomDBGrid.UpdateScrollBar;
var
  Pos: Integer;
  mPos, mMax: longint;
begin
  if FDatalink.Active and HandleAllocated then
    with FDatalink.DataSet do
    begin
      UpdateCursorPos;
      if (DBIGetSeqNo(Handle, mPos) = DBIERR_NONE) then
      begin
        mMax := RecordCount;
        while mMax > 1000 do
        begin
          mMax := mMax div 10;
          mPos := mPos div 10;
        end;
        SetScrollRange(Self.Handle, SB_VERT, 1, mMax, False);
      end
      else
      begin
        if BOF then
          mPos := 0
        else if EOF then
          mPos := 4
        else
          mPos := 2;
        SetScrollRange(Self.Handle, SB_VERT, 0, 4, False);
      end; (**)
      if GetScrollPos(Self.Handle, SB_VERT) <> mPos then
        SetScrollPos(Self.Handle, SB_VERT, mPos, True);
    end;
end;
procedure TCustomDBGrid.WMVScroll(var Message: TWMVScroll);
var
  mMin, mMax: integer;
  RecCount, RecNo, NewRecNo: longint;
begin
  if not AcquireFocus then
    Exit;
  if FDatalink.Active then
    with Message, FDataLink.DataSet, FDatalink do
      case ScrollCode of
        SB_LINEUP: MoveBy(-ActiveRecord - 1);
        SB_LINEDOWN: MoveBy(RecordCount - ActiveRecord);
        SB_PAGEUP: MoveBy(-VisibleRowCount);
        SB_PAGEDOWN: MoveBy(VisibleRowCount);
        SB_THUMBPOSITION:
          if (DBIGetSeqNo(Handle, RecNo) = DBIERR_NONE) then
          begin
            GetScrollRange(self.Handle, SB_VERT, mMin, mMax);
            NewRecNo := Pos * (FDataLink.DataSet.RecordCount div mMax);
            MoveBy(NewRecNo - RecNo);
          end
          else
            case Pos of
              0: First;
              1: MoveBy(-VisibleRowCount);
              2: Exit;
              3: MoveBy(VisibleRowCount);
              4: Last;
            end;
        SB_BOTTOM: Last;
        SB_TOP: First;
      end;
end;
Имейте в виду, что из-за небольшой ошибки в VCL (MoveBy использует integer-параметр вместо longint), могут быть проблемы с большими таблицами (RecordCount>MaxInt). Объяснение этому факту я нашел в журнале Delphi Magazine. Для больших таблиц вы должны заменить вызовы MoveBy на DBISetToSeqNo или DBIGetRelativeRecord. Не забудьте после данного вызова вызвать Resnyc([]) или Refresh!

P.S. Пока вы ковыряетесь в DBGRIDS.PAS: найдите и замените TitleColor на FixedColor в TCustomDBGrid.Create и в TCustomDBGrid.DrawCell. Значение свойства FixedColor влияет на показ заголовков колонок, и они будут выводится как и ожидалось.

Корректная работа при работе с колесиком мышки в TDBGrid.

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

{....}
 public
   procedure AppMessage(var Msg: TMsg; var Handled: Boolean);
      {....}
 procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
 var
   i: SmallInt;
 begin
   {Mouse wheel behaves strangely with dgbgrids - this proc sorts this out}
   if Msg.message = WM_MOUSEWHEEL then
   begin
     Msg.message := WM_KEYDOWN;
     Msg.lParam := 0;
     i := HiWord(Msg.wParam);
     if i > 0 then
       Msg.wParam := VK_UP
     else
       Msg.wParam := VK_DOWN;
     Handled := False;
   end;
 end;
   // Und im Projekt Source: 
  // And in the project source: 
{....}
 Application.OnMessage := Form1.AppMessage;
 {....}

DBGrid компонент, который отображает разными цветами удалённые, обновлённые и добавленные записи.

Статьи по Delphi (005)…
"...Для продолжения установки Win'2000 вставьте 45234-ю дискету..."
unit atcDBGrid;
(*
(c) Aveen Tech
2001 - 2002
FileName: atcDBGrid.pas
Version Date Author Comment
1.0 13/06/2000 Majid Vafai Jahan Create.
OVERVIEW
- This grid is inherited from DBGrid and add some required functionality to it.
Functionality:
- Record type are all records that may be modified, unmodified, inserted, deleted.
- Coloring according to Record type.
- show selected Record Type.
*)
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Grids, DBGrids, dbTables, db;
const
  AlignFlags : array [TAlignment] of Integer =
  ( DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX,
  DT_RIGHT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX,
  DT_CENTER or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX );
  RTL: array [Boolean] of Integer = (0, DT_RTLREADING);
type
  TCachedShow = (csModify, csUnModify, csRemoved, csInserted, csAll, csNormal);
  TatcDBGrid = class(TDBGrid)
private
  FCachedShow: TCachedShow;
  FModifiedColor: TColor;
  FInsertedColor: TColor;
  FDeletedColor: TColor;
  procedure SetCachedShow(const Value: TCachedShow);
protected
  procedure DrawDataCell(const Rect: TRect; Field: TField; State: TGridDrawState); override;
  procedure DrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); override;
public
  constructor Create(AOwner: TComponent); override;
published
  property atcCachedShow: TCachedShow read FCachedShow write SetCachedShow;
  property atcDeletedColor: TColor read FDeletedColor write FDeletedColor;
  property atcInsertedColor: TColor read FInsertedColor write FInsertedColor;
  property atcModifiedColor: TColor read FModifiedColor write FModifiedColor;
end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('ATC DB Compo', [TatcDBGrid]);
end;
constructor TatcDBGrid.Create(AOwner: TComponent);
(*
Description: Record Type Showing is All except Deletes.
*)
begin
  inherited;
  FCachedShow := csNormal;
  FDeletedColor := clGray;
  FInsertedColor := clAqua;
  FModifiedColor := clRed;
end;
procedure TatcDBGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer;
Column: TColumn; State: TGridDrawState);
(*
Description: On Drawing Column Color Updated Records.
*)
var
  ARect: TRect;
begin
  inherited;
  if not Assigned(Column.Field) then
    exit;
  // Copy Rect into Variable.
  CopyRect(ARect, Rect);
  if Assigned(DataLink) and (DataLink.Active) and (DataLink.DataSet <> nil) then
  begin
    // если текущая запись изменена
    if DataLink.DataSet.UpdateStatus = usModified then
    begin
      Canvas.Brush.Color := atcModifiedColor;
      Canvas.Font.Color := clBlack;
      Canvas.FillRect(Rect);
      DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
      AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
    end
    // если текущая запись добавлена.
    else
    if DataLink.DataSet.UpdateStatus = usInserted then
    begin
      Canvas.Brush.Color := atcInsertedColor;
      Canvas.Font.Color := clBlack;
      Canvas.FillRect(Rect);
      DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
      AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
    end
    // если текущая запись удалена.
    else
    if DataLink.DataSet.UpdateStatus = usDeleted then
    begin
      Canvas.Brush.Color := atcDeletedColor;
      Canvas.Font.Color := clWhite;
      Canvas.FillRect(Rect);
      DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
      AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
    end;
  end;
end;
procedure TatcDBGrid.DrawDataCell(const Rect: TRect; Field: TField;
State: TGridDrawState);
(*
Описание: Рисуем ячейки
*)
var
  ARect: TRect;
begin
  inherited;
  CopyRect(ARect, Rect);
  if Assigned(DataLink) and (DataLink.Active) and (DataLink.DataSet <> nil) then
  begin
    // если текущая запись изменена
    if DataLink.DataSet.UpdateStatus = usModified then
    begin
      Canvas.Brush.Color := clRed;
      Canvas.Font.Color := clBlack;
      Canvas.FillRect(Rect);
      DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
      AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
    end
    // если текущая запись добавлена.
    else
    if DataLink.DataSet.UpdateStatus = usInserted then
    begin
      Canvas.Brush.Color := clAqua;
      Canvas.Font.Color := clBlack;
      Canvas.FillRect(Rect);
      DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
      AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
    end
    // если текущая запись удалена.
    else
    if DataLink.DataSet.UpdateStatus = usDeleted then
    begin
      Canvas.Brush.Color := clGray;
      Canvas.Font.Color := clWhite;
      Canvas.FillRect(Rect);
      DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
      AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
    end;
  end;
end;
procedure TatcDBGrid.SetCachedShow(const Value: TCachedShow);
(*
Description: Record type for showing in grid.
Parameters: Value cached record show.
*)
begin
  FCachedShow := Value;
  if ComponentState = [csDesigning] then
    exit;
  if not Assigned(DataSource) or not Assigned(DataSource.DataSet) then
    exit;
  // для показа только выбранного типа записей.
  if Assigned(DataLink) and Assigned(DataLink.DataSet) and (DataLink.Active) then
  begin
    case FCachedShow of
      csAll:
        TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified, rtInserted, rtDeleted, rtUnmodified];
      csModify:
        TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified];
      csUnModify:
        TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtUnmodified];
      csInserted:
        TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtInserted];
      csRemoved:
        TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtDeleted];
      csNormal:
        TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified, rtInserted, rtUnmodified];
    end;
  end;
end;
end.

DBGrid c FixedCols.

Статьи по Delphi (005)…
Автор: Cheers
Фирма Microsoft выпустила свой новый продукт - Презерватив 2000. Отличие этого продукта от Презерватив 98 состоит в следующем: - он еще легче надевается на "инструмент", чем раньше; - теперь он интегрируется с "инструментом" так, что практически получается одно целое; - и главное, значительно улучшена навигация по пути прохождения, т.е. прекрасно входит и замечательно выходит. К сожалению, не прошло и двух дней со дня выхода нового изделия, как известный хакер NoPreserve нашел дыру в Презерватив 2000. Надо отдать должное Microsoft, которая буквально через день оперативно среагировала на найденный брак, выпустив заплатку, которую каждый пользователь может бесплатно получить по почте со склада фирмы и пришить в нужное место, согласно инструкции.
Кам мне при прокрутке "зафиксировать" левое поле табличной сетки?

Я делаю это таким образом:

unit Fcdgrid;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Grids, DBGrids, DBCtrls, DB, Menus;
type
  TFixedColDBGrid = class(TDBGrid)
  private
    FUserFixedCols: Integer;
  protected
    procedure LayoutChanged; override;
    procedure SetUserFixedCols(I: Integer);
  published
    property UserFixedCols: Integer read FUserFixedCols write SetUserFixedCols;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Data Controls', [TFixedColDBGrid]);
end;
procedure TFixedColDBGrid.LayoutChanged;
begin
  inherited LayoutChanged; {   присваиваем FixedCols 1 если индикатор, иначе 0 }
  if ((inherited FixedCols + FUserFixedCols) < inherited ColCount) then
    inherited FixedCols := (FUserFixedCols + inherited FixedCols);
end;
procedure TFixedColDBGrid.SetUserFixedCols(I: Integer);
begin
  FUserFixedCols := I;
  LayoutChanged;
end;
end.

DBGrid с номером строки.

Автор: Mark Meyer
Другой кибернетик придумал робота-андроида и настроил его на оптимальное выполнение задач. В качестве теста приказал ему принести 5 тысяч долларов. Робот вернулся через 2 минуты со страховкой маленького сына кибернетика.

Скомпилируйте это, и вы получите новый компонент с нужными свойствами:

unit RowGrid;
interface
uses
  WinTypes, WinProcs, Classes, Grids, DBGrids;
type
  TRowDBGrid = class(TDBGrid)
  public
    property Row;
    property RowCount;
    property VisibleRowCount;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Data Controls', [TRowDBGrid]);
end;
end.
{вот небольшой испытательный демо-проект.. мы
поместили на форму нашу сетку-наследницу, 3 компонента
EditBox и поместили следующий код в обработчик события
ondrawdatacell вашего TRowGrid}
procedure TForm1.RowDBGrid1DrawDataCell(Sender: TObject; const Rect:
  TRect; Field: TField; State: TGridDrawState);
begin
  eb_row.text := inttostr(rowdbgrid1.row);
  eb_rowcount.text := inttostr(rowdbgrid1.rowcount);
  eb_visiblerowcount.text := inttostr(rowdbgrid1.visiblerowcount);
end;

Синтаксис функции DbiAddAlias.

DbiAddAlias (пропущено в BDE.HLP & Руководстве пользователя) Синтаксис:
   DBIResult DbiAddAlias( [hCfg], pszAliasName, pszDriverType, pszParams, bPersistent );
Описание:
   DbiAddAlias добавляет псевдоним в конфигурационный файл, связанный с
   текущим сеансом.
Параметры:
   hCfg              Type: hDBICfg              (Входящий)
      Для BDE 2.5 данный параметр должен быть NULL. Указывает, что
      конфигурация действует в течение текущего сеанса.
      Другие значения для BDE 2.5 не поддерживаются.
   pszAliasName      Type: pCHAR                (Входящий)
      Указатель на имя псевдонима. Это имя нового псевдонима, который
      должен быть добавлен.
   pszDriverType     Type: pCHAR                (Входящий)
      Указатель на тип устройства. Это тип устройства для добавляемого
      псевдонима. Если данный параметр NULL, псевдоним будет добавлен 
      для базы данных STANDARD. Если указан szPARADOX, szDBASE или szASCII, 
      будет добавлена запись в генератор псевдонимов базы данных STANDARD 
      для указания того, что данный тип будет предпочтительным типом 
      устройства. Если указано имя устройства, то оно должно существовать
      в измененном файле конфигурации.
   pszParams         Type: pCHAR                (Входящий)
      Указатель на список дополнительных параметров. Данный список 
      определяется следующим образом: 
      "AliasOption: Option Data[;AliasOption: Option Data][;...]".
      AliasOption должен соответствовать одному из значений, возвращаемому 
      DbiOpenCfgInfoList. Для псевдонимов базы данных STANDARD, единственно
      необходимый параметр - PATH, остальные игнорируются (без ошибок). 
      
      Пример 1: Чтобы установить путь для использования базы данных
        STANDARD:
         "PATH:c:\mydata"
      Пример 2: Чтобы установить имя сервера и имя пользователя для 
        использования драйвера SQL: 
         "SERVER NAME: server:/path/database;USER NAME: myname"
   bPersistent       Type: BOOL                 (Входящий)
      Определяет область действия нового псевдонима.
         TRUE     Сохраняется в файле конфигурации для будующих сеансов.
         FALSE    Для использования только в течение текущего сеанса. 
                  Псевдоним удаляется в конце сеанса (или при выходе из
                  программы).
Использование:
   Созданный данной функцией псевдоним будет иметь параметры по умолчанию,
   хранимые в списке параметров драйверов "DB OPEN", если только оне не
   перекрыты в параметре pszParams. Вы можете использовать 
   DbiOpenCfgInfoList, чтобы изменить значение по умолчанию после добавления 
   псевдонима с помощью DbiAddAlias.
   
   Для псевдонимов стандартной базы данных все параметры pszParams за
   исключением PATH игнорируются.
Предварительные условия:
   DbiInit должен вызываться до вызова DbiAddAlias. 
   
Возвращаемые значения DBIResult:
   DBIERR_INVALIDPARAM
      Имя псевдонима Null или один из следующих типов pszDriverType: 
      szASCII, szDBASE, szPARADOX. В последнем случае используйте
      NULL pszDriverType для указания на базу данных STANDARD.
   DBIERR_NONE
      Псевдоним был успешно добавлен.
   DBIERR_NAMENOTUNIQUE
      Существует другой псевдоним с тем же именем (работает
      когда bPersistent равен TRUE).
   DBIERR_OBJNOTFOUND
      Один (или более) из дополнительных параметров, указанных в 
      pszParams не соответствуют правильным типам в секции драйверов 
      конфигурационного файла.
   DBIERR_UNKNOWNDRIVER
      Имя устройства в конфигурационном файле при сопоставлении с 
      pszDriverType не найдено.
Смотри также:
   DbiInit, DbiOpenCfgInfoList

Пример DbiAddFilter.

Автор: Mark Erbaugh

type
  TmyFilter = record
    Expr: CANExpr;
    Nodes: array[0..2] of CANNode;
    literals: array[0..7] of char;
  end;
const
  myFilter: TMyFilter = (Expr:
    (iVer: 1; iTotalSize: sizeof(TMyFilter); iNodes: 3;
    iNodeStart: sizeof(CANExpr); iLiteralStart: sizeof(CANExpr) +
    3 * sizeof(CANNode));
    Nodes:
    ((canBinary: (nodeClass: nodeBinary; canOP: canEQ;
    iOperand1: sizeof(CANNode); iOperand2: 2 * sizeof(CANNode))),
    (canField: (nodeClass: nodeField; canOP: canField2;
    iFieldNum: 0; iNameOffset: 0)),
    (canConst: (nodeClass: nodeConst; canOP: canCONST2;
    iType: fldZSTRING; iSize: 3; iOffset: 5)));
    literals:
    ('T', 'Y', 'P', 'E', #0, 'I', 'N', #0));
var
  dbResult: DBIResult;
  hFilter, hFilter1: hDBIFilter;
begin (* procedure SetupFilter *)
  dbResult := DbiAddFilter(tblAP_.Handle, 1, 1,
    False, addr(myFilter), nil, hFilter);
  dbResult := DbiActivateFilter(tblAP_.Handle, hFilter);
  tblAP_.First;
  myFilter.nodes[0].canBinary.canOp := canNE;
  dbResult := DbiAddFilter(tblAP1_.Handle, 1, 1,
    False, addr(myFilter), nil, hFilter1);
  dbResult := DbiActivateFilter(tblAP1_.Handle, hFilter1);
  tblAP1_.First;
  myFilter.nodes[0].canBinary.canOp := canEQ;
end;
Этот пример устанавливает два фильтра. Первый (применяемый к tblAP_) выводит все записи, где ТИП поля имеет значение 'IN'. Второй (применяемый к tblAP1_) выводит все записи, где ТИП поля не имеет значения 'IN'.

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

Как поместить картинку из базы данных, например MsSQL, в компонент TIMAGE.

Статьи по Delphi (005)…
Общеизвестно, что люди на Земле делятся на нормальнах и тех кто занимается компьтерами. Причем первых с каждым годом становится все меньше и меньше. СТАДИИ ЗАБОЛЕВАИЯ,СИМТОМЫ И МЕТОД ЛЕЧЕНИЯ 1. Первая стадия (легкая) Симптомы: Человек садиться за компутер, включает его, работает и уходит домой в конце рабочего дня, после чего не вспоминает о компутере до следующего утра. Аппетит и сон нормален головные боли и понос отсутствуют. Лечение: В лечении временно не нуждается. 2. Вторая стадия (полулегкая) Симптомы: У больного наблюдается повышенный интерес к компутеру, выражающийся в нездоровом возбуждении, охватывающим его при виде указанного обьекта. Аппетит нормальный. Сон беспокойный со вскрикиваниями и повизгиваниями. Задерживается на работе на 2-3 часа и топчет кнопки. Лечение: Удалить больного от компутера, принимать внутрь медецинский спирт 3 раза в день по 0.5 столовой ложки. Компутерную литературу убрать в недоступное место. С работы встречать. 3. Третия стадия (средней тяжести) Симптомы: Больной задерживается на работе более 4-5 часов после окончания рабочего дня, копит деньги на домашний компутер. В обиходе начинает употреблять компьютерную терминологию и не реагирует на расширенные глаза окружающих. Аппетит повышенный. Сон беспокойный с выкрикиванием компутерных словечек и беспричинным смехом. Приходит в резкое возбуждение при виде компутера или при встречи с больным 3-й стадии и выше. В этом случае болезнь может перейти в 4-ю стадию. Лечение: Больного изолировать от общества и от компутера, деньги отобрать, женить. При бурном поведении и отказе от лечения вводить внутрижелудочно 1-2 ковша водки с портвейном "777", смешаных в пропорции 1:2. Тазик не давать. 4. Четвертая стадия (тяжелая) Симптомы: Больной покупает модем и компутер. Речь изобилует различными компутерными словечками и их сочетаниями. Изобретает новые слова, копит денег на выделенную телефонную линию. Аппетит сильно повышен. Ест любую пищу в любое время суток при ее наличии. Спит 3-4 часа в день, т.к.ночью звонит по модему и при каждои соединении издает вопли, описанные в 3-ем томе книги "Жизнь Животных" (глава 1 поведение самца макаки-резуса в брачный период). Половое влечение сниженно. Рвота, работа, бред и понос отсутсвуют. Лечение: Лечению подлежит только в стационаре. 5.(Безнадежная) Симптомы: Больной заводит у себя BBS, которой уделяет все свободное от звонков и программирования время. Речь невнятная, состоящая на 80 и более процентов компутерного жаргона со спецтерминами. Аппетит и сон отсутствуют. Ест только то что попадает в пределы прямой видимости, независимо от вида и качества продукта. На окружающих обращает внимание только в том случае если они произносят фразы связанные с компутером. Половое влечение отсутствует полностью, т.к. периодически испытывает чувство глубокого удовлетворения от строки на экране "Connect 33600...". держит около компутера ночную вазу и пачку чая, которую забывает развести в воде. Лечение: Лечению не подлежит.
Предполагается, что поле BLOB (например, Pict) в запросе Query.SQL пишется что-то вроде 'select Pict from sometable where somefield=somevalue' запрос открывается делается "присваивание":
Image1.Picture.Assing(TBlobField(Query.FieldByName('Pict'))

Или, если известно, что эта картинка - Bitmap, то можно.

Image1.Picture.Bitmap.Assing(TBlobField(Query.FieldByName('Pict'))

А можно воспользоваться компонентом TDBImage.

Значение DBLookupComboBox.

Я думаю что у меня есть то, что вы хотите. Если вы обратитесь к свойству LookUpValue, то вы получите поле, которое .... ищете.

Я надеюсь что помог вам.

unit clookup;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, DB, DBLookup;
type
  TDBJustLookupCombo = class(TDBLookupCombo)
  private
    { Private declarations }
  protected
    { Protected declarations }
    function GetLValue: TField;
  public
    { Public declarations }
    property LookUpValue: TField read GetLValue;
  published
    { Published declarations }
  end;
  TDBJustLookupList = class(TDBLookupList)
  private
    { Private declarations }
  protected
    { Protected declarations }
    function GetLValue: TField;
  public
    { Public declarations }
    property LookUpValue: TField read GetLValue;
  published
    { Published declarations }
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Data Controls', [TDBJustLookupList,
    TDBJustLookupCombo]);
end;
function TDBJustLookupCombo.GetLValue: TField;
begin
  Result := LookupSource.DataSet.FieldByName(LookUpField);
end;
function TDBJustLookupList.GetLValue: TField;
begin
  Result := LookupSource.DataSet.FieldByName(LookUpField);
end;
end.

Обновить DBLookUpComboBox при скролинге таблицы.

Автор: Stone

Есть табличка, к ней привязан TDBLookupComboBox. Значение в нем отображается только после клика мышкой. Как заставить его перерисоваться после изменения текущей записи в таблице, к которой он привязан?

DBLookupComboBox.KeyValue := Table1.FieldByName('Field1').Value;

Показываемое DBLookupComboBox значение.

Автор: Michael Pratt

Следующая строка в обработчике события OnActivate:

DBLookupCombox1.Perform(WM_KeyDown,38,0);

Заполняет показываемое в dblookupcombox значение. Вы посылаете сообщение индекса непосредственно в dblookupcombox.

Поиск текста в DBMemo.

Попробуйте так: "Подключите" следующую процедуру к событию OnFind для FindDialog. Единственная проблема заключается в том, что в DBMemo я не могу получить выделенный текст, тем не менее в стандартном Memo такой проблемы нет.

procedure TMainForm.FindDialog1Find(Sender: TObject);
var
  Buff, P, FT: PChar;
  BuffLen: Word;
begin
  with Sender as TFindDialog do
  begin
    GetMem(FT, Length(FindText) + 1);
    StrPCopy(FT, FindText);
    BuffLen := DBMemo1.GetTextLen + 1;
    GetMem(Buff, BuffLen);
    DBMemo1.GetTextBuf(Buff, BuffLen);
    P := Buff + DBMemo1.SelStart + DBMemo1.SelLength;
    P := StrPos(P, FT);
    if P = nil then
      MessageBeep(0)
    else
    begin
      DBMemo1.SelStart := P - Buff;
      DBMemo1.SelLength := Length(FindText);
    end;
    FreeMem(FT, Length(FindText) + 1);
    FreeMem(Buff, BuffLen);
  end;
end;

Попробуйте так: "Подключите" следующую процедуру к событию OnFind для FindDialog. Единственная проблема заключается в том, что в DBMemo я не могу получить выделенный текст, тем не менее в стандартном Memo такой проблемы нет.

begin
  DBMemo1.SelStart:= P - Buff;
  DBMemo1.SelLength:= Length(FindText);
end;
  FreeMem(FT, Length(FindText) + 1);
  FreeMem(Buff,BuffLen);
  DBMemo1.SetFocus;
end;

Выводить текст с помощью DBMS_OUTPUT.PUT_LINE в режиме отладки.

Автор: Nomadic Эта функция используется действительно только для отладки. Для того, чтобы результаты ее работы были видны из SQL Plus, необходимо в нем выдать команду:
set serveroutput on size 10000;

Выключение кнопок в DBNavigator.

Microsoft - маленький софт с большими сюрпризами.
{ Расширение DBNavigator: позволяет разработчику включать и выключать
отдельные кнопки через методы EnableButton и DisableButton }
unit GNav;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, DBCtrls;
type
  TMyNavigator = class(TDBNavigator)
  public
    procedure EnableButton(Btn: TNavigateBtn);
    procedure DisableButton(Btn: TNavigateBtn);
  end;
procedure Register;
implementation
procedure TMyNavigator.EnableButton(Btn: TNavigateBtn);
begin
  Buttons[Btn].Enabled := True;
end;
procedure TMyNavigator.DisableButton(Btn: TNavigateBtn);
begin
  Buttons[Btn].Enabled := False;
end;
procedure Register;
begin
  RegisterComponents('Samples', [TMyNavigator]);
end;
end.

Свойства кнопок DBNavigator.

Принято решение переименовать Windows 98 в Windows Diana. Во-первых, выглядит очень привлекательно, во-вторых, требует дофига ресурсов, а в-третьих, живописно крушится.
Как можно узнать значения свойств кнопок компонента DBNavigator (enabled/disabled или видимая/невидимая)?

Для определения видимости вы можете использовать свойство VisibleButtons. Например:

if nbRefresh in DBNavigator1.VisibleButtons then
  ShowMessage('Кнопка Refresh видимая') ;

Для того, чтобы узнать, активизирована (enabled/disabled) кнопка или нет:

{Вместо nbFirst вы можете определить другой
член TNavigateBtn (например, nbFirst, nbPrior,
nbNext, nbLast, nbInsert, nbDelete, nbEdit,
nbPost, nbCancel, nbRefresh)}
if DBNavigator1.Controls[Ord(nbFirst)].Enabled then
  ShowMessage('Кнопка First активизирована') ;

Вызов кнопок DBNavigator.

Встречаются два хакера. Один говорит: - А я знаю что надо сделать чтоб OSR2 ваабще не глючила! - Что??? - Да всего один байт поменять! - Какой ??? - Да в названии OSR2 третий байт надо поменять на "/"!!! Ж:-)))
Я думаю для этого отлично подойдет вызов public-метода BtnClick(nbButton). Я догадываюсь, почему они не назвали его просто "Click", поскольку это метод события OnClick :-) .

Я делаю приблизительно так:

DBNavigator1.BtnClick(nbInsert);
Это автоматически переключит таблицу в режим Insert, как будто была нажата соответствующая кнопка DBNavigator.

Я не знаю, задокументирована эта "фича", рекомендована ли для таких случаев... Просто у меня это работает и я этим пользуюсь.

Настройки всплывающих подсказок в DBNavigator.

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

Автор: Freddy Hansson
Звонок интернет повайдеру: - Алло, это интернет? - Да. - Тогда соедените меня с www.delphiworld.ext
procedure TForm1.Button1Click(Sender: TObject);
var
  ix: integer;
begin
  with DBNavigator1 do
    for ix := 0 to ControlCount - 1 do
      if Controls[ix] is TNavButton then
        with Controls[ix] as TNavButton do
          case index of
            nbFirst: Hint := 'Подсказка для кнопки First';
            nbPrior: Hint := 'Подсказка для кнопки Prior';
            nbNext: Hint := 'Подсказка для кнопки Next';
            nbLast: Hint := '';
            {......}
          end;
end;

DBNavigator без иконок.

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

Автор: Ralph Friedman
Фидошник играет в Поле Чудес, на табло: *** Якубович читает задание: - Часть тела Фидошник сразу: - Нога! - Но ведь в задании только три буквы... - :-/ а я думал у вас H не настроена.
var
  c: shortint;
  s: string;
begin
  s := 'A';
  with DBNavigator1 do
    for c := 0 to ControlCount - 1 do
      if Controls[c] is TNavButton then
        with TNavButton(Controls[c]) do
        begin
          ListBox1.Items.Add(Name);
          Glyph := nil;
          Caption := s;
          Inc(s[1]);
        end;
end;

DBTreeView своими руками.

Автор: Елена Филиппова Специально для Королевства Delphi

Введение

В статье речь пойдет об отображении данных, хранящихся в БД и имеющих иерархическую (древовидную) структуру. Визуальное представление таких данных требует соответствующего инструмента. Существует немало компонент, которые позволяют представлять данные в виде дерева — для краткости будем называть их все DB TreeView. Компоненты эти довольно удобны, но, как правило, "заточены" под определенные задачи и каждый "шаг в сторону" в структуре данных заставляет многих пускаться в поиски. И на Круглом Столе появляются вопросы: "помогите найти компонент DB TreeView, который позволяет делать еще и ..." и так далее.

А ведь в Delphi существует стандартный компонент для представления древовидных данных, это знакомый всем TTreeView, его возможностей хватает с лихвой практически для всех задач по отображению деревьев. Сделать из TreeView самый настоящий DB TreeView, да еще полностью контролировать его развитие, более перспективный путь, нежели каждый раз искать новый чужой компонент.

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

Рассмотрим два принципиально разных случая:

Дерево подразделений Данные представляют собой классическую древовидную структуру. Глубина вложенности дерева не ограничена, заранее не известна и не одинакова для разных ветвей. Дерево может не иметь общего корневого узла, то есть распадаться на несколько деревьев.; Дерево аналитических признаков Реально данные не являются иерархическими, но могут быть представлены в таком виде. Глубина вложенности дерева одинакова для всех его веток и фиксирована в данном примере. Правило построения иерархии может меняться в режиме run-time.

Отличаются эти примеры стуктурой данных и, соответственно, способом формирования дерева.

Тем не менее сущестуют общие правила для генерации дерева. Во-первых, я исхожу из предположения, что все дерево сразу с большой степенью вероятности не понадобится пользователю. Более того, сам процесс формирования TTreeView довольно длительный и занимает ощутимое время, особенно если работать с большим объемом БД.

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

То есть, в самый начальный момент необходимо сформировать только самый верхний уровень дерева. Затем, по мере требования, можно достраивать очередную ветвь.

Какой же момент можно считать моментом требования? В принципе есть два равнозначных варианта и отличаются они только внешним видом.

Первый: по двойному клику на текущей ветви дерева проверять, была ли она достроена, если нет то формировать ее и генерировать событие OnExpanding, раскрывая новую ветвь. Второй: при попытке "раскрыть" ветку проводить аналогичные действия.

Основное отличие этих подходов: в первом случае пользователю необходимо сделать явно лишнее и не очень естественное для дерева движение , то есть двойной клик. А во втором случае мы должны обеспечить возможность раскрытия для каждой ветки дерева, ведь если у ветви нет значка [+] , то и попытаться раскрыть ее будет невозможно. А значит для всех новых, свеже сформированных ветвей, принудительно нужно обеспечить существования фиктивной дочерней ветви.

Можете сами выбрать, что Вам удобнее, я использую второй вариант.

На сервере http://ib.demo.ru/ была опубликована статья Кузьменко Дмитрия Древовидные (иерархические) структуры данных в реляционных базах данных , часть которой посвящена теме визуального представления древовидных данных. В этой статье предлагалось хранить в таблице дополнительное поле с количеством дочерних записей для каждой ветви именно для того, чтобы иметь возможность принимать решение рисовать или не рисовать знак [+] возле текущей ветки дерева. То есть на ходу определять, имеет ли шанс эта ветка быть раскрытой.

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

Каждый способ имеет свои достоинства и недостатки, но мне кажется несколько неоправданным такой подход, когда структура таблиц зависит от способа их отображаения. В примерах, которые иллюстрируют материал этой статьи применяется использование фиктивных дочерних ветвей для обеспечения появления у каждой ветви значка [+].

Примечание:

Такой вопрос, как способы представления иерархических данных в БД, не является предметом данной статьи. Пример построен на локальных Paradox-таблицах. Следовательно для переноса его на SQL-серверные БД стоит учитывать их особенности.

Дерево подразделеений

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

Используемая в примере таблица — COMPANY.DB.

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

CompanyID — номер подразделения ParentID — номер родительского подразделения Name — название.

Для тех подразделений, которые не имеют головных над собой, поле ParentID равно 0.

Формировать уровни дерева мы будем с помощью запроса к таблице ( компонент qTreeCompanies : TQuery ).

Select * From COMPANY
Where ParentID=:ParentID

Параметр ParentID будет определять, какую ветку мы сейчас достраиваем. То есть, к какой ищем дочерние подразделения.

Процедура, формирующая очередной уровень (дочерний для ветви Node ) реализована следующим образом:

Procedure TFormTree.ExpandLevel( Node : TTreeNode);
Var ID , i   : Integer;
    TreeNode : TTreeNode;
Begin
    // Для самого верхнего уровня выбрать только тех,
    // кто не имеет родителей.
    IF Node = nil Then ID:=0
    Else ID:=Integer(Node.Data);
    qTreeCompanies.Close;
    qTreeCompanies.ParamByName('ParentID').AsInteger:=ID;
    qTreeCompanies.Open;
          
    TreeCompanies.Items.BeginUpdate;
 
    // Для каждой строки из полученного набора данных
    // формируем ветвь в TreeView, как дочерние ветки к той,
    // которую мы только что "раскрыли"
    For i:=1 To qTreeCompanies.RecordCount Do
    Begin
       // Запишем в поле Data ветки ее идентификационный номер(ID) в таблице
       TreeNode:=TreeCompanies.Items.AddChildObject(Node ,
                                  qTreeCompanies.FieldByName('Name').AsString ,
                                  Pointer(qTreeCompanies.FieldByName('ID').AsInteger));
       TreeNode.ImageIndex:=1;
       TreeNode.SelectedIndex:=2;
       // Добавим фиктивную (пустую) дочернюю ветвь только для того,
       // чтобы был отрисован [+] на ветке и ее можно было бы раскрыть
       TreeCompanies.Items.AddChildObject(TreeNode , '' , nil);
       qTreeCompanies.Next;
    End;   
 
    TreeCompanies.Items.EndUpdate;
End;

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

if Node.getFirstChild.Data = nil then
begin
  Node.DeleteChildren;
  ExpandLevel(Node);
end;
Статьи по Delphi (005)…

На форме в проекте кроме дерева расположена еще и сетка (Grid), в которой отображаются записи текущего уровня подразделений. Это, по сути, список дочерних ветвей для текущей ветки дерева. Для того, чтобы синхронизировать TreeView и DBGrid используем нехитрый прием — на событие TTreeView.OnChange (шаг по ветке) добавим следующий код:

IF TreeCompanies.Selected <> nil Then
Begin
  // ID родительской ветки , для нее и ищем все дочерние
  ID:=Integer(TreeCompanies.Selected.Data);
  
  qCompanies.Close;
  qCompanies.ParamByName('ParentID').AsInteger:=ID;
  qCompanies.Open;
End;

Помните, в процедуре ExpandLevel мы записывали в поле Data каждой ветки ее идентификационный номер? Вот его то мы сейчас и используем.

Для полного ощущения передвижения по Grid'у, как по дереву можно добавить эффект "проваливания" на более глубокий уровень. По двойному клику на записи в Grid'е пользователь проваливается на один уровень вниз по иерархии, если такой уровень, конечно же еще есть.

На событие OnDblClick для грида:

ID:=qCompanies.FieldByName('ID').AsInteger;
// принудительное "невидимое" раскрытие той ветки, на которой стоим
TreeCompanies.OnExpanding(TreeCompanies ,TreeCompanies.Selected , Allow);
// Перебираем все получившиеся дочерние ветки и ищем ту, ID которой
// совпадает с ID строки в правой таблице. То есть ищем ветку в дереве,
// которая соответсвует той записи в таблице, на которой мы стоим
// Как только нашли, визуально раскрываем ветку и делаем ее выделенной,
// то есть визуально "встаем" на нее в дереве
FOR i:=0 To TreeCompanies.Selected.Count-1 Do
  IF Integer(TreeCompanies.Selected.Item[i].Data) = ID Then
  Begin
    TreeCompanies.Selected.Item[i].Expand(False);
    TreeCompanies.Selected.Item[i].Selected:=True;
    TreeCompanies.Repaint;
    Exit;
  End;

Параллельно с этим раскрывается соответствующая ветка самого дерева. Очень эффектно. :о).

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

Дерево аналитических признаков

"Куст — это пучок веток, растущих из одного места" из военных афоризмов.

Пусть у нас есть таблица документов, каждый документ, например, описывает некоторую операцию по покупке( или продаже ) товара. В этой операции участвуют: определенный товар, клиент, у которого куплен (или которому продан) этот товар, и город, в котором данная операция совершена. Таким образом, мы имеем таблицу документов, каждая запись в которой наделена тремя аналитическими признаками: Город, Клиент и Товар.

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

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

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

Описание примера, реализующего данную задачу

Используемые таблицы:

Таблица документов DOCUMENTS.DB , где каждый документ определен полями:

DocumentID : номер документа Name : название CityID : ID города ClientID : ID клиента GoodsID : ID товара

Таблицы аналитики, соответственно CITIES.DB, CLIENTS.DB и GOODS.DB, содержат поля названия Name и номера (CityID , ClientID , GoodID).

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

Так как порядок следования аналитики произвольный, зараннее невозможно написать текст SQL-запроса, который будет возвращать данные для очередного уровня дерева. Этот текст придется формировать в run-time, когда все данные будут известны.

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

Поля таблицы ENTITIES.DB.

EntityID — идентификатор Name — русское название аналитического признака TableName — название таблицы, в которой он содержится KeyColumn — ключевой столбец этой таблицы OrderNo — номер по порядку в списке используемых признаков IsSelect — выбран или не выбран в текущий список признаков ImageIndex — номер изображения (используется в отображении дерева)

В нашем случае эта таблица будет выглядеть так:

EntityID Name TableName KeyColumn ...
1 Город CITIES CityID ...
2 Товар GOODS GoodID ...
3 Клиент CLIENTS ClientID ...

В примере я использую список ListEntities (TCollection), каждый элемент которого содержит поля TableName, KeyColumn и ImageIndex. Элементы в этом списке расположены в том порядке, в каком будет строиться дерево. Заполняется этот список только той аналитикой, которая требуется для конкретного дерева. Например, только города и клиенты или товары и клиенты, или сразу все вместе. Следовательно этот список (ListEntities) и содержит полную информацию для построения дерева в каждый конкретный момент.

Заполнение списка аналитики проводится в модуле setupEntities.pas.

//считывает аналитику из таблицы
procedure GetEntities( var List : TEntityLists ); 
// вызывает диалог для настойки аналитики пользователем
function SetEntities( var List : TEntityLists ) : Boolean;

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

Так же в дерево добавляется единый для всех веток самый верхний корневой узел. С точки зрения аналитики это "фиктивный" узел, так как он будет отображать ВСЕ документы, без указания конкретного значения аналитического признака. Кроме того, достраивается еще один, самый нижний уровень — список документов по зафиксированной для текущей ветки аналитики.

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

В итоге, глубина вложенности дерева будет равна "количество аналитики" + 2.

Итак, процедура ExpandLevel будет модифицирована следующим образом:

Procedure TFormTree.ExpandLevelAnalytic(Node : TTreeNode );
Var NewItem    : TListsItem;
    ImageIndex ,
    Level , i  : Integer;
    TreeNode   : TTreeNode;
    Sql,Name   : String;
Begin
     IF Node = nil Then Exit;
     TreeAnalytic.Items.BeginUpdate;
     Level:=Node.Level + 1; // уровень, который будет раскрываться
     // Самому первому аналитическому признаку в списке  ListEntities
     // соответсвует _второй_ физический уровень веток дерева.
     // Так как самый верхний уровень дерева фиктивный -"все документы"
     // Отсюда и игра с (+/-) 1 при обращении к списку
     qTreeAnalytic.Close;
     // Определим, на каком типе уровня мы сейчас находимся
     //
     IF Level > ListEntities.Count
     Then Begin  // Уровень документов, аналитка закончилась
            Sql:='SELECT * FROM Documents Where '+ GetSqlPath(Node);
            Name:= 'DocumentID';
            ImageIndex:=3;
          End
     Else Begin // Очередной уровень аналитики
            Sql:='SELECT DISTINCT '+ ListEntities[Level-1].AsString + '.* ' +
                 ' FROM Documents , ' +  ListEntities[Level-1].AsString + ' WHERE ' +
                 ListEntities[Level-1].AsString + '.' +ListEntities[Level-1].Name + '=' +
                 'Documents.'+ListEntities[Level-1].Name + ' AND ' + GetSqlPath(Node) ;
            Name:=ListEntities[Level-1].Name;
            ImageIndex:=ListEntities[Level-1].ImageIndex;
          End;
     qTreeAnalytic.Sql.Clear;
     qTreeAnalytic.Sql.Add(Sql);
     qTreeAnalytic.Open;
      
  // Получен очередной уровень ветвей дерева
     For i:=1 To qTreeAnalytic.RecordCount Do
     Begin
         NewItem:=List.AddItem(qTreeAnalytic.FieldByName(Name).AsInteger , Name);
         TreeNode:=TreeAnalytic.Items.AddChildObject( Node ,
                              qTreeAnalytic.FieldByName('Name').AsString, NewItem );
         TreeNode.ImageIndex:=ImageIndex;
         TreeNode.SelectedIndex:=TreeNode.ImageIndex;
         // Фиктивная дочерняя ветка ТОЛЬКО для уровней аналитики,
         // так как документы - последний уровень, за которым ничего и не может быть
         IF Level <= ListEntities.Count
         Then TreeAnalytic.Items.AddChild(TreeNode , '' );
         qTreeAnalytic.Next;
     End;
     TreeAnalytic.Items.EndUpdate;
End;

В предыдущем примере мы запоминали ID строки из таблицы в поле Data каждой ветви дерева. Сейчас нам не годится такой вариант, так как аналитический признак определяется не одним идентификатором, а целым элементом списка ListEntities, вот его то и надо запоминать. Поэтому в поле Data сохраняется ссылка на конкретный элемент этого списка. Благо это Pointer и записать туда можно все, что угодно.

В процедуре используется функция GetSqlPath, которая возвращает полный путь от корня до указанной ветки дерева. Полный путь это есть зафиксированные значения для каждого уровня аналитики. Эти значения необходимы для того, чтобы верно построить запрос. То есть мы фактически формируем дополнительный фильтр для последующих выборок, напрмер — получаем всех клиентов для конкретного города и указанного товара.

Function TFormTree.GetSqlPath( Node : TTreeNode ) : String;
Begin
   Result:=' 0=0 ' ;
   // Участвуют все ветви дерева, кроме самого верхнего фиктивного уровня
   While Node.Level > 0   Do
   Begin
      Result:= Result + ' AND ' +
               'Documents.' + TListsItem(Node.Data).Name + '=' +
                              TListsItem(Node.Data).AsString ;
      // Делаем шаг назад по ветке дерева
      Node:=Node.Parent;
   End;
End;

Пример текстов SQL запроса, который будет сформирован при движении по дереву:

Уровень "товары" — Все товары, которые встречаются в документах, созданных в городе номер 6 и для клиента номер 3.

SELECT DISTINCT Goods.*  FROM Documents , Goods 
WHERE Goods.GoodsID=Documents.GoodsID AND  0=0  
AND Documents.CityID=6 AND Documents.ClientID=3

Последний уровень "документы" — Все документы, созданные в городе номер 6 и для клиента номер 3, по товару номер 1.

SELECT * FROM Documents Where  0=0  AND
Documents.GoodsID=1 AND Documents.CityID=6 AND Documents.ClientID=3

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

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

Для получения набора данных не обязательно использовать именно запросы, точно так же можно использовать хранимые процедуры для SQL-серверных СУБД. Изменится структура метаданных, но не изменится принцип формирования дерева.

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

Итак...

Итак, были рассмотрены две принципиально разные задачи, а реализация DBTreeView оказалась практически идентична. Собственно, этот факт и является важным результатом статьи — воспользуйтесь примерами, добавьте собственный функционал и создайте для себя несложный компонент для отображения древовидной структуры. Это не значит, что не стоит пользоваться сторонними компонентами, ни в коем случае. Просто если существующие вас не устраивают полностью, вы будете знать, как это исправить.

Для иллюстрации материала статьи подготовлен проект TreeDB :

Проект откомпилирован в Delphi 5, использует BDE и настроен на алиас TreeDB.

Как можно открыть отчёт (в режиме Print Preview а также Print Direct) в MS Access.

Статьи по Delphi (005)…
От: Святой Петр Кому: Всем на Земле Дата Октябрь, 1999 от рождества Христова Тема: Не помирать! Для ускорения регистрационного процесса у врат Рая, мы стали использовать Microsoft Access, после чего у нас начались серьезные проблемы. А посему, пожалуйста, не помирайте пока!
var
  Access: Variant;
begin
  // Открываем Access
  try
    Access := GetActiveOleObject('Access.Application');
  except
    Access := CreateOleObject('Access.Application');
  end;
  Access.Visible := True;
  // Открываем базу данных
  // Второй параметр указывает - будет ли база открыта в Exclusive режиме
  Access.OpenCurrentDatabase('C:\My Documents\Books.mdb', True);
  // открываем отч¸т
  {Значение второго пораметра может быть одним из следующих
  acViewDesign, acViewNormal, or acViewPreview. acViewNormal,
  которые устанавливаются по умолчанию, для печати отч¸та.
  Если Вы не используете библиотеку типов, то можете определить
  эти значения следующими:
  const
  acViewNormal = $00000000;
  acViewDesign = $00000001;
  acViewPreview = $00000002;
  Третий параметр - это имя очереди для текущей базы данных.
  Четв¸ртый параметр - это строка для SQL-евского WHERE -
  то есть строка SQL, минус WHERE.}
  Access.DoCmd.OpenReport('Titles by Author', acViewPreview, EmptyParam,
    EmptyParam);
  < ... >
  // Закрываем базу данных
  Access.CloseCurrentDatabase;
  // Закрываем Access
  {const
  acQuitPrompt = $00000000;
  acQuitSaveAll = $00000001;
  acQuitSaveNone = $00000002;}
  Access.Quit(acQuitSaveAll);
end;

Как в Delphi сбросить кэш БД на диск.

- Ты слишком много знал, - сказал Windows HardDisk-у.
uses BDE {в Delphi 1.x не помню, но вроде bdeprocs};
dbiSaveChanges

На Delphi 1.x (16bit) дополнительно вызовите эту процедуру:

procedure DropCache; assembler;
asm
  mov ah,$0D
  int $21
end;

История изменений объектов.

Оформил: DeeCo

Автор: Виноградов С. А.

Введение

Большинство объектов, хранящихся в базе данных, могут меняться с течением времени. Например, человек может сменить фамилию, паспорт, место жительства. У различных объектов учета может поменяться цена, количество, стоимость. К сожалению, почти все современные СУБД не хранят историю этих изменений и не позволяют получить состояние объектов на определенную дату. Об этом должен позаботиться проектировщик базы данных.

В качестве примера, возьмем таблицу Objects, которая содержит код объекта, его наименование, примечание и другие поля (здесь и далее — синтаксис MS SQL):

create table[Objects]
(
  [Id]int not null identity primary key,
  [Code]varchar(120) not null,
  [Name]varchar(120) not null,
  [Note]varchar(250),
  ...
  )

Рассмотрим варианты хранения истории для этой таблицы.

Дополнительная таблица истории

По многим соображениям, бывает необходимо оставить таблицу объектов на текущий момент в неизменном виде, а историю вести в отдельной таблице. Самый простой способ хранения истории — при любом изменении в объекте, добавлять в таблицу запись с новыми значениями объекта и приписывать к этой записи дату изменения. Тогда, структура таблиц Objects и ObjectsHistory будет примерно такой:

create table[Objects]
(
  [Id]int not null identity primary key,
  [Code]varchar(120) not null,
  [Name]varchar(120) not null,
  [Note]varchar(250),
  ...
  )
create table[ObjectsHistory]
(
  [object]int not null references[Objects]([Id]),
  [Code]varchar(120) not null,
  [Name]varchar(120) not null,
  [Note]varchar(250),
  ...
  [DateBegin]datetime not null default(getdate()),
  [DateEnd]datetime not null default('3000-01-01')
  constraint[ObjectHistory]primary key([object], [DateBegin])
  )

Первичный ключ в таблице истории состоит из двух полей: идентификатора объекта из основной таблицы (Object) и даты начала действия истории объекта (DateBegin). Дата окончания действия истории (DateEnd) всегда равна дате начала следующей истории объекта, и используется исключительно для более простых условий выборки.

При вставке записи в основную таблицу, необходимо добавить такую же запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата.

При модификации записи в основной таблице, нужно вставить измененную запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата. При этом у предыдущей записи с таким же идентификатором в таблице истории необходимо установить DateEnd = текущая_дата.

При удалении записи в основной таблице, у последней записи с таким же идентификатором в таблице истории надо установить DateEnd = текущая_дата.

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

Выборка из основной таблицы производится обычным образом, а из таблицы истории с условием:

заданная_дата beetwen DateBegin and DateEnd

Основным достоинством данного метода является отсутствие изменений для хранения и получения последнего состояния объектов.

Недостаток — повышенное дублирование информации (к примеру, последнее состояние будет одинаково в обеих таблицах) и, соответственно, большой объем базы данных.

Одна таблица для всех состояний

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

При использовании автонумеруемого поля в качестве идентификатора объекта, это поле придется вынести в отдельную таблицу ключей Objects, так как в таблице ObjectsHistory оно не будет уникальным:

create table[Objects]
(
  [Id]int not null identity primary key
  )
create table[ObjectsHistory]
(
  [object]int not null references[Objects]([Id]),
  [Code]varchar(120) not null,
  [Name]varchar(120) not null,
  [Note]varchar(250),
  ...
  [DateBegin]datetime not null default(getdate()),
  [DateEnd]datetime not null default('3000-01-01')
  constraint[ObjectHistory]primary key([object], [DateBegin])
  )

Первичный ключ в таблице истории состоит из двух полей: идентификатора объекта из таблицы ключей (Object) и даты начала действия истории объекта (DateBegin). Дата окончания действия истории (DateEnd) равна дате начала следующей истории объекта, и используется для более простых условий выборки.

При вставке объекта, необходимо вначале добавить запись в таблицу ключей и получить идентификатор объекта. Затем надо произвести запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата.

При модификации объекта, нужно вставить измененную запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата. При этом, у предыдущей записи с таким же идентификатором необходимо установить DateEnd = текущая_дата.

При удалении объекта, для последней записи с таким же идентификатором надо установить DateEnd = текущая_дата.

Выборка последнего состояния объектов производится с условием:

текущая_дата beetwen DateBegin and DateEnd

Условие для выборки на определенную дату:

заданная_дата beetwen DateBegin and DateEnd

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

Недостатком будет более сложное получение текущего состояния. Информация по-прежнему остается избыточной — при изменении одного из свойств объекта, он копируется целиком.

Заключение

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

Создание компонентов для работы с базами данных.

Базу селектом не испортишь.
Обзор Данный документ описывает минимально необходимые шаги, необходимые для создания компонента для работы с базами данных, который может отображать данные отдельного поля. Примером такого компонента может служить панель со свойствами DataSource и DataField, похожая на компонент TDBText. Для получения дополнительных примеров обратитесь к Руководству по написанию компонентов "Making a Control Data-Aware". Как пользоваться данным документом Для наилучшего понимания данного документа, вы должны быть знакомы с механизмом функционирования элементов управления для работы с базами данных и основополагающими принципами создания компонент, такими, как создание компонентов на основе существующих перекрытие конструкторов и деструкторов создание новых свойств чтение и запись значений свойств назначение обработчиков событий Основные шаги по созданию компоненты, осуществляющей навигацию по данным Создайте или наследуйте компонент, который допускает свое отображение, но не ввод данных. Например, вы могли бы использовать компонент TMemo с установленным в True свойством ReadOnly. В примере, приведенном в данном документе, мы используем TCustomPanel. TCustomPanel позволяет себя отображать, но не вводить данные. Добавьте к вашему компоненту data-link object (объект для связи с данными). Данный объект позволяет управлять связью между компонентом и таблицей базы данных. Добавьте к компоненту свойства DataField и DataSource. Добавьте методы для получения и установления DataField и DataSource. Добавьте к компоненту метод DataChange, позволяющий управлять событиями OnDataChange объекта data-link. Перекройте конструктор компонента для создания datalink и перехвата метода DataChange. Перекройте деструктор компонента для очищения datalink. Создание TDBPANEL Создайте или наследуйте компонент, который допускает свое отображение, но не ввод данных. В качестве отправной точки для нашего примера мы будем использовать TCustomPanel. Выберите соответствующий пункт меню для создания нового компонента (он меняется от версии к версии Delphi), определите TDBPanel как имя класса, и TCustomPanel в качестве наследуемого типа. Определите любую страницу Палитры компонентов. Добавьте DB и DBTables в список используемых модулей. Добавьте data-link объект в секцию private вашего компонента. Данный пример отображает данные одного поля, поэтому мы используем TFieldDataLink для обеспечения связи между нашим новым компонентом и DataSource. Имя нового data-link объекта - FDataLink.
{ пример }
private
FDataLink: TFieldDataLink;
Добавьте к компоненту свойства DataField и DataSource. Мы добавим соответствующий код для методов записи/чтения в последующих шагах.

Примечание: Наш новый компонент будет иметь свойства DataField и DataSource, FDataLink также будет иметь собственные свойства DataField и Datasource.

{ пример }
published
property DataField: string
read   GetDataField
write  SetDataField;
property DataSource: TDataSource
read   GetDataSource
write  SetDataSource;
Добавьте частные методы для чтения/записи значений свойств DataField и DataSource, и свойств DataField и DataSource для FDataLink.
{ пример }
private
FDataLink: TFieldDataLink;
function GetDataField: String;
function GetDataSource: TDataSource;
procedure SetDataField(Const Value: string);
procedure SetDataSource(Value: TDataSource);
.
.
implementation
.
.
function TDBPanel.GetDataField: String;
begin
Result := FDataLink.FieldName;
end;
function TDBPanel.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TDBPanel.SetDataField(Const Value: string);
begin
FDataLink.FieldName := Value;
end;
procedure TDBPanel.SetDataSource(Value: TDataSource);
begin
FDataLink.DataSource := Value;
end;
Добавьте частный метод DataChange, назначая событие объекта datalink OnDataChange. В методе DataChange добавьте код для отображения данных поля актуальной базы данных, связь с которой обеспечивает объект data-link. В нашем примере мы назначаем значение поля FDataLink заголовку панели.
{ пример }
private
.
.
procedure DataChange(Sender: TObject);
implementation
.
.
procedure TDBPanel.DataChange(Sender: TObject);
begin
if FDataLink.Field = nil then
Caption := '';
else
Caption := FDataLink.Field.AsString;
end;
Перекройте метод конструктора компонента Create. При реализации Create, создайте объект FDataLink и назначьте частный метод DataChange событию FDataLink OnDataChange.
{ пример }
public
constructor Create(AOwner: TComponent); override;
.
.
implementation
.
.
constructor TMyDBPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDataLink := TFieldDataLink.Create;
FDataLink.OnDataChange := DataChange;
end;
Перекройте метод деструктора компонента Destroy. При реализации Destroy, установите OnDataChange в nil (чтобы избежать GPF), и освободите FDatalink.
{ пример }
public
.
.
destructor Destroy; override;
.
.
implementation
.
.
destructor TDBPanel.Destroy;
begin
FDataLink.OnDataChange := nil;
FDataLink.Free;
inherited Destroy;
end;
Сохраните модуль и установите компонент (смотрите документацию Users Guide и Component Writers Guide для получения дополнительной информации по сохранению модулей и установке компонентов). Для тестирования функциональности компонента расположите на форме компоненты TTable, TDatasource, TDBNavigator и TDBPanel. Установите TTable DatabaseName и Tablename в 'DBDemos' и 'BioLife', а свойство Active в True. Установите свойство TDatasource Dataset в Table1. Установите TDBNavigator и свойство TDBPanel DataSource в Datasource1. Имя TDBpanel DataField должно быть установлено в 'Common_Name'. Запустите приложение и, используя навигатор и перемещаясь по записям, убедитесь в том, что TDBPanel обнаруживает изменение данных и отображает значение соответствующего поля.

Полный код компонента.

unit Mydbp;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, DB, DBTables;
type
  TDBPanel = class(TCustomPanel)
  private
    FDataLink: TFieldDataLink;
    function GetDataField: string;
    function GetDataSource: TDataSource;
    procedure SetDataField(const Value: string);
    procedure SetDataSource(Value: TDataSource);
    procedure DataChange(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property DataField: string
      read GetDataField
      write SetDataField;
    property DataSource: TdataSource
      read GetDataSource
      write SetDataSource;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Samples', [TDBPanel]);
end;
function TDBPanel.GetDataField: string;
begin
  Result := FDataLink.FieldName;
end;
function TDBPanel.GetDataSource: TDataSource;
begin
  Result := FDataLink.DataSource;
end;
procedure TDBPanel.SetDataField(const Value: string);
begin
  FDataLink.FieldName := Value;
end;
procedure TDBPanel.SetDataSource(Value: TDataSource);
begin
  FDataLink.DataSource := Value;
end;
procedure TDBPanel.DataChange(Sender: TObject);
begin
  if FDataLink.Field = nil then
    Caption := ''
  else
    Caption := FDataLink.Field.AsString;
end;
constructor TDBPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FDataLink := TFieldDataLink.Create;
  FDataLink.OnDataChange := DataChange;
end;
destructor TDBPanel.Destroy;
begin
  FDataLink.Free;
  FDataLink.OnDataChange := nil;
  inherited Destroy;
end;
end.

Использование DB Controls без базы данных.

Оформил: DeeCo

Область применения

Довольно часто на различных форумах задаются вопросы примерно такого характера: “Как мне красиво (как в DBGrid) отобразить содержимое списка структур?”, “Как можно вывести данные в DBGrid/DBControlGrid?”.

Конечно, существуют различные варианты ответа на каждый из этих вопросов. В первом случае можно попытаться создать потомка TDBGrid. Во втором – создать потомка TDataSource. Однако на мой взгляд, предпочтительнее (как в отношении предоставляемого программисту набора функций, так и в отношении применимости конкретного ответа для решения целого круга похожих задач) способ, применяемый обычно для разрешения более принципиальной задачи: как обеспечить единообразную работу с данными из имеющейся БД (DBase, Interbase, MS SQL Server и т.п.), и с другими, в частности, локальными данными программы?

Работа с базами данных при помощи стандартного набора компонентов Delphi строится по следующей схеме:

Статьи по Delphi (005)… Рисунок 1. Схема взаимодействия программы и элементов управления с БД.

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

Создание своего TDataSet – вовсе не такая сложная работа, как кажется на первый взгляд при просмотре исходного кода TDataSet и/или его стандартных потомков (например, TIBCustomDataSet). Основным препятствием является почти полное отсутствие документации от Borland. В поставляемом с Delphi простом примере TTextDataSet (потомка TDataSet, работающего с текстовым файлом на диске) прямо говорится: “Currently, the notes in this source file represent the only documentation on how to create a TDataSet implantation”, что в приблизительном переводе означает: “в настоящее время данный пример представляет собой единственную документацию по созданию реализации TDataSet”.

В данной работе я рассмотрю создание двух TDataSet. Один будет обеспечивать доступ в режиме “только чтение” к данным, вычисляемым “на лету” (в качестве примера выбрана таблица квадратов натуральных чисел от 1 до 20). Второй же позволит осуществлять доступ с возможностью модификации к списку структур вида (имя, адрес электронной почты). Любой промежуточный уровень функциональности легко может быть получен путем усечения функциональности второго примера.

В настоящей работе не рассматриваются:

реализация установки фильтров на записи и поиска записей. Впрочем, получить представление о том, как это следует делать, можно, просмотрев исходные тексты методов FindRecord, FindFirst, FindNext, FindLast, FindPrior, Locate и Lookup (модули DB.PAS и DBTABLES.PAS, классы TDataSet и TBDEDataSet ); поля-агрегаты (методы GetAggregateValue, GetAggRecordCount, ResetAggField); реализация интерфейса IProviderSupport (семейство методов PSxxxxx). Простейший DataSet

Есть такое хорошее правило – прежде чем переходить к программированию, четко определить круг задач, которые обязательно должны быть решены. Давайте ему последуем. Я хочу создать потомка TDataSet, работающего с набором данных, содержащим два столбца: целое число и его квадрат. Для простоты этот набор данных будет содержать лишь 20 записей – для чисел от 1 до 20. Borland выдвигает еще одно требование: каждый потомок TDataSet должен поддерживать работу с закладками. Однако я позволю себе пока его проигнорировать – тем более, что и без этого получается практически вполне работоспособный класс. Желающие могут добавить в этот пример поддержку закладок, предварительно ознакомившись с механизмом закладок в следующем разделе.

ПРИМЕЧАНИЕ

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

Чтобы создать потомка TDataSet, нужно понять, как TDataSet выполняет доступ к данным. Общедоступный (public) интерфейс полностью реализуется средствами самого TDataSet; на плечи конкретных потомков ложится лишь обеспечение средств коммуникации общедоступных методов с набором данных. В основном, коммуникация осуществляется методами InternalXXX, часть из которых в TDataSet объявлена пустыми, а часть – и вовсе абстрактными.

Эти методы можно разделить по их назначению на три группы: навигация (перемещение) по набору и чтение записи из БД в кэш-буфер (а также запись из кэш-буфера в набор данных), вставка/удаление записей, чтение/изменение значений полей записи, прочие методы (открытие и закрытие TDataSet, закладки, выделение и освобождение памяти).

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

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

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

Курсор - это произвольная информация, придающая смысл в каждый момент времени таким понятиям, как “текущая”, “следующая” и (для двунаправленных наборов данных) “предыдущая” запись. Позволю себе вольность пользоваться в отношении курсора фразой “перемещение курсора к некоторой записи”, подразумевая изменение составляющей курсор информации так, чтобы курсор указывал на эту запись.

Тот курсор, который определяет целевую позицию для действий, запрашиваемых у TDataSet сторонним кодом, я в дальнейшем буду называть логическим курсором. С логическим курсором в TDataSet связан буфер активной записи (свойство ActiveBuffer хранит адрес этого буфера).

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

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

ПРИМЕЧАНИЕ

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

Для поддержания согласованности логического курсора с физическим используется следующая техника: после вызова метода, от которого ожидается перемещение физического курсора на непредсказуемое расстояние от его текущего положение (например, при переходе на первую запись или поиске по закладке), вызывается метод Resync, считывающий в кэш-буферы запись, на которую теперь указывает физический курсор, а также несколько окружающих ее записей. Если же перемещение предсказуемо (как в случае метода MoveBy, вызываемого из Prior и Next), то читается лишь часть окружающих записей. Понятно, что в случае, когда вообще не ожидается никаких перемещений, никакие записи и не перечитываются.

Если потомку требуется переместить физический курсор вопреки ожиданиям TDataSet, необходимо оповестить его об этом вызовом CursorPosChanged. Такое оповещение обрабатывается не сразу, и, говоря откровенно, меня до сих пор гложут сомнения, не надежнее ли сразу вызывать Resync (хотя в таком случае можно проиграть в скорости, если после этого Resync будет вызван повторно).

Таким образом, в момент вызова методов InternalXXX логический курсор совпадает с физическим (от возможных сбоев этого соответствия в ходе выполнения закрытым кодом каких-либо внутренних операций спасает выполняющийся перед входом в InternalXXX вызов метода UpdateCursorPos), а после выхода из InternalXXX логический курсор опять-таки приводится в соответствие физическому.

ПРИМЕЧАНИЕ

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

Единственное обнаруженное мной исключение из этого правила – метод InternalInsert (вызывающийся при вставке записи), перед вызовом которого не выполняется UpdateCursorPos, из-за чего приходится искать способ переместить физический курсор на ту же запись, на которую указывает логический, при этом не пользуясь private-членами TDataSet). Подробнее об этом – в следующем разделе.

При открытии (инициализации) логического курсора (TDataSet.OpenCursor, вызывается при TDataSet.Active:=true и по выходу из режима дизайнера) вызывается метод InternalOpen. Он должен сформировать список определений полей FieldDefs и, если свойство DefaultFields равно True, создать на их базе список полей Fields. Он также должен связать поля из списка Fields с полями набора данных и установить физический курсор в позицию перед первой записью. Кроме того, если до начала манипулирования содержащимися в наборе данными нужно предпринять какие-либо действия (например, установить соединение с удаленным сервером), их также следует выполнить в этом методе.

InternalOpen может вызываться не только из OpenCursor. Запрос на формирование списка FieldDefs может поступить и отдельно: в этом случае вызывается метод InitFieldDefs, который может вызывать InternalInitFieldDefs (последний должен инициализировать FieldDefs).

Для проверки того, что логический курсор открыт (т.е. что набор готов предоставить доступ к своим данным), вызывается функция IsCursorOpen, возвращающая True или False. Ясно, что до выполнения Open она должна возвращать False, а после Open и до последующего Close – True.

Для закрытия набора данных (TDataSet.Close) вызывается метод InternalClose, назначение которого симметрично InternalOpen: если DefaultFields установлено в True, то уничтожить список полей Fields (в этом случае они были созданы самим потомком в ходе выполнения InternalOpen); также, в случае необходимости, выполнить такие действия, как разрыв соединения с сервером БД.

Для навигации (перемещения логического курсора) по набору применяются общедоступные методы First, Prior, Next и Last.

Метод First вызывает метод InternalFirst, который должен установить физический курсор на позицию перед первой записью в наборе. После этого First выполняет серию вызовов GetNextRecord (а те, в конечном счете, GetRecord) для чтения в кэш-буферы следующей (т.е. первой) записи набора и нескольких последующих записей. Затем при помощи вызова DataEvent генерируется уведомление об изменении данных в кэш-буферах.

Зачем нужно устанавливать курсор на позицию перед первой записью, а потом читать следующую запись? Почему бы просто не поместить курсор сразу на первую запись? Дело, возможно, в том, что если для позиционирования на первую запись необходимо переоткрытие набора (например, в однонаправленных наборах), то эта обязанность возлагается на InternalFirst. То есть для помещения курсора на первую запись InternalFirst должен был бы переоткрыть набор и выполнить переход к следующей записи. По-видимому, команда разработчиков Borland решила, что будет лучше, если все команды навигации по записям будут подаваться из находящегося в базовом классе TDataSet слоя абстрактной логики, а не из методов, отвечающих за конкретную реализацию доступа к данным (таких, как InternalFirst). Таким образом, работа IntrnalFirst должна в такой ситуации завершаться переоткрытием (а после этой операции курсор, как уже говорилось, находится перед первой записью).

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

Методы Prior и Next апеллируют к одному и тому же методу GetRecord. Последний многофункционален: он должен проверить, не выйдет ли позиция курсора при выполнении операции за пределы БД, изменить позицию физического курсора, считать из набора в буфер записи, переданный в качестве аргумента, данные записи, ставшей текущей, и (в зависимости от параметров вызова) в случае ошибки возбудить исключение.

Когда необходимо прочитать значение некоторого поля текущей записи, вызывается GetFieldData, в который передается ссылка на соответствующий нужному полю объект TField и адрес буфера, содержащего запись. GetFieldData – перегруженный метод, но фактическое извлечение значения поля из буфера записи в так называемом Native-формате (его мы рассмотрим далее) осуществляет лишь одна из его версий (остальные версии в конечном счете просто вызывают именно ее).

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

Для выделения памяти под новый буфер вызывается метод AllocRecordBuffer, для освобождения занятой под буфер памяти – FreeRecordBuffer. Надо заметить, что TDataSet может повторно использовать уже выделенный однажды буфер для хранения другой записи. Обработку вытеснения записи из буфера при таком повторном использовании можно поместить туда же, куда и действия по подготовке буфера перед помещением туда записи – в InitRecordBuffer. Этот метод вызывается перед каждым помещением в буфер какой-либо записи.

Редактирование данных, в т.ч. добавление и удаление записей, становится возможно, когда метод GetCanModify возвращает True. Процессы, происходящие при редактировании, будут рассмотрены в следующем разделе, поэтому пока мы считаем, что GetCanModify всегда возвращает False.

Метод InternalHandleException служит для обработки возможных исключений, возникающих при чтении объекта из потока. Насколько мне удалось понять из исходных текстов потомков TDataSet, предлагаемых Borland, хорошим тоном является такая его реализация: Application.HandleException(Self).

Метод IsSequenced должен возвращать True, если в наборе каждую запись можно однозначно сопоставить с ее порядковым номером (т.е. числом, показывающим, какой по счету окажется запись при последовательном переборе всех записей, начиная с первой).

Помимо этих методов, в работе с данными может участвовать еще несколько семейств методов, предназначенных для расширения возможностей TDataSet. Одно из них - работа с закладками - как я уже говорил, будет рассмотрено в следующем разделе. Поддержка остальных не является обязательной (см. также перечень не рассматриваемых здесь функций в конце раздела “Область применения”). Однако три из них стоит реализовать, иначе довольно некрасиво смотрится полоса прокрутки в DBGrid и подобных ему элементах управления, а клиенты теряют возможность простого позиционирования на запись по ее номеру.

GetRecordCount должен возвращать общее число содержащихся в наборе записей. GetRecNo должен возвращать номер (начиная с 1) текущей записи. SetRecNo должен устанавливать физический и логический курсоры на запись с указанным номером.

Последние два метода фактически являются реализацией свойства RecNo.

Два слова о Native-формате данных: детально мы его обсудим в следующем разделе, пока же – основное. Извлеченные из записи данные следует возвращать в особом формате (Native), своем для каждого типа поля. В нашем примере используются только поля типа ftInteger, для которых Native-формат представляет собой 4-байтовое целое число со знаком, т.е. значение типа integer. (см. чтение в методе GetFieldData).

Листинг 1 TMyDataSet (read-only)

unit DataSet0;
//Простой Read-Only BiDirectional DataSet в виде таблицы
// квадратов целых чисел (два поля – число и его квадрат)
interface
uses DB, Classes {для TComponent};
type
  TMyDataSet = class(TDataset)
  protected
    procedure InternalHandleException; override;
    // Инициализация/деинициализация курсора
    procedure InternalInitFieldDefs; override;
    procedure InternalOpen; override;
    function IsCursorOpen: Boolean; override;
    procedure InternalClose; override;
    // Выборка
    function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean):
      TGetResult; override;
    function AllocRecordBuffer: PChar; override;
    procedure FreeRecordBuffer(var Buffer: PChar); override;
    procedure InternalInitRecord(Buffer: PChar); override;
    // Навигация
    procedure InternalFirst; override;
    procedure InternalLast; override;
    procedure InternalSetToRecord(Buffer: PChar); override;
    // Разрешение доступа
    function GetCanModify: Boolean; override;
    // Необязательные методы
    function GetRecordCount: Integer; override;
    //Везде в данном коде, где используется RecordCount, можно было бы просто
    //написать List.Count. Реализовывать GetRecordCount не обязательно.
    procedure SetRecNo(Value: Integer); override;
    function GetRecNo: Integer; override;
    //Здесь номер записи считается от 1
  public
    // Чтение поля
    function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
    // Создание/уничтожение
    constructor Create(AOwner: TComponent); override;
  private
    FIsOpen: boolean;
    FCursor: integer;
  end;
  TRecordData = record
    //Собственно копия записи – по замыслу, она должна считываться из БД
    Number, Square: integer;
  end;
  TRecordBuffer = record //Формат буфера, хранящего запись: данные записи +
    //служебная информация
    RecordData: TRecordData;
    RecordIndex: integer; //Номер записи, считая от 1
  end;
  PRecordBuffer = ^TRecordBuffer;
implementation
procedure TMyDataSet.InternalHandleException;
begin
  Application.HandleException(Self)
end;
procedure TMyDataSet.InternalInitFieldDefs;
begin
  FieldDefs.Clear;
  with FieldDefs.AddFieldDef do
  begin
    DataType := ftInteger;
    FieldNo := 1;
    Name := 'Число';
  end;
  with FieldDefs.AddFieldDef do
  begin
    DataType := ftInteger;
    FieldNo := 2;
    Name := 'Квадрат';
  end;
end;
procedure TMyDataSet.InternalOpen;
begin
  InternalInitFieldDefs;
  if DefaultFields then
    CreateFields;
  BindFields(true); //Привязываем поля к БД
  FIsOpen := true;
  FCursor := 0;
end;
function TMyDataSet.IsCursorOpen: Boolean;
begin
  result := FIsOpen
end;
procedure TMyDataSet.InternalClose;
begin
  BindFields(False); //Отвязываем поля
  if DefaultFields then
    DestroyFields;
  FIsOpen := false;
end;
procedure TMyDataSet.InternalFirst;
begin
  FCursor := 0
end;
procedure TMyDataSet.InternalLast;
begin
  FCursor := RecordCount + 1
end;
function TMyDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
begin
  result := grOK;
  case GetMode of
    gmPrior: if FCursor <= 1 then
        result := grBOF
      else
        Dec(FCursor);
    gmNext: if FCursor >= RecordCount then
        result := grEOF
      else
        Inc(FCursor);
    gmCurrent: if (FCursor < 1) or (FCursor > RecordCount) then
        Result := grError;
  end;
  if result = grOK then
    with PRecordBuffer(Buffer)^ do
    begin
      RecordData.Number := FCursor;
      RecordData.Square := sqr(RecordData.Number);
      RecordIndex := FCursor;
    end;
  if (result = grError) and DoCheck then
    DatabaseError('Error in GetRecord()');
end;
function TMyDataSet.GetRecordCount: Integer;
begin
  Result := 20
end;
procedure TMyDataSet.SetRecNo(Value: Integer);
begin
  if (Value < 1) or (Value >= RecordCount + 1) then
    exit;
  FCursor := Value;
  Resync([])
end;
//Мне не пришло в голову более умного средства синхронизации логического
// курсора с физическим, чем выполнение Resync (ресинхронизация – сброс
// логического курсора и последующее чтение записей, окружающих
// физический курсор)
function TMyDataSet.GetRecNo: Integer;
//Возвращает номер записи, соответствующей активному в данный момент буферу
begin
  Result := PRecordBuffer(ActiveBuffer)^.RecordIndex
end;
procedure TMyDataSet.InternalSetToRecord(Buffer: PChar);
begin
  FCursor := PRecordBuffer(Buffer)^.RecordIndex
end;
function TMyDataSet.AllocRecordBuffer: PChar;
begin
  GetMem(result, sizeof(TRecordBuffer))
end;
procedure TMyDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
  FreeMem(Buffer, sizeof(TRecordBuffer))
end;
procedure TMyDataSet.InternalInitRecord(Buffer: PChar);
begin
  inherited; //Нам не нужна какая-то особая инициализация буфера записи
  // до чтения туда фактической записи
end;
function TMyDataSet.GetCanModify: Boolean;
begin
  result := false
end;
function TMyDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
begin
  result := true;
  case Field.FieldNo of
    1: PInteger(Buffer)^ := PRecordBuffer(ActiveBuffer)^.RecordData.Number;
    2: PInteger(Buffer)^ := PRecordBuffer(ActiveBuffer)^.RecordData.Square;
  else
    result := false;
  end;
end;
constructor TMyDataSet.Create(AOwner: TComponent);
begin
  inherited;
  FIsOpen := false;
end;
end.

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

Листинг 2

program Dataset;
uses Forms, DB, DBGrids, DBCtrls, Controls, DataSet0;
var
  F: TForm;
  DS: TDataSource;
  MyDataSet: TMyDataSet;
begin
  Application.Initialize;
  Application.CreateForm(TForm, F);
  F.Width := 300;
  F.Height := 300;
  F.Position := poDesktopCenter;
  MyDataSet := TMyDataSet.Create(F);
  MyDataSet.Open;
  DS := TDataSource.Create(F);
  DS.DataSet := MyDataSet;
  with TDBNavigator.Create(F) do
  begin
    Parent := F;
    Align := alTop;
    DataSource := DS
  end;
  with TDBGrid.Create(F) do
  begin
    Parent := F;
    Align := alClient;
    DataSource := DS
  end;
  Application.Run;
end.

Добавление возможностей редактирования

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

Рассмотрим более сложный и более приближенный к реальности пример потомка TDataSet. Наш класс TMyDataSet будет предоставлять доступ для чтения/изменения/удаления/добавления записей в адресную книгу, представляющую собой список (обычный TList), содержащий указатели на записи, состоящие из двух строковых компонентов: имени и электронного адреса. Кроме того, TMyDataSet будет поддерживать работу с закладками.

Итак, какие действия предпринимает TDataSet при редактировании, добавлении и удалении записей?

Самая простая операция – удаление. Вызов общедоступного метода Delete приводит (если TDataSet не находится в состоянии вставки записи, в противном случае Delete просто вызывает Cancel) к вызову InternalDelete, на который возлагается обязанность удалить запись из набора. Работа завершается вызовом Resync, так что InternalDelete может поместить физический курсор на любую запись по своему усмотрению.

Что касается редактирования, то можно выделить два его вида: обычное редактирование записи (состояние dsEdit), которое я и буду в дальнейшем называть редактированием, и вставка записи (dsInsert).

Для начала редактирования вызывается метод Edit. Он, в свою очередь, вызывает метод InternalEdit. В большинстве ситуаций никаких действий в InternalEdit можно не предпринимать – TDataSet переведет объект в состояние dsEdit, особым образом обработает вычисляемые (calculated) поля, выдаст сообщение об изменении записи и т.д. Для начала вставки записи вызовается Insert или Append. Как в состоянии dsInsert, так и в dsEdit TDataSet позволяет вносить в запись изменения.

Выводится из этих состояний TDataSet также одинаковыми способами. Их два: вызов метода Post (подтверждение изменений – и вставки, если мы находимся в состоянии dsInsert) или метода Cancel (отмена изменений/вставки). Первый из них вызывает InternalPost, второй – InternalCancel. Отметим, что перед вызовом каждого из них осуществляется вызов UpdateCursorPos – а это означает, что физический курсор будет совпадать с логическим. Методы Post и Cancel могут быть вызваны не только напрямую сторонним кодом, но и из других методов TDataSet: так, при попытке, пока TDataSet находится в состоянии редактирования (dsEdit) или вставки (dsInsert), переместить логический курсор на другую запись, операция вставки или редактирования будет либо отменена, либо подтверждена (в зависимости от того, внесены ли в запись изменения). Логика TDataSet полностью возлагает реализацию добавления/удаления/редактирования записей на Internal-методы; в частности, именно они определяют, в какую позицию набора будет при вставке помещаться вставляемая запись, будет ли она добавлена сразу (а при вызове InternalCancel – удалена) или будет существовать только в виде буфера (а физически добавляться при вызове InternalPost).

Серьезное обсуждение операции вставки невозможно без знакомства с аппаратом закладок.

Закладка (bookmark) – это некоторая информация, идентифицирующая положение записи в наборе данных. Предпочтительно представлять закладку ANSI-строкой символов (т.е. последовательностью ненулевых символов, завершающейся нулевым), т.к. клиент может пытаться читать ее из свойства Bookmark методом GetBookmarkStr именно как ANSI-строку (в примере от Borland закладка представляется 4-байтовым адресом записи, что, безусловно, неправильно, т.к. адрес может содержать нулевые байты). Свойство Bookmark возвращает закладку, соответствующую текущей записи. Впоследствии с помощью этой закладки можно очень быстро сделать текущей соответствующую ей запись. Закладка всегда указывает на одну и ту же запись, вне независимости от изменения положения этой записи в наборе.

Если задача потомка TDataSet – обмен данными с каким-либо сервером БД, то, скорее всего, сервер уже снабжен поддержкой закладок, и можно просто перенаправлять все запросы ему.

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

bfInserted – “вставленная” запись (запись, подлежащая просмотру и редактированию, но еще не включенная в набор). bfBOF – запись, находящаяся перед первой записью (например, вставленная в пустой набор). Для установки физического курсора на запись с таким флагом закладки логика TDataSet вызывает InternalFirst вместо InternalSetToRecord; bdEOF – запись, находящаяся за последней записью. Вызывается InternalLast.

Для клиента (т.е. внешнего по отношению к потомку TDataSet кода) работа с закладками сводится к двум операциям: чтению закладки для текущей записи и перемещению к записи, для которой известна закладка. Этим действиям соответствуют два метода TDataSet: GetBookmarkData и InternalGotoBookmark. Есть, впрочем, и третий метод, SetBookmarkData, служащий для изменения значения закладки текущей записи. Замена закладки исключительно плохо документирована. Разбор исходного текста класса TDataSet показывает, что код TDataSet вызывает SetBookmarkData в одном-единственном случае – перед вызовом InternalInsert новой записи приписывается та же закладка, что и предыдущей активной (если только вставляемая запись не окажется первой и единственной в наборе).

Смысл этого дублирования мне (после просмотра примера от Borland) видится таким: так как значение физического курсора, соответствующее предыдущей активной записи, неизвестно, а добавленная в набор данных запись может до вызова Post быть “висячей” (а значит, также не иметь соответствующего ей значения физического курсора), то предлагается вместо перехода на запись использовать переход на закладку. При этом до вызова Post предыдущая активная запись и новая запись имеют одинаковые закладки. Никакой коллизии здесь нет – при попытке получить закладку вставленной, но не добавленной в набор записи (у нее флаг закладки равен bfInserted, bfEOF или bfBOF), возвращается пустая строка (т.к. метод BookmarkAvailable может вернуть True лишь для записей с флагом закладки bfCurrent). То есть, с точки зрения внешнего по отношению к TDataSet кода, новая запись закладки не имеет. Это, кстати, следует учитывать в реализации поиска записи по закладке: поиск нужно проводить лишь по записям с флагом закладки bfCurrent.

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

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

Вообще говоря, в TDataSet существует два способа добавления записи. Это методы Insert и Append (оба в итоге вызывают InternalInsert) – их назначение описано в справочной службе. Для обоих методов жестко определено лишь одно требование: логический курсор должен переместиться на добавленную запись. О том, в какую именно позицию вставляется новая запись каждым из методов, можно прочитать в справочной системе Delphi (каждая конкретная БД поступает по-своему).

В принципе, можно сделать так, чтобы не только по Append, но и по Insert записи всегда добавлялись в конец набора. Со вставкой в конец проблем обычно не возникает, т.к. в этом случае приходится перемещать физический и логический курсоры, а значит, начальное значение физического курсора можно не принимать во внимание. Добавление в конец, однако, не всегда приемлемо, и вдобавок вызывает неприятные визуальный эффект (особенно если мы одновременно обозреваем несколько соседних записей, как в DBControlGrid или DBGrid): как только курсор уходит с записи, она “прыгает” из зоны видимости в конец набора.

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

Сама по себе вставка проблемой не является и в этом случае. Проблема – в том, как корректно обрабатывать возникающую “виртуальную” запись, реально не существующую в наборе? А именно, как правильно обрабатывать команды на перемещение курсора на эту запись, или между ней, предыдущей и следующей записями.

Перемещение физического курсора может произойти при вызове одного из следующих методов: InternalFirst, InternalLast, GetRecord, SetRecNo, InternalSetToRecord и InternalGotoBookmark. Пока TDataSet находится в состоянии dsInsert – то есть до отмены или подтверждения вставки записи – один из этих методов может быть вызван только при вызове методов UpdateCursorPos, Resync, SetBufferCount или при записи в свойство Bookmark. В других случаях TDataSet предварительно выходит из состояния редактирования. Например, попытка выполнить метод First приведет еще до вызова InternalFirst к вызову либо Cancel, либо Post.

С SetRecNo есть небольшая тонкость: непонятно, имеет ли вставленная запись номер до того, как она будет включена в набор. Я буду предполагать, что не имеет. В противном случае пришлось бы в GetRecNo и SetRecNo сравнивать номер записи с номером вставленной записи и перемещение курсора производить в зависимости от результата. А раз не имеет, то работа SetRecNo не зависит от виртуальных записей.

Ясно, что методы InternalSetToRecord и InternalGotoBookmark для любой записи в наборе данных, кроме вставленной, будут работать так же хорошо, как работали без этой виртуальной записи (т.к. целевая запись – единственная идентифицируемая переданными в метод аргументами запись, реально существующая в наборе). То есть опасность может представлять лишь попытка перейти на саму редактируемую запись, и именно этот случай мы должны распознавать. Но куда же в такой ситуации помещать курсор?

Вызов InternalGotoBookmark приведет, т.к. закладка вставленной записи дублирует закладку какой-то уже имеющейся, к переходу на эту уже имеющуюся запись. Естественно ожидать, что так же должна себя вести и InternalSetToRecord. Для этого есть и более существенные основания – они будут рассмотрены чуть позже при анализе методов GetNextRecord и GetPriorRecord. Есть несколько способов добиться такого поведения InternalSetToRecord, их мы тоже рассмотрим позже.

Для UpdateCursorPos принципиально, для чего (перед какой операцией) осуществляется позиционирование курсора. Этот метод вызывается при ошибках перед Resync, а также: в Refresh, Cancel, AddRecord, Edit, Post, Delete. Вызов Edit в состоянии dsInsert игнорируется, вызов Delete вырождается в обращение к Cancel, а вызов AddRecord приводит еще до UpdateCursorPos к выходу из режима редактирования. Вызов перед Resync, так же, как и внутри Refresh, производится для того, чтобы Resync прочитал записи именно вокруг текущей. Вызов в Post дает InternalPost знать, в какое место набора данных вставляется запись. Вызов в Cancel позволяет InternalCancel сделать, например, вывод о том, какую запись нужно сделать текущей после отмены вставки.

С учетом этого для UpdateCursorPos курсор вполне корректно устанавливать на запись, перед которой осуществляется вставка. Действительно, в InternalPost легко по значению курсора, указывающему на следующую запись, понять, в какое место в наборе нужно поместить вставленную запись (отодвинув последующие): именно на это место. InternalCancel можно не перекрывать. Тогда после “вычеркивания” вставленной записи текущей станет запись, следующая за ней, что выглядит вполне логично. По поводу Resync см. замечания далее.

Как обеспечить правильную работу SetBufferCount? Если взглянуть на код этого метода, видно, что опасны лишь два ситуации:

Для метода GetNextRecord, вызывающего SetCurrentRecord, опасен случай, когда самый последний буфер содержит вставленную запись. Ясно, что в качестве “следующей” нужно прочитать запись, находящуюся в наборе за этой записью.

Для метода GetPriorRecord, также обращающегося к SetCurrentRecord, опасен случай, когда вставленная запись находится в первом буфере. Нужно прочитать запись, находящуюся в наборе перед вставленной.

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

В GetNextRecord имеется следующий код:

if (State = dsInsert) and (FCurrentRecord = FActiveRecord) and
  (GetBookmarkFlag(ActiveBuffer) = bfCurrent) then
  GetMode := gmCurrent;

Если бы вместо bfCurrent стояло fbInserted, эти строки можно было бы понять так: если TDataSet находится в состоянии редактирования, и имеет место случай (1), причем вставленная запись пока отсутствует в наборе, то читается не запись, следующая за той, на которую указывает физический курсор, а именно та, на которую физический курсор и указывает. Следовательно, здесь код TDataSet рассчитывает, что InternalSetToRecord при попытке установки физического курсора на вставленную запись будет устанавливать его на следующую за ней запись.

Однако в исходных текстах TDataSet написано именно bfCurrent, что очень меня удивляет. Возможно, я чего-то не понимаю, но не исключено, что это ошибка разработчиков из Borland. Последствия, к которым приводит эта недоработка, читатель легко может увидеть сам: нужно поместить на форму DBGrid с Align=alClient и DBNavigator с Align=alTop. Высота формы должна быть небольшой, чтобы в DBGrid была видна лишь одна запись БД. Если теперь пользователь добавит запись (щелчок по кнопке из DBNavigator) перед этой видимой записью, а затем увеличит высоту окна, то в DBGrid отобразятся не все записи, следующие за вставленной: не будет той записи, перед которой мы производим вставку. Конечно, необходимо для демонстрации связать с сеткой потомок TDataSet, позволяющий производить вставку в произвольное место набора – например, TADOTable.

Дело в том, что при увеличении высоты DBGrid, последний автоматически увеличит количество буферов в используемом TDataSet, чтобы иметь по одному буферу для каждой из одновременно отображаемых в сетке записей. Для этого будет вызван SetBufferCount, который забудет прочитать одну из записей (ту, перед которой происходит вставка).

Кстати, подобный эффект можно увидеть, если между созданием потомка TDataSet и отображением DBGrid выполнить у TDataSet метод Insert – одна из записей не будет показана в DBGrid.

А вот если бы вместо проверки на bfCurrent выполнялась проверка на bfInserted, все было бы в порядке.

ПРИМЕЧАНИЕ

Из этого следует, что идеально правильной работы, возможно, удастся добиться лишь таким образом: записи следует добавлять в набор сразу же при вызове InternalInsert, но не устанавливать для них флаг закладки bfCurrent вместо bfInserted (иначе GetNextRecord будет работать с ошибками в силу все того же фрагмента кода).

Проведенная мною небольшая серия испытаний пока не выявила противопоказаний к такому решению. Однако есть некоторые сомнения – все-таки это вмешательство в обычную схему работы TDataSet. Желающие могут провести самостоятельные эксперименты. Я сделал так: код, добавляющий запись, перенес из InternalPost в InternalInsert, в internalPost действия в состоянии dsInsert сделал такими же, как и в dsEdit, а InternalCancel просто свел к вызову InternalDelete.

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

Метод Resync, вообще говоря, также работает неправильно, но относительно него в файлах справки Delphi есть предупреждение, гласящее, что Resync предназначен для внутреннего использования и не должен вызываться извне TDataSet напрямую. Сам же TDataSet никогда и не вызывает Resync в состоянии dsInsert. Остается надеяться, что потребители нашего потомка TDataSet учтут предупреждение.

Итак, как же реализовать InternalSetToRecord?

Сложность заключается в том, что к моменту вызова InternalInsert метод UpdateCursorPos не был вызван, а буфер активной ранее записи уже перезаписан вызовом InternalInitRecord, так что, даже если мы вызовем UpdateCursorPos, он попытается вызвать InternalSetToRecord для перемещения физического курсора на запись, соответствующую данному буферу – но буфер-то теперь содержит новую информацию.

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

Есть два пути. Начнем с простого. Будем хранить в буфере записи значение физического курсора, при котором он (курсор) указывает на эту запись.

ПРИМЕЧАНИЕ

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

Чтобы определить, какая запись была активной до вызова Insert, можно перекрыть метод DoBeforeInsert, который вызывается еще до изменения активного буфера, и в нем тем или иным образом запомнить значение физического курсора. Между вызовами DoBeforeInsert и InternalInsert не выполняется никаких действий, изменяющих положение физического курсора. В случае Append это не так, но тут нам может помочь другое. Если на момент вызова Insert набор не содержит записей, то после InitRecord для новой записи ставится флаг закладки bfBOF. Если вызван Append, ставится флаг fbEOF. В остальных случаях флаг остается таким, каким его сформировал InitRecord, то есть fbInserted. Поэтому при вызове InternalInsert можно поступить так: если флаг не равен bfInserted, то установить значение физического курсора, хранимое в буфере активной записи, так, как это по смыслу нужно для выполнения Append, в противном же случае – равным значению, выясненному в DoBeforeInsert. Конечно, это означает привязку к последовательности вызовов в логике TDataSet, и есть опасность несовместимости с грядущими новыми версиями VCL.

Второй вариант – его использует Borland в своих потомках TDataSet – заключается в сведении InternalSetToRecord к вызову InternalGotoBookmark. Модификация этого решения используется в компоненте TRxMemoryData из RX Library.

Основания таковы. Если запись имеет флаг закладки bfCurrent, то у нее есть закладка; если флаг bfInserted, то, хотя BookmarkAvailable для нее вернет False, значение закладки все-таки установлено и совпадает с закладкой записи, “отодвигаемой” при вставке; если bfBOF или bfEOF, то нам вообще беспокоиться не о чем – TDataSet вызовет не InternalSetToRecord, а InternalFirst или InternalLast.

В таком подходе есть один недостаток: когда мы отодвигаем одну из записей набора, чтобы вставить новую, закладки записей не должны изменяться – иначе весь механизм закладок окажется лишенным смысла, т.к. клиент не сможет перейти по ранее сохраненной закладке на соответствовавшую ей в момент получения закладки запись. С другой стороны, значения физического курсора, соответствующие отодвигаемым записям, должны измениться. Следовательно, есть два пути. Можно в паре с каждой закладкой хранить информацию (скрытую от внешнего по отношению к нашему потомку TDataSet кода), позволяющую вычислить значение физического курсора. В таком случае при добавлении/удалении записей нужно изменять внутреннее преставление по меньшей мере нескольких, а то и всех, закладок (в зависимости от того, насколько эффективные алгоритмы использовать и какую информацию хранить).

С другой стороны, в случае, если операций вставки производится существенно больше, чем операций поиска по закладке (путем присваивания свойству Bookmark, т.е. не считая вызовов InternalSetToRecord), может оказаться выгоднее (и уж точно – проще) искать записи последовательным перебором, а InternalSetToRecord реализовать так, как описано ранее (DoBeforeInsert и InternalInsert).

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

Чуть не забыл! Insert и Append – не единственные методы для вставки записей. Если имеется буфер записи, в который уже записаны значения полей, то можно добавить в набор запись с такими значениями полей вызовом InsertRecord. Этот метод обращается к AddRecord, а тот, в свою очередь, к InternalInsertRecord. Такой способ добавления записи тоже нужно обрабатывать.

Native-формат данных

Приведу обещанное описание Native-формата данных:

Данные для поля (каждому полю соответствует экземпляр потомка TField), имеющего DataType=ftInteger или ftLargeint, в Native-формате представлены просто в виде числа типа longint (или, что то же самое, Integer); данные для ftSmallint – числом типа Smallint, ftWord – типа Word. Для полей с DataType=ftString данные хранятся в виде последовательности символов, завершающейся #0 (символом с кодом 0); длина последовательности вместе с #0 не должна превышать значение_поля_Size + 1. Если поле Transliterate данного экземпляра TStringField равно True, то потомок TDataSet должен предоставить метод Translate для преобразования данных из кодировки символов, используемой в конкретном наборе данных, в кодировку ANSI (и обратно). В том виде, как он реализован в TDataSet, метод Translate просто возвращает исходную строку. Для полей с DataType=ftWideString данные хранятся в формате WideString. Для полей ftFloat и ftCurrency – в виде числа типа double. Для полей ftBoolean – в виде WordBool. Для полей ftDateTime, ftDate и ftTime – в виде TDateTime. Для полей с DataType=ftTimeStamp – в виде TSQLTimeStamp (модуль SqlTimSt). Для полей ftBytes и ftVarBytes – в виде последовательности байтов. Для полей ftBCD и ftFMTBCD – в виде TBcd (модуль FMTBcd). Для полей ftBLOB, ftMemo, ftGraphic данные так же, как и для ftBytes, хранятся в виде последовательности байтов; потомок TDataSet обязан предоставить метод CreateBlobStream для создания объекта-потомка TStream, обеспечивающего доступ к данным. Для полей ftReference – в виде массива байтов. Для полей ftVariant – в любом виде, совместимом с одним из представлений данных в Variant (см. описание Variant в справочной системе Delphi; подробное описание доступно в MSDN (ключевые слова, например, – “VARIANT and VARIANTARG”). Для полей ftInterface, ftIDispatch – в виде указателя на интерфейс. Для полей ftGuid – в виде строки, которая может быть расшифрована вызовом StringToGuid. Для полей ftADT и ftArray – определяются типами содержащихся в них полей (каждое поле читается отдельно). Для полей ftDataSet – насколько я понял, формат хранимых данных не имеет значения; потомок TDataSet обязан предоставить метод CreateNestedDataset для создания экземпляра нужного потомка TDataSet на основе содержащейся в данном поле информации. Упрощение интерфейса

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

С учетом сказанного, второй пример будет состоять из двух частей: TMyDataSet – переходник, обеспечивающий упрощенное взаимодействие с методами TDataSet, используя обычный для Delphi механизм событий, и TMyData – класс, содержащий собственно адресную книгу и методы работы с ней, которые и назначаются в качестве реакций на события TMyDataSet.

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

Вот введенные мной события:

FPerformOpenConnection: procedure(DS: TDataSet) of object;
-Установление соединения с носителем набора данных
FPerformCloseConnection: procedure(DS: TDataSet) of object;
-Разрыв соединения
FPerformFillFieldDefs: procedure(FieldDefsRef: TFieldDefs) of object;
-Формирование списка определений полей(аргумент - объект - список полей)
FPerformGetFieldData: procedure(Index: integer; Field: TField; out Data)
of object;
-Чтение значения поля Field записи с номером Index
FPerformSetFieldData: procedure(Index: integer; Field: TField; var Data)
of object;
-Изменение значения поля
PerformGetCanModify: function: boolean of object;
-Определение того, можно ли вносить изменения в набор
FPerformGetRecordCount: function: integer of object;
-Определение количества записей в наборе
FPerformDeleteRecord: procedure(Index: integer) of object;
-Удаление записи
FPerformClearFields: procedure(Index: integer) of object;
-Очистка полей записи
FPerformCreateFloatingRecord: function(SourceIndex: integer): integer
of object;
-Создание “плавающей” записи.Передается номер записи, значения полей
которой нужно скопировать в “плавающую”(0, если копировать ничего
  не нужно)
FPerformFreeFloatingRecord: procedure(Index: integer) of object;
-Удаление “плавающей” записи с номером Index
FPerformDefloatRecord: procedure(FlIndex, DestIndex: integer;
  DefloatMode: TDefloatMode) of object;
-Помещение “плавающей” записи с номером Index в набор в позицию, занимаемую
записью DestIndex.DefloatMode указывает, вставляется ли “плавающая”
запись вместо DestIndex или перед ней
FPerformGetBookmarkSize: function: integer of object;
-Определение размера закладки
FPerformGetBookmark: function(Index: integer): TBookmarkStr of object;
-Опрос закладки
FPerformSetBookmark: procedure(Index: integer; NewBookmark: TBookmarkStr) of
object;
-Установка закладки для записи
FPerformFindBookmark: function(Bookmark: TBookmarkStr): integer of object;
-Переход к записи по закладке

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

Особо следует пояснить понятие “плавающей” записи. Предполагается, что поставщик не считывает записи набора в какие-то свои кэш-буферы, а читает содержимое полей прямо из набора. В случае чтения это работает, но в случае редактирования записей производимые изменения следует где-то накапливать, то есть в этой ситуации временное хранилище создавать придется. Однако, есть способ упростить эту операцию. Хранилище можно (так как одновременно при таком подходе будет использоваться не более одного хранилища) реализовать в виде, например, какого-то зарезервированного элемента набора (массива, списка). Соответственно, можно сделать единообразным доступ как к записям набора, так и к этому хранилищу. Когда поступает запрос на начало редактирования существующей записи или на добавление новой, у поставщика запрашивается создание такого хранилища. В ответ поставщик возвращает число – это может быть, например, номер зарезервированной записи, или вообще любой номер такой, что в дальнейшем поставщик будет в состоянии понять, что этот номер относится именно к хранилищу. Таким образом, роль хранилища с точки зрения нашего потомка TDataSet будет играть запись с этим номером – ее мы и назовем “плавающей записью”.

Затем происходит либо очистка полей плавающей записи, либо (в случае редактирования существующей записи) копирование в эти поля значений полей исходной записи. В дальнейшем, пока изменения не будут подтверждены или отменены, вместо чтения и записи полей исходной будет производиться чтение и запись полей “плавающей”. При отмене изменений у поставщика запрашивается освобождение “плавающей” записи, при подтверждении – вызывается FPerformDefloatRecord (см. описание этого события).

Отмечу небольшое затруднение с подменой чтения/записи исходной записи на чтение/запись плавающей. Как уже говорилось, некоторые элементы управления в цикле, в котором они опрашивают поля записей (что приводит к вызову GetFieldData), изменяют единственную реализацию логического курсора в TDataSet, указатель ActiveBuffer (присваивая ему затем прежнее значение). Поэтому для того, чтобы определить, потребовали ли от TDataSet доступ именно к редактируемой записи, адрес буфера редактируемой записи надо в момент начала редактирования сохранить в какой-либо переменной, а в GetFieldData сравнивать значение ActiveBuffer с этим сохраненным значением.

Все вышесказанное естественным образом воплощается в примерно такой код:

Листинг 3 TMyDataSet с возможностью редактирования

unit DataSet3;
interface
uses DB, Classes {for TComponent};
type
  TDBCursor = integer; //Тип данных для физического курсора - номер записи
  TDefloatMode = (dmInsert, dmOverwrite);
  TRecordBuffer = packed record
    RecordIndex: TDBCursor; //Номер записи (считая от 1)
    BookmarkFlag: TBookmarkFlag;
  end;
  PRecordBuffer = ^TRecordBuffer;
  TMyDataSet = class(TDataset)
  private
    FIsOpen: boolean;
    FCursor: integer;
    FInsertingBefore: integer;
    FFloatingRecordIndex: integer;
    FEditingBuffer: pchar; //Хранит адрес буфера для редактируемой записи
    //При редактировании большую часть времени совпадает с ActiveBuffer,
    //но в процессе отрисовки связанных элементов управления последний
    //временно изменяется
  protected
    procedure InternalHandleException; override;
    procedure InternalInitFieldDefs; override;
    procedure InternalOpen; override;
    function IsCursorOpen: Boolean; override;
    procedure InternalClose; override;
    procedure SetActive(Value: Boolean); override; //Вызывает Finalize при
    // закрытии TDataSet
    procedure Finalize; virtual; //логическое завершение текущих операций
    //(вызов Close/Post и т.п.)
    function GetRecord(Buffer: PChar; GetMode: TGetMode;
      DoCheck: Boolean): TGetResult; override;
    function AllocRecordBuffer: PChar; override;
    procedure FreeRecordBuffer(var Buffer: PChar); override;
    procedure InternalFirst; override;
    procedure InternalLast; override;
    procedure InternalSetToRecord(Buffer: PChar); override;
    function GetCanModify: Boolean; override;
    procedure InternalEdit; override;
    procedure InternalCancel; override;
    procedure DoBeforeInsert; override;
    procedure InternalInsert; override;
    procedure InternalInitRecord(Buffer: PChar); override;
    procedure InternalPost; override;
    procedure InternalDelete; override;
    function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
    procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
      override;
    procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
      override;
    procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
    procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
    procedure InternalGotoBookmark(Bookmark: Pointer); override;
    function GetRecordCount: Integer; override;
    procedure SetRecNo(Value: Integer); override;
    function GetRecNo: Integer; override;
  public
    constructor Create(AOwner: TComponent); override;
    function GetFieldData(Field: TField; Buffer: Pointer): Boolean;
      override;
    procedure SetFieldData(Field: TField; Buffer: Pointer); override;
    function BookmarkValid(Bookmark: TBookmark): Boolean; override;
  public //Конечно, это лучше реализовывать в виде свойств, но в целях
    // экономии места я сделал эти процедурные переменные полями
    FPerformOpenConnection: procedure(DS: TDataSet) of object;
    FPerformCloseConnection: procedure(DS: TDataSet) of object;
    FPerformFillFieldDefs: procedure(FieldDefsRef: TFieldDefs) of object;
    FPerformGetFieldData: procedure(Index: integer; Field: TField; out Data)
    of object;
    FPerformSetFieldData: procedure(Index: integer; Field: TField; var Data)
    of object;
    FPerformGetCanModify: function: boolean of object;
    FPerformGetRecordCount: function: integer of object;
    FPerformDeleteRecord: procedure(Index: integer) of object;
    FPerformClearFields: procedure(Index: integer) of object;
    FPerformCreateFloatingRecord: function(SourceIndex: integer): integer
    of object;
    FPerformFreeFloatingRecord: procedure(Index: integer) of object;
    FPerformDefloatRecord: procedure(FlIndex, DestIndex: integer;
      DefloatMode: TDefloatMode) of object;
    FPerformGetBookmarkSize: function: integer of object;
    FPerformGetBookmark: function(Index: integer): TBookmarkStr of object;
    FPerformSetBookmark: procedure(Index: integer; NewBookmark: TBookmarkStr)
    of object;
    FPerformFindBookmark: function(Bookmark: TBookmarkStr): integer
    of object;
  end;
implementation
uses SysUtils, forms;
procedure TMyDataSet.InternalHandleException;
begin
  Application.HandleException(Self)
end;
procedure TMyDataSet.InternalInitFieldDefs;
begin
  if Assigned(FPerformFillFieldDefs) then
    FPerformFillFieldDefs(FieldDefs)
  else
    FieldDefs.Clear;
end;
procedure TMyDataSet.InternalOpen;
begin
  InternalInitFieldDefs;
  if DefaultFields then
    CreateFields;
  BindFields(true);
  FIsOpen := true;
  FCursor := 0;
  if Assigned(FPerformGetBookmarkSize) then
    BookmarkSize := FPerformGetBookmarkSize()
  else
    BookmarkSize := 0;
  if Assigned(FPerformOpenConnection) then
    FPerformOpenConnection(self);
end;
function TMyDataSet.IsCursorOpen: Boolean;
begin
  result := FIsOpen
end;
procedure TMyDataSet.InternalClose;
begin
  if Assigned(FPerformCloseConnection) then
    FPerformCloseConnection(self);
  BindFields(False);
  if DefaultFields then
    DestroyFields;
  FIsOpen := false;
end;
procedure TMyDataSet.InternalFirst;
begin
  FCursor := 0
end;
procedure TMyDataSet.InternalLast;
begin
  FCursor := RecordCount + 1
end;
function TMyDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
begin
  result := grOK;
  case GetMode of
    gmPrior: if FCursor <= 1 then
        result := grBOF
      else
        Dec(FCursor);
    gmNext: if FCursor >= RecordCount then
        result := grEOF
      else
        Inc(FCursor);
    gmCurrent: if (FCursor < 1) or (FCursor > RecordCount) then
        Result := grError;
  end;
  if result = grOK then
    with PRecordBuffer(Buffer)^ do
    begin
      RecordIndex := FCursor;
      BookmarkFlag := bfCurrent;
    end;
  if (result = grError) and DoCheck then
    DatabaseError('Ошибка в GetRecord()');
end;
function TMyDataSet.GetRecordCount: Integer;
begin
  if Assigned(FPerformGetRecordCount) then
    Result := FPerformGetRecordCount()
  else
    raise EDatabaseError.Create('Для работы с ' + ClassName
      + ' требуется задать обработчик события OnGetRecordCount!');
end;
procedure TMyDataSet.SetRecNo(Value: Integer);
begin
  CheckBrowseMode;
  if (Value < 1) or (Value >= RecordCount + 1) then
    exit;
  FCursor := Value;
  Resync([])
end;
function TMyDataSet.GetRecNo: Integer;
begin
  Result := PRecordBuffer(ActiveBuffer)^.RecordIndex + 1
end;
procedure TMyDataSet.InternalSetToRecord(Buffer: PChar);
begin
  FCursor := PRecordBuffer(Buffer)^.RecordIndex;
end;
function TMyDataSet.AllocRecordBuffer: PChar;
begin
  GetMem(result, sizeof(TRecordBuffer))
end;
procedure TMyDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
  FreeMem(Buffer)
end;
procedure TMyDataSet.InternalInitRecord(Buffer: PChar);
//Вообще-то, это просто операция очистки записи.
//Так как наш буфер не содержит указателей на динамически создаваемые
//структуры данных, то нам не нужно удалять структуры, связанные с
//предыдущим содержимым буфера. Но вот для поставщика это может быть
//необходимо, и его надо подробно уведомить о ситуации
begin
  //Если это повторный вызов (когда TDataSet уже находится в одном из режимов
  //редактирования) - такой выполняется при ClearFields - то надо просто
  //очистить запись, в противном случае TDataSet мы хотим вставить новую
  //запись, и поставщик должен сначала создать “плавающую” запись.
  if not (State in dsEditModes) then
    if Assigned(FPerformCreateFloatingRecord) then
      FFloatingRecordIndex := FPerformCreateFloatingRecord(0);
  if Assigned(FPerformClearFields) then
    FPerformClearFields(PRecordBuffer(ActiveBuffer)^.RecordIndex);
end;
function TMyDataSet.GetCanModify: Boolean;
begin
  if assigned(FPerformGetCanModify) then
    result := FPerformGetCanModify
  else
    result := false
end;
function TMyDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
  Index: integer;
begin
  result := assigned(FPerformGetFieldData);
  if (State in dsEditModes) and (ActiveBuffer = FEditingBuffer) then
    Index := FFloatingRecordIndex //перенаправляем к “плавающей” записи
  else
    Index := PRecordBuffer(ActiveBuffer)^.RecordIndex;
  if result then
    FPerformGetFieldData(Index, Field, Buffer^);
end;
constructor TMyDataSet.Create(AOwner: TComponent);
begin
  inherited;
  FIsOpen := false;
  if assigned(FPerformGetBookmarkSize) then
    BookmarkSize := FPerformGetBookmarkSize()
  else
    BookmarkSize := 0;
end;
function TMyDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
  result := PRecordBuffer(Buffer)^.BookmarkFlag;
end;
procedure TMyDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
begin
  PRecordBuffer(Buffer)^.BookmarkFlag := Value;
end;
procedure TMyDataSet.InternalPost;
begin
  inherited; //=CheckRequiredFields
  case State of
    dsEdit:
      if Assigned(FPerformDefloatRecord) then
        FPerformDefloatRecord(FFloatingRecordIndex, FCursor, dmOverwrite);
    dsInsert:
      begin
        if Assigned(FPerformDefloatRecord) then
          FPerformDefloatRecord(FFloatingRecordIndex, FCursor, dmInsert);
        PRecordBuffer(ActiveBuffer)^.RecordIndex := FCursor;
        PRecordBuffer(ActiveBuffer)^.BookMarkFlag := bfCurrent;
      end;
  end;
end;
procedure TMyDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
  //Для этой операции Index всегда является индексом “плавающей” записи
  //Перенаправляем изменения в Floating record
  if Assigned(FPerformSetFieldData) then
    FPerformSetFieldData(FFloatingRecordIndex, Field, Buffer^);
  DataEvent(deFieldChange, cardinal(Field));
end;
procedure TMyDataSet.InternalDelete;
begin
  if not Assigned(FPerformGetRecordCount) then
    raise EDatabaseError.Create('Для работы с ' + ClassName
      + ' требуется задать обработчик события PerformGetRecordCount!');
  if Assigned(FPerformDeleteRecord) then
    FPerformDeleteRecord(PRecordBuffer(ActiveBuffer)^.RecordIndex);
  if FCursor = FPerformGetRecordCount() then
    Dec(FCursor);
end;
procedure TMyDataSet.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
//Согласно справке, при вызове InsertRecord активной становится вставленная
//запись. Причем можно отследить, что заботиться об этом должна
//InternalAddRecord. Поэтому в случае DoAppend просто прыгаем в конец
//(вернее, за конец)
begin
  //В момент вызова уже создана Floating-запись, и в нее скопированы значения
  if DoAppend then
    InternalLast;
  FPerformDefloatRecord(FFloatingRecordIndex, FCursor, dmInsert);
end;
procedure TMyDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
var
  res: string;
begin
  if BookmarkSize = 0 then
    exit;
  if Assigned(FPerformGetBookmark) then
    res := FPerformGetBookmark(PRecordBuffer(Buffer)^.RecordIndex)
  else
    res := '';
  strpcopy(Data, res);
end;
procedure TMyDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
  if Assigned(FPerformSetBookmark) then
    FPerformSetBookmark(PRecordBuffer(Buffer)^.RecordIndex, string(pchar(Data)))
end;
procedure TMyDataSet.InternalGotoBookmark(Bookmark: Pointer);
var
  Pos: integer;
begin
  if not assigned(FPerformFindBookmark) then
    exit;
  Pos := FPerformFindBookmark(string(Bookmark));
  if Pos > -1 then
    FCursor := Pos;
end;
function TMyDataSet.BookmarkValid(Bookmark: TBookmark): Boolean;
begin
  result := assigned(FPerformFindBookmark);
  if result then
    result := (-1 <> FPerformFindBookmark(string(Bookmark)))
end;
procedure TMyDataSet.DoBeforeInsert;
begin
  inherited;
  FInsertingBefore := PRecordBuffer(ActiveBuffer)^.RecordIndex;
end;
procedure TMyDataSet.InternalInsert;
begin
  FEditingBuffer := ActiveBuffer;
  //Чтобы InternalSetToRecord переходил на правильную позицию
  with PRecordBuffer(ActiveBuffer)^ do
    if BookmarkFlag = bfInserted then
      RecordIndex := FInsertingBefore;
  //А если bfEOF или bfBOF, то InternalSetToRecord и не будет вызываться
end;
procedure TMyDataSet.InternalCancel;
begin
  if Assigned(FPerformFreeFloatingRecord) then
    FPerformFreeFloatingRecord(FFloatingRecordIndex);
end;
procedure TMyDataSet.InternalEdit;
begin
  FEditingBuffer := ActiveBuffer;
  if Assigned(FPerformCreateFloatingRecord) then
    FFloatingRecordIndex :=
      FPerformCreateFloatingRecord(PRecordBuffer(ActiveBuffer)^.RecordIndex);
end;
procedure TMyDataSet.SetActive(Value: Boolean);
begin
  if (Value <> Active) and (Value = false) then
    Finalize;
  inherited;
end;
procedure TMyDataSet.Finalize;
begin
  Cancel; //вдруг что-то редактировалось
end;
end.

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

Листинг 4

unit MyData;
interface
uses Classes, DB, DataSet3, SysUtils;
type
  TMyRecord = class
    Name: string;
    EMail: string;
    Bookmark: pointer; //В нормальном состоянии - указатель на саму запись
    constructor Create(sName, sEMail: string);
    procedure Change(sName, sEMail: string);
  end;
  TMyData = class
    List: TList;
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure FillFieldDefs(FieldDefsRef: TFieldDefs);
    procedure GetFieldData(Index: integer; Field: TField; out Data);
    procedure SetFieldData(Index: integer; Field: TField; var Data);
    function GetCanModify: boolean;
    function GetRecordCount: integer;
    procedure DeleteRecord(Index: integer);
    procedure ClearFields(Index: integer);
    function CreateFloatingRecord(SourceIndex: integer): integer;
    procedure DefloatRecord(FlIndex, DestIndex: integer;
      DefloatMode: TDefloatMode);
    function GetBookmarkSize: integer;
    function GetBookmark(Index: integer): TBookmarkStr;
    procedure SetBookmark(Index: integer; NewBookmark: TBookmarkStr);
    function FindBookmark(Bookmark: TBookmarkStr): integer;
  end;
implementation
procedure TMyRecord.Change(sName, sEMail: string);
begin
  Name := sName;
  EMail := sEMail
end;
constructor TMyRecord.Create(sName, sEMail: string);
begin
  inherited Create;
  Change(sName, sEMail);
  Bookmark := self;
end;
{ TMyData }
constructor TMyData.Create;
begin
  List := TList.Create;
  List.Add(TMyRecord.Create('Temporary', 'Temporary'));
  //Первый элемент - место под “плавающую” запись
  List.Add(TMyRecord.Create('Name1', 'email1'));
  List.Add(TMyRecord.Create('Name2', 'email2'));
  List.Add(TMyRecord.Create('Name3', 'email3'));
end;
destructor TMyData.Destroy;
var
  i: integer;
begin
  for i := 0 to List.Count - 1 do
    TMyRecord(List[i]).free;
  List.Free;
  inherited;
end;
procedure TMyData.ClearFields(Index: integer);
begin
  TMyRecord(List[0]).Name := '(name)';
  TMyRecord(List[0]).EMail := '(email)';
end;
function TMyData.CreateFloatingRecord(SourceIndex: integer): integer;
begin
  result := 0;
  if SourceIndex <> 0 then
    with TMyRecord(List[SourceIndex]) do
    begin
      TMyRecord(List[0]).Name := Name;
      TMyRecord(List[0]).EMail := EMail
    end;
end;
procedure TMyData.DefloatRecord(FlIndex, DestIndex: integer;
  DefloatMode: TDefloatMode);
begin
  if DefloatMode = dmInsert then
    List.Insert(DestIndex, TMyRecord.Create(
      TMyRecord(List[0]).Name, TMyRecord(List[0]).EMail))
      //При этом автоматически ставится новый Bookmark
  else
    with TMyRecord(List[DestIndex]) do
    begin
      Name := TMyRecord(List[0]).Name;
      Email := TMyRecord(List[0]).EMail
    end;
end;
procedure TMyData.FillFieldDefs(FieldDefsRef: TFieldDefs);
begin
  FieldDefsRef.Clear;
  with FieldDefsRef.AddFieldDef do
  begin
    DataType := ftString;
    FieldNo := 1;
    Name := 'Имя';
    Size := 40;
  end;
  with FieldDefsRef.AddFieldDef do
  begin
    DataType := ftString;
    FieldNo := 2;
    Name := 'E-Mail';
    Size := 50;
  end;
end;
procedure TMyData.DeleteRecord(Index: integer);
begin
  List.Delete(Index)
end;
function TMyData.GetCanModify: boolean;
begin
  result := true
end;
procedure TMyData.SetFieldData(Index: integer; Field: TField; var Data);
begin
  case Field.FieldNo of
    1: TMyRecord(List[Index]).Name := strpas(@Data);
    2: TMyRecord(List[Index]).Email := strpas(@Data);
  end;
end;
procedure TMyData.GetFieldData(Index: integer; Field: TField; out Data);
begin
  case Field.FieldNo of
    1: strpcopy(@Data, TMyRecord(List[Index]).Name);
    2: strpcopy(@Data, TMyRecord(List[Index]).EMail);
  end;
end;
function TMyData.GetRecordCount: integer;
begin
  result := List.Count - 1; {0-й элемент не считаем}
end;
function TMyData.GetBookmarkSize: integer;
begin
  result := 10; {'$xxxxxxxx'+NULL}
end;
procedure TMyData.SetBookmark(Index: integer; NewBookmark: TBookmarkStr);
begin
  TMyRecord(List[Index]).BookMark := pointer(strtoint(NewBookmark))
end;
function TMyData.FindBookmark(Bookmark: TBookmarkStr): integer;
var
  i: integer;
  p: pointer;
begin
  p := pointer(strtoint(pchar(Bookmark)));
  result := -1;
  for i := 1 to List.Count - 1 do
    if TMyRecord(List[i]).Bookmark = p then
    begin
      result := i;
      break
    end;
end;
function TMyData.GetBookmark(Index: integer): TBookmarkStr;
begin
  result := '$' + IntToHex(cardinal(TMyRecord(List[Index]).BookMark), 8)
end;
end.

Для проверки работы можно использовать все тот же небольшой проект, включив в Uses модуль DataSet3 вместо DataSet0, а также модуль MyData, и добавив подключение к нашему экземпляру TMyDataSet определенных в модуле MyData обработчиков событий. Для этого, конечно, потребуется создать экземпляр TMyData.

Заключение

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

Приведенной информации, на мой взгляд, достаточно, чтобы адаптировать свои структуры данных для работы с TDataSource и DB-Aware элементами управления. Конечно, на практике могут встретиться ситуации, требующие дополнительных усилий и реализации еще каких-либо действий (например, поиска записи по значению части ее полей, фильтрации записей и т.п.); смею надеяться, однако, что выполненная мною работа поможет и в этом, несколько развеяв туман вокруг внутренних взаимосвязей методов TDataSet и восполнив катастрофических масштабов недостаток документации по этой теме.

Как создать БД в кодировке CP1251.

Автор: Nomadic

Вот такая конструкция проходит на DB2 2.1.2/NT и UDB5/NT...

CREATE DATABASE Efes2
USING CODESET 1251 TERRITORY RU
COLLATE USING IDENTITY;

Курсоры в ADO.

Статьи по Delphi (005)…
Программы пропили всё... Даже курсор от мышки...

В Delphi компоненты ADOExpress довольно приятны в использовании. Однако программирование компонент ADOExpress весьма отличается от традиционного Delphi программирования в компонентах TTable и TQuery, основанных на BDE. Естевственно, что если Вы привыкли к компонентам BDE dataset, то сразу же заметите различие в количестве возможностей и свойств, а так же в стиле программирования BDE и ADO.

В основе ADO лежит объект Recordset (aka Dataset). Этот объект является результатом Query команды (например, выражение SELECT компонента TADOQuery). Когда ADO-приложение получает строки из базы данных, то объект ADO Recordset формирует необходимую информацию и операции, допустимые для получаемых данных. При этом ADO использует курсоры, чтобы хранить набор строк для обработанной записи. Так же курсор содержит в себе текущую позицию в записи (recordset). Обычно, при разработке приложения, курсоры используются при создании записей, а так же при перемещении по записям (вперёд или назад).

В ADO курсоры имеют три функции. Первая, это тип курсора, определющая допустимые перемещения в пределах курсора, а так же будут ли отражаться изменения пользователей в записи. Вторая, это местоположение курсора, определяющая место хранения записи, в то время, пока курсор остаётся открытым. Третья, это тип блокировки курсора, которая определяет как ADO datastore будет блокировать строки, если Вам потребуется внести изменения.

Каждый раз когда мы будем открывать определённую запись ADO, то мы будем открывать её с определённым типом курсора.

Класс TCustomADODataSet содержит набор свойств, событий и методов, для работы с данными, доступными через ADO datastore. Все классы-потомки от TCustomADODataSet (такие как TADODataSet, TADOTable, TADOQuery, и TADOStoredProc) совместно используют некоторые общие свойства. В каждом из этих классов присутствуют три свойства, соответствующие функциям курсоров, описанным выше: CursorType, CursorLocation, и LockType. Давайте рассмотрим эти свойства по-подробнее.

CursorType ADO содержит четы опции для данного типа курсора: dynamic, keyset, forward-only и static. Так как каждый тип курсора ведёт себя работает по разному, то несомненно Вы извлечёте пользу из каждого из этих видов курсоров. Свойство CursorType указывает на то, каким образом Вы будете перемещаться по записям а так же какие изменения будут видны в записях базы данных, после того как Вы получите из неё данные. В Delphi классах ADO типы курсоров задаются в TCursorType. ctDynamic Позволит Вам видеть добавления, изменения и удаления, сделанные другими пользователями, а также позволит делать все типы перемещения по записи (Recordset) не полагаясь на закладки. Однако закладки можно использовать, если они поддерживаются. Для этого существует метод Supports в ADODataset, который сигнализирует о поддержке определённых типов операций. Следующее выражение позволяет определить, поддерживаются закладки или нет:
if ADOTable1.Supports(coBookmark) then ... 
Если несколько пользователей одновременно вставляют (insert), апдейтят (update), или удаляют (delete) строки в базе данных, то лучше всего выбрать курсор dynamic. ctKeyset Ведет себя подобно динамическому курсору, за исключением того, что Вы не сможете видеть записи, которые добавляют другие пользователи, а так же не сможете получить доступ к записям, которые удаляются другими пользователями. Изменение данных другими пользователями будет все еще видимо. Этот тип всегда поддерживает закладки и поэтому позволяет все типы перемещения по записям (Recordset). ctStatic Обеспечивает статическую копию набора записей, чтобы использовать её для поиска данных и генерации отчётов. Данный тип всегда разрешает закладки и поэтому позволяет все типы движения по записям. Добавления, изменения, или удаления другими пользователями не будут видимы. Статический курсор ведет себя подобно компоненту BDE Query со свойством RequestLive установленным в False. ctForward-only Ведет себя идентично динамическому курсору за исключением того, что позволяет Вам пролистывать по записям только вперед. Это увеличивает производительность в ситуациях, где Вы необходимо делать только один проход по набору записей (Recordset). Обратите внимание: если свойство CursorLocation в компоненте ADO dataset установлено в clUseClient, то Вы сможете использовать только опцию ctStatic. Так же обратите внимание: что если Вы запросите тип курсора неподдерживаемый базой данных, то она может вернуть другой тип курсора. То есть если Вы пробуете установить CursorLocation в clUseServer и CursorType в ctDynamic, в базе данных Access, то Delphi заменит CursorType на ctKeyset. CursorLocation Свойство CursorLocation определяет, где будет создан набор записей (recordset) когда он будет открыт — у клиента или на сервере. Данные в клиентском (client-side) курсоре не сязаны постоянно ("inherently disconnected") с базой данных. ADO получает результаты запроса (все строки) и копирует данные клиенту перед тем, как Вы начнёте использовать их (в курсоре ADO). После того, как Вы сделаете изменения в наборе записей (Recordset), ADO преобразует эти изменения в запрос и отправляет этот запрос в Вашу базу данных через OLE DB. Клиентский (client-side) курсор ведёт себя подобно локальному кэшу. В большинстве случаев, клиентский (client-side) курсор предпочтителен, потому что перемещения и обновления быстрее и более эффективны. Но, соответственно, увеличивается сетевой трафик при возвращении данных клиенту. Использование серверного (server-side) курсора означает получение только необходимых записей. Естевственно, что на сервер падает большая нагрузка. Серверные (Server-side) курсоры полезны при вставке, модификации, удалении записей. Данный тип курсоров иногда обеспечивает лучшую производительность чем клиентский курсор, особенно когда сеть перегружена. При выборе типа курсора Вам необходимо продумать множество факторов, таких как: будет ли у Вас большое количество обновлений либо Вы будете производить только выборку из базы данных; будете ли Вы использовать ADO как настольное приложение или Ваше приложение будет Internet-ориентированным; размер получаемых данных и т.д. Так же есть некоторое ограничения: например, MS Access не поддерживает динамических курсоров; вместо этого он использует keyset. Некоторые средства доступа к данным автоматически мастабируют свойства CursorType и CursorLocation, в то время как другие генерируют ошибку при использовании неподдерживаемых CursorType или CursorLocation. LockType Свойство LockType сообщает провайдеру о блокировках, которые будут помещены в записи в процессе редактирования. Блокировка позволяет предотвратить чтение данных одним пользователем в то время как другой пользователь изменяет эти данные, а так же не дать пользователю изменить данные, если они были изменены другим пользователем. Такой эффект наблюдается в базе данных Access, которая блокирует некоторые соседние записи. Дело в том, что Access использует так называемую стратегию фиксации страницы. Поэтому, если пользователь редактирует запись, то другой пользователь уже не сможет получить доступ к изменению данной записи и, даже не сможет модифицировать ближе стоящие записи (до или после неё). В Delphi, для этой цели используется TADOLockType в которой указывается тип блокировки, которая будет использоваться. Вы можете управлять строкой и блокировкой страницы, устанавливая соответствующую опцию блокировки курсора. Чтобы использовать определенную схему блокировки, провайдер и тип базы данных должны поддержать эту схему. ltOptimistic Оптимистическая блокировка блокирует запись только в том случае, если она была физически изменена. Этот тип блокировки полезен, если существует очень маленький шанс того, что второй пользователь может модифицировать строку в интервале между тем, когда курсор открыт, и когда строка окончательно модифицирована. Текущие значения в строке сравниваются со значением полученным когда строка была последний раз выбрана. ltPessimistic Пессимистическая блокировка блокирует каждую запись, до тех пор пока она находится в процессе редактирования. Эта опция заставляет ADO устанавливать исключительную блокировку на строку, когда пользователь делает любое изменения в любом столбцу записи. Компоненты ADOExpress непосредственно не поддерживают пессимистическую блокировку записей, потому что сама ADO не имеет возможности произвольно блокировать данную запись и до сих пор поддерживает навигацию в другие записи. ltReadOnly Данная блокировка просто не позволяет редактировать данные. Полезна в тех случаях, когда Ваше приложение должно временно предотвратить изменение данных, при этом чтение данной записи разрешено. Самый идеальный способ использования данной блокировки для создания отчётов, если установить CursorType как ctForwardOnly. ltBatchOptimistic Блокировка BatchOptimistic используется в клиентских курсорах. Наборы записей с данным курсором апдейтятся локально и все изменения отправляются в базу данных пакетами.

Часто задаваемые вопросы по базам данных.

Оформил: DeeCo

Здесь представлены ответы на наиболее типичные вопросы, возникающие при проектировании баз данных.

Содержание Разновидности СУБД Q1: Что такое СУБД? Q2: Чем отличаются «серверная» и «настольная» СУБД? Q3: Что такое реляционная база данных? Q4: Кто такой SQL-сервер? Q5: Какой SQL-сервер лучше всего использовать? Q6: Что такое объектно-ориентированная СУБД? Q7: Какие объектно-ориентированные СУБД существуют в природе? Q8: Что такое пост-реляционная база данных? Q9: Какие еще бывают разновидности СУБД? Сервер и клиент Q1: Что такое сервер баз данных? Q2: Что такое клиент? Q3: Как клиент общается с сервером? Q4: В чем отличие «многозвенной» архитектуры от «двухзвенной»? Q5: Зачем нужен сервер приложений? Q6: Чем отличается «тонкий» клиент от «толстого»? Реляционные базы данных: Теория Q1: Как данные хранятся в реляционной базе? Q2: Как записи отличаются друг от друга? Q3: Каким образом таблицы связаны между собой? Q4: Что такое нормализация? Q5: Имеет ли значение порядковый номер записи в таблице? Реляционные базы данных: Особенности SQL-серверов Q1: Что можно делать при помощи SQL? Q2: Зачем нужны транзакции? Q3: Что такое журнал транзакций? Q4: Когда появляются блокировки? Q5: Чем отличаются «версионники» от «блокировочников»? Q6: Почему возникает deadlock? Q7: Зачем нужны индексы? Q8: Для чего необходим первичный ключ в таблице? Q9: Что такое триггер? Q10: Можно ли использовать свою функцию в SQL-запросе? Q11: Как автоматически генерировать первичный ключ? Q12: Какую функцию выполняет оптимизатор запросов? Q13: Что такое представление? Q14: Для чего нужны хранимые процедуры? Q15: Какие типы данных есть в SQL-сервере? Q16: Чему равен null? Q17: Зачем нужен внешний ключ? Q18: Существует ли стандарт SQL, общий для всех серверов? Q19: Есть ли стандартное клиентское API для доступа к SQL-серверам? Q20: Что такое репликация? Реляционные базы данных: Практические вопросы Q1: Что такое суррогатный ключ, и чем он лучше или хуже естественного ключа? Q2: Можно ли полностью избежать дублирования информации? Q3: Хотелось бы иметь возможность восстанавливать удаленные записи. Как это сделать? Q4: Как организовать хранение истории изменений записей, с возможностью просмотра и отката этих изменений? Q5: Есть объекты, которые меняются с течением времени. Нужно получать их состояния на любую дату. Каким образом для этого надо организовать хранение объектов в базе? Q6: Каким образом можно хранить в реляционной базе иерархические объекты? Q7: Как получить полную информацию об определенной таблице в базе (какие поля есть в таблице, их тип, размер, связи с другой таблицей и т.п.)? Q8: Необходимо хранить в базе много картинок (файлов). Как лучше всего это сделать? Q9: Есть запрос, который возвращает данные, отсортированные по какому-либо признаку. Можно ли получить не весь результат, а только определенную его часть, например, записи с 20 по 40? Q10: Как из таблицы получить записи, в которых одно из полей максимально (или минимально)? Q11: Можно ли сделать в SQL перекрестный запрос? То есть, в качестве полей для результата, брались бы не поля таблицы, а данные из нее? Q12: Как обеспечить нумерацию записей в результате запроса? Q13: Есть необходимость добавлять произвольные виды объектов (еще неизвестные на этапе разработке) в БД. Создавать и менять таблицы на «живой» базе не хочется. Как быть? Дополнительно Стандарные аббревиатуры Литература Разновидности СУБД Q1: Что такое СУБД?

A1: СУБД — Система Управления Базами Данных (DBMS — DataBase Management System). Программа, либо комплекс программ, предназначенных для полнофункциональной работы с данными. Как правило, включает в себя инструменты для создания и изменения структуры хранения наборов данных, а также средства доступа к хранимым данным, с возможностью их чтения, добавления, изменения и удаления. При этом, у большинства СУБД имеется собственный встроенный язык (возможно не один) для работы с данными. Сама база данных (БД) обычно находится просто в файлах закрытого, либо открытого формата.

Q2: Чем отличаются «серверная» и «настольная» СУБД?

A2: Под настольной (desktop) обычно подразумевается СУБД, которая всегда запускается на компьютере пользователя, хотя сама база данных может находиться в другом месте. В результате несколько копий СУБД могут обращаться к одной базе данных. Серверная (server) СУБД, как правило, запускается в на той же машине (сервере баз данных), где находятся файлы БД. Непосредственно к базе данных обращается лишь один экземпляр СУБД. Пользовательские приложения общаются только с этой СУБД через ее API, независимо от того, работают они на той же машине или на другой. Для многопользовательских баз данных более эффективным и надежным вариантом является серверная СУБД. В ней гораздо быстрее происходит доступ к данным, и значительно проще решаются конфликты между разными пользователями.

Q3: Что такое реляционная база данных?

A3: Реляционная (relational) БД отличается способом представления информации, находящейся в ней. Данные в такой базе хранятся в плоских таблицах. Каждая таблица имеет собственный, заранее определенный набор именованных колонок (полей). Поля таблицы обычно соответствуют атрибутам сущностей, которые необходимо хранить в базе. Количество строк (записей) в таблице неограниченно, и каждая запись соответствует отдельной сущности. Каждая таблица должна иметь первичный ключ (ПК) — поле или набор полей, содержимое которых однозначно определяет запись в таблице и отличает ее от других. Связь между двумя таблицами обычно образуется при добавлении в первую таблицу поля, содержащего значение первичного ключа второй таблицы. Реляционные СУБД (РСУБД) предоставляют средства для всевозможных пересечений и объединений любых таблиц, отбора записей по разнообразным условиям, группировки и сортировки результатов. Реляционная база данных сочетает наглядность представления информации с простотой (относительной) реализации своей концепции и является наиболее популярной структурой для хранения данных на сегодняшний день. Достаточно подробно реляционные БД описаны в книгах [1], [2] и [3].

Q4: Кто такой SQL-сервер?

A4: Сервер для управления реляционными БД обычно называют SQL-сервером. SQL (Structured Query Language — язык структурированных запросов) является стандартным языком для работы с реляционными БД. Кроме стандартных реляционных операций, этот язык предоставляет возможности для изменений структуры таблиц. Различные варианты SQL используются во всех, как серверных, так и в настольных реляционных СУБД.

Q5: Какой SQL-сервер лучше всего использовать?

A5: На какой хватит денег.:) Вообще-то это может сильно зависеть от постановки задачи, количества пользователей и прихотей заказчика. Ниже приведена таблица самых распространенных SQL-серверов в порядке (примерно) убывания их возможностей:

Сервер Достоинства Недостатки IBM DB2 Universal Database Самый навороченный язык запросов, лучший оптимизатор, возможность писать функции на других языках. Высокая стоимость. Oracle Database Великое множество дополнительных возможностей. Версионный сервер. Очень высокая стоимость сервера и поддержки. Microsoft SQL Server Быстро развивающийся продукт, уже вплотную приближающийся к своим более развитым конкурентам. Средняя стоимость. Существует только для одной платформы (Win32). IBM Informix Dynamic Server Довольно развитый быстрый сервер. — Sybase Adaptive Server Enterprise Достаточно развитый сервер. Средняя стоимость. — Sybase Adaptive Server Anywhere Существует под множество платформ, включая самые экзотичные. Низкая стоимость. — Borland InterBase Приличный набор возможностей. Версионный сервер. Бесплатный. Относительно медленно работает. PostgreSQL Поддерживает историческую модель. Возможность создавать свои типы данных. Бесплатный. — MySQL Быстро работает на простых запросах. Бесплатный. Очень бедный язык запросов. Мало дополнительных возможностей.

Q6: Что такое объектно-ориентированная СУБД?

A6: В объектно-ориентированных БД (ООБД), данные представлены в виде объектов различных классов. Как правило, имеются возможности создавать новые классы, наследовать их от уже имеющихся, задавать произвольные атрибуты и методы для классов. Для доступа к объектам, в каждой ООБД обычно предусматривается свой собственный язык, либо расширение другого языка. Пока еще ООБД недостаточно развиты и не представляют серьезной конкуренции SQL-серверам, хотя и выглядят более предпочтительными для разработчиков. Производители SQL-серверов тоже, в свою очередь, иногда делают попытки соорудить над реляционным ядром сервера объектно-ориентированную надстройку. Более подробно ООБД описаны в статье [4].

Q7: Какие объектно-ориентированные СУБД существуют в природе?

A7: Достаточно распространены следующие ООБД: Cache FastObjects GemStone/S Jasmine ObjectStore Objectivity/DB Versant

Q8: Что такое пост-реляционная база данных?

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

Q9: Какие еще бывают разновидности СУБД?

A9: Кроме реляционных, объектно-ориентированных и многомерных СУБД, также давно известны иерархические и сетевые базы данных. Данные и связи между ними, в иерархических БД представлены в виде деревьев. Для некоторых задач, такая форма представления данных может оказаться гораздо более эффективной, чем любая другая. В сетевых базах, данные могут быть связаны произвольным образом, но эти связи должны создаваться предварительно, вместе со структурой данных. По сравнению с реляционными БД, сетевая модель может давать выигрыш в быстродействии, при некоторой потере гибкости.

Сервер и клиент Q1: Что такое сервер баз данных?

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

Q2: Что такое клиент?

A2: Клиентом к БД, обычно называют пользовательское приложение, которое общается с сервером БД. Модель работы, в которой клиент общается непосредственно с сервером, не используя промежуточных приложений, называется архитектурой клиент-сервер.

Q3: Как клиент общается с сервером?

A3: На пользовательских машинах, обычно устанавливаются специальные программы-шлюзы, которые, через сетевой протокол, обеспечивают связь с сервером БД. Через эти шлюзы, приложения передают запросы серверу и получают результаты. Часто, дополнительно устанавливается библиотека (ODBC, OLE DB и т.п.), предоставляющая приложениям API для работы с сервером БД.

Q4: В чем отличие «многозвенной» архитектуры от «двухзвенной»?

A4: Довольно распространена модель работы, когда клиент обращается не непосредственно к серверу БД, а к промежуточной программе. Эта программа обычно называется сервером приложений. Такую архитектуру называют «трехзвенной», в отличие от «двухзвенной» архитектуры клиент-сервер.

Q5: Зачем нужен сервер приложений?

A5: Сервер приложений может использоваться для многих целей. Как правило, сервер приложений находится на отдельной машине. На него можно переложить всю функциональность программы, оставив клиенту только интерфейсную часть. Это разгрузит клиента и сервер БД от вычислений. Также, при большом количестве пользователей, можно использовать несколько серверов приложений для распределения нагрузки. А для ускорения доступа к часто используемым таблицам, их обычно кэшируют на сервере приложений.

Q6: Чем отличается «тонкий» клиент от «толстого»?

A6: «Тонким» клиентом обычно называют пользовательское приложение, не содержащее никакой функциональности, и предназначенное только для ввода/вывода информации. Вся обработка данных производится на сервере БД, либо на сервере приложений. Зачастую, такой клиент изначально не содержит вообще никаких возможностей, а подгружает дополнительные модули с сервера, по мере необходимости. Обычно, в качестве «тонкого» клиента, выступают Web броузер + HTML/ASP/Java. «Толстый» клиент содержит всю функциональность и интерфейсную часть в себе, и при любом изменении, требует замены у всех пользователей.

Реляционные базы данных: Теория Q1: Как данные хранятся в реляционной базе?

A1: Данные в реляционной БД хранятся в плоских таблицах. Каждая таблица имеет собственный, заранее определенный набор именованных колонок (полей). Поля таблицы обычно соответствуют атрибутам сущностей, которые необходимо хранить в базе. Количество строк (записей) в таблице неограниченно, и каждая запись соответствует отдельной сущности. Полное описание реляционных БД можно найти в книгах [1], [2] и [3].

Q2: Как записи отличаются друг от друга?

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

Q3: Каким образом таблицы связаны между собой?

A3: Простейшая связь между двумя таблицами образуется при добавлении в первую таблицу поля, содержащего значение первичного ключа второй таблицы. В общем случае, реляционные БД предоставляют очень гибкий механизм для всевозможных пересечений и объединений любых таблиц, с разнообразными условиями. Для описания множеств, получающихся при пересечении и объединении таблиц, используется специальный математический аппарат — реляционная алгебра.

Q4: Что такое нормализация?

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

Q5: Имеет ли значение порядковый номер записи в таблице?

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

Реляционные базы данных: Особенности SQL-серверов Q1: Что можно делать при помощи SQL?

A1: SQL (Structured Query Language — язык структурированных запросов) является стандартным языком для работы с реляционными БД. Разделяется на две основные части: DDL (Data Definition Language — язык определения данных) и DML (Data Manipulation Language — язык обработки данных). DDL предоставляет средства для создания и изменения структуры хранения данных (БД, таблиц, процедур, типов данных и т.п.). DML предназначен для чтения и изменения данных. Основные операторы DML: select — выборка, insert — вставка, update — изменение, delete — удаление. Также, с помощью SQL, часто реализован доступ к служебным функциям SQL-сервера (заведение пользователей, создание резервных копий БД и т.д.). Подробное описание операторов и возможностей SQL имеется в книге [1].

Q2: Зачем нужны транзакции?

A2: Во многих случаях, необходимо проведение группы операций по изменению данных таким образом, чтобы эта группа обладала свойством атомарности (либо вся целиком выполняется, либо вся целиком не выполняется). Такая группа операций называется транзакцией. В SQL-серверах существуют операторы, позволяющие обозначить начало транзакции (begin transaction), ее успешное завершение (commit transaction), либо откат транзакции (rollback transaction).

Q3: Что такое журнал транзакций?

A3: Любые изменения данных, проведенные внутри транзакции, записываются в специальный журнал транзакций (transaction log). При откате транзакции, данные восстанавливаются в прежнем виде, а записи об изменениях удаляются из журнала транзакций.

Q4: Когда появляются блокировки?

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

Q5: Чем отличаются «версионники» от «блокировочников»?

A5: Классические «блокировочники» не дают возможности разным транзакциям одновременно изменять одни и те же записи, блокируя их на время транзакции. В результате, при попытке изменить заблокированную запись, другая транзакция будет простаивать, пока не завершиться первая. В свою очередь, «версионники» позволяют одновременно модифицировать одни и те же записи, создавая при этом разные версии одной записи.

Q6: Почему возникает deadlock?

A6: Перекрестная блокировка (deadlock) двух транзакций возникает при изменении одних и тех же записей в разном порядке. Последовательность действий, приводящая к перекрестной блокировке: 1. Транзакция A изменяет запись X. Заблокирована X. 2. Транзакция B изменяет запись Y. Заблокирована Y. 3. Транзакция A пытается изменить запись Y. Остановлена A. 4. Транзакция B пытается изменить запись X. Остановлена B. Сервер определяет перекрестную блокировку и откатывает одну из транзакций, возвращая ошибку соответствующему соединению. Аминь. Чтобы не выводить ошибку пользователю, обломанное соединение должно молча повторить транзакцию.

Q7: Зачем нужны индексы?

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

Q8: Для чего необходим первичный ключ в таблице?

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

Q9: Что такое триггер?

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

Q10: Можно ли использовать свою функцию в SQL-запросе?

A10: Можно, практически во всех современных SQL-серверах. Различия только в синтаксисе определения и вызова функции. Кроме того, некоторые сервера позволяют использовать функции, написанные на других языках (не SQL).

Q11: Как автоматически генерировать первичный ключ?

A11: Обычно, в SQL-серверах можно указать одно из числовых полей как автоматически нумеруемое, причем синтаксис определения сильно зависит от разновидности сервера. При вставке новой записи, такому полю присваивается значение поля предыдущей записи + заданное приращение. Как правило, значение подобного поля нельзя явно изменять с помощью DML.

Q12: Какую функцию выполняет оптимизатор запросов?

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

Q13: Что такое представление?

A13: Представление (view) — это запрос на выборку, хранящийся на сервере, как отдельный объект. Так как, результат этого запроса можно рассматривать в качестве таблицы, представление допускается использовать в других запросах, также как любую обычную таблицу. Материализованное представление хранится на сервере в виде таблицы, которая автоматически обновляется при изменении данных, имеющих отношение к этому представлению.

Q14: Для чего нужны хранимые процедуры?

A14: Хранимые процедуры (SP — Stored Procedure) представляют собой последовательность команд на расширениях SQL, либо на других языках, поддерживаемых сервером. Могут принимать параметры и возвращать значение заданного типа. Часто используются для выполнения операций, напрямую связанных с логикой задачи, для которой проектировалась БД. Иногда, используются вместе с представлениями, для обеспечения безопасности БД (все изменения через SP, все выборки через view).

Q15: Какие типы данных есть в SQL-сервере?

A15: Обычно, для полей в таблицах могут использоваться только самые простые типы: числа (целые и дробные), строки (сильно ограниченные по длине), дата (и время), бинарные данные большого размера (для текста, графики и т.п.). В некоторых серверах допускается использование массивов и самодельных структур.

Q16: Чему равен null?

A16: А хрен его знает. И это совсем не шутка, а его фактическое определение. Null не является пустым значением. Поэтому null + 1 = null, а не 1. Использование null означает переход от двузначной логики (true/false) к трехзначной (true/false/unknown), которая является более сложной и несколько непривычной. С другой стороны, логические операторы SQL обычно приспособлены для работы только с двузначной логикой. Поэтому использовать null для значений полей следует очень осторожно и только в тех случаях, когда без него никак нельзя обойтись. Поведение null хорошо описано в статье [5].

Q17: Зачем нужен внешний ключ?

A17: Внешний ключ (FK — Foreign Key) используется для создания жесткой связи (многие к одному) между двумя таблицами. Внешний ключ задается только в том случае, если в первой таблице есть поле, содержащее значение первичного ключа из второй таблицы. При изменении значения первичного ключа во второй таблице, могут быть изменены все соответствующие значения связанного поля в первой таблице. При удалении записи с определенным первичным ключом из второй таблице, могут быть удалены все записи с соответствующим значением связанного поля в первой таблице. Обычно, при определении внешнего ключа, по нему автоматически создается индекс, который используется в запросах при объединении этих двух таблиц.

Q18: Существует ли стандарт SQL, общий для всех серверов?

A18: Вообще-то он есть (ANSI SQL 92), только вот поддержка его у производителей СУБД сильно хромает. Причем дело с эти настолько плохо, что может различаться даже синтаксис основных операторов DML, не говоря уже о функциях, триггерах и прочих особенностях SQL. Поэтому о совместимости говорить сложно, хотя кое-какие сдвиги в этом направлении имеются. Наиболее близки к стандарту: Borland InterBase, Microsoft SQL Server, IBM DB2 Universal Database.

Q19: Есть ли стандартное клиентское API для доступа к SQL-серверам?

A19: Наиболее распространенным клиентским API является ODBC (Open DataBase Connectivity). ODBC-драйвера существуют на многих платформах и почти для всех SQL-серверов.

Q20: Что такое репликация?

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

Реляционные базы данных: Практические вопросы Q1: Что такое суррогатный ключ, и чем он лучше или хуже естественного ключа?

A1: Естественным ключом (ЕК) обычно называют первичный ключ, который является смысловым атрибутом (либо набором атрибутов) сущности. Как и любой другой атрибут, ЕК подвержен изменениям, а его уникальность часто бывает притянута за уши. Суррогатным ключом (СК), как правило, называется уникальное автонумеруемое целочисленное поле в таблице, которое и является первичным ключом. СК гораздо компактнее, чем ЕК, везде занимает одно поле одинакового типа, всегда неизменен и уникален. Более обоснованное сравнение СК и ЕК производится в статье [6].

Q2: Можно ли полностью избежать дублирования информации?

A2: Только в теории. Полностью нормализованная БД хороша для хранения данных. Но, на практике постоянно встречаются структуры, получение информации из которых, слишком сложно и медленно (а иногда и невозможно с помощью лишь оператора select). Дублирование информации в том или ином виде обычно необходимо для получения результатов выборки простыми запросами за приемлемое время.

Q3: Хотелось бы иметь возможность восстанавливать удаленные записи. Как это сделать?

A3: А не надо их удалять, тогда и проблемы не будет. Проще всего держать в таблице дополнительное поле Deleted (default = false), а вместо удаления записи устанавливать Deleted = true. Для выборки можно использовать представление с внутреннем условием:

Deleted = false

Возможен и более сложный вариант: два дополнительных поля DateBegin (defaul = текущая_дата) и DateEnd (defaul = максимальная_дата). Вместо удаления записи необходимо установить DateEnd = текущая_дата, а условие для выборки будет:

текущая_дата beetwen DateBegin and DateEnd

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

заданная_дата beetwen DateBegin and DateEnd
Q4: Как организовать хранение истории изменений записей, с возможностью просмотра и отката этих изменений?

A4: Самый простой вариант — это создать таблицу-журнал, в которую, при изменении записи, помещать следующую информацию: дату изменения, имя (Id) пользователя, имя (Id) изменяемой таблицы, ПК записи, имя (Id) изменяемого поля и предыдущее значение поля, преобразованное в строку. Запись в журнал можно делать либо триггерами, либо из приложения. Откат изменений следует производить специальной процедурой, которая должна восстановить значения полей на заданную дату и удалить соответствующие записи из журнала. Один из вариантов журнала имеется в статье [9].

Q5: Есть объекты, которые меняются с течением времени. Нужно получать их состояния на любую дату. Каким образом для этого надо организовать хранение объектов в базе?

A5: Можно использовать сервер, который поддерживает историческую модель, например, PostgreSQL Server. В ином случае, можно, в дополнение к основной таблице, создать аналогичную таблицу истории, с двумя дополнительными полями: DateBegin и DateEnd. При вставке записи в основную таблицу, необходимо добавить такую же запись в таблицу истории, где DateBegin = текущая_дата, DateEnd = максимальная_дата. При модификации записи в основной таблице, нужно вставить измененную запись в таблицу истории, где DateBegin = текущая_дата, DateEnd = максимальная_дата. При этом у предыдущей соответствующей записи в таблице истории необходимо установить DateEnd = текущая_дата. При удалении записи в основной таблице, у последней соответствующей записи в таблице истории надо установить DateEnd = текущая_дата. Выборка из основной таблицы производится обычным образом, а из таблицы истории с условием:

заданная_дата beetwen DateBegin and DateEnd

Сходные варианты представления истории рассматриваются в статье [10].

Q6: Каким образом можно хранить в реляционной базе иерархические объекты?

A6: В таблицу, которую надо сделать иерархической, надо добавить поле Parent, содержащее значение ПК родительской записи. Далее, все зависит от задачи и возможностей сервера. Если нет необходимости углубляться в иерархию более чем на один уровень, либо сервер поддерживает рекурсивные запросы, то этого вполне хватит. Иначе, придется использовать дополнительные поля, либо вспомогательную таблицу. Подробно способы представления деревьев в реляционных БД описаны в статьях [7] и [8].

Q7: Как получить полную информацию об определенной таблице в базе (какие поля есть в таблице, их тип, размер, связи с другой таблицей и т.п.)?

A7: Вообще-то, в большинстве SQL-серверов есть системный каталог — набор стандартных таблиц, содержащих информацию обо всех объектах сервера и БД. Правда, таблицы эти предназначены не для разработчика, а для внутреннего использования самим сервером, поэтому найти там что-либо весьма проблематично. При этом, структура их может меняться даже в разных версиях одного сервера. Поэтому, желательно создать в БД собственный системный каталог, полностью описывающий структуру базы и взаимосвязи ее объектов. Такой каталог можно переносить на другой сервер вместе с БД, не заботясь о совместимости, а информацию из него можно легко использовать в приложении для каких-либо действий (вплоть до автоматической генерации самого приложения). Пример системного каталога приведен в статье [9].

Q8: Необходимо хранить в базе много картинок (файлов). Как лучше всего это сделать?

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

Q9: Есть запрос, который возвращает данные, отсортированные по какому-либо признаку. Можно ли получить не весь результат, а только определенную его часть, например, записи с 20 по 40?

A9: Такие действия проще всего выполнять на клиенте. Если надо на сервере, то лучше использовать предназначенные для этого возможности оператора выборки:

1. select top n * from t order by a
2. select * from t order by a limit n,m

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

select * from t as t_outer where 
( select count( * ) from t as t_inner where t_inner.a <= t_outer.a ) between n and m
order by a

В крайнем случае, можно выбирать данные во временную таблицу с дополнительным автонумеруемым полем RowNumber, а из нее затем делать выборку:

select ... where RowNumber >= n and RowNumber <= m
Q10: Как из таблицы получить записи, в которых одно из полей максимально (или минимально)?

A10: Самый правильный способ — это использовать вложенный запрос, в котором ищется максимальное значение поля. Затем найденное значение используется в условии основного запроса.

select *
from t
where a = ( select max( a ) from t )

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

select *
from t as t_outer
where a = ( select max( t_inner.a ) from t as t_inner where t_inner.b = t_outer.b )

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

Q11: Можно ли сделать в SQL перекрестный запрос? То есть, в качестве полей для результата, брались бы не поля таблицы, а данные из нее?

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

Q12: Как обеспечить нумерацию записей в результате запроса?

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

select *,
( select count( * ) from t as t_inner where t_inner.a <= t_outer.a ) as RowNumber
from t as t_outer
order by a

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

Q13: Есть необходимость добавлять произвольные виды объектов (еще неизвестные на этапе разработке) в БД. Создавать и менять таблицы на «живой» базе не хочется. Как быть?

A13: Можно вообще хранить все дополнительные объекты в одной таблице, содержащей идентификатор объекта, идентификатор атрибута и само значение атрибута, преобразованное в строку. Чтобы не было мучительно больно при получении результатов запросов, лучше все-таки использовать несколько таблиц — отдельно для каждого типа данных. Более серьезный вариант универсального хранилища объектов рассмотрен в статье [11]. PS: А чем была плоха идея насчет создания/изменения таблиц?

Стандарные аббревиатуры Английские

API — Application Program Interface DB — DataBase DBMS — DataBase Management System DDL — Data Definition Language DML — Data Manipulation Language FK — Foreign Key ODBC — Open DataBase Connectivity PK — Primary Key RDBMS — Relational DataBase Management System SP — Stored Procedure SQL — Structured Query Language.

Русские

БД — База Данных ЕК — Естественный Ключ ООБД — Объектно-Ориентированная База Данных ООСУБД — Объектно-Ориентированная Система Управления Базами Данных ПК — Первичный Ключ РСУБД — Реляционная Система Управления Базами Данных СУБД — Система Управления Базами Данных СК — Суррогатный Ключ.

Литература Мартин Грабер «Понимание SQL». Пушников А. Ю. «Введение в системы управления базами данных». Кузнецов С. Д. «Основы современных баз данных». Кузнецов С. Д. «Объектно-ориентированные базы данных — основные концепции, организация и управление: краткий обзор». Акжан Абдулин, Лиля Козленко «Представление отсутствующей информации». Тенцер А. «Естественные ключи против искусственных ключей». Joe Celko «A Look at SQL Trees», II, III. Виноградов С. А. «Моделирование иерархических объектов». Виноградов С. А. «Минимальный набор стандартных таблиц». Виноградов С. А. «История изменений объектов». Тенцер А. «База данных — хранилище объектов», II.

Запуск файлов хрянящихся в БД.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Запуск файлов хрянящихся в БД
Данный код реализует чтение из Бд строки где спрятан путь к файлу,
далее для удачного запуска "курсор" переместим в папку, от куда надо
запустить разбиваем строку на файл(для запуска) и на директорию
(для перемещения "курсора").
Переброска "курсора" необходима для правильного запуска приложения.
Приложение может быть как под Windows, так и под Dos.
Зависимости: Windows, SysUtils
Автор:       Дмитрий, ahmaev@mail.ru, ICQ:100716670, Пенза
Copyright:   Дмитрий
Дата:        9 июля 2002 г.
***************************************************** }
procedure TForm1.dxfColorButton1Click(Sender: TObject);
var
  s, files1, files: string;
  dir, dir1: string;
  apchar: array[0..500] of char;
  i_s1: integer;
begin
  {Находим нужную строку в Бд}
  table1.locate('kod_s', treeview1.selected.parent.index + 1,
    [loCaseInsensitive]);
  table2.locate('game', treeview1.selected.Text, [loCaseInsensitive]);
  {Перепишем из БД строки содержащие ссылки в переменные}
  s := table2.fields[4].asstring;
  k_s := length(table2.fields[4].asstring) + 1;
  files := '';
  dir := '';
  repeat
    i_s := i_s + 1;
    k_s := k_s - 1;
    {Поставим семафор, чтоб разделить путь и имя файла}
    if s[k_s] = '\' then
      en := 1;
    if s[k_s] <> '\' then
      if en = 0 then
        files := files + s[k_s];
    if en = 1 then
      dir := dir + s[k_s];
  until i_s = length(table2.fields[4].asstring);
  {Переворачиваем считанное "путь до файла" и "имя файла"}
  for i_s1 := length(files) downto 1 do
    files1 := files1 + files[i_s1];
  for i_s1 := length(dir) downto 1 do
    dir1 := dir1 + dir[i_s1];
  {Переводим курсор в папку откуда должна запустится программа}
  chdir(dir1);
  strpcopy(apchar, files1);
  {Запускаем программу}
  shellexecute(handle, 'open', apchar, 0, 0, SW_maximize);
end;

Отобразить определенного формата файлы базы данных.

unit DdhDynDb;
interface
uses
  Controls, Db, Forms, Classes, DbTables;
function ConvertClass(FieldClass: TFieldClass): TControlClass;
procedure NormalizeString(var S: string);
procedure ConnectDataFields(DbComp: TControl;
  DataSource: TDataSource; FieldName: string);
function GenerateForm(StrList: TStringList;
  SourceTable: TTable): TForm;
function GenerateSource(AForm: TForm;
  FormName, UnitName: string): string;
implementation
uses
  TypInfo, DbCtrls, SysUtils, StdCtrls, ExtCtrls, Windows;
const
  FieldTypeCount = 15;
type
  CVTable = array[1..FieldTypeCount, 1..2] of TClass;
  // TBytesField and TVarBytesField are missing
const
  ConvertTable: CVTable = (
    (TAutoIncField, TDBEdit),
    (TStringField, TDBEdit),
    (TIntegerField, TDBEdit),
    (TSmallintField, TDBEdit),
    (TWordField, TDBEdit),
    (TFloatField, TDBEdit),
    (TCurrencyField, TDBEdit),
    (TBCDField, TDBEdit),
    (TBooleanField, TDBCheckBox),
    (TDateTimeField, TDBEdit),
    (TDateField, TDBEdit),
    (TTimeField, TDBEdit),
    (TMemoField, TDBMemo),
    (TBlobField, TDBImage), {just a guess}
    (TGraphicField, TDBImage));
function ConvertClass(FieldClass: TFieldClass):
  TControlClass;
var
  I: Integer;
begin
  Result := nil;
  for I := 1 to FieldTypeCount do
    if ConvertTable[I, 1] = FieldClass then
    begin
      Result := TControlClass(
        ConvertTable[I, 2]);
      break; // jump out of for loop
    end;
  if Result = nil then
    raise Exception.Create('ConvertClass failed');
end;
procedure NormalizeString(var S: string);
var
  N: Integer;
begin
  // remove the T
  Delete(S, 1, 1);
  {chek if the string is a valid Pascal identifier:
  if not, replace spaces and other characters with underscores}
  if not IsValidIdent(S) then
    for N := 1 to Length(S) do
      if not ((S[N] in ['A'..'Z']) or (S[N] in ['a'..'z'])
        or ((S[N] in ['0'..'9']) and (N <> 1))) then
        S[N] := '_';
end;
procedure ConnectDataFields(DbComp: TControl;
  DataSource: TDataSource; FieldName: string);
var
  PropInfo: PPropInfo;
begin
  if not Assigned(DbComp) then
    raise Exception.Create(
      'ConnectDataFields failed: Invalid control');
  // set the DataSource property
  PropInfo := GetPropInfo(
    DbComp.ClassInfo, 'DataSource');
  if PropInfo = nil then
    raise Exception.Create(
      'ConnectDataFields failed: Missing DataSource property');
  SetOrdProp(DbComp, PropInfo,
    Integer(Pointer(DataSource)));
  // set the DataField property
  PropInfo := GetPropInfo(
    DbComp.ClassInfo, 'DataField');
  if PropInfo = nil then
    raise Exception.Create(
      'ConnectDataFields failed: Missing DataField property');
  SetStrProp(DbComp, PropInfo, FieldName);
end;
function GenerateForm(StrList: TStringList;
  SourceTable: TTable): TForm;
var
  I, NumField, YComp, HForm, Hmax: Integer;
  NewName: string;
  NewLabel: TLabel;
  NewDBComp: TControl;
  CtrlClass: TControlClass;
  ATable: TTable;
  ADataSource: TDataSource;
  APanel: TPanel;
  ANavigator: TDBNavigator;
  AScrollbox: TScrollBox;
begin
  // generate the form and connect the table
  Result := TForm.Create(Application);
  Result.Position := poScreenCenter;
  Result.Width := Screen.Width div 2;
  Result.Caption := 'Table Form';
  // create a Table component in the result form
  ATable := TTable.Create(Result);
  ATable.DatabaseName := SourceTable.DatabaseName;
  ATable.TableName := SourceTable.TableName;
  ATable.Active := True;
  ATable.Name := 'Table1';
  // component position (at design time)
  ATable.DesignInfo := MakeLong(20, 20);
  // create a DataSource
  ADataSource := TDataSource.Create(Result);
  ADataSource.DataSet := ATable;
  ADataSource.Name := 'DataSource1';
  // component position (at design time)
  ADataSource.DesignInfo := MakeLong(60, 20);
  // create a toolbar panel
  APanel := TPanel.Create(Result);
  APanel.Parent := Result;
  APanel.Align := alTop;
  APanel.Name := 'Panel1';
  APanel.Caption := '';
  // place a DBNavigator inside it
  ANavigator := TDBNavigator.Create(Result);
  ANavigator.Parent := APanel;
  ANavigator.Left := 8;
  ANavigator.Top := 8;
  ANAvigator.Height := APanel.Height - 16;
  ANavigator.DataSource := ADataSource;
  ANavigator.Name := 'DbNavigator1';
  // create a scroll box
  AScrollbox := TScrollBox.Create(Result);
  AScrollbox.Parent := Result;
  AScrollbox.Width := Result.ClientWidth;
  AScrollbox.Align := alClient;
  AScrollbox.BorderStyle := bsNone;
  AScrollbox.Name := 'ScrollBox1';
  // generates field editors
  YComp := 10;
  for I := 0 to StrList.Count - 1 do
  begin
    NumField := Integer(StrList.Objects[I]);
    // create a label with the field name
    NewLabel := TLabel.Create(Result);
    NewLabel.Parent := AScrollBox;
    NewLabel.Name := 'Label' + IntToStr(I);
    NewLabel.Caption := StrList[I];
    NewLabel.Top := YComp;
    NewLabel.Left := 10;
    NewLabel.Width := 120;
    // create the data aware control
    CtrlClass := ConvertClass(
      ATable.FieldDefs[NumField].FieldClass);
    NewDBComp := CtrlClass.Create(Result);
    NewDBComp.Parent := AScrollBox;
    NewName := CtrlClass.ClassName +
      ATable.FieldDefs[NumField].Name;
    NormalizeString(NewName);
    NewDBComp.Name := NewName;
    NewDBComp.Top := YComp;
    NewDBComp.Left := 140;
    NewDbComp.Width :=
      AScrollBox.Width - 150; // width of label plus border
    // connect the control with the data source
    // and field using RTTI support
    ConnectDataFields(NewDbComp,
      ADataSource,
      ATable.FieldDefs[NumField].Name);
    // compute the position of the next component
    Inc(YComp, NewDBComp.Height + 10);
  end; // for each field
  // computed requested height for client area
  HForm := YComp + APanel.Height;
  // max client area hight = screen height - 40 - form border
  HMax := (Screen.Height - 40 -
    (Result.Height - Result.ClientHeight));
  // limit form height to HMax and reserve space for scrollbar
  if HForm > HMax then
  begin
    HForm := HMax;
    Result.Width := Result.Width +
      GetSystemMetrics(SM_CXVSCROLL);
  end;
  Result.ClientHeight := HForm;
end;
function GenerateSource(AForm: TForm;
  FormName, UnitName: string): string;
var
  I: Integer;
begin
  SetLength(Result, 20000);
  // generate the first part of the unit source
  Result :=
    'unit ' + UnitName + ';'#13#13 +
    'interface'#13#13 +
    'uses'#13 +
    '  SysUtils, WinTypes, WinProcs, Messages, Classes,'#13 +
    '  Forms, Graphics, Controls, Dialogs, DB, DBCtrls,'#13 +
    '  DBTables, ExtCtrls;'#13#13 +
    'type'#13 +
    '  T' + FormName + ' = class(TForm)'#13;
  // add each component of the form
  for I := 0 to AForm.ComponentCount - 1 do
    Result := Result +
      '    ' + AForm.Components[I].Name +
      ': ' + AForm.Components[I].ClassName + ';'#13;
  // generate the final part of the source code
  Result := Result +
    '  private'#13 +
    '    { Private declarations }'#13 +
    '  public'#13 +
    '    { Public declarations }'#13 +
    '  end;'#13#13 +
    'var'#13 +
    '  ' + FormName + ': T' + FormName + ';'#13#13 +
    'implementation'#13#13 +
    '{$R *.DFM}'#13#13 +
    'end.'#13;
end;
end.
unit DdhDbwF;
interface
uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, Grids, DB, DBTables,
  Buttons, Mask, DBCtrls;
type
  TFormDbWiz = class(TForm)
    Notebook1: TNotebook;
    Label1: TLabel;
    ListDatabases: TListBox;
    BitBtnNext1: TBitBtn;
    BitBtnNext2: TBitBtn;
    Label2: TLabel;
    ListTables: TListBox;
    BitBtnBack2: TBitBtn;
    ListFields: TListBox;
    Label3: TLabel;
    BitBtnNext3: TBitBtn;
    BitBtnBack3: TBitBtn;
    Label4: TLabel;
    BitBtnNext4: TBitBtn;
    BitBtnBack4: TBitBtn;
    GroupFilter: TRadioGroup;
    BitBtnAll: TBitBtn;
    BitBtnNone: TBitBtn;
    StringGrid1: TStringGrid;
    Table1: TTable;
    procedure Notebook1PageChanged(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListDatabasesClick(Sender: TObject);
    procedure BitBtnNext1Click(Sender: TObject);
    procedure ListTablesClick(Sender: TObject);
    procedure BitBtnBack2Click(Sender: TObject);
    procedure BitBtnNext2Click(Sender: TObject);
    procedure BitBtnBack3Click(Sender: TObject);
    procedure BitBtnAllClick(Sender: TObject);
    procedure BitBtnNoneClick(Sender: TObject);
    procedure BitBtnNext3Click(Sender: TObject);
    procedure BitBtnBack4Click(Sender: TObject);
    procedure ListFieldsClick(Sender: TObject);
    procedure BitBtnNext4Click(Sender: TObject);
  private
    { Private declarations }
  public
    SourceCode, FormName, UnitName: string;
    ResultForm: TForm;
    procedure GeneratedFormClose(
      Sender: TObject; var Action: TCloseAction);
  end;
var
  FormDbWiz: TFormDbWiz;
implementation
{$R *.DFM}
uses
  DdhDynDb, ExptIntf;
////// form code //////
procedure TFormDbWiz.Notebook1PageChanged(Sender: TObject);
begin
  // copy the name of the page into the caption
  Caption := Format(
    'Ddh DB Form Wizard - Page %d/%d: ',
    [NoteBook1.PageIndex + 1,
    NoteBook1.Pages.Count,
      NoteBook1.ActivePage]);
end;
procedure TFormDbWiz.FormCreate(Sender: TObject);
begin
  // fill the first listbox with database names
  Session.GetDatabaseNames(
    ListDatabases.Items);
  // start in the first page
  Notebook1.PageIndex := 0;
  // default values (modified by the wizard)
  FormName := 'TResultForm';
  UnitName := 'ResultUnit';
end;
procedure TFormDbWiz.ListDatabasesClick(Sender: TObject);
begin
  // database selected: enable the Next button
  BitBtnNext1.Enabled := True;
end;
procedure TFormDbWiz.BitBtnNext1Click(Sender: TObject);
var
  CurrentDB, CurrentFilter: string;
begin
  // get the database and filters
  CurrentDB := ListDatabases.Items[
    ListDatabases.ItemIndex];
  CurrentFilter := GroupFilter.Items[
    GroupFilter.ItemIndex];
  // retrieve the tables
  Session.GetTableNames(CurrentDB,
    CurrentFilter, True, False, ListTables.Items);
  // move to the next page
  NoteBook1.PageIndex := 1;
  BitBtnNext2.Enabled := False;
end;
procedure TFormDbWiz.ListTablesClick(Sender: TObject);
begin
  // table selected: enable next button
  BitBtnNext2.Enabled := True;
end;
procedure TFormDbWiz.BitBtnBack2Click(Sender: TObject);
begin
  // go back to first page
  NoteBook1.PageIndex := 0;
end;
procedure TFormDbWiz.BitBtnNext2Click(Sender: TObject);
var
  I: Integer;
begin
  // set the properties of the selected table
  with Table1 do
  begin
    DatabaseName := ListDatabases.Items[
      ListDatabases.ItemIndex];
    TableName := ListTables.Items[
      ListTables.ItemIndex];
    // load the field definitions
    FieldDefs.Update;
  end;
  // clear the list box, then fill it
  ListFields.Clear;
  for I := 0 to Table1.FieldDefs.Count - 1 do
    // add number, name, and class name of each field
    ListFields.Items.Add(Format(
      '%d) %s [%s]',
      [Table1.FieldDefs[I].FieldNo,
      Table1.FieldDefs[I].Name,
        Table1.FieldDefs[I].FieldClass.ClassName]));
  // move to the next page
  NoteBook1.PageIndex := 2;
  BitBtnNext3.Enabled := False;
end;
procedure TFormDbWiz.BitBtnBack3Click(Sender: TObject);
begin
  // back to the second page
  NoteBook1.PageIndex := 1;
end;
procedure TFormDbWiz.BitBtnAllClick(Sender: TObject);
var
  I: Integer;
begin
  // select every available field
  for I := 0 to ListFields.Items.Count - 1 do
    ListFields.Selected[I] := True;
  // enable Next button
  BitBtnNext3.Enabled := True;
end;
procedure TFormDbWiz.BitBtnNoneClick(Sender: TObject);
var
  I: Integer;
begin
  // deselect all the fields
  for I := 0 to ListFields.Items.Count - 1 do
    ListFields.Selected[I] := False;
  // disable next button (no fields are selected)
  BitBtnNext3.Enabled := False;
end;
procedure TFormDbWiz.ListFieldsClick(Sender: TObject);
begin
  // enable button if there at least one field selected
  BitBtnNext3.Enabled := ListFields.SelCount > 0;
end;
procedure TFormDbWiz.BitBtnNext3Click(Sender: TObject);
var
  I, RowNum: Integer;
begin
  // reserve enough rows in the string grid
  StringGrid1.RowCount := ListFields.Items.Count;
  // empty the string grid
  for I := 0 to StringGrid1.RowCount - 1 do
  begin
    StringGrid1.Cells[0, I] := '';
    StringGrid1.Cells[1, I] := '';
  end;
  // for each field, if selected list it with the
  // corresponding data aware component
  RowNum := 0;
  for I := 0 to ListFields.Items.Count - 1 do
    if ListFields.Selected[I] then
    begin
      StringGrid1.Cells[0, RowNum] := Format('%d) %s [%s]',
        // field number, name, classname of data aware control
        [Table1.FieldDefs[I].FieldNo,
        Table1.FieldDefs[I].Name,
          ConvertClass(Table1.FieldDefs[I].FieldClass).ClassName]);
      StringGrid1.Cells[1, RowNum] := Table1.FieldDefs[I].Name;
      Inc(RowNum);
    end;
  // set the real number of rows
  StringGrid1.RowCount := RowNum;
  NoteBook1.PageIndex := 3;
end;
procedure TFormDbWiz.BitBtnBack4Click(Sender: TObject);
begin
  NoteBook1.PageIndex := 2;
end;
// generate button
procedure TFormDbWiz.BitBtnNext4Click(Sender: TObject);
var
  StrList: TStringList;
  I, RowNum: Integer;
begin
  StrList := TStringList.Create;
  Screen.Cursor := crHourGlass;
  try
    RowNum := 0;
    for I := 0 to ListFields.Items.Count - 1 do
      if ListFields.Selected[I] then
      begin
        StrList.AddObject(
          StringGrid1.Cells[1, RowNum], TObject(I));
        // move to next row in string grid
        Inc(RowNum);
      end;
    ResultForm := GenerateForm(StrList, Table1);
    if not Assigned(ToolServices) then
    begin
      // stand alone form
      ResultForm.OnClose := GeneratedFormClose;
      ResultForm.Show;
    end
    else
    begin
      // wizard
      SourceCode := GenerateSource(ResultForm,
        FormName, UnitName);
      ModalResult := mrOK;
    end;
  finally
    Screen.Cursor := crDefault;
    StrList.Free;
  end;
end;
procedure TFormDbWiz.GeneratedFormClose(
  Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;
end.
Скачать весь проект

Как узнать содержание активной записи в БД.

Следующая функция возвращает в виде указателя на строку содержание активной записи в БД.

function TBDEDirect.GetCurRecord(Lock: DBILockType): PChar;
var
  Res: DBIResult;
  RecSize: Word;
  RecBuf: PChar;
  Bookmark: TBookmark;
begin
  Result := StrNew('');
  if CheckDatabase then
  begin
    RecSize := GetPhysicalRecSize;
    RecBuf := StrAlloc(RecSize+1);
    FillChar(RecBuf^, RecSize+1, #0);
    Bookmark := FDataLink.DataSource.DataSet.GetBookmark;
    DbiSetToBookmark(FDataLink.DataSource.DataSet.Handle, Bookmark);
    FDataLink.DataSource.DataSet.FreeBookmark(Bookmark);
    Res := DbiGetRecord(FDataLink.DataSource.DataSet.Handle, Lock, RecBuf, nil);
    if Res = 0 then
      Result := RecBuf
    else
      Check(Res);
  end;
end;

Как активизировать компоненты доступа к данным связанные с определенным TDatabase.

for i:=0 to pred(DB1.DataSetCount) do
  {if DataSets.Tag = 1 then}
  DataSets[i].Open;

Быстрый доступ к нужной записи в таблице Paradox.

var
  NeedNumber: Integer;
...
NeedNumber := Table.RecNo;
{сохранение номера нужной записи}
...
{код меняющий номе записи}
...
Table.RecNo := NeedNumber;
{востановление номера нужной записи}

Контекстное меню на основе базы данных.

Приходит програмер вечером домой весь в крови и без руки. Жена спрашивает: - Что случилось дорогой??? Програмер: - Да так, в кулер засосало.
var
  m: TMenuItem;
  navidummy: TComponent;
  ..........................................................
  procedure TMyForm.CreatePopUpMM(Sender: TObject);
begin
  Navidummy.free;
  Navidummy := TComponent.create(self);
  while not NaviT.EOF do
  begin
    m := TMenuItem.create(navidummy);
    II := II + 1;
    with m do
    begin
      name := 'MM' + IntToStr(II);
      caption := NaviT.Fieldbyname('MyWHAT').AsString;
      tag := NaviT.Fieldbyname('MyTAG').AsInteger;
      visible := True;
      OnClick := NaviExec;
    end;
    MyMenuItem.add(m);
    NaviT.Next;
  end;
  NaviT.Close;
end;
procedure TMyForm.NaviExec(Sender: TObject);
begin
  { Здесь я получаю то, что хочу ! }
  What.text := (Sender as TMenuItem).Caption;
  Key := (Sender as TMenuItem).Tag;
end;

БД-дерево взаимоотношений.

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

Все это я делал раньше. Я не могу вам все это показать на развернутом примере, но я дам вам идею как сделать это. Вы должны иметь таблицу, осуществляющую взаимоотношение между людьми. Если на Peter работают Jane и Simon, вы должны иметь таблицу (RELATION) с этими двумя записями.
Master    Slave  ------- имена полей
Peter     Jane
Peter     Simon
Если George и Elisa работают на Jane, то таблица становится такой:
Master    Slave  ------- имена полей
Peter     Jane
Peter     Simon
Jane      George
Jane      Elisa
и так далее. Если в таблице RELATION необходимо создать дерево, начинающееся на Peter, то нужно добавить к дереву главный узел (запись), где Master = Peter. Затем каждая дочерняя запись располагается ниже записи Master = Peter. После добавления дочерней записи вы сразу увидите, если ребенок имеет собственного ребенка. Ребенок становится теперь, вероятно, отцом, поэтому вы должны позиционировать таблицу RELATION к первой записи, где Master = child, и так далее, рекурсивно. Такой способ гарантирует построение правильного дерева.

Пример:

AddFather('Peter')
AddChild('Peter', 1)
procedure AddFather(Name: string)
begin
  Tree.Add(Name);
end;
procedure AddChildr(Name: string, Index: Integer)
begin
  Relation.FindKey([Name])
  while RelationMaster.AsString = Name do
  begin
    Tree.AddChild(Index, RelationSlave.AsString);
    AddChild(RelationSlave.AsString, Tree.ItemsCount);
    Relation.Next;
  end;
end;

По-моему, ошибок нет.

Сортировка связанного списка.

Автор: Peter Below

program noname;
type
  PData = ^TData;
  TData = record
    next: PData;
    Name: string[40];
    { ...другие поля данных }
  end;
var
  root: PData; { это указатель на первую запись в связанном списке }
procedure InsertRecord(var root: PData; pItem: PData);
(* вставляем запись, на которую указывает pItem в список начиная
с root и с требуемым порядком сортировки *)
var
  pWalk, pLast: PData;
begin
  if root = nil then
  begin
    (* новый список все еще пуст, просто делаем запись,
    чтобы добавить root к новому списку *)
    root := pItem;
    root^.next := nil
  end { If }
  else
  begin
    (* проходимся по списку и сравниваем каждую запись с одной
    включаемой. Нам необходимо помнить последнюю запись,
    которую мы проверили, причина этого станет ясна немного позже. *)
    pWalk := root;
    pLast := nil;
    (* условие в следующем цикле While определяет порядок сортировки!
    Это идеальное место для передачи вызова функции сравнения,
    которой вы передаете дополнительный параметр InsertRecord для
    осуществления общей сортировки, например:
    While CompareItems( pWalk, pItem ) < 0 Do Begin
    where
    Procedure InsertRecord( Var list: PData; CompareItems: TCompareItems );
    and
    Type TCompareItems = Function( p1,p2:PData ): Integer;
    and a sample compare function:
    Function CompareName( p1,p2:PData ): Integer;
    Begin
    If p1^.Name < p2^.Name Then
    CompareName := -1
    Else
    If p1^.Name > p2^.Name Then
    CompareName := 1
    Else
    CompareName := 0;
    End;
    *)
    while pWalk^.Name < pItem^.Name do
      if pWalk^.next = nil then
      begin
        (* мы обнаружили конец списка, поэтому добавляем
        новую запись и выходим из процедуры *)
        pWalk^.next := pItem;
        pItem^.next := nil;
        Exit;
      end { If }
      else
      begin
        (* следующая запись, пожалуйста, но помните,
        что одну мы только что проверили! *)
        pLast := pWalk;
        (* если мы заканчиваем в этом месте, то значит мы нашли
        в списке запись, которая >= одной включенной. Поэтому
        вставьте ее перед записью, на которую в настоящий момент
        указывает pWalk, которая расположена после pLast. *)
        if pLast = nil then
        begin
          (* Упс, мы вывалились из цикла While на самой первой итерации!
          Новая запись должна располагаться в верхней части списка,
          поэтому она становится новым корнем (root)! *)
          pItem^.next := root;
          root := pItem;
        end { If }
        else
        begin
          (* вставляем pItem между pLast и pWalk *)
          pItem^.next := pWalk;
          pLast^.next := pItem;
        end; { Else }
        (* мы сделали это! *)
      end; { Else }
  end; { InsertRecord }
procedure SortbyName(var list: PData);
var
  newtree, temp, stump: PData;
begin { SortByName }
  (* немедленно выходим, если сортировать нечего *)
  if list = nil then
    Exit;
  (* в
  newtree := Nil;
  (********
  Сортируем, просто беря записи из оригинального списка и вставляя их
  в новый, по пути "перехватывая" для определения правильной позиции в
  новом дереве. Stump используется для компенсации различий списков.
  temp используется для указания на запись, перемещаемую из одного
  списка в другой.
  ********)
  stump := list;
  while stump <> nil do
  begin
    (* временная ссылка на перемещаемую запись *)
    temp := stump;
    (* "отключаем" ее от списка *)
    stump := stump^.next;
    (* вставляем ее в новый список *)
    InsertRecord(newtree, temp);
  end; { While }
  (* теперь помещаем начало нового, сортированного
  дерева в начало старого списка *)
  list := newtree;
end; { SortByName }
begin
  New(root);
  root^.Name := 'BETA';
  New(root^.next);
  root^.next^.Name := 'ALPHA';
  New(root^.next^.next);
  root^.next^.next^.Name := 'Torture';
  WriteLn(root^.name);
  WriteLn(root^.next^.name);
  WriteLn(root^.next^.next^.name);
end.

Структура DCB.

Структура DCB определяет установку управления для последовательного порта ввода-вывода (нам она понадобится для разбора примера с программой управления весами ПетрВес) Примечание : В местах где нельзя дать точный перевод будет дано определение на английском из MSDK и приблизительный его перевод

Описание в эквиваленте C.

typedef struct _DCB { // dcb  
    DWORD DCBlength;           // Размер DCB
    DWORD BaudRate;            // Скорость пересылки данных в бодах; 
                               // текущая скорость в бодах 
    DWORD fBinary: 1;          // binary mode, no EOF check 
                               // двоичный режим , не проверять конец 
                               // данных (по умолчанию значение = 1)
    DWORD fParity: 1;          // Включить проверку четность (по умолчанию
                               // значение = 1)
    DWORD fOutxCtsFlow:1;      // CTS управление потоком выхода 
    DWORD fOutxDsrFlow:1;      // DSR управление потоком выхода 
    DWORD fDtrControl:2;       // DTR Тип управления потоком скорости 
                               // передачи данных
    DWORD fDsrSensitivity:1;   // DSR sensitivity (чувствительность)
    DWORD fTXContinueOnXoff:1; // XOFF continues Tx (стоп-сигнал 
                               // продалжает выполнение)
    DWORD fOutX: 1;            // XON/XOFF out flow control (СТАРТ-  
                               // СИГНАЛ / СТОП-СИГНАЛ для управления 
                               // выходящим потоком (по умолчанию
                               // значение = 1)
    DWORD fInX: 1;             // XON/XOFF in flow control (СТАРТ-
                               // СИГНАЛ / СТОП-СИГНАЛ для управления 
                               // входящим потоком (по умолчанию
                               // значение = 1)
    DWORD fErrorChar: 1;       // enable error replacement (включить 
                               // проверку погрешностей по умолчанию=1)
    DWORD fNull: 1;            // enable null stripping (отвергать
                               // пустой поток данных (по умолчанию=1))
    DWORD fRtsControl:2;       // RTS управление потоком данных
    DWORD fAbortOnError:1;     // abort reads/writes on error  
                               // (проверять операции чтения/записи 
                               // по умолчанию=1)
    DWORD fDummy2:17;          // reserved ЗАРЕЗЕРВИРОВАНО 
    WORD wReserved;            // not currently used НЕ ДЛЯ 
                               // ИСПОЛЬЗОВАНИЯ 
    WORD XonLim;               // transmit XON threshold (порог 
                               // чувствительности старт-сигнала)
    WORD XoffLim;              // transmit XOFF threshold (порог
                               // чувствительности стоп-сигнала)
    BYTE ByteSize;             // Бит в байте  (обычно 8)
    BYTE Parity;               // 0-4=no,odd,even,mark,space 
                               // (четность байта)
    BYTE StopBits;             // 0,1,2 = 1, 1.5, 2 (стоповые биты)
    char XonChar;              // Tx and Rx XON character (вид
                               // старт сигнал в потоке)
    char XoffChar;             // Tx and Rx XOFF character (вид
                               // стоп сигнал в потоке)
    char ErrorChar;            // error replacement character (какой     
                               // сигнал погрешности,его вид)
    char EofChar;              // end of input character (сигнал 
                               // окончания потока)
    char EvtChar;              // received event character  РЕЗЕРВ
    WORD wReserved1;           // reserved; do not use НЕ ДЛЯ 
                               // ИСПОЛЬЗОВАНИЯ
} DCB; 

Пример :

with Mode do
begin
  BaudRate := 9600;
  ByteSize := 8;
  Parity := NOPARITY;
  StopBits := ONESTOPBIT; // одиночный стоп-бит
  Flags := EV_RXCHAR + EV_EVENT2;
end;
Параметры : DCBlength Размер DCB структуры.

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

CBR_110 	CBR_19200
CBR_300 	CBR_38400
CBR_600 	CBR_56000
CBR_1200	CBR_57600
CBR_2400	CBR_115200
CBR_4800	CBR_128000
CBR_9600	CBR_256000
CBR_14400	
fBinary Определяет, допускается ли двоичный (бинарный) способ передачи данных. Win32 API не поддерживает недвоичные (небинарные) способы передачи данных в потоке порта, так что этот параметр должен быть всегда ИСТИНЕН. Попытка использовать ЛОЖЬ в этом параметре не будет работать. Примечание: Под Windows 3.1 небинарный способ передачи допускается,но для работы данного способа необходимо заполнит параметр EofChar который будет восприниматься конец данных. fParity Определяет, допускается ли проверка четности. Если этот параметр ИСТИНЕН, проверка четности допускается fOutxCtsFlow CTS (clear-to-send) управление потоком выхода fOutxDsrFlow DSR (data-set-ready) управление потоком выхода fDtrControl DTR (data-terminal-ready) управление потоком выхода Принимает следующие значения : DTR_CONTROL_DISABLE Отключает линию передачи дынных DTR_CONTROL_ENABLE Включает линию передачи дынных DTR_CONTROL_HANDSHAKE Enables DTR handshaking. If handshaking is enabled, it is an error for the application to adjust the line by using the EscapeCommFunction function. Допускает подтверждению связи передачи данных Если подтверждение связи допускается, это - погрешность для того чтобы регулировать(корректировать) линию связи, используя функцию EscapeCommFunction. fDsrSensitivity Specifies whether the communications driver is sensitive to the state of the DSR signal. If this member is TRUE, the driver ignores any bytes received, unless the DSR modem input line is high. Определяет возможна ли по порту двухсторонняя передача в ту и в другую сторону сигнала. fTXContinueOnXoff Определяет, останавливается ли передача потока , когда входной буфер становится полный, и драйвер передает сигнал XoffChar. Если этот параметр ИСТИНЕН, передача продолжается после того, как входной буфер становится в пределах XoffLim байтов, и драйвер передает сигнал XoffChar, чтобы прекратить прием байтов из потока . Если этот параметр ЛОЖНЫЙ, передача не продолжается до тех пор , пока входной буфер не в пределах XonLim байтов, и пока не получен сигнал XonChar, для возобновления приема . fOutX Определяет, используется ли управление потоком СТАРТ-СИГНАЛА / СТОП-СИГНАЛА в течение передачи потока порту. Если этот параметр ИСТИНЕН, передача останавливается, когда получен сигнал XoffChar и начинается снова, когда получен сигнал XonChar. fInX Specifies whether XON/XOFF flow control is used during reception. If this member is TRUE, the XoffChar character is sent when the input buffer comes within XoffLim bytes of being full, and the XonChar character is sent when the input buffer comes within XonLim bytes of being empty. Определяет, используется ли управление потоком СТАРТ-СИГНАЛА / СТОП-СИГНАЛА в течение приема потока портом. Если этот параметр ИСТИНЕН,сигнал XoffChar посылается , когда входной буфер находится в пределах XoffLim байтов, а сигнал XonChar посылается тогда когда входной буфер находится в пределах XonLim байтов или является пустым fErrorChar Определяет, заменены ли байты, полученные с ошибками четности особенностью, указанной параметром ErrorChar Если этот параметр ИСТИНЕН, и fParity ИСТИНЕН, замена происходит. fNull Определяет, отвергнуты ли нулевые(пустые) байты. Если этот параметр ИСТИНЕН, нулевые(пустые) байты, будут отвергнуты при получении их. fRtsControl RTS управление потоком " запрос пересылки ". Если это значение нулевое, то по умолчанию устанавливается RTS_CONTROL_HANDSHAKE. Принимает одно из следующих значений: RTS_CONTROL_DISABLE Отключает строку RTS, когда устройство открыто RTS_CONTROL_ENABLE Включает строку RTS RTS_CONTROL_HANDSHAKE Enables RTS handshaking. The driver raises the RTS line when the "type-ahead" (input) buffer is less than one-half full and lowers the RTS line when the buffer is more than three-quarters full. If handshaking is enabled, it is an error for the application to adjust the line by using the EscapeCommFunction function. Допускает RTS подтверждение связи. Драйвер управляет потоком пересылки.RTS выравнивается , когда входной буфер - меньше чем половина полного и понижается, когда буфер - больше 2/3 полного .Если подтверждение связи допускается, это используется для регулирования передачи данных EscapeCommFunction. RTS_CONTROL_TOGGLE Specifies that the RTS line will be high if bytes are available for transmission. After all buffered bytes have been sent, the RTS line will be low. Определяет, что буфер будет высокий при подготовке данных для передачи. После того, как все байты отосланы, буфер RTS будет низок. FAbortOnError Определяет, закончена ли операции чтения/записи, если происходит погрешность. Если этот параметр ИСТИНЕН, драйвер закрывает все операции чтения/записи с состоянием погрешности при возникновении оной. Драйвер не будет принимать никакие дальнейшие действия, пока не дождется подтверждения погрешности в передоваемых (принимаемых) данных, вызывая функцию ClearCommError. fDummy2 ЗАРЕЗЕРВИРОВАНО Microsoft wReserved ЗАРЕЗЕРВИРОВАНО Microsoft XonLim Определяет минимальное число байтов, находящихся во входном буфере прежде, чем будет генерирована подача СТАРТ-СИГНАЛА XoffLim Определяет максимальное число байтов, находящихся во входном буфере прежде, чем будет генерирована подача СТОП-СИГНАЛА. Максимальное число байтов, позволенных во входном буфере вычитается из размеров, в байтах, самого входного буфера. ByteSize Определяет число битов в байтах, переданных и полученных. Parity Определяет схему четности, которую нужно использовать. Этот параметр может быть одним из следующих значений: EVENPARITY MARKPARITY NOPARITY ODDPARITY StopBits Определяет число стоповых битов, которые нужно использовать. Этот параметр может быть одним из следующих значений: ONESTOPBIT 1 stop bit ONE5STOPBITS 1.5 stop bits TWOSTOPBITS 2 stop bits XonChar Определяет значение СТАРТ-СИГНАЛА для передачи и приема. XoffChar Определяет значение СТОП-СИГНАЛА для передачи и приема. ErrorChar Определяет значение СИГНАЛА ОШИБКИ (генерируемого при ошибке четности) для передачи и приема. EofChar Определяет значение сигнала конца данных. EvtChar Определяет значение сигнала события. wReserved1 ЗАРЕЗЕРВИРОВАНО Microsoft Дополнение: Когда структура DCB использует «ручной» выбор конфигурации , следующие ограничения используются для ByteSize и StopBits параметров : Число информационных разрядов должно быть от 5 до 8 битов.

Использование 5 информационных разрядов с 2 стоповыми битами - недопустимая комбинация, как - 6, 7, или 8 информационных разрядов с 1.5 стоповыми битами.

Использование компилятора Delphi (dcc32.exe) в прикладных программах.

Оформил: DeeCo

Автор: Сергей Гурин.

Введение. Технология.

Хотя в названии статьи тема выглядит довольно узко, я хотел бы рассказать не только об использовании dcc32, но и о технологии, которой я дал условное название "многозвенное программирование", хотя вынести это название в заголовок статьи мне показалось неправильным. Какой смысл я вкладываю в термин "многозвенное программирование"? Начну издалека. Работу над более или менее большими программами можно разделить на два крупных этапа. Первый этап - это собственно разработка, которая включает формулировку технического задания, увязку требований заказчика, проектную фазу, итеративное уточнение структуры проекта, программирование, отладку и тестирование. Первый этап заканчивается выпуском первой версии и началом эксплуатации программы у заказчика (или у массы пользователей, если программа была разработана по собственной инициативе для распространения или продажи). Затем наступает этап сопровождения, который включает устранение обнаруживаемых ошибок, адаптацию к постоянно изменяющимся требованиям заказчика, ввод дополнительных возможностей, которые не были оговорены в исходном задании. Часто бывает так - за время сопровождения программа претерпевает настолько существенные изменения, что сопровождение ее становится делом гораздо более трудоемким и хлопотным, чем разработка.

Если первый этап достаточно хорошо поддерживается разнообразным программистским инструментарием, то второй этап в этом смысле поддержан значительно хуже. Главной целью "многозвенного программирования" как раз и является поддержка этапа сопровождения. В чем основная идея этого подхода? Рассмотрим простую схему:

разработчик <---> заказчик

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

разработчик <---> технолог <---> пользователь

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

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

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

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

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

Вопросы реализации.

Вот собственно то, что я хотел сказать о технологии "многозвенного программирования". Теперь практическая часть. Как можно реализовать такую технологию? Один из наиболее используемых вариантов - это встраивание в программу интерпретатора проблемно-ориентированного языка. Встраивание компилятора я встречал значительно реже. В своей программистской практике я использовал оба варианта. Достоинства интерпретатора очевидны, но также очевидны и недостатки. Общий принцип состоит в том, что интерпретатор либо каждый раз интерпретирует исходное представление программы на проблемно-ориентированном языке, либо порождает промежуточный код, исполняемый некоторой виртуальной машиной. Примеры - пакет Microsoft Office с языком VB for Applications, реализация языка Java. Сюда же относятся все скриптовые языки. Если требуется межплатформенная совместимость, то интерпретаторы, вероятно, будут лучшим вариантом. Если же более важны соображения эффективности, то компиляция оказывается более предпочтительным выбором. Общий принцип - берем описание задачи на проблемно-ориентированном языке, генерируем соответствующий код на некотором универсальном языке, компилируем его и выполняем.

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

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

1. Генерация кода

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

Генерировать исходный текст можно любым способом, например, просто посылая строки текста в файл. Более удобный способ, как мне кажется, это направление текста в строко-ориентированный поток. Такой поток предоставляет дополнительное удобство при диагностике ошибок. Библиотека DccUsing содержит два потоковых класса: TFileCompileOut и TStringCompileOut, которые порождаются от TCompileOut. Классы очень просты, их реализацию можно посмотреть в исходном файле библиотеки, поэтому я дам только обзор. Базовый класс имеет методы:
public
  procedure IncLevel;
  procedure DecLevel;
  procedure AddSpace;
  procedure AddLine(const aLine: String);
  procedure AddLines(const aLines: array of String);
  procedure AddFile(const aFileName: String);
  procedure AddLineTemplate(const aLine: String;
                            const aArgs: array of String);
  procedure AddLinesTemplate(const aLines, aArgs: array of String);
  procedure AddFileTemplate(const aFileName: String;
                            const aArgs: array of String);
  procedure AddPoint(aPoint: Integer);
  function  FindPoint(aLine: Integer): Integer;
  property  Level: Integer read FLevel;
  property  LinesCount: Integer read FLinesCount;

Первые три метода позволяют управлять форматированием кода. Хотя форматирование совсем не обязательно (код никто не читает), но дает удобства при отладке, а, кроме того, мне нравится, когда программа выглядит эстетично. IncLevel увеличивает отступ текста, DecLevel уменьшает, а AddSpace добавляет в поток пустую строку. Два следующих метода добавляют в поток соответственно строку и массив строк, а метод AddFile - весь указанный файл. Свойства позволяют узнать текущий уровень отступа и текущее число строк в потоке. Назначение методов AddPoint и FindPoint будет объяснено в разделе диагностики ошибок.

Методы AddLineTemplate, AddLinesTemplate и AddFileTemplate более сложны, чем предыдущие методы, представляют собой простые макропроцессоры и позволяют параметризовать генерируемый текст. Параметризующие аргументы - это массив строк, которые заменяют метасимволы в исходном тексте шаблона. Метасимволы выглядят так: {{X}}, где Х - это порядковый номер аргумента, начиная от 1. Макроподстановка производится без всякого учета лексики. Поэтому можно параметризовать все что угодно - идентификаторы, строки, комментарии, операторы и т.д. Например, если шаблон текста таков:
const
  tFunc: array[0..5] of string = (
    'function {{1}}.SortProc{{2}}(const a1, a2: {{2}}): Integer;',
    'begin',
    '  if      a2 > a1 then result := 1',
    '  else if a2 = a1 then result := 0',
    '  else                 result := -1;',
    'end;'
    );
  //то при использовании
  c.AddLinesTemplate(tFunc, ['TTestClass1', 'Integer']);
  //мы получим такой результат:
function TTestClass1.SortProcInteger(const a1, a2: Integer): Integer;
begin
  if a2 > a1 then
    result := 1
  else if a2 = a1 then
    result := 0
  else
    result := -1;
end;
//а при использовании
c.AddLinesTemplate(tFunc, ['TTestClass2', 'String']);
//такой:
function TTestClass2.SortProcString(const a1, a2: string): Integer;
begin
  if a2 > a1 then
    result := 1
  else if a2 = a1 then
    result := 0
  else
    result := -1;
end;
Наследуемые классы переопределяют абстрактную процедуру записи строки в поток и имеют специфические методы. Класс TFileCompileOut специализируется на построчном выводе в файл:
public
  constructor Create(const aFileName: String);
  destructor  Destroy; override;
  property    FileName: String read FFileName;
{Конструктор принимает имя файла и открывает файл на чтение, а деструктор
закрывает файл. 
Класс TStringCompileOut хранит генерируемый текст в памяти:}
public
  procedure Clear;
  procedure SaveToFile(const aFileName: String);
  procedure SaveToOut(aOut: TCompileOut);
  property  Capacity: Integer ...
  property  Items[aIndex: Integer]: String ... default;

Методы класса позволяют очистить поток, сохранить поток в файле и добавить его к другому потоку. Свойства позволяют изменить резервируемый объем памяти для списка строк и получить доступ на запись и чтение строк по индексу. Общее число строк определяет наследуемое свойство LinesCount. Примеры использования этих классов смотрите в DccExamples.pas.

Отметим, что часть неизменяемого или шаблонного кода может быть заготовлена заранее, располагаться в файлах и объединяться в нужных местах результирующего кода с помощью AddFile и AddFileTemplate. По ходу генерации кода может быть создано несколько потоков - для деклараций переменных и констант, деклараций и реализаций классов и так далее. После просмотра всей задачи, сформулированной технологом, эти потоки сшиваются в один результирующий поток. Для частных потоков можно использовать строковую реализацию, а для результирующего потока - файловую.

2. Компиляция

После того, как исходный код создан, требуется его откомпилировать. Компилятор dcc32 замечательно подходит для этой роли - он очень быстрый, качественный и объединяет в себе все, что необходимо для построения exe-файлов, dll-библиотек и пакетов. Размер файла dcc32.exe (версия 12.0, из Delphi 5) всего 545 Кб, ранние версии имеют еще меньший размер. К нему нужно добавить только три файла - rlink32.dll, sysinit.dcu и system.dcu (это минимум). Компилятор и указанные файлы можно разместить в подкаталоге прикладной программы, например, bin. Генерировать текст целесообразно в подкаталоге компилятора, например, bin\pas, чтобы использовать короткие пути файлов и не засорять каталог компилятора.

Для вызова dcc32.exe в библиотеке DccUsing определена функция ExecDcc32. Она устанавливает текущий каталог, создает файл для перехвата ошибок компиляции, вызывает компилятор, дожидается завершения компиляции и определяет наличие ошибок.

function ExecDcc32(const aDccDir, aOptions,
  aProjectPath, aErrorPath: string;
  aCheckPaths: Boolean = False): Boolean;
Функция принимает аргументы: aDccDir - каталог, в котором находится компилятор Dcc32, aOptions - опции компилятора (рекомендации по их использованию смотрите в файле DccUsing.pas), aProjectPath - путь файла проекта (обычно dpr), aErrorPath - путь файла, куда будут направляться сообщения об ошибках компиляции. Необязательный аргумент aCheckPaths позволяет разрешить или запретить контроль наличия каталога и файла dcc32.exe. Функция возвращает True, если компиляция была успешной и False в противном случае. Предупреждения (hints и warnings) ошибками не считаются - их выводом можно управлять с помощью опций -H и -W. Опуская детали, рассмотрим немного подробнее эту функцию:
// сохранение текущего каталога и установка нового
CurDir := GetCurrentDir;
if not SetCurrentDir(DccDir) then
  raise Exception.Create(SCantChangeDir + DccDir);
try
  hStdOut := INVALID_HANDLE_VALUE;
  try
    // установки атрибутов безопасности
    with SecurAtt do
    begin
      nLength := SizeOf(SecurAtt);
      lpSecurityDescriptor := nil;
      // разрешить наследование дочернему процессу
      bInheritHandle := BOOL(True);
    end;
    // создание файла, в который будут направляться ошибки
    hStdOut := CreateFile(PChar(aErrorPath), GENERIC_WRITE, 0,
      @SecurAtt, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if hStdOut = INVALID_HANDLE_VALUE then
      raise Exception.Create(SCantCreateFile + aErrorPath);
    // заполнение структуры, специфицирующей создание процесса
    ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
    with StartupInfo do
    begin
      cb := SizeOf(StartupInfo);
      // скрывать окно компилятора и наследовать потоки ввода-вывода
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      wShowWindow := SW_HIDE;
      hStdOutput := hStdOut;
    end;
    // создать и стартовать процесс компилятора
    s := 'dcc32.exe ' + aOptions + ' ' + aProjectPath;
    if not CreateProcess('dcc32.exe', PChar(s), @SecurAtt, @SecurAtt,
      BOOL(True), 0, nil, PChar(DccDir), StartupInfo, ProcessInfo) then
      raise Exception.Create(SCantCreateProcess + 'dcc32.exe');
    // ждать завершение компиляции неопределенное время
    WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
    // получить результат компиляции
    ResultCode := 0;
    GetExitCodeProcess(ProcessInfo.hProcess, ResultCode);
    result := ResultCode = 0;
  finally
    // закрыть файл ошибок
    if hStdOut <> INVALID_HANDLE_VALUE then
      CloseHandle(hStdOut);
  end;
finally
  // восстановить прежний каталог по умолчанию
  SetCurrentDir(CurDir);
end;

Установка каталога компилятора, как текущего, позволяет не заботиться о мелочах, связанных с назначением путей. Компилятор направляет сообщения об ошибках в стандартный файл вывода. Для его перехвата создаем свой файл, дескриптор которого передаем компилятору. Для того, чтобы процесс компилятора мог наследовать дескриптор открытого файла, устанавливаем его атрибут наследования. При заполнении структуры StartupInfo указываем, что окно компилятора должно быть скрытым и порождаемый процесс должен наследовать стандартные потоки ввода-вывода. Атрибуты безопасности, передаваемые функции создания процесса, нужны для правильной работы в NT, в Windows 95-98 их можно было бы опустить. Функция CreateProcess сохраняет параметры процесса в структуре ProcessInfo - мы используем дескриптор процесса, чтобы передать его функции ожидания системного события - в данном случае, завершения процесса. С помощью GetExitCodeProcess получаем значение, которое возвращает компилятор. Если компиляция была успешной, то возвращается 0, иначе - ненулевое значение. Операции закрытия файла ошибок и восстановления предыдущего каталога произойдут независимо от возможных исключительных ситуаций по ходу функции ExecDcc32.

Компилятору, вместе с исходным файлом (файлами), нужно также передать файл проекта (dpr) и уточнить в опциях, что же будет результатом компиляции. Возможных вариантов много - GUI или консольное приложение, dll, пакет, ActiveX (наверное, есть еще варианты). Выбор вида компиляции связан со спецификой задачи, требованиями пользователя и вкусами разработчика. К этому вопросу я еще раз вернусь в разделе Исполнение кода.

3. Диагностика ошибок Идеальный вариант - это генерация синтаксически и семантически правильного кода. Но проверка семантики в большинстве случаев вряд ли возможна, поэтому желательно генерировать, по крайней мере, синтаксически правильный код. В этом случае компиляция всегда будет успешной. Если проверка синтаксической корректности затруднительна или невозможна, то приходится полагаться на диагностику, которую сформирует компилятор. Конечно, давать эту диагностику технологу - это самый последний случай, когда уже ничего не остается. Более спокойный вариант - это извлечь из файла ошибок номера ошибочных строк и определить, чему они соответствуют в том описании, который сделал технолог. Для разбора файла ошибок, библиотека DccUsing содержит класс TParseDcc32Errors. Класс весьма прост, поэтому я только обрисую его интерфейс:
TCompileMessageStatus = (cmsNone, cmsHint, cmsWarning,
                         cmsError, cmsFatal);
public
  procedure ParseFile(const aFileName: String);
  function  MessagesCount: Integer;
  function  StatusCount(aStatus: TCompileMessageStatus): Integer;
  function  MessageText(aIndex: Integer): String;
  function  MessageStatus(aIndex: Integer): TCompileMessageStatus;
  function  MessageFile(aIndex: Integer): String;
  function  MessageLine(aIndex: Integer): Integer;

TCompileMessageStatus перечисляет все возможные статусы ошибок. Процедура ParseFile выполняет разбор файла ошибок и сохраняет результат в своем приватном списке. Функция MessagesCount возвращает общее количество сообщений, а StatusCount - количество сообщений с заданным статусом. Оставшиеся 4 функции разбирают строку сообщения компилятора на составляющие - текст сообщения, статус, имя файла, в котором обнаружена ошибка и номер строки.

Вот теперь можно вернуться к необъясненным методам TCompileOut. Метод AddPoint добавляет в поток контрольную точку. Контрольная точка - это просто целое число, которое помечает уникальным номером начало некоторой части генерируемого кода и жестко связывается с номером строки. Контрольная точка может служить, например, индексом в таблице ошибок. Расставив при генерации кода такие точки-метки, мы можем локализовать место ошибки. Для поиска ошибки нужно повторить генерацию кода без вызова компилятора (чтобы опять сформировать выходной поток), а затем, для результирующего выходного потока, вызвать функцию FindPoint, передав ей номер ошибочной строки. Эта функция определит ближайшую точку ошибки. Если генерируется несколько файлов исходных кодов, то выбор ошибочного файла сделать с помощью функции, возвращающей имя файла - MessageFile.

4. Исполнение кода

Как я уже говорил, возможны различные варианты того, в какой вид будет скомпилирована задача, сформулированная технологом. Если технолог передает результаты своей работы конечному пользователю, то удобный вариант - exe-файл. Если технолог решает некоторую задачу и сразу же пользуется результатами решения, то сам факт компиляции должен быть для него полностью прозрачен (или максимально незаметен). Технолог работает в программе, которая сделана разработчиком и, по большому счету, ему совершенно безразлично, каким конкретно способом разработчик предоставляет возможность изменять функциональность программы. Существует несколько технологий построения гибко подгружаемых модулей, и они описаны в литературе. Я остановлюсь только на одной технологии - динамическая загрузка и выгрузка DLL. Если результирующий проект, который нужен технологу, содержит визуальные формы (а их можно генерировать как dfm-файлы), то вероятно, более предпочтительными будут пакеты.

Возможен также вариант, когда исполняемый код делится на две составляющие - исполняемое ядро (exe-файл) и подгружаемый модуль. Ядро создается разработчиком и динамически подключает модули, создаваемые технологом. Достоинство такого подхода в том, что работу с визуальными компонентами можно сосредоточить в ядре, а в DLL формировать только алгоритмическую часть задачи. Другое достоинство такого подхода - технолог может работать в мощной интегрированной среде, а конечному пользователю он передает только ядро и нужный модуль, скрывая от пользователя все технологические детали.

Для работы с DLL, в библиотеку DccUsing добавлен класс TDllWrap - простая оболочка, инкапсулирующая дескриптор загруженной DLL. Основные методы класса:
public
  constructor Create(const aDllPath: String);
  destructor  Destroy; override;
  function    Execute(const aFunctionName: String;
               const aInterface: Pointer): Pointer;

Конструктор Create просто сохраняет путь к файлу DLL и больше ничего не делает, деструктор Destroy выгружает DLL из памяти, если она была загружена. Основную работу делает метод Execute - он вызывает экспортируемую функцию DLL по имени и передает ей указатель на интерфейс вызывающей части. Экспортируемая функция возвращает интерфейс вызываемой части. Более подробно о взаимодействии вызывающей и вызываемой частей поговорим в следующем разделе, а пока рассмотрим реализацию метода Execute.

function TDllWrap.Execute(const aFunctionName: string;
  const aInterface: Pointer): Pointer;
var
  f: TDllFunction;
begin
  if FDllInst = 0 then
  begin
    if not FileExists(FDllPath) then
      raise Exception.Create(SFileNotFound + FDllPath);
    FDllInst := LoadLibrary(PChar(FDllPath));
    if FDllInst = 0 then
      raise Exception.Create(SCantLoadDll +
        SysErrorMessage(GetLastError));
  end;
  f := TDllFunction(GetProcAddress(FDllInst,
    PChar(aFunctionName)));
  if not Assigned(f) then
    raise Exception.Create(SCantFindFunction + aFunctionName);
  result := f(aInterface);
end;

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

5. Взаимодействие с DLL С самых общих позиций можно считать, что вызывающая (Master) и вызываемая (Slave) части обладают своими интерфейсами. Экспортируемая функция конструирует Slave-интерфейс и возвращает его. Экспортируемая функция играет в этом случае роль фабрики класса. Сигнатура экспортируемой функции выглядит так:
TDllFunction = function(aInterface: Pointer): Pointer; StdCall;
После вызова этой функции Master и Slave части взаимодействуют друг с другом через свои интерфейсы. В качестве интерфейса наиболее удобно использовать чистый абстрактный класс, например:
IMaster = class
public
  procedure Method1; virtual; abstract;
  .............
end;
Виртуальный абстрактный класс не содержит переменных, а все его методы - виртуальные и абстрактные. Декларация интерфейса включается в обе взаимодействующие части. Для реализации интерфейса создается класс, наследуемый от абстрактного интерфейса и переписывающий все его виртуальные методы. Интерфейсный объект Master-части конструируется и удаляется в основной программе. Интерфейсный объект Slave-части конструируется в экспортируемой функции DLL, а уничтожается в блоке finalization при выгрузке DLL или с помощью другой экспортируемой функции. Например:
uses
  UnitIMaster, UnitISlave;
type
  TSlaveObject = class(ISlave)
  private
    FMain: IMain;
  public
    constructor Create(aMain: IMain);
    destructor Destroy; override;
    procedure Method1; override;
    ............
  end;
function CreateSlave(aInterface: Pointer): Pointer; stdcall;
  function DestroySlave(aInterface: Pointer): Pointer; stdcall;
  implementation
var
    SlaveObject: TSlaveObject;
    // Реализация TSlaveObject
    ............
  function CreateSlave(aInterface: Pointer): Pointer;
begin
  SlaveObject := TSlaveObject.Create(IMaster(aInterface));
  result := SlaveObject;
end;
function DestroySlave(aInterface: Pointer): Pointer;
begin
  SlaveObject.Free;
  SlaveObject := nil;
  result := nil;
end;
initialization
  SlaveObject := nil;
finalization
  SlaveObject.Free;
end.
Пример реализации

Эффект использования описанной технологии повышается при увеличении сложности программы, для простых программ она вряд ли целесообразна. В качестве примера я расскажу об одной из своих разработок: Visual2k - Интегрированная среда для программирования микроконтроллерных кукол-роботов. Подробнее о ней и о других программах, использующих "многозвенное программирование" можно узнать на моем web-сайте.

Программа Visual2k разработана для томского театра кукол "2+Ку" и проекта "Оживление пространства". Суть проекта состоит в создании кукол-роботов, используемых в рекламных целях, в витринах магазинов и кафе, в качестве гидов на выставках. Куклы могут быть одиночными или работать совместно в автоматическом спектакле. Разработка каждого нового проекта включает в себя такие фазы - художник и сценарист по заказу придумывают сценарий спектакля, затем, вместе с конструкторами обсуждают детали реализации. Когда детали проекта уточнились, инженер-электронщик изготавливает микроконтроллерную аппаратуру, инженер-механик конструирует механику кукол и приводы двигателей, а режиссер - создает с готовыми куклами спектакль. То есть, здесь мы имеем целую цепочку технологов, каждый из которых работает со своей предметной областью.

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

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

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

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

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

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

Здесь стоит отметить, что Visual2k немного напоминает Delphi, с той разницей, что здесь все представлено визуальными компонентами, даже переменные и операторы. Режиссер выстраивает сценарий, выкладывая на рабочую область компоненты-операторы, и назначает их свойства с помощью инспектора объектов. Программа сценария, полученная таким образом, может выполняться либо на персональном компьютере (для сложных говорящих кукол или спектаклей), либо на одном микроконтроллере или сети микроконтроллеров. Исходный вид сценария один и тот же, но генерируется либо текст на языке Object Pascal, который компилируется dcc32, либо текст на языке Си, объединяемый с частью, заданной электронщиком и компилируемый Си-компилятором. Поскольку в операторах присваивания и в условиях могут быть выражения, Visual2k включает в себя синтаксический анализатор выражений, обнаруживающий все синтаксические ошибки и заменяющий русские имена переменных и функций на имена, допустимые для языка Pascal. Если кукольный проект будет выполняться под управлением персонального компьютера, то сценарий компилируется в DLL, которая вызывается исполняющим ядром. Такая структура позволяет передавать заказчику только исполняющее ядро и DLL, не раскрывая фирменных секретов спектакля. Параллельные процессы, необходимые для адекватного описания сценария реализуются на базе библиотеки параллельного программирования Gala. Если же сценарий выполняется в микроконтроллере, то используется специально разработанная многозадачная среда с кооперативной мультизадачностью и сценарий зашивается прямо в ПЗУ.

Когда мы получили первый заказ на кукольный спектакль, программы Visual2k еще не было, и я писал сценарий самостоятельно (на Delphi) - режиссер сидел рядом ничего не понимая в том, что я делаю, и только давал советы. Я плохо понимал, чего хочет режиссер, а режиссер вообще не понимал, что я делаю. После создания Visual2k, я занимался только развитием интегрированной среды, добавлял поддержку новых типов микроконтроллеров и новых типов приводов, совершенно не вникая в то, какие делались спектакли. Режиссер очень быстро освоил простой язык описания сценариев и получил полную свободу в реализации своих режиссерских замыслов. Так мне удалось расправиться с целым стадом зайцев - существенно облегчить себе задачу сопровождения программы, освободиться от написания конкретных сценариев, освободить электронщика от написания программ для микроконтроллера и высвободить себе время для других разработок.

Скачать файлы проекта DccUsing.Zip (80K).

В чем разница между сокетами, DCOM и OLE Enterprise при использовании их в качестве транспорта.

Автор: Nomadic Sockets (TCP/IP): на клиентах и сервере требуется наличие стека TCP/IP; не требуется дополнительной настройки клиентов;DCOM: на клиентах и серверах требуется наличие DCOM (входит в состав Windows NT 4.0, для Windows 95 доступен как опция) требуется настройка клиентов (DCOM Configuration Utility - DCOMCNFG.EXE); встроенная поддержка модели безопасности Windows NT; поддержка обратных вызовов (методов);CORBA на клиентах и серверах требуется наличие Common Object Request Broker; требуется настройка клиентов; поддержка обратных вызовов (методов);OLE Enterprise: на клиентах и серверах требуется наличие OLE Enterprise; требуется настройка клиентов; поддержка обратных вызовов (методов).

DCOM permissions.

Автор: Абдулин Марат WEB-сайт: ООО "АТОЛ технологии"

Технология DCOM основана на технологии COM и представляет собой ее продолжение. Основное назначение DCOM - организация взаимодействия клиента с удаленным сервером.

Как пользоваться DCOM.

Чтобы воспользоваться возможностями DCOM должны быть соблюдены следующие требования: Наличие Клиент.exe, Сервер.exe. ("Каркасы" этих приложений прилагаются к документу см Samples\DCOMSvr). Наличие сети как минимум из двух компьютеров (платформы 9x, Me, NT, 2000). Компьютеры должны "видеть" друг друга. На клиентском и серверном компьютере должна быть установлена поддержка DCOM (на NT и 2000 поддержка DCOM есть по умолчанию, в 9x и Me поддержка отключена, ее можно получить по адресу http://www.microsoft.com/com/resources/downloads.asp). Компьютеры должны быть в одном домене (на сколько критично это требование под вопросом, я не исследовал, информация из http://www.delphikingdom.com/). Сервер.exe должен быть зарегистрирован на клиентской машине и серверной машине (после регистрации на клиентской машине Сервер.exe можно удалить). Регистрация Сервер.exe производится из командной строки: сервер.exe regserver. Разрегистрация также из командной строки: сервер.exe unregserver Должен быть настроен DCOM (можно не задумываясь продублировать настройки, как на клиенте, так и на сервере) для запуска и доступа к Сервер.exe (настроить DCOM можно при помощи утилиты DCOMCNFG.EXE или программно, см. Samples\DcomPerm). Если DCOM настраивается для Win9x, то после настройки необходимо перезагрузить компьютер. Если изменяются настройки протоколов используемых в DCOM, то следует перезагрузить компьютер (действительно для любой платформы). Если Сервер.exe запускается на платформе 9x, то сервер должен быть предварительно загружен, можно поместить запуск сервера в StartUp. Скачать исходные коды: DComPermSrc.zip (28 K)

Настройка DCOM при помощи DCOMCNFG.EXE

Закладка DefaultProperties
Элемент Значение 9x NT
Enable Distributed COM on this computer True + +
Enable COM Internet Services on this computer Не имеет значения - +
Default Authentication Level None + +
Default Impersonation Level Impersonate + +
Provide additional security for reference tracking False + +
Закладка DefaultSecurity
Элемент Значение 9x NT
Enable remote connection True + -
Default access permissions Everyone = allow access - +
Default access permissions The world = grant access + -
Default launch permissions Everyone = allow launch - +

Закладка Applications Позиционироваться на сервер.exe. Кнопка "Properties...".

Закладка Location
Элемент Значение 9x NT
Run application on the computer where the data is located False + +
Run application on this computer True + +
Run application on the following computer False + +
Закладка Security
Элемент Значение 9x NT
Use default access permissions True + +
Use default launch permissions True - +
Use custom configuration permissions False - +
Закладка Identity
Элемент Значение 9x NT
Which user account do you want to use to run this application The interactive user - +

Программная настройка DCOM

Для программной настройки DCOM можно воспользоваться процедурами модуля BDcomPrm

DefaultProperties.

InitializeDefaultSecurity Устанавливает параметры Security по умолчанию.Вызов данной процедуры необходимо поместить перед Application.Initialize в клиентской и серверной программе.Процедуру можно вызывать только один раз для текущего процесса.Процедура должна быть вызвана до первого обращения к COM-объекта, требующего маршалинга . + + SetDefaultDCOMCommunicationProperties Устанавливает параметры по умолчанию для Authentication Level, Impersonation Level + + CreateRemoteComObjectEx Определяет Authentication Level, Impersonation Level запускаемого серверного приложения.Данная процедура может использоваться вместо CreateRemoteComObject + + Provide additional security for reference tracking RemoveLegacySecure-References При разрешении DCOM необходимо вызвать эту процедуру, чтобы сбросить флажок Повышенной безопасности для отслеживания ссылок + +
Элемент Процедура Описание 9x NT
Enable Distributed COM on this computer IsDCOMOk Проверяет наличие поддержки DCOM + +
IsEnabledDCOM Проверяет разрешен ли DCOM на данной машине + +
SetEnableDCOM Разрешает DCOM на данной машине + +
IsDCOMProtocolsEnabled Проверяет наличие протоколов DCOM - +
Default Authentication LevelDefault Impersonation Level IsInitializeSecurityOk Проверяет можно ли устанавливать параметры Security. Возвращает True для платформы NT и False для 9x - +

DefaultSecurity.

ЭлементПроцедураОписание 9x NT Enable remote connection IsDCOMOk, IsEnabledDCOM, SetEnabledDCOM + + Default access permissions ListDefaultAccessACL Возвращает в строке описание разрешений доступа к DCOM приложениям по умолчанию. Данное описание может быть выведено в Memo. - + ChangeDefaultAccessACL ChangeDefaultAccessACL Изменяет параметры доступ к DCOM приложениям по умолчанию.Principal - имя пользователя (например Everyone)SetPrincipal - True добавить пользователя в список, False - удалить.Permit - разрешить параметры для указанного пользователя - + Default launch permissions ListDefaultLaunchACL Возвращает в строке описание разрешений запуска DCOM приложений по умолчанию. Данное описание может быть выведено в Memo - + ChangeDefaultLaunchACL ChangeDefaultLaunchACL Изменяет параметры запуска DCOM приложений по умолчанию - + - + IsDefaultLaunchAccess-Allowed Возвращает True, если разрешен запуск DCOM приложений по умолчанию. - +

Application Security.

Все процедуры и функции данной категории получают в качестве входного параметра AppID - CLSID объекта сервера. ЭлементПроцедураОписание 9x NT Access permissions ListAppIDAccessACL Возвращает в строке описание разрешений доступа к DCOM приложению. Данное описание может быть выведено в Memo.AppID - CLSID объекта сервера. - + ChangeAppIDAccessACL Изменяет параметры доступ к DCOM приложениям по умолчанию.AppID - CLSID объекта сервера.Principal - имя пользователя (например Everyone)SetPrincipal - True добавить пользователя в список, False - удалить.Permit - разрешить параметры для указанного пользователя - + Default launch permissions ListAppIDLaunchACL Возвращает в строке описание разрешений запуска DCOM приложения. Данное описание может быть выведено в Memo.AppID - CLSID объекта сервера. - + ChangeAppIDLaunchACL Изменяет параметры запуска DCOM приложений по умолчанию.AppID - CLSID объекта сервера. - + IsLaunchAccessAllowed Возвращает True, если разрешен запуск DCOM приложения. - + AllowLaunchAccess Разрешает запуск DCOM приложения - +

Закладка Identity.

Все процедуры и функции данной категории получают в качестве входного параметра AppID - CLSID объекта сервера. ЭлементПроцедураОписание 9x NT Which user account do you want to use to run this application IsInteractiveUser Проверяет, используется ли для запуска приложения учетная запись взаимодействующего пользователя - + SetInteractiveUser Устанавливает параметр: использовать для запуска приложения учетную запись взаимодействующего пользователя - +
Остальные процедуры и функции

Все остальные процедуры и функции модуля BDcomPrm носят служебный характер.

Как можно работать с DDE под Delphi, используя вызовы API.

Кстати, достаточно легко: следующий пример демонстрирует как можно научить общаться клиентскую программу с программой-сервером. Обе программы полностью созданы на Delphi. В итоге мы имеет 2 проекта, 3 формы и 3 модуля. Для работы с DDE-запросами данный пример использует методы DDE ML API. Сервер должен начать свою работу перед тем, как клиент будет загружен. Данный пример демонстрирует 3 способа взаимодействия между клиентом и сервером: Клиент может "пропихивать" (POKE) данные на сервер. Сервер может автоматически передавать данные клиенту, после чего клиент обновляет свой вид на основе результатов, полученных от сервера. Данные сервера изменяются, после чего клиент делает запрос серверу для получения новых данных и обновляет свой вид.Как работает программа.

Ниже приведены 8 файлов, сконкатенированных в единое целое. Каждый файл имеет следующую структуру: { *** НАЧАЛО КОДА FILENAME.EXT *** } КОД { *** КОНЕЦ КОДА FILENAME.EXT *** }, поэтому вам остается всего-лишь взять код, расположенный между маркерами { *** }, скопировать в файл с соответствующим именем, и собрать оба проекта в среде Delphi.

{ *** НАЧАЛО КОДА DDEMLCLI.DPR *** }
program Ddemlcli;
uses
  Forms,
  Ddemlclu in 'DDEMLCLU.PAS' {Form1};
{$R *.RES}
begin
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
{ ***  КОНЕЦ КОДА DDEMLCLI.DPR *** }
{ *** НАЧАЛО КОДА DDEMLCLU.DFM *** }
object Form1: TForm1
  Left = 197
    Top = 95
    Width = 413
    Height = 287
    HorzScrollBar.Visible = False
    VertScrollBar.Visible = False
    Caption = 'Демонстрация DDEML, Клиентское приложение'
    Font.Color = clWindowText
    Font.Height = -13
    Font.Name = 'System'
    Font.Style = []
    Menu = MainMenu1
    PixelsPerInch = 96
    OnCreate = FormCreate
    OnDestroy = FormDestroy
    OnShow = FormShow
    TextHeight = 16
    object PaintBox1: TPaintBox
    Left = 0
      Top = 0
      Width = 405
      Height = 241
      Align = alClient
      Color = clWhite
      ParentColor = False
      OnPaint = PaintBox1Paint
  end
  object MainMenu1: TMainMenu
    Top = 208
      object File1: TMenuItem
      Caption = '&Файл'
        object exit1: TMenuItem
        Caption = 'В&ыход'
          OnClick = exit1Click
      end
    end
    object DDE1: TMenuItem
      Caption = '&DDE'
        object RequestUpdate1: TMenuItem
        Caption = '&Запрос на обновление'
          OnClick = RequestUpdate1Click
      end
      object AdviseofChanges1: TMenuItem
        Caption = '&Сообщение об изменениях'
          OnClick = AdviseofChanges1Click
      end
      object N1: TMenuItem
        Caption = '-'
      end
      object PokeSomeData: TMenuItem
        Caption = '&Пропихивание данных'
          OnClick = PokeSomeDataClick
      end
    end
  end
end
{ ***  КОНЕЦ КОДА DDEMLCLU.DFM *** }
{ *** НАЧАЛО КОДА DDEMLCLU.PAS *** }
{***************************************************}
{                                                   }
{   Delphi 1.0 DDEML Демонстрационная программа     }
{   Copyright (c) 1996 by Borland International     }
{                                                   }
{***************************************************}
{ Это демонстрационное приложение, демонстрирующее использование
DDEML API в клиентском приложении. Оно использует серверное
приложение DataEntry, которое является частью данной демонстрации,
и служит для ввода данных и отображения их на графической панели.
Сначала вы должны запустить приложение-сервер (в DDEMLSRV.PAS),
а затем стартовать клиента. Если сервер не запущен, клиент при
попытке соединения потерпит неудачу.
Интерфейс сервера определен списком имен (Service, Topic и Items)
в отдельном модуле с именем DataEntry (DATAENTR.TPU). Сервер
делает Items доступными в формате cf_Text; они преобразовываются
и хранятся локально как целые. }
unit Ddemlclu;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, VBXCtrl, ExtCtrls, DDEML, Menus, StdCtrls;
const
  NumValues = 3;
type
  { Структура данных, представленная в примере }
  TDataSample = array[1..NumValues] of Integer;
  TDataString = array[0..20] of Char; { Размер элемента как текста }
  { Главная форма }
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    exit1: TMenuItem;
    DDE1: TMenuItem;
    RequestUpdate1: TMenuItem;
    AdviseofChanges1: TMenuItem;
    PokeSomeData: TMenuItem;
    N1: TMenuItem;
    PaintBox1: TPaintBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure RequestUpdate1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure AdviseofChanges1Click(Sender: TObject);
    procedure PokeSomeDataClick(Sender: TObject);
    procedure Request(HConversation: HConv);
    procedure exit1Click(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
  private
    { Private declarations }
  public
    Inst: Longint;
    CallBackPtr: ^TCallback;
    ServiceHSz: HSz;
    TopicHSz: HSz;
    ItemHSz: array[1..NumValues] of HSz;
    ConvHdl: HConv;
    DataSample: TDataSample;
  end;
var
  Form1: TForm1;
implementation
const
  DataEntryName: PChar = 'DataEntry';
  DataTopicName: PChar = 'SampledData';
  DataItemNames: array[1..NumValues] of pChar = ('DataItem1',
    'DataItem2',
    'DataItem3');
{$R *.DFM}
  { Локальная функция: Процедура обратного вызова для DDEML }
function CallbackProc(CallType, Fmt: Word; Conv: HConv; hsz1, hsz2: HSZ;
  Data: HDDEData; Data1, Data2: Longint): HDDEData; export;
begin
  CallbackProc := 0; { В противном случае смотрите доказательство }
  case CallType of
    xtyp_Register:
      begin
        { Ничего ... Просто возвращаем 0 }
      end;
    xtyp_Unregister:
      begin
        { Ничего ... Просто возвращаем 0 }
      end;
    xtyp_xAct_Complete:
      begin
        { Ничего ... Просто возвращаем 0 }
      end;
    xtyp_Request, Xtyp_AdvData:
      begin
        Form1.Request(Conv);
        CallbackProc := dde_FAck;
      end;
    xtyp_Disconnect:
      begin
        ShowMessage('Соединение разорвано!');
        Form1.Close;
      end;
  end;
end;
{ Посылка DDE запроса для получения cf_Text данных с сервера.
Запрашиваем данные для всех полей DataSample, и обновляем
окно для их отображения. Данные с сервера получаем синхронно,
используя DdeClientTransaction.}
procedure TForm1.Request(HConversation: HConv);
var
  hDdeTemp: HDDEData;
  DataStr: TDataString;
  Err, I: Integer;
begin
  if HConversation <> 0 then
  begin
    for I := Low(ItemHSz) to High(ItemHSz) do
    begin
      hDdeTemp := DdeClientTransaction(nil, 0, HConversation, ItemHSz[I],
        cf_Text, xtyp_Request, 0, nil);
      if hDdeTemp <> 0 then
      begin
        DdeGetData(hDdeTemp, @DataStr, SizeOf(DataStr), 0);
        Val(DataStr, DataSample[I], Err);
      end; { if }
    end; { for }
    Paintbox1.Refresh; { Обновляем экран }
  end; { if }
end;
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
  { Создаем экземпляр окна DDE-клиента. Создаем окно, используя
  унаследованный конструктор, инициализируем экземпляр данных.}
begin
  Inst := 0; { Должен быть нулем для первого вызова DdeInitialize }
  CallBackPtr := nil; { MakeProcInstance вызывается из SetupWindow    }
  ConvHdl := 0;
  ServiceHSz := 0;
  TopicHSz := 0;
  for I := Low(DataSample) to High(DataSample) do
  begin
    ItemHSz[I] := 0;
    DataSample[I] := 0;
  end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
{ Уничтожаем экземпляр клиентского окна. Освобождаем дескрипторы
DDE строк, и освобождаем экземпляр функции обратного вызова,
если она существует. Также, для завершения диалога, вызовите
DdeUninitialize. Затем, для завершения работы, вызовите
разрушителя предка. }
var
  I: Integer;
begin
  if ServiceHSz <> 0 then
    DdeFreeStringHandle(Inst, ServiceHSz);
  if TopicHSz <> 0 then
    DdeFreeStringHandle(Inst, TopicHSz);
  for I := Low(ItemHSz) to High(ItemHSz) do
    if ItemHSz[I] <> 0 then
      DdeFreeStringHandle(Inst, ItemHSz[I]);
  if Inst <> 0 then
    DdeUninitialize(Inst); { Игнорируем возвращаемое значение }
  if CallBackPtr <> nil then
    FreeProcInstance(CallBackPtr);
end;
procedure TForm1.RequestUpdate1Click(Sender: TObject);
begin
  { Генерируем запрос DDE в ответ на выбор пункта меню DDE | Request.}
  Request(ConvHdl);
end;
procedure TForm1.FormShow(Sender: TObject);
{ Завершаем инициализацию окна сервера DDE. Выполняем те действия,
которые требует правильное окно. Инициализируем использование DDEML. }
var
  I: Integer;
  InitOK: Boolean;
begin
  CallBackPtr := MakeProcInstance(@CallBackProc, HInstance);
  { Инициализируем DDE и устанавливаем функцию обратного вызова.
  Если сервер отсутствует, вызов терпит неудачу. }
  if CallBackPtr <> nil then
  begin
    if DdeInitialize(Inst, TCallback(CallBackPtr), AppCmd_ClientOnly,
      0) = dmlErr_No_Error then
    begin
      ServiceHSz := DdeCreateStringHandle(Inst, DataEntryName, cp_WinAnsi);
      TopicHSz := DdeCreateStringHandle(Inst, DataTopicName, cp_WinAnsi);
      InitOK := True;
      {     for I := Low(DataItemNames) to High(DataItemNames) do begin }
      for I := 1 to NumValues do
      begin
        ItemHSz[I] := DdeCreateStringHandle(Inst, DataItemNames[I],
          cp_WinAnsi);
        InitOK := InitOK and (ItemHSz[I] <> 0);
      end;
      if (ServiceHSz <> 0) and (TopicHSz <> 0) and InitOK then
      begin
        ConvHdl := DdeConnect(Inst, ServiceHSz, TopicHSz, nil);
        if ConvHdl = 0 then
        begin
          ShowMessage('Не могу инициализировать диалог!');
          Close;
        end
      end
      else
      begin
        ShowMessage('Не могу создать строки!');
        Close;
      end
    end
    else
    begin
      ShowMessage('Не могу осуществить инициализацию!');
      Close;
    end;
  end;
end;
procedure TForm1.AdviseofChanges1Click(Sender: TObject);
{ Переключаемся на режим DDE Advise с помощью пункта меню DDE |
Advise (уведомление). При выборе этого пункта меню все три
элемента переключаются на уведомление. }
var
  I: Integer;
  TransType: Word;
  TempResult: Longint;
begin
  with TMenuITem(Sender) do
  begin
    Checked := not Checked;
    if Checked then
      TransType := (xtyp_AdvStart or xtypf_AckReq)
    else
      TransType := xtyp_AdvStop;
  end; { with }
  for I := Low(ItemHSz) to High(ItemHSz) do
    if DdeClientTransaction(nil, 0, ConvHdl, ItemHSz[I], cf_Text,
      TransType, 1000, @TempResult) = 0 then
      ShowMessage('Не могу выполнить транзакцию-уведомление');
  if TransType and xtyp_AdvStart <> 0 then
    Request(ConvHdl);
end;
procedure TForm1.PokeSomeDataClick(Sender: TObject);
{ Генерируем DDE-Poke транзакцию в ответ на выбор пункта
меню DDE | Poke. Запрашиваем значение у пользователя,
которое будем "проталкивать" в DataItem1 в качестве
иллюстрации Poke-функции.}
var
  DataStr: pChar;
  S: string;
begin
  S := '0';
  if InputQuery('PokeData', 'Задайте проталкиваемую (Poke) величину', S) then
  begin
    S := S + #0;
    DataStr := @S[1];
    DdeClientTransaction(DataStr, StrLen(DataStr) + 1, ConvHdl,
      ItemHSz[1], cf_Text, xtyp_Poke, 1000, nil);
    Request(ConvHdl);
  end;
end;
procedure TForm1.exit1Click(Sender: TObject);
begin
  close;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
{ После запроса обновляем окно. Рисуем график объема текущих продаж.}
const
  LMarg = 30; { Левое поле графика }
var
  I,
    Norm: Integer;
  Wd: Integer;
  Step: Integer;
  ARect: TRect;
begin
  Norm := 0;
  for I := Low(DataSample) to High(DataSample) do
  begin
    if abs(DataSample[I]) > Norm then
      Norm := abs(DataSample[I]);
  end; { for }
  if Norm = 0 then
    Norm := 1; { В случае если у нас все нули }
  with TPaintBox(Sender).Canvas do
  begin
    { Рисуем задний фон }
    Brush.color := clWhite;
    FillRect(ClipRect);
    { Рисуем ось }
    MoveTo(0, ClipRect.Bottom div 2);
    LineTo(ClipRect.Right, ClipRect.Bottom div 2);
    MoveTo(LMarg, 0);
    LineTo(LMarg, ClipRect.Bottom);
    { Печатаем текст левого поля }
    TextOut(0, 0, IntToStr(Norm));
    TextOut(0, ClipRect.Bottom div 2, '0');
    TextOut(0, ClipRect.Bottom + Font.Height, IntToStr(-Norm));
    TextOut(0, ClipRect.Bottom div 2, '0');
    TextOut(0, ClipRect.Bottom div 2, '0');
    TextOut(0, ClipRect.Bottom div 2, '0');
    { Печатаем текст оси X }
    { Теперь рисуем бары на основе нормализованного значения.
    Вычисляем ширину баров (чтобы они все вместились в окне)
    и ширину пробела между ними, который приблизительно равен
    20% от их ширины. }
    {        SelectObject(PaintDC, CreateSolidBrush(RGB(255, 0, 0)));
    SetBkMode(PaintDC, Transparent);
    }
    ARect := ClipRect;
    Wd := (ARect.Right - LMarg) div NumValues;
    Step := Wd div 5;
    Wd := Wd - Step;
    with ARect do
    begin
      Left := LMarg + (Step div 2);
      Top := ClipRect.Bottom div 2;
    end; { with }
    { Выводим бары и текст для оси X }
    for i := Low(DataSample) to High(DataSample) do
    begin
      with ARect do
      begin
        Right := Left + Wd;
        Bottom := Top - Round((Top - 5) * (DataSample[I] / Norm));
      end; { with }
      { Заполняем бар }
      Brush.color := clFuchsia;
      FillRect(ARect);
      { Выводим текст для горизонтальной оси }
      Brush.color := clWhite;
      TextOut(ARect.Left, ClipRect.Bottom div 2 - Font.Height,
        StrPas(DataItemNames[i]));
      with ARect do
        Left := Left + Wd + Step;
    end; { for }
  end; { with }
end;
end. { ***  КОНЕЦ КОДА DDEMLCLU.PAS *** }
{ *** НАЧАЛО КОДА DDEMLSVR.DPR *** }
program Ddemlsvr;
uses
  Forms,
  Ddesvru in 'DDESVRU.PAS' {Form1},
  Ddedlg in '\DELPHI\BIN\DDEDLG.PAS' {DataEntry};
{$R *.RES}
begin
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TDataEntry, DataEntry);
  Application.Run;
end.
{ ***  КОНЕЦ КОДА DDEMLSVR.DPR *** }
{ *** НАЧАЛО КОДА DDESVRU.DFM *** }
object Form1: TForm1
  Left = 712
    Top = 98
    Width = 307
    Height = 162
    Caption = 'Демонстрация DDEML, Серверное приложение'
    Color = clWhite
    Font.Color = clWindowText
    Font.Height = -13
    Font.Name = 'System'
    Font.Style = []
    Menu = MainMenu1
    PixelsPerInch = 96
    OnCreate = FormCreate
    OnDestroy = FormDestroy
    OnShow = FormShow
    TextHeight = 16
    object Label1: TLabel
    Left = 0
      Top = 0
      Width = 99
      Height = 16
      Caption = 'Текущие значения:'
  end
  object Label2: TLabel
    Left = 16
      Top = 24
      Width = 74
      Height = 16
      Caption = 'Data Item1:'
  end
  object Label3: TLabel
    Left = 16
      Top = 40
      Width = 74
      Height = 16
      Caption = 'Data Item2:'
  end
  object Label4: TLabel
    Left = 16
      Top = 56
      Width = 74
      Height = 16
      Caption = 'Data Item3:'
  end
  object Label5: TLabel
    Left = 0
      Top = 88
      Width = 265
      Height = 16
      Caption = 'Выбор данных | Ввод данных для изменения значений.'
  end
  object Label6: TLabel
    Left = 96
      Top = 24
      Width = 8
      Height = 16
      Caption = '0'
  end
  object Label7: TLabel
    Left = 96
      Top = 40
      Width = 8
      Height = 16
      Caption = '0'
  end
  object Label8: TLabel
    Left = 96
      Top = 56
      Width = 8
      Height = 16
      Caption = '0'
  end
  object MainMenu1: TMainMenu
    Left = 352
      Top = 24
      object File1: TMenuItem
      Caption = '&Файл'
        object Exit1: TMenuItem
        Caption = '&Выход'
          OnClick = Exit1Click
      end
    end
    object Data1: TMenuItem
      Caption = '&Данные'
        object EnterData1: TMenuItem
        Caption = '&Ввод данных'
          OnClick = EnterData1Click
      end
      object Clear1: TMenuItem
        Caption = '&Очистить'
          OnClick = Clear1Click
      end
    end
  end
end
{ ***  КОНЕЦ КОДА DDESVRU.DFM *** }
{ *** НАЧАЛО КОДА DDESVRU.PAS *** }
{***************************************************}
{                                                   }
{   Delphi 1.0 DDEML Демонстрационная программа     }
{   Copyright (c) 1996 by Borland International     }
{                                                   }
{***************************************************}
{ Данный демонстрационный пример использует библиотеку DDEML
на стороне сервера кооперативного приложения. Данный сервер
является простым приложением для ввода данных и позволяет
оператору осуществлять ввод трех элементов данных, которые
становятся доступными через DDE "заинтересованным" клиентам.
Данный сервер предоставляет свои услуги (сервисы) для данных
со следующими именами:
Service: 'DataEntry'
Topic  : 'SampledData'
Items  : 'DataItem1', 'DataItem2', 'DataItem3'
В-принципе, в качестве сервисов могли бы быть определены
и другие темы. Полезными темами, на наш взгляд, могут быть
исторические даты, информация о сэмплах и пр..
Вы должны запустить этот сервер ПЕРЕД тем как запустите
клиента (DDEMLCLI.PAS), в противном случае клиент не
сможет установить связь.
Интерфейс для этого сервера определен как список имен
(Service, Topic и Items) в отдельном модуле с именем
DataEntry (DATAENTR.TPU). Сервер делает Items доступными
в формате cf_Text; они преобразовываются и хранятся у
клиента локально как целые. }
unit Ddesvru;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Menus,
  DDEML, { DDE APi }
  ShellApi;
const
  NumValues = 3;
  DataItemNames: array[1..NumValues] of PChar = ('DataItem1',
    'DataItem2',
    'DataItem3');
type
  TDataString = array[0..20] of Char; { Размер элемента как текста }
  TDataSample = array[1..NumValues] of Integer;
  {type
  { Структура данных, составляющих образец }
  {  TDataSample = array [1..NumValues] of Integer;
  {  TDataString = array [0..20] of Char;     { Размер элемента как текста }
const
  DataEntryName: PChar = 'DataEntry';
  DataTopicName: PChar = 'SampledData';
type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Exit1: TMenuItem;
    Data1: TMenuItem;
    EnterData1: TMenuItem;
    Clear1: TMenuItem;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    procedure Exit1Click(Sender: TObject);
    function MatchTopicAndService(Topic, Service: HSz): Boolean;
    function MatchTopicAndItem(Topic, Item: HSz): Integer;
    function WildConnect(Topic, Service: HSz; ClipFmt: Word): HDDEData;
    function AcceptPoke(Item: HSz; ClipFmt: Word;
      Data: HDDEData): Boolean;
    function DataRequested(TransType: Word; ItemNum: Integer;
      ClipFmt: Word): HDDEData;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure EnterData1Click(Sender: TObject);
    procedure Clear1Click(Sender: TObject);
  private
    Inst: Longint;
    CallBack: TCallback;
    ServiceHSz: HSz;
    TopicHSz: HSz;
    ItemHSz: array[1..NumValues] of HSz;
    ConvHdl: HConv;
    Advising: array[1..NumValues] of Boolean;
    DataSample: TDataSample;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
uses DDEDlg; { Форма DataEntry }
{$R *.DFM}
procedure TForm1.Exit1Click(Sender: TObject);
begin
  Close;
end;
{ Глобальная инициализация }
const
  DemoTitle: PChar = 'DDEML демо, серверное приложение';
  MaxAdvisories = 100;
  NumAdvLoops: Integer = 0;
  { Локальная функция: Процедура обратного вызова для DDEML }
  { Данная функция обратного вызова реагирует на все транзакции,
  генерируемые DDEML. Объект "target Window" (окно-цель)
  берется из глобально хранимых, и для реагирования на данную
  транзакцию, тип которой указан в параметре CallType,
  используются подходящие методы этих объектов.}
function CallbackProc(CallType, Fmt: Word; Conv: HConv; HSz1, HSz2: HSZ;
  Data: HDDEData; Data1, Data2: Longint): HDDEData; export;
var
  ItemNum: Integer;
begin
  CallbackProc := 0; { В противном случае смотрите доказательство }
  case CallType of
    xtyp_WildConnect:
      CallbackProc := Form1.WildConnect(HSz1, HSz2, Fmt);
    xtyp_Connect:
      if Conv = 0 then
      begin
        if Form1.MatchTopicAndService(HSz1, HSz2) then
          CallbackProc := 1; { Связь! }
      end;
    { После подтверждения установки соединения записываем
    дескриптор связи как родительское окно.}
    xtyp_Connect_Confirm:
      Form1.ConvHdl := Conv;
    { Клиент запрашивает данные, делает прямой запрос или
    отвечает на уведомление. Возвращаем текущее состояние данных.}
    xtyp_AdvReq, xtyp_Request:
      begin
        ItemNum := Form1.MatchTopicAndItem(HSz1, HSz2);
        if ItemNum > 0 then
          CallbackProc := Form1.DataRequested(CallType, ItemNum, Fmt);
      end;
    { Отвечаем на Poke-запрос ... данная демонстрация допускает
    только Pokes для DataItem1. Для подтверждения получения
    запроса возвращаем dde_FAck, в противном случае 0.}
    xtyp_Poke:
      begin
        if Form1.AcceptPoke(HSz2, Fmt, Data) then
          CallbackProc := dde_FAck;
      end;
    { Клиент сделал запрос для старта цикла-уведомления.
    Имейте в виду, что мы организуем "горячий" цикл.
    Устанавливаем флаг Advising для указания открытого
    цикла, который будет проверять данные на предмет
    их изменения.}
    xtyp_AdvStart:
      begin
        ItemNum := Form1.MatchTopicAndItem(HSz1, HSz2);
        if ItemNum > 0 then
        begin
          if NumAdvLoops < MaxAdvisories then
          begin { Произвольное число }
            Inc(NumAdvLoops);
            Form1.Advising[ItemNum] := True;
            CallbackProc := 1;
          end;
        end;
      end;
    { Клиент сделал запрос на прерывание цикла-уведомления.}
    xtyp_AdvStop:
      begin
        ItemNum := Form1.MatchTopicAndItem(HSz1, HSz2);
        if ItemNum > 0 then
        begin
          if NumAdvLoops > 0 then
          begin
            Dec(NumAdvLoops);
            if NumAdvLoops = 0 then
              Form1.Advising[ItemNum] := False;
            CallbackProc := 1;
          end;
        end;
      end;
  end; { Case CallType }
end;
{ Возращает True, если данные Topic и Service поддерживаются
этим приложением. В противном случае возвращается False.}
function TForm1.MatchTopicAndService(Topic, Service: HSz): Boolean;
begin
  Result := False;
  if DdeCmpStringHandles(TopicHSz, Topic) = 0 then
    if DdeCmpStringHandles(ServiceHSz, Service) = 0 then
      Result := True;
end;
{ Определяем, один ли Topic и Item поддерживается этим
приложением. Возвращаем номер заданного элемента (Item Number)
(в пределах 1..NumValues), если он обнаружен, и ноль в
противном случае.}
function TForm1.MatchTopicAndItem(Topic, Item: HSz): Integer;
var
  I: Integer;
begin
  Result := 0;
  if DdeCmpStringHandles(TopicHSz, Topic) = 0 then
    for I := 1 to NumValues do
      if DdeCmpStringHandles(ItemHSz[I], Item) = 0 then
        Result := I;
end;
{ Отвечаем на запрос wildcard-соединения (дословно -
дикая карта, шаблон). Такие запросы возникают всякий раз,
когда клиент пытается подключиться к серверу с сервисом
или именем топика, установленного в 0. Если сервер
обнаруживает использование такого рода шаблона, он
возвращает дескриптор массива THSZPair, содержащего
найденные по шаблону Service и Topic.}
function TForm1.WildConnect(Topic, Service: HSz; ClipFmt: Word): HDDEData;
var
  TempPairs: array[0..1] of THSZPair;
  Matched: Boolean;
begin
  TempPairs[0].hszSvc := ServiceHSz;
  TempPairs[0].hszTopic := TopicHSz;
  TempPairs[1].hszSvc := 0; { 0-завершает список }
  TempPairs[1].hszTopic := 0;
  Matched := False;
  if (Topic = 0) and (Service = 0) then
    Matched := True { Шаблон обработан, элементов не найдено }
  else if (Topic = 0) and (DdeCmpStringHandles(Service, ServiceHSz) = 0) then
    Matched := True
  else if (DdeCmpStringHandles(Topic, TopicHSz) = 0) and (Service = 0) then
    Matched := True;
  if Matched then
    WildConnect := DdeCreateDataHandle(Inst, @TempPairs, SizeOf(TempPairs),
      0, 0, ClipFmt, 0)
  else
    WildConnect := 0;
end;
{ Принимаем и проталкиваем данные по просьбе клиента.
Для демонстрации этого способа используем только
значение DataItem1, изменяемое Poke.}
function TForm1.AcceptPoke(Item: HSz; ClipFmt: Word;
  Data: HDDEData): Boolean;
var
  DataStr: TDataString;
  Err: Integer;
  TempSample: Integer;
begin
  if (DdeCmpStringHandles(Item, ItemHSz[1]) = 0) and
    (ClipFmt = cf_Text) then
  begin
    DdeGetData(Data, @DataStr, SizeOf(DataStr), 0);
    Val(DataStr, TempSample, Err);
    if IntToStr(TempSample) <> Label6.Caption then
    begin
      Label6.Caption := IntToStr(TempSample);
      DataSample[1] := TempSample;
      if Advising[1] then
        DdePostAdvise(Inst, TopicHSz, ItemHSz[1]);
    end;
    AcceptPoke := True;
  end
  else
    AcceptPoke := False;
end;
{ Возвращаем данные, запрашиваемые значениями TransType
и ClipFmt. Такое может произойти в ответ на просьбу
xtyp_Request или xtyp_AdvReq. Параметр ItemNum указывает
на поддерживаемый (в диапазоне 1..NumValues) и требуемый
элемент (обратите внимание на то, что данный метод
подразумевает, что вызывающий оператор уже установил
достоверность и ID требуемого пункта с помощью
MatchTopicAndItem). Соответствующие данные из переменной
экземпляра DataSample преобразуются в текст и возвращаются
клиенту.}
function TForm1.DataRequested(TransType: Word; ItemNum: Integer;
  ClipFmt: Word): HDDEData;
var
  ItemStr: TDataString; { Определено в DataEntry.TPU }
begin
  if ClipFmt = cf_Text then
  begin
    Str(DataSample[ItemNum], ItemStr);
    DataRequested := DdeCreateDataHandle(Inst, @ItemStr,
      StrLen(ItemStr) + 1, 0, ItemHSz[ItemNum], ClipFmt, 0);
  end
  else
    DataRequested := 0;
end;
{ Создаем экземпляр окна DDE сервера. Вызываем унаследованный
конструктор, затем устанавливаем эти объекты родителями
экземпляров данных. }
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  Inst := 0; { Должен быть нулем для первого вызова DdeInitialize }
  @CallBack := nil; { MakeProcInstance вызывается из SetupWindow         }
  for I := 1 to NumValues do
  begin
    DataSample[I] := 0;
    Advising[I] := False;
  end; { for }
end;
{ Разрушаем экземпляр окна DDE сервера. Проверяем, был ли
создан экземпляр процедуры обратного вызова, если он существует.
Также, для завершения диалога, вызовите DdeUninitialize.
Затем, для завершения работы, вызовите разрушителя предка.}
procedure TForm1.FormDestroy(Sender: TObject);
var
  I: Integer;
begin
  if ServiceHSz <> 0 then
    DdeFreeStringHandle(Inst, ServiceHSz);
  if TopicHSz <> 0 then
    DdeFreeStringHandle(Inst, TopicHSz);
  for I := 1 to NumValues do
    if ItemHSz[I] <> 0 then
      DdeFreeStringHandle(Inst, ItemHSz[I]);
  if Inst <> 0 then
    DdeUninitialize(Inst); { Игнорируем возвращаемое значение }
  if @CallBack <> nil then
    FreeProcInstance(@CallBack);
end;
procedure TForm1.FormShow(Sender: TObject);
var
  I: Integer;
  { Завершаем инициализацию окна DDE сервера. Процедура инициализации
  использует DDEML для регистрации сервисов, предусмотренных данным
  приложением. Помните о том, что реальные имена, использованные в
  регистрах, определены в отдельном модуле (DataEntry), поэтому они
  могут быть использованы и клиентом. }
begin
  @CallBack := MakeProcInstance(@CallBackProc, HInstance);
  if DdeInitialize(Inst, CallBack, 0, 0) = dmlErr_No_Error then
  begin
    ServiceHSz := DdeCreateStringHandle(Inst, DataEntryName, cp_WinAnsi);
    TopicHSz := DdeCreateStringHandle(Inst, DataTopicName, cp_WinAnsi);
    for I := 1 to NumValues do
      ItemHSz[I] := DdeCreateStringHandle(Inst, DataItemNames[I],
        cp_WinAnsi);
    if DdeNameService(Inst, ServiceHSz, 0, dns_Register) = 0 then
      ShowMessage('Ошибка в процессе регистрации.');
  end;
end;
procedure TForm1.EnterData1Click(Sender: TObject);
{ Активизируем диалог ввода данных и обновляем
хранимые данные по окончании ввода.}
var
  I: Integer;
begin
  if DataEntry.ShowModal = mrOk then
  begin
    with DataEntry do
    begin
      Label6.Caption := S1;
      Label7.Caption := S2;
      Label8.Caption := S3;
      DataSample[1] := StrToInt(S1);
      DataSample[2] := StrToInt(S2);
      DataSample[3] := StrToInt(S3);
    end; { with }
    for I := 1 to NumValues do
      if Advising[I] then
        DdePostAdvise(Inst, TopicHSz, ItemHSz[I]);
  end; { if }
end;
procedure TForm1.Clear1Click(Sender: TObject);
{ Очищаем текущую дату. }
var
  I: Integer;
begin
  for I := 1 to NumValues do
  begin
    DataSample[I] := 0;
    if Advising[I] then
      DdePostAdvise(Inst, TopicHSz, ItemHSz[I]);
  end;
  Label6.Caption := '0';
  Label7.Caption := '0';
  Label8.Caption := '0';
end;
end.
{ ***  КОНЕЦ КОДА DDESVRU.PAS *** }
{ *** НАЧАЛО КОДА DDEDLG.DFM *** }
object DataEntry: TDataEntry
  Left = 488
    Top = 132
    ActiveControl = OKBtn
    BorderStyle = bsDialog
    Caption = 'Ввод данных'
    ClientHeight = 264
    ClientWidth = 199
    Font.Color = clBlack
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = [fsBold]
    PixelsPerInch = 96
    Position = poScreenCenter
    OnShow = FormShow
    TextHeight = 13
    object Bevel1: TBevel
    Left = 8
      Top = 8
      Width = 177
      Height = 201
      Shape = bsFrame
      IsControl = True
  end
  object OKBtn: TBitBtn
    Left = 16
      Top = 216
      Width = 69
      Height = 39
      Caption = '&OK'
      ModalResult = 1
      TabOrder = 3
      OnClick = OKBtnClick
      Glyph.Data = {
    BE060000424DBE06000000000000360400002800000024000000120000000100
    0800000000008802000000000000000000000000000000000000000000000000
    80000080000000808000800000008000800080800000C0C0C000C0DCC000F0CA
    A600000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000
    000000000000000000000000000000000000F0FBFF00A4A0A000808080000000
    FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00030303030303
    0303030303030303030303030303030303030303030303030303030303030303
    03030303030303030303030303030303030303030303FF030303030303030303
    03030303030303040403030303030303030303030303030303F8F8FF03030303
    03030303030303030303040202040303030303030303030303030303F80303F8
    FF030303030303030303030303040202020204030303030303030303030303F8
    03030303F8FF0303030303030303030304020202020202040303030303030303
    0303F8030303030303F8FF030303030303030304020202FA0202020204030303
    0303030303F8FF0303F8FF030303F8FF03030303030303020202FA03FA020202
    040303030303030303F8FF03F803F8FF0303F8FF03030303030303FA02FA0303
    03FA0202020403030303030303F8FFF8030303F8FF0303F8FF03030303030303
    FA0303030303FA0202020403030303030303F80303030303F8FF0303F8FF0303
    0303030303030303030303FA0202020403030303030303030303030303F8FF03
    03F8FF03030303030303030303030303FA020202040303030303030303030303
    0303F8FF0303F8FF03030303030303030303030303FA02020204030303030303
    03030303030303F8FF0303F8FF03030303030303030303030303FA0202020403
    030303030303030303030303F8FF0303F8FF03030303030303030303030303FA
    0202040303030303030303030303030303F8FF03F8FF03030303030303030303
    03030303FA0202030303030303030303030303030303F8FFF803030303030303
    030303030303030303FA0303030303030303030303030303030303F803030303
    0303030303030303030303030303030303030303030303030303030303030303
    0303}
    Margin = 2
      NumGlyphs = 2
      Spacing = -1
      IsControl = True
  end
  object CancelBtn: TBitBtn
    Left = 108
      Top = 216
      Width = 69
      Height = 39
      Caption = '&Отмена'
      TabOrder = 4
      Kind = bkCancel
      Margin = 2
      Spacing = -1
      IsControl = True
  end
  object Panel2: TPanel
    Left = 16
      Top = 88
      Width = 153
      Height = 49
      BevelInner = bvLowered
      BevelOuter = bvNone
      TabOrder = 1
      object Label1: TLabel
      Left = 24
        Top = 8
        Width = 5
        Height = 13
    end
    object Label2: TLabel
      Left = 8
        Top = 8
        Width = 48
        Height = 13
        Caption = 'Значение 2:'
    end
    object Edit2: TEdit
      Left = 8
        Top = 24
        Width = 121
        Height = 20
        MaxLength = 10
        TabOrder = 0
        Text = '0'
    end
  end
  object Panel1: TPanel
    Left = 16
      Top = 16
      Width = 153
      Height = 49
      BevelInner = bvLowered
      BevelOuter = bvNone
      TabOrder = 0
      object Label4: TLabel
      Left = 8
        Top = 8
        Width = 48
        Height = 13
        Caption = 'Значение 1:'
    end
    object Edit1: TEdit
      Left = 8
        Top = 24
        Width = 121
        Height = 20
        MaxLength = 10
        TabOrder = 0
        Text = '0'
    end
  end
  object Panel3: TPanel
    Left = 16
      Top = 144
      Width = 153
      Height = 49
      BevelInner = bvLowered
      BevelOuter = bvNone
      TabOrder = 2
      object Label6: TLabel
      Left = 8
        Top = 8
        Width = 48
        Height = 13
        Caption = 'Значение 3:'
    end
    object Edit3: TEdit
      Left = 8
        Top = 24
        Width = 121
        Height = 20
        MaxLength = 10
        TabOrder = 0
        Text = '0'
    end
  end
end
{ ***   КОНЕЦ КОДА DDEDLG.DFM *** }
{ *** НАЧАЛО КОДА DDEDLG.PAS *** }
{***************************************************}
{                                                   }
{   Delphi 1.0 DDEML Демонстрационная программа     }
{   Copyright (c) 1996 by Borland International     }
{                                                   }
{***************************************************}
{ Данный модуль определяет интерфейс сервера DataEntry DDE
(DDEMLSRV.PAS). Здесь определены имена Service, Topic,
и Item, поддерживаемые сервером, и также определена
структура данных, которая может использоваться
клиентом для локального хранения "показательных" данных.
Сервер Data Entry Server делает свои "показательные"
данные доступными в текстовом виде (cf_Text)
сформированными в виде трех различных топика (Topics).
Клиент может их преобразовывать в целое для
использования со структурой данных, которая здесь определена.
}
unit Ddedlg;
interface
uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Buttons,
  StdCtrls, Mask, ExtCtrls;
type
  TDataEntry = class(TForm)
    OKBtn: TBitBtn;
    CancelBtn: TBitBtn;
    Bevel1: TBevel;
    Panel2: TPanel;
    Label1: TLabel;
    Label2: TLabel;
    Panel1: TPanel;
    Label4: TLabel;
    Panel3: TPanel;
    Label6: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    procedure OKBtnClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    S1, S2, S3: string;
    { Public declarations }
  end;
var
  DataEntry: TDataEntry;
implementation
{$R *.DFM}
procedure TDataEntry.OKBtnClick(Sender: TObject);
begin
  S1 := Edit1.Text;
  S2 := Edit2.Text;
  S3 := Edit3.Text;
end;
procedure TDataEntry.FormShow(Sender: TObject);
begin
  Edit1.Text := '0';
  Edit2.Text := '0';
  Edit3.Text := '0';
  Edit1.SetFocus;
end;
end.
{ ***  КОНЕЦ КОДА DDEDLG.PAS *** }

DDE - передача текста.

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

Если Microsoft будет выпускать автомобили, то они несомненно станут бестселлером из-за их надежной тормозной системы.

Вот я как работаю с Excel:

type
  DDEClientConv1.SetLink('Excel', 'Sheet1');
try
  DDEClientConv1.OpenLink;
  DDEClientItem1.DDEItem := 'R1C1';
  DDEClientConv1.PokeData(DDEClientItem1.DDEItem,
    StrPCopy(P, SomeString)));
finally
  DDEClientConv1.CloseLink;
end;

Как вы можете здесь видеть, свойство DDEItem определяется сервером. Если ваш сервер является приложением Delphi, то DDEItem - имя DDEServerItem. На вашем месте я бы не стал так долго заниматься отладкой DDE-программ. Воспользуйтесь синхронизацией, позволяющей понять при отладке правильность действий.

DDE для захвата текущего URL из окна Internet Explorer или Netscape Navigator.

Статьи по Delphi (005)…
Вопрос: "Можно ли сообщение "Программа выполнила недопустимую операцию....обратитесь к разработчику" считать официальным вызовом в США?
uses
  windows, ddeman, ...
function Get_URL(Servicio: string): string;
var
  Cliente_DDE: TDDEClientConv;
  temp: PChar;      //
begin
  Result := '';
  Cliente_DDE:= TDDEClientConv.Create( nil );
  with Cliente_DDE do
  begin
    SetLink( Servicio,'WWW_GetWindowInfo');
    temp := RequestData('0xFFFFFFFF');
    Result := StrPas(temp);
    StrDispose(temp);  // 
    CloseLink;
  end;
  Cliente_DDE.Free;
end;
procedure TForm1.Button1Click(Sender);
begin
   showmessage(Get_URL('Netscape'));
// или
   showmessage(Get_URL('IExplore'));
end;

DDE.

Оформил: DeeCo

Содержание Обзор Основы DDE Использование DDE DDE-серверы DDE-клиенты Управление ReportSmith по DDE Обзор В данной статье приводятся основные факты о DDEML и показывается, как можно использовать DDE в программе. Предмет данной статьи технически сложен, однако библиотека Delphi упрощает наиболее трудные аспекты программирования DDE . В статье предполагается, что читатель может знать очень мало о предмете. Цель статьи - научить его использовать концепцию DDE при создании приложений в среде Delphi. Основы DDE Аббревиатура DDEML обозначает Dynamic Data Exchange Management Library (библиотека управления динамическим обменом данными). DDEML это надстройка над сложной системой сообщений, называемой Dynamic Data Exchange (DDE). Библиотека, содержащая DDE била разработана для усиления возможностей первоначальной системы сообщений Windows. DDE дает возможность перейти через рамки приложения и взаимодействовать с другими приложениями и системами Windows. Dynamic Data Exchange получило свое имя потому, что позволяет двум приложениям обмениваться данными (текстовыми, через глобальную память) динамически во время выполнения. Связь между двумя программами можно установить таким образом, что изменения в одном приложении будут отражаться во втором. Например, если Вы меняете число в электронной таблице, то во втором приложении данные обновятся автоматически и отобразят изменения. Кроме того, с помощью DDE можно из своего приложения управлять другими приложениями такими, как Word for Windows, Report Smith, Excel и др. Надеюсь, что данное краткое вступление поможет понять что предмет обсуждения представляет интерес. Далее рассказывается, как использовать компоненты Delphi для построения DDE приложений. Использование DDE Приложение, получающее данные из другого приложения по DDE и/или управляющее другим приложением с помощью команд через DDE является DDE-клиентом. В этом случае второе приложение является DDE-сервером. Одно и то-же приложение может быть одновременно и сервером, и клиентом (например, MicroSoft Word). Построение DDE-серверов и DDE-клиентов удобно рассмотреть на примере, поставляемом с Delphi (каталог x:\delphi\demos\ddedemo). Сперва давайте рассмотрим логику работы примера. Для начала нужно откомпилировать проекты DDESRVR.DPR и DDECLI.DPR, после этого запустите программу DDECLI.EXE (DDE-клиент) и выберите пункт меню File|New Link. При этом должна запуститься программа DDESRVR (DDE-сервер). Если теперь редактировать текст в окне сервера, то изменения мгновенно отразятся в приложении-клиенте (см. рис.1, рис.2) Статьи по Delphi (005)… Рис.1: Приложение - DDE-сервер. Здесь идет редактирование текста. Статьи по Delphi (005)… Рис.2: Приложение - DDE-клиент. Здесь отображаются изменения. Пример демонстрирует и другие возможности DDE: - пересылка данных из клиента на сервер (Poke Data); наберите любой текст в правом окне DDE-клиента и нажмите кнопку Poke Data, этот текст появится в окне сервера. - исполнение команд (макросов) на сервере; наберите любой текст в правом окне DDE-клиента и нажмите кнопку Exec Macro, DDE-сервер выдаст соответствующее диалоговое окно. - установление связи через Clipboard; закройте оба DDE-приложения и запустите их заново, затем в DDE-сервере выберите пункт меню Edit|Copy, далее в клиенте выберите пункт меню Edit|Paste Link. Теперь давайте рассмотрим эти демонстрационные программы с технической точки зрения и узнаем, каким образом в Delphi можно создать DDE-приложения. Начнем с DDE-сервера. DDE-серверы На рис.3 представлен пример DDE-сервера во время дизайна в среде Delphi. Статьи по Delphi (005)… Рис.3: DDE-сервер в среде Delphi. Для построении DDE-сервера в Delphi имеются два объекта, расположенные на странице System Палитры Компонент - TDdeServerConv и TDdeServerItem. Обычно в проекте используется один объект TDdeServerConv и один или более TDdeServerItem. Для получения доступа к сервису DDE-сервера, клиенту потребуется знать несколько параметров : имя сервиса (Service Name) - это имя приложения (обычно - имя выполняемого файла без расширения EXE, возможно с полным путем); Topic Name - в Delphi это имя компоненты TDdeServerConv; Item Name - в Delphi это имя нужной компоненты TDdeServerItem. Назначение объекта TDdeServerConv - общее управление DDE и обработка запросов от клиентов на выполнение макроса. Последнее выполняется в обработчике события OnExecuteMacro, например, как это сделано в нашем случае:
procedure TDdeSrvrForm.doMacro(Sender: TObject;
  Msg: TStrings);
var
  Text: string;
begin
  Text := '';
  if Msg.Count &gt;
  0 then
    Text := Msg.Strings[0];
  MessageDlg('Executing Macro - ' + Text, mtInformation,
    [mbOK], 0);
end;
Объект TDdeServerItem связывается с TDdeServerConv и определяет, что, собственно, будет пересылаться по DDE. Для этого у него есть свойства Text и Lines. (Text имеет то же значение, что и Lines[0].) При изменении значения этих свойств автоматически происходит пересылка обновленных данных во все приложения-клиенты, установившие связь с сервером. В нашем приложении изменение значения свойства Lines происходит в обработчике события OnChange компонента Edit1:
procedure TDdeSrvrForm.doOnChange(Sender: TObject);
begin
  if not FInPoke then
    DdeTestItem.Lines := Edit1.Lines;
end;
Этот же компонент отвечает за получение данных от клиента, в нашем примере это происходило при нажатии кнопки Poke Data, это выполняется в обработчике события OnPokeData:
procedure TDdeSrvrForm.doOnPoke(Sender: TObject);
begin
  FInPoke := True;
  Edit1.Lines := DdeTestItem.Lines;
  FInPoke := False;
end;
И последнее - установление связи через Clipboard. Для этого служит метод CopyToClipboard объекта TDdeServerItem. Необходимая информации помещается в Clipboard и может быть вызвана из приложения-клиента при установлении связи. Обычно, в DDE-серверах для этого есть специальный пункт меню Paste Special или Paste Link. Итак, мы рассмотрели пример полнофункционального DDE-сервера, построенного с помощью компонент Delphi. Очень часто существующие DDE-серверы не полностью реализуют возможности DDE и предоставляют только часть сервиса. Например, ReportSmith позволяет по DDE только выполнять команды (макросы). DDE-клиенты На рис.4 представлен пример DDE-клиента во время дизайна в среде Delphi. Статьи по Delphi (005)… Рис.4: DDE-клиент в среде Delphi. Для построения DDE-клиента в Delphi используются два компонента TDDEClientConv и TDDEClientItem. Аналогично серверу, в программе обычно используется один объект TDDEClientConv и один и более связанных с ним TDDEClientItem. TDDEClientConv служит для установления связи с сервером и общим управлением DDE-связью. Установить связь с DDE-сервером можно как во время дизайна, так и во время выполнения программы, причем двумя способами. Первый способ - заполнить вручную необходимые свойства компонента. Это DdeService, DdeTopic и ServiceApplication. Во время дизайна щелкните дважды на одно из первых двух свойств в Инспекторе Объектов - Вы получите диалог для определения DDE-связи (см. рис.5). Статьи по Delphi (005)… Рис.5: Диалог для установления связи с DDE-сервером (Report Smith). Укажите в диалоге имена DDE Service и DDE Topic. Эти имена можно узнать из документации по тому DDE-серверу, с которым Вы работаете. В случае DDE-сервера, созданного на Delphi, это имя программы (без .EXE) и имя объекта TDdeServerConv. Для установления связи через Clipboard в диалоге есть специальная кнопка Past Link. Ей можно воспользоваться, если Вы запустили DDE-сервер, сохранили каким-то образом информацию о связи и вошли в этот диалог. Например, если DDE-сервером является DataBase Desktop, то нужно загрузить в него какую-нибудь таблицу Paradox, выбрать любое поле и выбрать пункт меню Edit|Copy. После этого войдите в диалог и нажмите кнопку Paste Link. Поля в диалоге заполнятся соответствующим образом. Свойство ServiceApplication заполняется в том случае, если в поле DDEService содержится имя, отличное от имени программы, либо если эта программа не находится в текущей директории. В этом поле указывается полный путь и имя программы без расширения (.EXE). При работе с Report Smith здесь нужно указать, например : C:\RPTSMITH\RPTSMITH Данная информация нужна для автоматического запуска сервера при установлении связи по DDE, если тот еще не был запущен. В нашей демо-программе связь устанавливается во время выполнения программы в пунктах меню File|New Link и Edit|Paste Link. В пункте меню File|New Link программно устанавливается связь по DDE с помощью соответствующего метода объекта TDdeServerConv, OpenLink делать не надо, поскольку свойство ConnectMode имеет значение ddeAutomatic:
procedure TFormD.doNewLink(Sender: TObject);
begin
  DdeClient.SetLink(AppName.Text, TopicName.Text);
  DdeClientItem.DdeConv := DdeClient;
  DdeClientItem.DdeItem := ItemName.Text;
end;
Здесь же заполняются свойства объекта TDdeClietItem. В пункте меню Edit|Past Link программно устанавливается связь по DDE с использованием информации из Clipboard:
procedure TFormD.doPasteLink(Sender: TObject);
var
  Service, Topic, Item: string;
begin
  if GetPasteLinkInfo(Service, Topic, Item) then
  begin
    AppName.Text := Service;
    TopicName.Text := Topic;
    ItemName.Text := Item;
    DdeClient.SetLink(Service, Topic);
    DdeClientItem.DdeConv := DdeClient;
    DdeClientItem.DdeItem := ItemName.Text;
  end;
end;
После того, как установлена связь, нужно позаботиться о поступающих по DDE данных, это делается в обработчике события OnChange объекта TDdeClietItem:
procedure TFormD.DdeClientItemChange(Sender: TObject);
begin
  DdeDat.Lines := DdeClientItem.Lines;
end;
Это единственная задача объекта TDdeClientItem. На объект TDdeClientConv возлагаются еще две задачи : пересылка данных на сервер и выполнение макросов. Для этого у данного объекта есть соответствующие методы. Посмотрим, как это можно было бы сделать. Выполнение макроса на сервере:
procedure TFormD.doMacro(Sender: TObject);
begin
  DdeClient.ExecuteMacroLines(XEdit.Lines, True);
end;
Пересылка данных на сервер:
procedure TFormD.doPoke(Sender: TObject);
begin
  DdeClient.PokeDataLines(DdeClientItem.DdeItem, XEdit.Lines);
end;
Управление ReportSmith по DDE В прилагаемом примере run-time версия ReportSmith выполняет команду, переданную по DDE. Имена DDE сервиса для ReportSmith и некоторых других приложений можно узнать в Справочнике в среде ReportSmith. Перед запуском примера нужно правильно установить в свойстве ServiceApplication путь к run-time версии ReportSmith и в тексте программы в строке Cmd:='LoadReport "c:\d\r\video\summary.rpt","@Repvar1=<40>,@Repvar2=<'#39'Smith'#39'>"'#0; правильно указать путь к отчету.

Регистрация программ в меню Пуск Windows95.

- Можно ли по-настоящему любить двух женщин одновременно? - Можно, но только под UNIX.

Подобная проблема возникает при создании инсталляторов и деинсталляторов. Наиболее простой и гибкий путь - использование DDE. При этом посылаются запросы к PROGMAN. Для этого необходимо поместить на форму компонент для посылки DDE запросов - объект типа TDdeClientConv. Для определенности назовем его DDEClient. Затем добавим метод для запросов к PROGMAN:

function TForm2.ProgmanCommand(Command: string): boolean;
var
  macrocmd: array[0..88] of char;
begin
  DDEClient.SetLink('PROGMAN', 'PROGMAN');
  DDEClient.OpenLink; { Устанавливаем связь по DDE }
  strPCopy(macrocmd, '[' + Command + ']'); { Подготавливаем ASCIIZ строку }
  ProgmanCommand := DDEClient.ExecuteMacro(MacroCmd, false);
  DDEClient.CloseLink; { Закрываем связь по DDE }
end;
// Пример использования:
ProgmanCommand('CreateGroup(Комплекс программ для
  каталогизации литературы, )');
ProgmanCommand('AddItem(' + path + 'vbase.hlp, Справка по VBase,
  '+ path +' vbase.hlp, 0, , , '+ path + ', , )');
// где path - строка типа String, содержащая
// полный путь к каталогу ('C:\Catalog\');

При вызове ProgmanCommand возвращает true, если посылка макроса была успешна. Система команд (основных) приведена ниже:

Create(Имя группы, путь к GRP файлу) Создать группу с именем "Имя группы", причем в нем могут быть пробелы и знаки препинания. Путь к GRP файлу можно не указывать, тогда он создастся в каталоге Windows. Delete(Имя группы) Удалить группу с именем "Имя группы" ShowGroup(Имя группы, состояние) Показать группу в окне, причем состояние - число, определяющее параметры окна: 1-нормальное состояние + активация 2-миним.+ активация 3-макс. + активация 4-нормальное состояние 5-Активация AddItem(командная строка, имя раздела, путь к иконке, индекс иконки (с 0), Xpos,Ypos, рабочий каталог, HotKey, Mimimize) Добавить раздел к активной группе. В командной строке, имени размера и путях допустимы пробелы, Xpos и Ypos - координаты иконки в окне, лучше их не задавать, тогда PROGMAN использует значения по умолчанию для свободного места. HotKey - виртуальный код горячей клавиши. Mimimize - тип запуска, 0-в обычном окне, <>0 - в минимизированном. DeleteItem(имя раздела) Удалить раздел с указанным именем в активной группе

DDE для вызова диалога поиска файлов и папок.

Статьи по Delphi (005)…
uses DdeMan;
procedure TForm1.Button1Click(Sender: TObject);
begin
  with TDDEClientConv.Create(Self) do
  begin
    ConnectMode := ddeManual;
    ServiceApplication := 'explorer.exe';
    SetLink( 'Folders', 'AppProperties');
    OpenLink;
    ExecuteMacro('[FindFolder(, C:\Мои документы)]', False);
    CloseLink;
    Free;
  end;
end;

Пример DDE и WordPerfect.

Автор: John Studt

Вот небольшой пример, скопированный из моего проекта:

procedure TForm1.PrintSave(Doc: string);
var
  ProdDoc, ArchDoc: string;
  WPCommands: TStringList;
begin
  ProdDoc := ProdDrive + Doc;
  ArchDoc := ArchDrive + Doc;
  WPCommands := TStringList.Create;
  with WPCommands do
  begin
    Add('FileOpen(Filename:"' + ProdDoc + '")');
    Add('FileSave(Filename:"' + ProdDoc + '";ExportType:3;Overwrite:1)');
    Add('PrintCopies(NumberOfCopies:2)');
    Add('PrintCopiesBy(CopiesBy:1) ');
    Add('PrintFullDoc() ');
    Add('DocCompare(FileName:"' + ArchDoc + '";CompFlags:1) ');
    Add('FileSave(Filename:"' + EMailDoc + '";ExportType:3;Overwrite:1');
    Add('Close(Save:0) ');
  end;
  if PDDE.ExecuteMacroLines(WPCommands, True) then
  begin
    log('WPCommand Worked!')
      {  Теперь необходимо подождать WP, чтобы завершить команду... }
  end
  else
    log('Ошибка WPCommand!');
  WPCommands.Free;
end;
Примечание: Вы не можете использовать 'True!' или 'False!', как это делается в макросах WP. Вы должны использовать числовые значения. Как узнать числовой эквивалент команды: если в WP использовать построитель макросов, то можно передавать перечислимые типы в диалоговое окно и узнавать их числовой эквивалент.

Все это проверено, DDE работает в связке WP/Delphi, первая команда возвращает сообщение 'Ok, я получил это', и запускает макрос. При попытке послать второй запрос DDE, он ожидает завершение обработки первого, выводит сообщение типа 'Необходимо подождать....', после чего немедленно передает управление. Мне хотелось бы дождаться команды завершения прежде, чем я возвращусь из своей процедуры.

Работа с Netscape Navigator через DDE.

Статьи по Delphi (005)…
Инетчика жена отправила в магазин за кофе. Продавщица его спрашивает: - Вам какой кофе? - Ну этот, самый, как его... Нетскейп. - ???? - Блин!!!! home.netscape.com
uses DDEman;
...
procedure GotoURL(sURL: string);
var
  dde: TDDEClientConv;
begin
  dde := TDDEClientConv.Create(nil);
  with dde do
  begin
    // specify the location of netscape.exe
    ServiceApplication :='C:\Program Files\Netscape\Communicator\Program\NETSCAPE.EXE';
    // activate the Netscape Navigator
    SetLink( 'Netscape', 'WWW_Activate' );
    RequestData('0xFFFFFFFF');
    // go to the specified URL
    SetLink( 'Netscape', 'WWW_OpenURL' );
    RequestData( sURL+',,0xFFFFFFFF,0x3,,,' );
    // ... CloseLink;
  end;
  dde.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  GotoURL('http://www.site.ru');
end;

Деактивация приложения.

Автор: Marco Romanini (Delphi Tech Support)

Если вы хотите что-то сделать когда ваше приложение теряет фокус, используйте обработчик события Application.onDeactivate. Добавьте следующую строку в обработчик формы FormCreate:

Application.OnDeactivate=AppDeactivate; 

Затем создайте следующий метод:

procedure Form1.AppDeactivate(Sender: TObject);
begin
  ...
  { здесь ваш код}
  ...
end;

Набор данных для отладки.

unit DebugSet;
interface
uses
  DbTables, Classes, Controls, Db, SysUtils;
type
  TDebugNotify = procedure(const DegubStr: string) of object;
  TDebugDataSet = class(TTable)
  private
    FDebugNotify: TDebugNotify;
  protected
    // TDataSet virtual abstract methods
    function AllocRecordBuffer: PChar; override;
    procedure FreeRecordBuffer(var Buffer: PChar); override;
    procedure GetBookmarkData(Buffer: PChar;
      Data: Pointer); override;
    function GetBookmarkFlag(Buffer: PChar):
      TBookmarkFlag; override;
    function GetFieldData(Field: TField;
      Buffer: Pointer): Boolean; override;
    function GetRecord(Buffer: PChar; GetMode: TGetMode;
      DoCheck: Boolean): TGetResult; override;
    function GetRecordSize: Word; override;
    procedure InternalAddRecord(Buffer: Pointer;
      Append: Boolean); override;
    procedure InternalClose; override;
    procedure InternalDelete; override;
    procedure InternalFirst; override;
    procedure InternalGotoBookmark(
      Bookmark: Pointer); override;
    procedure InternalHandleException; override;
    procedure InternalInitFieldDefs; override;
    procedure InternalInitRecord(Buffer: PChar); override;
    procedure InternalLast; override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    procedure InternalSetToRecord(Buffer: PChar); override;
    function IsCursorOpen: Boolean; override;
    procedure SetBookmarkFlag(Buffer: PChar;
      Value: TBookmarkFlag); override;
    procedure SetBookmarkData(Buffer: PChar;
      Data: Pointer); override;
    procedure SetFieldData(Field: TField;
      Buffer: Pointer); override;
    // TDataSet virtual methods (optional)
    procedure InternalRefresh; override;
    function GetRecordCount: Integer; override;
    procedure SetRecNo(Value: Integer); override;
    function GetRecNo: Integer; override;
  public
    property OnDebugNotify: TDebugNotify
      read FDebugNotify write FDebugNotify;
  end;
implementation
procedure TDebugDataSet.InternalOpen;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalOpen');
  inherited;
end;
procedure TDebugDataSet.InternalInitFieldDefs;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalInitFieldDefs');
  inherited;
end;
procedure TDebugDataSet.InternalClose;
begin
  FDebugNotify('InternalClose');
  inherited;
end;
function TDebugDataSet.IsCursorOpen: Boolean;
begin
  Result := inherited IsCursorOpen;
  if Result then
    if Assigned(FDebugNotify) then
      FDebugNotify('IsCursorOpen: True')
    else if Assigned(FDebugNotify) then
      FDebugNotify('IsCursorOpen: False');
end;
procedure TDebugDataSet.InternalGotoBookmark(Bookmark: Pointer);
begin
  FDebugNotify('InternalGotoBookmark' +
    IntToStr(Integer(Bookmark)));
  inherited;
end;
procedure TDebugDataSet.InternalSetToRecord(Buffer: PChar);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalSetToRecord');
  inherited;
end;
function TDebugDataSet.GetBookmarkFlag(
  Buffer: PChar): TBookmarkFlag;
begin
  FDebugNotify('GetBookmarkFlag');
  Result := inherited GetBookmarkFlag(Buffer);
end;
procedure TDebugDataSet.SetBookmarkFlag(Buffer: PChar;
  Value: TBookmarkFlag);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('SetBookmarkFlag');
  inherited;
end;
procedure TDebugDataSet.GetBookmarkData(
  Buffer: PChar; Data: Pointer);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('GetBookmarkData');
  inherited;
end;
procedure TDebugDataSet.SetBookmarkData(
  Buffer: PChar; Data: Pointer);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('SetBookmarkData');
  inherited;
end;
function TDebugDataSet.GetRecordSize: Word;
begin
  Result := inherited GetRecordSize;
  if Assigned(FDebugNotify) then
    FDebugNotify('GetRecordSize: ' + IntToStr(Result));
end;
function TDebugDataSet.AllocRecordBuffer: PChar;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('AllocRecordBuffer');
  Result := inherited AllocRecordBuffer;
end;
procedure TDebugDataSet.InternalInitRecord(Buffer: PChar);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalInitRecord');
  inherited;
end;
procedure TDebugDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('FreeRecordBuffer');
  inherited;
end;
function TDebugDataSet.GetRecord(Buffer: PChar;
  GetMode: TGetMode; DoCheck: Boolean): TGetResult;
begin
  case GetMode of
    gmNext:
      if Assigned(FDebugNotify) then
        FDebugNotify('GetRecord: Next');
    gmPrior:
      if Assigned(FDebugNotify) then
        FDebugNotify('GetRecord: Prior');
    gmCurrent:
      if Assigned(FDebugNotify) then
        FDebugNotify('GetRecord: Current');
  end;
  Result := inherited GetRecord(Buffer, GetMode, DoCheck);
end;
procedure TDebugDataSet.InternalFirst;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalFirst');
  inherited;
end;
procedure TDebugDataSet.InternalLast;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalLast');
  inherited;
end;
procedure TDebugDataSet.InternalPost;
begin
  if State = dsEdit then
    if Assigned(FDebugNotify) then
      FDebugNotify('InternalPost ***** dsEdit')
    else if Assigned(FDebugNotify) then
      FDebugNotify('InternalPost ***** dsInsert');
  inherited;
end;
procedure TDebugDataSet.InternalAddRecord(
  Buffer: Pointer; Append: Boolean);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalAddRecord ****');
  inherited;
end;
procedure TDebugDataSet.InternalDelete;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalDelete');
  inherited;
end;
function TDebugDataSet.GetFieldData(
  Field: TField; Buffer: Pointer): Boolean;
begin
  Result := inherited GetFieldData(Field, Buffer);
end;
procedure TDebugDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('SetFieldData');
  inherited;
end;
function TDebugDataSet.GetRecordCount: Longint;
begin
  Result := inherited GetRecordCount;
  if Assigned(FDebugNotify) then
    FDebugNotify('GetRecordCount: ' + IntToStr(Result));
end;
function TDebugDataSet.GetRecNo: Longint;
begin
  Result := inherited GetRecNo;
  if Assigned(FDebugNotify) then
    FDebugNotify('GetRecNo: ' + IntToStr(Result));
end;
procedure TDebugDataSet.SetRecNo(Value: Integer);
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('SetRecNo: ' + IntToStr(Value));
  inherited;
end;
procedure TDebugDataSet.InternalRefresh;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalRefresh');
  inherited;
end;
procedure TDebugDataSet.InternalHandleException;
begin
  if Assigned(FDebugNotify) then
    FDebugNotify('InternalHandleException');
  inherited;
end;
end.
unit DebugDSForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs, DebugSet, Grids, DBGrids, DBCtrls, ExtCtrls,
  StdCtrls, Db, DBTables, Menus;
type
  TForm1 = class(TForm)
    Table1: TTable;
    DataSource1: TDataSource;
    ListBox1: TListBox;
    Splitter1: TSplitter;
    Panel1: TPanel;
    DBNavigator1: TDBNavigator;
    DBGrid1: TDBGrid;
    Panel2: TPanel;
    BtnOpen: TButton;
    BtnClose: TButton;
    BtnClear: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure BtnClearClick(Sender: TObject);
    procedure BtnOpenClick(Sender: TObject);
    procedure BtnCloseClick(Sender: TObject);
  private
    TableClone: TDebugDataSet;
  public
    procedure LocalNotify(const DebugText: string);
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  Table1.Close;
  TableClone := TDebugDataSet.Create(nil);
  TableClone.DatabaseName := Table1.DatabaseName;
  TableClone.TableName := Table1.TableName;
  TableClone.OnDebugNotify := LocalNotify;
  DataSource1.DataSet := TableClone;
end;
procedure TForm1.LocalNotify(const DebugText: string);
var
  nItem: Integer;
begin
  if Assigned(ListBox1) then
  begin
    // add the text to the string
    nItem := ListBox1.Items.Add(DebugText);
    // select the new item
    ListBox1.ItemIndex := nItem;
  end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  TableClone.Close;
  TableClone.Free;
end;
procedure TForm1.BtnClearClick(Sender: TObject);
begin
  ListBox1.Items.Clear;
end;
procedure TForm1.BtnOpenClick(Sender: TObject);
begin
  TableClone.Active := True;
end;
procedure TForm1.BtnCloseClick(Sender: TObject);
begin
  TableClone.Active := False;
end;
end.

В режиме отладки приложения не разрешается открытие базы данных.

Автор: Nomadic
"Bugs" - фильм ужасов для программистов-отладчиков.
Необходимо отключить (деинсталлировать через Oracle Installer) Trace Service на клиенте - совет от ORACLE.

Глюк имеет место быть только под Windows NT 4.xx.

Крах отладчика при передаче параметров.

Оформил: DeeCo

Автор: Александр Малыгин.

type
  TBTZ = packed record
    case integer of
      0: (BTZ1: array[0..6] of byte;
        BTZ2: array[0..1] of byte;
        BTZ3: array[0..1] of byte;
        BTZ4: array[0..2] of byte);
      1: (BTZW, BTZH: array[0..6] of byte);
      2: (BTZ: array[0..13] of byte);
  end;
  TPrmTS = packed record
    PrmC, PrmN: array[0..3] of TBTZ;
  end;
procedure Stone(var P: TPrmTS);
begin
  // ...
end;
При прогоне без остановки никаких сбоев не происходит. Если сделать шаг или отпустить на выполнение внутри процедуры, отладчик тихо исчезает вместе с приложением, без каких либо сообщений. Проблема не проявляется, если записи не packed, либо если TPrmTS содержит только одно поле, либо если массивы PrmC,PrmN состоят из менее 4 элементов. Манипуляции с размерами массивов и вставкой дополнительных полей в TBTZ перед case приводят к разным результатам: ничего не происходит; исключение Access Violation; исключение Privileged Instruction. В последних двух случаях аналогичное сообщение возникает при закрытии приложения, отладчик не может выйти из режима отладки и приходится перезагружать Delphi.

РЕШЕНИЕ: Передавать проблемный тип как нетипизированный var-параметр.

Определение и использование курсора.

Если твоя мама - женщина, если Клава - для тебя имя, а мышь - грызун, то ты "ламер"!!!

Сначала (поскольку многие попадаются в эту ловушку) убедитесь в том, что имя .RES-файла, в котором вы храните ваш курсор, отличается от имени .RES-файла вашего проекта, т.е., если ваш проект имеет имя MyApp.DPR, то не используйте для хранения новых ресурсов файл MyApp.RES. Вы должны создать отдельный .RES-файл с другим именем (например, MyApp01.RES) и включить его в ваш проект, например так:

implementation
{$R MyApp01.Res}
Вы не можете назначить курсор свойству компонента Cursor или DragCursor из .RES-файла напрямую, необходимо выполнить несколько промежуточных шагов. В каждом проекте Delphi определяет глобальный объект с именем Screen (тип TScreen), который, между прочим, определяет массив курсоров, называемый, как ни странно, Cursors. Когда вы щелкаете на свойстве Cursor/DragCursor в Инспекторе объектов, выпадающий список и есть список элементов указанного массива. Для предустановленных курсоров Delphi использует элементы массива с индексами начиная с -1 и ниже (т.е. только отрицательные числа), поэтому собственные курсоры вы можете размещать с порядковым номером, начинающимся с нуля и выше.

Для начала определите константу, допустим так:

Const
  MyCursor = 1;

Далее необходимо загрузить курсор. Сделать это можно в обработчике события формы OnCreate:

Screen.Cursors[MyCursor] := LoadCursor(HInstance, 'MYCURSOR');

Затем просто установите в свойстве DragCursor любого элемента управления:

MyListbox.DragCursor := MyCursor;

Примечание: имя вашего курсора всегда должно писаться в ВЕРХНЕМ регистре, как при вызове LoadCursor, так и в его названии в .RES-файле.

Определение присутствия функции в DLL.

Данная функция определяет присутствие нужной функции в библиотеке (dll) и, в случае нахождения искомой функции возвращает True, иначе False.

function FuncAvail (VLibraryname, VFunctionname: string;
         var VPointer: pointer): boolean;
var
  Vlib: tHandle;
begin
  Result := false;
  VPointer := nil;
  if LoadLibrary(PChar(VLibraryname)) = 0 then
    exit;
  VPointer := GetModuleHandle(PChar(VLibraryname));
  if Vlib <> 0 then
  begin
    VPointer := GetProcAddress(Vlib, PChar(VFunctionname));
    if VPointer <> nil then
      Result := true;
  end;
end;

Декларация события OnPassword.

Автор: Tim Gooch Мне необходимо написать процедуру для OnPassword с использованием TPasswordEvent. Но я никак не могу его прикрутить к объекту! Объявление TPasswordEvent в исходном коде VCL неверно (исправлено в Delphi 2.0). Оно должно включать в себя ключевые слова "of object", как и все остальное объявления данного типа. Есть два пути решения проблемы: A) Изменить исходный код VCL B) Создать не-стандартный обработчик события, работающий с неправильно декларированным. Решение A - мой выбор, но для этого необходимо, чтобы вы имели копию исходного кода VCL. (Не пытайтесь пересобрать библиотеку VCL; просто измените файл и добавьте путь к файлу DB.PAS file в пути вашего проекта.)

Решение B немного более прагматичное и не требует изменения исходного кода VCL. Создайте следующую функцию:

procedure PasswordProc(Sender: TObject; var Continue: Boolean); far;
begin
  Session.AddPassword('Harrison');
  Continue := True;
end;

Затем измените обработчик события OnCreate...

procedure TForm1.FormCreate(Sender: TObject);
begin
  Session.OnPassword := PasswordProc;
  Table1.Active := True;
end;
Важно чтобы вы добавляли ключевое слово "far" в конец объявления данной функции. Если этого не сделать, вам понадобиться добавлять объявление функций в секцию модуля Interface, чтобы заставить Delphi скомпилировать их как "far"-процедуры (которые вызываются из другого модуля).

Если вы решаете воспользоваться методом A, то единственное отличие будет заключаться в том, что вам понадобиться добавить объявление процедуры к вашему классу формы, и в секции Implementation ваша процедура должна выглядеть примерно так:

procedure TForm1.PasswordProc(Sender: TObject; var Continue: Boolean);
begin
  Session.AddPassword('Harrison');
  Continue := True;
end;

Поскольку эта версия является функцией-членом, у нее существует преимущество при доступе к компонентам и private- или protected-членам вашей формы (TForm-производного класса).

Декомпиляция в Delphi.

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

Двухлетняя дочь программера спрашивает у мамы: - А правда, что программы после деинсталяции попадают на небо, на огромный сервер, который никогда не виснет? Мама задумчиво отвечает: - Если бы у твоего папы сервер никогда не вис, то ты, наверное, еще и не скомпилировалась бы...

Читая форумы по программированию, иногда натыкаешься на вопрос типа: "У меня есть откомпилированная программа на Delphi. Как мне получить её исходный код?". Обычно такой вопрос возникает, когда программист потерял файлы проекта и у него остался только .exe. Как правило полностью восстановить исходный код на языке высокого уровня невозможно. Значит ли это, что другие тоже не смогут восстановить исходный код Вашей программы ? Хм ... и да и нет ...

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

После компиляции и линковки проекта и получения исполняемого файла все имена, используемые в программе конвертируются в адреса. Потеря имён означет, что декомпилятор создаст уникальное имя для каждой константы, переменной, функции и процедуры. Даже если мы и достигнем какого-то успеха в декомпиляции исполняемого файла, то получим уже другой синтаксис программы. Данная проблема связана с тем, что при компиляции практически идентичные куски кода могут быть скомпилированы в разные последовательности машинных команд (ASM), которые присутствуют в .exe файле. Естевственно декомпилятор не обладает такой степенью интеллектуальности, чтобы решить - какова же была последовательность инструкций языка высокого уровня в исходном проекте.

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

Восстановление исходного кода; Перенос приложения на другую платформу; Определение наличия вирусов в коде программы или вредоносного кода; Исправление ошибок в программе, в случае, если создатель приложения не собирается этого делать :)

Легально ли всё это? Хотя декомпиляция и не является взломом, но утвердительно ответить на этот вопрос довольно сложно. Обычно программы защищены законом об авторских правах, однако в большинстве стран на декомпиляцию делается исключение. В часности, когда необходимо изменить интерфейс программы для конкретной страны, а сервис приложения не позволяет этого сделать.

На данный момент Borland не предоставляет никаких программных продуктов, способных декомпилировать исполняемые файлы (.exe) либо откомпилированные Delphi-модули (.dcu) в исходный код (.pas).

Если же Вы всё-таки решились попробовать декомпилировать исполняемый файл, то необходимо знать следующие вещи. Исходные коды на Delphi обычно хранятся в файлах двух типов: сам исходник в ASCII кодировке (.pas, .dpr) и файлы ресурсов (.res, .rc, .dfm, .dcr). Dfm файлы хранят в себе свойства объектов, содержащихся в форме. При создании конечного .exe, Delphi копирует в него информацию из .dfm файлов. Каждый раз, когда мы изменяем координаты формы, описания кнопок или связанные с ними события, то Delphi записывает эти изменения в .dfm (за исключением кода процедур. Он сохраняется в файлах pas/dcu ). И наконец, чтобы получить при декомпиляции файл .dfm, нужно знать - какие типы ресурсов хранятся внутри Win32 исполняемого модуля.

Все программы, скомпилированные в Delphi имеют следующие секции: CODE, DATA, BSS, .idata, tls, .rdata, .rsrc. Самые важные для декомпиляции секции CODE и .rsrc. В статье "Adding functionality to a Delphi program" приведены некоторые интересные факты о исполняемых форматах Delphi, а так же информация о классах и DFM ресурсах. В этой статье есть один интересный момент под заголовком: "Как добавить свой обработчик события в уже откомпилированный файл, например, чтобы изменять тект на кнопке".

Среди многих типов ресурсов, которые сохранены в .exe файле, интерес представляет RT_RCDATA, который хранит информацию, которая были в DFM файле перед трансляцией. Чтобы извлеч DFM данные из .exe файла, мы можем вызываться API функцией EnumResourceNames.

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

И в заключение, если Вы заинтересовались декомпилованием, то предлагаю Вам несколько Delphi декомпиляторов:

DeDe DeDe довольно шустрая программка, позволяющая анализировать экзешники, скомпилированные в Delphi. После декомпиляции DeDe даёт Вам следующее: Все dfm файлы. Вы сможете открывать их и редактировать в Delphi Все объявленные методы с хорошо комментированным кодом на ассемблере с ссылками на строки, импортированных функций, методов и компонент в юните, блоки Try-Except и Try-Finally. Большое количество дополнительной информации. Вы можете создать папку Delphi проекта со всеми файлами dfm, pas, dpr. Не забудьте, что pas файлы содержат ассемблерный код. Revendepro Revendepro находит почти все структуры (классы, типы, процедуры, и т.д.) в программе, и генерирует их паскальное представление, процедуры естевственно будут представлены на языке ассемблера. К сожалению, полученный ассемблерный код не может быть заново откомпилирован. Так же доступен исходник этого декомпилятора. К сожалению, этот декомпилятор не совсем рабочий - генерирует ошибку при декомпиляции. MRIP Позволяет извлекать из Delphi приложения любые ресурсы: курсоры, иконки, dfm файлы, pas файлы и т.д. Но главная его особенность - это способность извлекать файлы, хранящиеся в других файлах. Поддерживается более 100 форматов файлов. MRip работает под DOS. Exe2Dpr Эта программа может восстановить частично потерянные исходники проекта. Не имеет интерфейса и работает с командной строки, например: 'exe2dpr [-o] exeFile' ( исходники проекта будут созданы в текущей директории).

Декомпилляция звукового файла формата Wave и получение звуковых данных.

ИМХО - это аббревиатура. Истинное Мнение Хр#н Оспоришь.

Интересно, есть ли технология преобразования Wave-формата в обычный набор звуковых данных? К примеру, мне необходимо удалить заголовок и механизм (метод) сжатия, которые могут компилироваться и сохраняться вместе с Wave-файлами.

У меня есть программа под D1/D2, которая читает WAV-файлы и вытаскивает исходные данные, но она не может их восстанавить, используя зашитый алгоритм сжатия.

unit LinearSystem;
interface
{============== Тип, описывающий формат WAV ==================}
type
  WAVHeader = record
    nChannels: Word;
    nBitsPerSample: LongInt;
    nSamplesPerSec: LongInt;
    nAvgBytesPerSec: LongInt;
    RIFFSize: LongInt;
    fmtSize: LongInt;
    formatTag: Word;
    nBlockAlign: LongInt;
    DataSize: LongInt;
  end;
  {============== Поток данных сэмпла ========================}
const
  MaxN = 300; { максимальное значение величины сэмпла }
type
  SampleIndex = 0..MaxN + 3;
type
  DataStream = array[SampleIndex] of Real;
var
  N: SampleIndex;
  {============== Переменные сопровождения ======================}
type
  Observation = record
    Name: string[40]; {Имя данного сопровождения}
    yyy: DataStream; {Массив указателей на данные}
    WAV: WAVHeader; {Спецификация WAV для сопровождения}
    Last: SampleIndex; {Последний доступный индекс yyy}
    MinO, MaxO: Real; {Диапазон значений yyy}
  end;
var
  K0R, K1R, K2R, K3R: Observation;
  K0B, K1B, K2B, K3B: Observation;
  {================== Переменные имени файла ===================}
var
  StandardDatabase: string[80];
  BaseFileName: string[80];
  StandardOutput: string[80];
  StandardInput: string[80];
  {=============== Объявления процедур ==================}
procedure ReadWAVFile(var Ki, Kj: Observation);
procedure WriteWAVFile(var Ki, Kj: Observation);
procedure ScaleData(var Kk: Observation);
procedure InitAllSignals;
procedure InitLinearSystem;
implementation
{$R *.DFM}
uses VarGraph, SysUtils;
{================== Стандартный формат WAV-файла ===================}
const
  MaxDataSize: LongInt = (MaxN + 1) * 2 * 2;
const
  MaxRIFFSize: LongInt = (MaxN + 1) * 2 * 2 + 36;
const
  StandardWAV: WAVHeader = (
    nChannels: Word(2);
    nBitsPerSample: LongInt(16);
    nSamplesPerSec: LongInt(8000);
    nAvgBytesPerSec: LongInt(32000);
    RIFFSize: LongInt((MaxN + 1) * 2 * 2 + 36);
    fmtSize: LongInt(16);
    formatTag: Word(1);
    nBlockAlign: LongInt(4);
    DataSize: LongInt((MaxN + 1) * 2 * 2)
    );
  {================== Сканирование переменных сопровождения ===================}
procedure ScaleData(var Kk: Observation);
var
  I: SampleIndex;
begin
  {Инициализация переменных сканирования}
  Kk.MaxO := Kk.yyy[0];
  Kk.MinO := Kk.yyy[0];
  {Сканирование для получения максимального и минимального значения}
  for I := 1 to Kk.Last do
  begin
    if Kk.MaxO < Kk.yyy[I] then
      Kk.MaxO := Kk.yyy[I];
    if Kk.MinO > Kk.yyy[I] then
      Kk.MinO := Kk.yyy[I];
  end;
end; { ScaleData }
procedure ScaleAllData;
begin
  ScaleData(K0R);
  ScaleData(K0B);
  ScaleData(K1R);
  ScaleData(K1B);
  ScaleData(K2R);
  ScaleData(K2B);
  ScaleData(K3R);
  ScaleData(K3B);
end; {ScaleAllData}
{================== Считывание/запись WAV-данных ===================}
var
  InFile, OutFile: file of Byte;
type
  Tag = (F0, T1, M1);
type
  FudgeNum = record
    case X: Tag of
      F0: (chrs: array[0..3] of Byte);
      T1: (lint: LongInt);
      M1: (up, dn: Integer);
  end;
var
  ChunkSize: FudgeNum;
procedure WriteChunkName(Name: string);
var
  i: Integer;
  MM: Byte;
begin
  for i := 1 to 4 do
  begin
    MM := ord(Name[i]);
    write(OutFile, MM);
  end;
end; {WriteChunkName}
procedure WriteChunkSize(LL: Longint);
var
  I: integer;
begin
  ChunkSize.x := T1;
  ChunkSize.lint := LL;
  ChunkSize.x := F0;
  for I := 0 to 3 do
    Write(OutFile, ChunkSize.chrs[I]);
end;
procedure WriteChunkWord(WW: Word);
var
  I: integer;
begin
  ChunkSize.x := T1;
  ChunkSize.up := WW;
  ChunkSize.x := M1;
  for I := 0 to 1 do
    Write(OutFile, ChunkSize.chrs[I]);
end; {WriteChunkWord}
procedure WriteOneDataBlock(var Ki, Kj: Observation);
var
  I: Integer;
begin
  ChunkSize.x := M1;
  with Ki.WAV do
  begin
    case nChannels of
      1: if nBitsPerSample = 16 then
        begin {1..2 Помещаем в буфер одноканальный 16-битный сэмпл}
          ChunkSize.up := trunc(Ki.yyy[N] + 0.5);
          if N < MaxN then
            ChunkSize.dn := trunc(Ki.yyy[N + 1] + 0.5);
          N := N + 2;
        end
        else
        begin {1..4 Помещаем в буфер одноканальный 8-битный сэмпл}
          for I := 0 to 3 do
            ChunkSize.chrs[I]
              := trunc(Ki.yyy[N + I] + 0.5);
          N := N + 4;
        end;
      2: if nBitsPerSample = 16 then
        begin {2 Двухканальный 16-битный сэмпл}
          ChunkSize.dn := trunc(Ki.yyy[N] + 0.5);
          ChunkSize.up := trunc(Kj.yyy[N] + 0.5);
          N := N + 1;
        end
        else
        begin {4 Двухканальный 8-битный сэмпл}
          ChunkSize.chrs[1] := trunc(Ki.yyy[N] + 0.5);
          ChunkSize.chrs[3] := trunc(Ki.yyy[N + 1] + 0.5);
          ChunkSize.chrs[0] := trunc(Kj.yyy[N] + 0.5);
          ChunkSize.chrs[2] := trunc(Kj.yyy[N + 1] + 0.5);
          N := N + 2;
        end;
    end; {with WAV do begin..}
  end; {четырехбайтовая переменная "ChunkSize" теперь заполнена}
  ChunkSize.x := T1;
  WriteChunkSize(ChunkSize.lint); {помещаем 4 байта данных}
end; {WriteOneDataBlock}
procedure WriteWAVFile(var Ki, Kj: Observation);
var
  MM: Byte;
  I: Integer;
  OK: Boolean;
begin
  {Приготовления для записи файла данных}
  AssignFile(OutFile, StandardOutput); { Файл, выбранный в диалоговом окне }
  ReWrite(OutFile);
  with Ki.WAV do
  begin
    DataSize := nChannels * (nBitsPerSample div 8) * (Ki.Last + 1);
    RIFFSize := DataSize + 36;
    fmtSize := 16;
  end;
  {Записываем ChunkName "RIFF"}
  WriteChunkName('RIFF');
  {Записываем ChunkSize}
  WriteChunkSize(Ki.WAV.RIFFSize);
  {Записываем ChunkName "WAVE"}
  WriteChunkName('WAVE');
  {Записываем tag "fmt_"}
  WriteChunkName('fmt ');
  {Записываем ChunkSize}
  Ki.WAV.fmtSize := 16; {должно быть 16-18}
  WriteChunkSize(Ki.WAV.fmtSize);
  {Записываем  formatTag, nChannels}
  WriteChunkWord(Ki.WAV.formatTag);
  WriteChunkWord(Ki.WAV.nChannels);
  {Записываем  nSamplesPerSec}
  WriteChunkSize(Ki.WAV.nSamplesPerSec);
  {Записываем  nAvgBytesPerSec}
  WriteChunkSize(Ki.WAV.nAvgBytesPerSec);
  {Записываем  nBlockAlign, nBitsPerSample}
  WriteChunkWord(Ki.WAV.nBlockAlign);
  WriteChunkWord(Ki.WAV.nBitsPerSample);
  {Записываем метку блока данных "data"}
  WriteChunkName('data');
  {Записываем DataSize}
  WriteChunkSize(Ki.WAV.DataSize);
  N := 0; {первая запись-позиция}
  while N <= Ki.Last do
    WriteOneDataBlock(Ki, Kj); {помещаем 4 байта и увеличиваем счетчик N}
  {Освобождаем буфер файла}
  CloseFile(OutFile);
end; {WriteWAVFile}
procedure InitSpecs;
begin
end; { InitSpecs }
procedure InitSignals(var Kk: Observation);
var
  J: Integer;
begin
  for J := 0 to MaxN do
    Kk.yyy[J] := 0.0;
  Kk.MinO := 0.0;
  Kk.MaxO := 0.0;
  Kk.Last := MaxN;
end; {InitSignals}
procedure InitAllSignals;
begin
  InitSignals(K0R);
  InitSignals(K0B);
  InitSignals(K1R);
  InitSignals(K1B);
  InitSignals(K2R);
  InitSignals(K2B);
  InitSignals(K3R);
  InitSignals(K3B);
end; {InitAllSignals}
var
  ChunkName: string[4];
procedure ReadChunkName;
var
  I: integer;
  MM: Byte;
begin
  ChunkName[0] := chr(4);
  for I := 1 to 4 do
  begin
    Read(InFile, MM);
    ChunkName[I] := chr(MM);
  end;
end; {ReadChunkName}
procedure ReadChunkSize;
var
  I: integer;
  MM: Byte;
begin
  ChunkSize.x := F0;
  ChunkSize.lint := 0;
  for I := 0 to 3 do
  begin
    Read(InFile, MM);
    ChunkSize.chrs[I] := MM;
  end;
  ChunkSize.x := T1;
end; {ReadChunkSize}
procedure ReadOneDataBlock(var Ki, Kj: Observation);
var
  I: Integer;
begin
  if N <= MaxN then
  begin
    ReadChunkSize; {получаем 4 байта данных}
    ChunkSize.x := M1;
    with Ki.WAV do
      case nChannels of
        1: if nBitsPerSample = 16 then
          begin {1..2 Помещаем в буфер одноканальный 16-битный сэмпл}
            Ki.yyy[N] := 1.0 * ChunkSize.up;
            if N < MaxN then
              Ki.yyy[N + 1] := 1.0 * ChunkSize.dn;
            N := N + 2;
          end
          else
          begin {1..4 Помещаем в буфер одноканальный 8-битный сэмпл}
            for I := 0 to 3 do
              Ki.yyy[N + I] := 1.0 * ChunkSize.chrs[I];
            N := N + 4;
          end;
        2: if nBitsPerSample = 16 then
          begin {2 Двухканальный 16-битный сэмпл}
            Ki.yyy[N] := 1.0 * ChunkSize.dn;
            Kj.yyy[N] := 1.0 * ChunkSize.up;
            N := N + 1;
          end
          else
          begin {4 Двухканальный 8-битный сэмпл}
            Ki.yyy[N] := 1.0 * ChunkSize.chrs[1];
            Ki.yyy[N + 1] := 1.0 * ChunkSize.chrs[3];
            Kj.yyy[N] := 1.0 * ChunkSize.chrs[0];
            Kj.yyy[N + 1] := 1.0 * ChunkSize.chrs[2];
            N := N + 2;
          end;
      end;
    if N <= MaxN then
    begin {LastN    := N;}
      Ki.Last := N;
      if Ki.WAV.nChannels = 2 then
        Kj.Last := N;
    end
    else
    begin {LastN    := MaxN;}
      Ki.Last := MaxN;
      if Ki.WAV.nChannels = 2 then
        Kj.Last := MaxN;
    end;
  end;
end; {ReadOneDataBlock}
procedure ReadWAVFile(var Ki, Kj: Observation);
var
  MM: Byte;
  I: Integer;
  OK: Boolean;
  NoDataYet: Boolean;
  DataYet: Boolean;
  nDataBytes: LongInt;
begin
  if FileExists(StandardInput) then
    with Ki.WAV do
    begin { Вызов диалога открытия файла }
      OK := True; {если не изменится где-нибудь ниже}
      {Приготовления для чтения файла данных}
      AssignFile(InFile, StandardInput); { Файл, выбранный в диалоговом окне }
      Reset(InFile);
      {Считываем ChunkName "RIFF"}
      ReadChunkName;
      if ChunkName <> 'RIFF' then
        OK := False;
      {Считываем ChunkSize}
      ReadChunkSize;
      RIFFSize := ChunkSize.lint; {должно быть 18,678}
      {Считываем ChunkName "WAVE"}
      ReadChunkName;
      if ChunkName <> 'WAVE' then
        OK := False;
      {Считываем ChunkName "fmt_"}
      ReadChunkName;
      if ChunkName <> 'fmt ' then
        OK := False;
      {Считываем ChunkSize}
      ReadChunkSize;
      fmtSize := ChunkSize.lint; {должно быть 18}
      {Считываем  formatTag, nChannels}
      ReadChunkSize;
      ChunkSize.x := M1;
      formatTag := ChunkSize.up;
      nChannels := ChunkSize.dn;
      {Считываем  nSamplesPerSec}
      ReadChunkSize;
      nSamplesPerSec := ChunkSize.lint;
      {Считываем  nAvgBytesPerSec}
      ReadChunkSize;
      nAvgBytesPerSec := ChunkSize.lint;
      {Считываем  nBlockAlign}
      ChunkSize.x := F0;
      ChunkSize.lint := 0;
      for I := 0 to 3 do
      begin
        Read(InFile, MM);
        ChunkSize.chrs[I] := MM;
      end;
      ChunkSize.x := M1;
      nBlockAlign := ChunkSize.up;
      {Считываем  nBitsPerSample}
      nBitsPerSample := ChunkSize.dn;
      for I := 17 to fmtSize do
        Read(InFile, MM);
      NoDataYet := True;
      while NoDataYet do
      begin
        {Считываем метку блока данных "data"}
        ReadChunkName;
        {Считываем DataSize}
        ReadChunkSize;
        DataSize := ChunkSize.lint;
        if ChunkName <> 'data' then
        begin
          for I := 1 to DataSize do
            {пропуск данных, не относящихся к набору звуковых данных}
            Read(InFile, MM);
        end
        else
          NoDataYet := False;
      end;
      nDataBytes := DataSize;
      {Наконец, начинаем считывать данные для байтов nDataBytes}
      if nDataBytes > 0 then
        DataYet := True;
      N := 0; {чтение с первой позиции}
      while DataYet do
      begin
        ReadOneDataBlock(Ki, Kj); {получаем 4 байта}
        nDataBytes := nDataBytes - 4;
        if nDataBytes <= 4 then
          DataYet := False;
      end;
      ScaleData(Ki);
      if Ki.WAV.nChannels = 2 then
      begin
        Kj.WAV := Ki.WAV;
        ScaleData(Kj);
      end;
      {Освобождаем буфер файла}
      CloseFile(InFile);
    end
  else
  begin
    InitSpecs; {файл не существует}
    InitSignals(Ki); {обнуляем массив "Ki"}
    InitSignals(Kj); {обнуляем массив "Kj"}
  end;
end; { ReadWAVFile }
{================= Операции с набором данных ====================}
const
  MaxNumberOfDataBaseItems = 360;
type
  SignalDirectoryIndex = 0..MaxNumberOfDataBaseItems;
var
  DataBaseFile: file of Observation;
  LastDataBaseItem: LongInt; {Номер текущего элемента набора данных}
  ItemNameS: array[SignalDirectoryIndex] of string[40];
procedure GetDatabaseItem(Kk: Observation; N: LongInt);
begin
  if N <= LastDataBaseItem then
  begin
    Seek(DataBaseFile, N);
    Read(DataBaseFile, Kk);
  end
  else
    InitSignals(Kk);
end; {GetDatabaseItem}
procedure PutDatabaseItem(Kk: Observation; N: LongInt);
begin
  if N < MaxNumberOfDataBaseItems then
    if N <= LastDataBaseItem then
    begin
      Seek(DataBaseFile, N);
      Write(DataBaseFile, Kk);
      LastDataBaseItem := LastDataBaseItem + 1;
    end
    else
      while LastDataBaseItem <= N do
      begin
        Seek(DataBaseFile, LastDataBaseItem);
        Write(DataBaseFile, Kk);
        LastDataBaseItem := LastDataBaseItem + 1;
      end
  else
    ReportError(1); {Попытка чтения MaxNumberOfDataBaseItems}
end; {PutDatabaseItem}
procedure InitDataBase;
begin
  LastDataBaseItem := 0;
  if FileExists(StandardDataBase) then
  begin
    Assign(DataBaseFile, StandardDataBase);
    Reset(DataBaseFile);
    while not EOF(DataBaseFile) do
    begin
      GetDataBaseItem(K0R, LastDataBaseItem);
      ItemNameS[LastDataBaseItem] := K0R.Name;
      LastDataBaseItem := LastDataBaseItem + 1;
    end;
    if EOF(DataBaseFile) then
      if LastDataBaseItem > 0 then
        LastDataBaseItem := LastDataBaseItem - 1;
  end;
end; {InitDataBase}
function FindDataBaseName(Nstg: string): LongInt;
var
  ThisOne: LongInt;
begin
  ThisOne := 0;
  FindDataBaseName := -1;
  while ThisOne < LastDataBaseItem do
  begin
    if Nstg = ItemNameS[ThisOne] then
    begin
      FindDataBaseName := ThisOne;
      Exit;
    end;
    ThisOne := ThisOne + 1;
  end;
end; {FindDataBaseName}
{======================= Инициализация модуля ========================}
procedure InitLinearSystem;
begin
  BaseFileName := '\PROGRA~1\SIGNAL~1\';
  StandardOutput := BaseFileName + 'K0.wav';
  StandardInput := BaseFileName + 'K0.wav';
  StandardDataBase := BaseFileName + 'Radar.sdb';
  InitAllSignals;
  InitDataBase;
  ReadWAVFile(K0R, K0B);
  ScaleAllData;
end; {InitLinearSystem}
begin {инициализируемый модулем код}
  InitLinearSystem;
end. {Unit LinearSystem}

Разукрасьте Ваше приложение (Skins).

Статьи по Delphi (005)…
Открылся благотворительный фонд сбора средств на новый Boening 747 для глобальной реконструкции здания Microsoft.

SkinForm - это компонент, который поможет Вам создать "не-квадратное" окно и вто же время применить к нему так называемую технологию "skin", наподобие таких популярных программ как WinAMP, WPlay ..., зачем тратить часы на программирование, когда это можно сделать за пару минут! Скачать сам компонент можно с http://www.qwerks.com/download.asp?ProductID=2447. WYSIWYG скин билдер, который входит в состав архива, предназначен для создания информационного файла, описывающего структуру нашей формы. Всё, что надо сделать - это создать несколько картинок, и нарисовать кнопки, текстовые поля и т.д.. Есть возможность постоянно смотреть превью!

Теперь посмотрим, какие нам предоставлены возможности:

поддержка файлов bmp и jpeg поддержка графических кнопок, а так же переключающиеся и радои кнопки поддержка текста, графического текста и цифр поддержка графического track bar поддержка multi skin-ов, то есть Вы можете создать свой скин для каждой формы приложения поддержка внутренних скинов, то есть скины могут быть сохранены в ресурсном файле

Теперь наши действия:

Создаём bitmap файлы для скина Запускаем Skin Builder для создания информационного файла Создаём новый проект. Помещаем компонент TSkinForm в форму Компилируем

Посмотрим на script файл, созданный SkinBuilder-ом:

[VERSION]
Ver=210 // Версия скин-файла 
[BITMAPINFO] 
MaskBitmap=PlayerMask.bmp 
// Bitmap файл маски поверхности формы 
MouseUpBitmap=PlayerMain.jpg 
// Bitmap файл, если кнопка мыши была отпущена 
MouseDownBitmap=PlayerSel.jpg 
// Bitmap файл, если кнопка мыши была нажата 
MouseOnBitmap=PlayerSel.jpg 
// Bitmap файла, если на объекте находится курсор мыши 
NumbersBitmap= 
// Bitmap файл для графических цифр (не обязательно)
TextBitmap= 
// Bitmap файл для графического текста (не обязательно) 
[HOTAREAINFO] 
Count=19 // Общее количество кнопок 
// Описание каждой кнопки 
// number=ID, top, left, width, height, hint [, button state, group name] 
// Значения в [ ] не обязательны 
1=BUTTON_PLAY, 54, 165, 34, 35, Play/Pause 
2=BUTTON_STOP, 129, 165, 19, 35, Stop 
3=BUTTON_NEXT, 90, 165, 18, 17, Next 
4=BUTTON_PREV, 109, 165, 18, 17, Previous 
... 
[DISPLAYINFO] 
Charset=1 // Charset текстовой области 
Count=3 // количество тектовых полей 
// Описание каждой текстовой области 
// number=ID, font name, bold, italic, size, color, top, left, style, default text, hint, width, height 
1=TEXT_LEN, Arial, TRUE, FALSE, 9, $00366835, 116, 137, Normal, [00:00], Length, 51, 18 
2=TEXT_POS, Arial, TRUE, FALSE, 12, $00366835, 55, 133, Normal, 00:00, Position, 52, 23 
3=TEXT_SONG, MS Sans Serif, FALSE, FALSE, 7, $00366835, 56, 112, Normal, Song name, Song name, 111, 21 
[TRACKBARINFO] 
Count=2 // Количество track-баров 
// Описание каждого трак-бара 
// number=ID, trackbar bitmap file, trackbar bitmap file, top, left, length, style, position, hint 
1=TRACKBAR_VOLUME, TrackBar.BMP, TrackBar.BMP, 79, 205, 88, H, 65, Volume 
2=TRACKBAR_POS, TrackBar.BMP, TrackBar.BMP, 79, 217, 88, H, 0, Time

Ну вот Ваше приложение и разукрасилось и, как видите, безо всякого программирования.

Теперь можно добавить код, чтобы перехватывать события на кнопках - MouseUp, Down, Over ...

Далее следует пример кода:

...
procedure TForm1.FormCreate(Sender: TObject);
begin
  //Загружаем скин файл
  SkinForm1.LoadDefaultSkin;
  SkinForm1.SetDisplayText('VOLUME', IntToStr(SkinForm1.GetTrackBarPos('TRACKBAR_VOLUME')));
  SkinForm1.SetDisplayText('TIME', IntToStr(SkinForm1.GetTrackBarPos('TRACKBAR_POS')));
  SkinForm1.SetTrackBarMinMax('TRACKBAR_VOLUME', 20, 80);
end;
procedure TForm1.SkinForm1MouseUpNotify(ID: string; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  pos: TPoint;
begin
  if (Button = mbLeft) then
  begin
    if ID = 'BUTTON_EXIT' then
      Close;
    if ID = 'BUTTON_MINIMIZE' then
      Form1.Perform(WM_SYSCOMMAND, SC_MINIMIZE, 0);
    if ID = 'BUTTON_MENU' then
    begin
      pos := ClientToScreen(Point(X, Y));
      PopupMenu1.Popup(pos.x, pos.y);
    end;
  end;
  if (Button = mbRight) then
  begin
    pos := ClientToScreen(Point(X, Y));
    PopupMenu1.Popup(pos.x, pos.y);
  end;
...

Убывающий индекс.

Я нашел простой способ получения убывающего индекса. В Delphi это получается очень легко и красиво:

Table1.AddIndex('NewIndex', 'CustNo;CustName', [ixDescending]);

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

Передайте данной функции любую десятичную величину (1...3999), и она возвратит строку, содержащую точное значение в римской транскрипции.

function DecToRoman(Decimal: LongInt): string;
const
  Romans: array[1..13] of string =
  ('I', 'IV', 'V', 'IX', 'X', 'XL', 'L', 'XC', 'C', 'CD', 'D', 'CM', 'M');
  Arabics: array[1..13] of Integer =
  (1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000);
var
  i: Integer;
  scratch: string;
begin
  scratch := '';
  for i := 13 downto 1 do
    while (Decimal >= Arabics[i]) do
    begin
      Decimal := Decimal - Arabics[i];
      scratch := scratch + Romans[i];
    end;
  Result := scratch;
end;

Определить SMTP сервер по умолчанию.

Юзер за дедку, дедка за тоссер, тоссер за мэйлер - и отослали мессаж!
{ 
  Here is some code I successfully used te determine 
  the DEFAULT mailaccount, which is used in 
  Outlook Express, to send outgoing mail via SMTP. 
} 
procedure TForm1.ReadRegistryDefaults; 
var 
  Registry: TRegistry; 
  AccountStr: string; 
begin 
  Registry := TRegistry.Create; 
  try 
    Registry.RootKey := hkey_CURRENT_USER; 
    if Registry.OpenKey('software\microsoft\internet account manager', False) then  {} 
    begin 
      AccountStr := Registry.ReadString('default mail account'); 
      Registry.CloseKey; 
      if (AccountStr <> '') then 
        if Registry.OpenKey('software\microsoft\internet account manager\accounts\' + 
          AccountStr, False) then  {} 
        begin 
          Edit_Server.Text  := Registry.ReadString('SMTP Server'); 
          Edit_Account.Text := Registry.ReadString('SMTP Email Address'); 
          Registry.CloseKey; 
        end; 
    end; 
  finally 
    Registry.Free; 
  end; 
end;

Как узнать язык Windows по умолчанию.

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

Сына программера спрашивают на уроке: - Вася, расскажи нам, пожалуйста, русский алфавит. - Й, Ц, У, К, Е, Н...
GetSystemDefaultLangID
GetSystemDefaultLCID
GetLocaleInfo 

Демонстрация DefineProperties.

Автор: Mike Scott(Mobius Ltd.).

Хорошо, создайте на основе опубликованного ниже кода модуль PropDemo.pas и добавьте новый компонент в палитру компонентов. Расположите его на форме и сохраните ее. Затем посмотрите файл DFM каким-либо шестнадцатиричным редактором и проверьте наличие определенных свойств по их именнованным тэгам. Вы можете также попробовать закрыть форму и модуль, а затем открыть его с помощью пункта меню File | Open file..., изменив тип файла в выпадающем списке на *.DFM.

Удачи!

unit PropDemo;
{ Демонстрация DefineProperties.
Mike Scott, CIS 100140,2420. }
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs;
type
  TDemoProps = class(TComponent)
  private
    { Private declarations }
    FStringThing: string;
    FThing: record
      i, j, k: integer;
      x, y: real;
      ch: char;
    end;
    procedure ReadStringThing(Reader: TReader);
    procedure WriteStringThing(Writer: TWriter);
    procedure ReadThing(Stream: TStream);
    procedure WriteThing(Stream: TStream);
  protected
    { Protected declarations }
    procedure DefineProperties(Filer: TFiler); override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
  published
    { Published declarations }
  end;
procedure Register;
implementation
constructor TDemoProps.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  { создайте любые данные, чтобы было что передать в поток}
  FStringThing := 'Всем привет!';
  with FThing do
  begin
    i := 1;
    j := 2;
    k := 3;
    x := PI;
    y := 180 / PI;
    ch := '?';
  end;
end;
procedure TDemoProps.ReadStringThing(Reader: TReader);
begin
  FStringThing := Reader.ReadString;
end;
procedure TDemoProps.WriteStringThing(Writer: TWriter);
begin
  Writer.WriteString(FStringThing);
end;
procedure TDemoProps.ReadThing(Stream: TStream);
begin
  Stream.ReadBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.WriteThing(Stream: TStream);
begin
  Stream.WriteBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('StringThing', ReadStringThing, WriteStringThing,
    FStringThing <> '');
  Filer.DefineBinaryProperty('Thing', ReadThing, WriteThing, true);
end;
procedure Register;
begin
  RegisterComponents('Samples', [TDemoProps]);
end;
end.

Код определения свойств.

Автор: Mike Scott(Mobius Ltd.) Итак вам опять нужно "немного" кода. Вот небольшой примерчик компонента лично для вас и остальных моих читателей. Установите этот компонент в палитру Delphi, бросьте экземпляр на форму, закройте ее и модуль и откройте форму как файл формы, используя в диалоге открытия тип *.dfm. Вы увидите дополнительные свойства 'StringThing' и 'Thing'. Первое - свойство строки, второе - бинарное свойство, фактически запись. Если вы имеете HexEdit (шестнадцатиричный редактор) или что-то аналогичное, взгляните на ваш dfm-файл и вы увидите тэги ваших новых свойств вместе с их именами.

Если TReader/TWriter имеет специфические методы для чтения/записи свойств и вы хотите добавить, например, строку, целое, символ или что-то еще (проверьте описание соответствующих методов TReader в файлах помощи), то в этом случае используйте DefineProperty. В случае сложного объекта используйте DefineBinaryProperty и ваши методы чтения и записи получат TStream вместо TReader/TWriter.

unit PropDemo;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs;
type
  TDemoProps = class(TComponent)
  private
    { Private declarations }
    FStringThing: string;
    FThing: record
      i, j, k: integer;
      x, y: real;
      ch: char;
    end;
    procedure ReadStringThing(Reader: TReader);
    procedure WriteStringThing(Writer: TWriter);
    procedure ReadThing(Stream: TStream);
    procedure WriteThing(Stream: TStream);
  protected
    { Protected declarations }
    procedure DefineProperties(Filer: TFiler); override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
  published
    { Published declarations }
  end;
procedure Register;
implementation
constructor TDemoProps.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  { создайте любые данные, чтобы было что передать в поток}
  FStringThing := 'Всем привет!';
  with FThing do
  begin
    i := 1;
    j := 2;
    k := 3;
    x := PI;
    y := 180 / PI;
    ch := '?';
  end;
end;
procedure TDemoProps.ReadStringThing(Reader: TReader);
begin
  FStringThing := Reader.ReadString;
end;
procedure TDemoProps.WriteStringThing(Writer: TWriter);
begin
  Writer.WriteString(FStringThing);
end;
procedure TDemoProps.ReadThing(Stream: TStream);
begin
  Stream.ReadBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.WriteThing(Stream: TStream);
begin
  Stream.WriteBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('StringThing', ReadStringThing, WriteStringThing,
    FStringThing <> '');
  Filer.DefineBinaryProperty('Thing', ReadThing, WriteThing, true);
end;
procedure Register;
begin
  RegisterComponents('Samples', [TDemoProps]);
end;
end.

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

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

uses
   Registry;
 {....}
 procedure TForm1.Button1Click(Sender: TObject);
 var
   Reg: TRegistry;
   KeyName: string;
   ValueStr: string;
 begin
   Reg := TRegistry.Create;
   try
     Reg.RootKey := HKEY_CLASSES_ROOT;
     KeyName  := 'htmlfile\shell\open\command';
     if Reg.OpenKey(KeyName, False) then
     begin
       ValueStr := Reg.ReadString('');
       Reg.CloseKey;
       Label1.Caption := ValueStr;
     end
     else
       ShowMessage('No Default Webbrowser !');
   finally
     Reg.Free;
   end;
 end;

Библиотека DegisyDb.

Оформил: DeeCo

Автор: Alexander Deleon.

Уважаемые жители Королевства, Вашему вниманию предлагается Библиотека DegisyDb 1.0 Бесплатная библиотека DegisyDb, включает в себя 12 компонент для работы с базами данных и управления ими. Пакет компонент для Delphi 5,6 предназначен для работы или конфигурирования баз данных. Для работы большинства компонент требуется установленный BDE. Degisy Software выражает благодарность Андрею Дроздову, за некоторые функции, которые были скопированы из его модуля QStrings.pas в наш модуль DdaUtils.pas, что позволило достичь максимально лучших результатов для доступа к таблицам Dda. Данная библиотека быстро развивается. Описание функций TDbCheck Компонент для создания или изменения структуры таблиц dBase и Paradox без потери данных по шаблону, хранящемуся в dfm-файле формы, сформированному в design-time на основе эталонных таблиц разработчика. Компонент позволяет без особых усилий приводить в соответствие с новыми изменениями базы данных на машине пользователя, что облегчает сопровождение программ. Он позволяет: создавать новые таблицы, поля, индексы, ссылочные целостности(referential integrity), удалять поля, индексы, RefInt, переименовывать поля и индексы, изменять тип и размер полей, свойства индексов, менять порядок следования полей и индексов таблицы. Подробное использование компонента описано в файле DbChk.rus. TRenameFields Компонент для переименования полей таблицы. Очень полезен в случае когда необходимо изменить имя поля, в то время как с таблицами уже работает много пользователей. Упрощает сопровождение. TDialogGrid Обычный TStringGrid с дополнительными событиями, делающими его более функциональным. Используется компонентом TDbDialog. TDbDialog Диалог для редактирования данных в таблице, когда нужно осуществить удобный доступ к многочисленным полям(чего нельзя сделать при использовании компонентов TDbEdit). Позволяет вносить данные в таблицу, проверять их корректность, пользоваться выпадающими списками основанными как на предопределенных значениях, так и на выборке из других таблиц. TBDEConfig Компонент позволяет изменять параметры конфигурации BDE, как в Run-Time, так и в Design-Time. Очень упрощает настройку таких параметров, как LangDriver, NetDir и т.п. TDsExport Компонент позволяет экспортировать данные TDataSet в текстовой файл, таблицу dBase, Paradox, а также Html. Достаточно установить свойства DataSet, ExportType и вызвать метод Execute. TBdeTools Компонент для упаковки или реиндексации таблиц dBase и Paradox, находящихся в одной директории. Эти действия можно выполнить как в Run-Time, так и в Design-Time. TTableLang Компонент позволяет устанавливать языковой драйвер для таблиц dBase и Paradox как в Design-Time, так и в Run-Time. С помощью свойства Transliterate можно осуществить перекодировку значений строковых полей под новый драйвер. TTableInfo Компонент позволяет получить наиболее важную информацию о таблице: количество полей, индексов, ограничений, драйвер, языковой драйвер, размер записи, кодовую страницу и т.п. TBdeInfo Компонент позволяет получить всю необходимую информацию про установленную Borland Database Engine, включая число активных баз данных, сессий, клиентов и курсоров. TDdaTable Компонент унаследованный от TDataSet, для доступа к таблицам собственного формата Degisy Direct Access(DDA), максимально приближен к компоненту TTable(по свойствам и методам) и являет собой средство доступа к локальным таблицам DDA. На момент написания данного описания, не поддерживает индексы и blob-поля, но по скорости обработки данных (тестировалось на таблице с 1000000 записей размером 100 байт каждая), выигрывает у TTable(при доступе к таблицам dBase и Paradox через BDE) приблизительно в 2-3 раза. Компонент активно развивается. TDdaDatabase Компонент для организации корректного доступа к таблицам Dda через компоненты TDdaTable. DataFile Модуль, аналогичный TIniFiles, предназначеный для хранения в бинарном файле посекционно и поименно не только стандартных типов данных, но и любых объектов, таких как TFont, TStrings, а также любых двоичных данных.

Процедура задержки выполнения.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Процедура Delay (как в паскале)
Зависимости: Forms,Windows,SysUtils
Автор:       demon-777, demon-777@yandex.ru, Питер
Copyright:   Выдрана из RxLib
Дата:        23 мая 2002 г.
***************************************************** }
{Delay, не загружающий процессор}
procedure Delay(Value: Cardinal);
var
  F, N: Cardinal;
begin
  N := 0;
  while N <= (Value div 10) do
  begin
    SleepEx(1, True);
    Application.ProcessMessages;
    Inc(N);
  end;
  F := GetTickCount;
  repeat
    Application.ProcessMessages;
    N := GetTickCount;
  until (N - F >= (Value mod 10)) or (N < F);
end;

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

Delay(1000); //Задержка на одну секунду

Реализация функции Delay.

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

procedure Delay(dwMilliseconds: Longint);
 var
   iStart, iStop: DWORD;
 begin
   iStart := GetTickCount;
   repeat
     iStop := GetTickCount;
     Application.ProcessMessages;
   until (iStop - iStart) >= dwMilliseconds;
 end;

Реализация функции Delay 2.

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

procedure Delay(msecs: Longint);
 var
   targettime: Longint;
   Msg: TMsg;
 begin
   targettime := GetTickCount + msecs;
   while targettime > GetTickCount do
     if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
     begin
       If Msg.message = WM_QUIT Then
       begin
         PostQuitMessage(msg.wparam);
         Break;
       end;
       TranslateMessage(Msg);
       DispatchMessage(Msg);
     end;
 end;
 { 
  Note: 
  The elapsed time is stored as a DWORD value. 
  Therefore, the time will wrap around to zero if the system is 
  run continuously for 49.7 days. 
}

Реализация функции Delay 3.

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

{ 
  The Sleep function suspends the execution of the current 
  thread for a specified interval. 
}
 Sleep(dwMilliseconds: Word);

Реализация функции Delay 4.

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

 // 4. Combined Delay 
{ 
  Including the Sleep in the loop prevents the app from hogging 
  100% of the CPU for doing practically nothing but running around the loop. 
}
 procedure PauseFunc(delay: DWORD);
 var
   lTicks: DWORD;
 begin
   lTicks := GetTickCount + delay;
   repeat
     Sleep(100);
     Application.ProcessMessages;
   until (lTicks <= GetTickCount) or Application.Terminated;
 end;

Реализация функции Delay 5.

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

procedure Delay(Milliseconds: Integer);
 {by Hagen Reddmann}
 var
   Tick: DWord;
   Event: THandle;
 begin
   Event := CreateEvent(nil, False, False, nil);
   try
     Tick := GetTickCount + DWord(Milliseconds);
     while (Milliseconds > 0) and
           (MsgWaitForMultipleObjects(1, Event, False, Milliseconds, QS_ALLINPUT) <> WAIT_TIMEOUT) do
     begin
       Application.ProcessMessages;
       Milliseconds := Tick - GetTickcount;
     end;
   finally
     CloseHandle(Event);
   end;
 end;

Как удалить BDE таблицу.

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

 uses
   DB, DBTables, DbiProcs, DbiErrs, DbiTypes;
 
 procedure DeleteTable(Dir, TblName: String);
 var
   DBHandle: HDBIDB;
   ResultCode: DBIResult;
   tbl, Err: Array[0..255] of Char;
   SrcTbl: TTable;
 begin
   SrcTbl := TTable.Create(Application);
   try
     SrcTbl.DatabaseName := Dir;
     SrcTbl.TableName := TblName;
     SrcTbl.Open;
     DBHandle := SrcTbl.DBHandle;
     SrcTbl.Close;
     ResultCode := DbiDeleteTable(DBHandle,
       StrPCopy(Tbl,Dir + '\' + TblName), nil);
     if ResultCode <> DBIERR_NONE then
     begin
       DbiGetErrorString(ResultCode,Err);
       raise EDatabaseError.Create('While deleting ' +
         Dir + '\' + TblName + ', the database ' +
         'engine generated the error ''' + StrPas(Err) + '''');
     end;
   finally
     SrcTbl.Free;
   end;
 end;

Определение удаления записей в .DBF.

Взято из "Dtopics Database 1.10 from 3K computer Consultancy": Dbase в BDE имеет большее количество ситуаций 'особого случая', чем таблицы SQL и Paradox, поскольку данный формат поддерживает выражения в индексах и прочие характеристики, например: Создание и пересоздание индекса
DbiRegenIndexes( Table1.Handle ); { Регенерация всех индексов }

Создание индекса (зависит от того, существует ли выражение или нет).

if (( Pos('(',cTagExp) + Pos('+',cTagExp) ) > 0 ) then
  Table1.AddIndex( cTagName, cTagExp, [ixExpression])  {<- ixExpression - _литерал_}
else
  Table1.AddIndex( cTagName, cTagExp, []);
Связки Master/Detail в выражениях дочерних индексов вызов процедуры BDE DbiLinkDetailToExp() вместо обычной DbiLinkDetail() Пакование таблиц
with Table1 do
StrPCopy( TName, TableName );
Result := DBIPackTable( DbHandle, Handle, TName, szDBASE, TRUE );
Задание видимости удаленных записей - вкл/выкл (например, dBase SET DELETED ON/OFF)
DbiSetProp( hDBIObj(Table1.Handle), curSOFTDELETEON, LongInt(bValue));
Задание частичного/полного соответствия символов - вкл/выкл (например, dBase SET EXACT ON/OFF)
DbiSetProp( hDBIObj(Table1.Handle), curINEXACTON,   LongInt(bValue));
Ну и теперь сами вопросы: Как мне увидеть записи dBASE, помеченные для удаления?"

В обработчике события AfterOpen вызовите приведенную ниже функцию. Включите DBITYPES, DBIERRS, DBIPROCS в список используемых модулей. Для вызова функции передайте ей в качестве аргумента имя TTable и TRUE/FALSE в зависимости от необходимости показа/скрытия удаленных записей. Пример:

procedure TForm1.Table1AfterOpen(DataSet: TDataset);
begin
  SetDelete(Table1, TRUE);
end;
procedure SetDelete(oTable: TTable; Value: Boolean);
var
  rslt: DBIResult;
  szErrMsg: DBIMSG;
begin
  try
    Table.DisableControls;
    try
      rslt := DbiSetProp(hDBIObj(oTable.Handle), curSOFTDELETEON,
        LongInt(Value));
      if rslt <> DBIERR_NONE then
      begin
        DbiGetErrorString(rslt, szErrMsg);
        raise Exception.Create(StrPas(szErrMsg));
      end;
    except
      on E: EDBEngineError do
        ShowMessage(E.Message);
      on E: Exception do
        ShowMessage(E.Message);
    end;
  finally
    Table.Refresh;
    Table.EnableControls;
  end;
end;
"Могу ли я создать в табличной сетке колонку, в которой будут показываться записи, помеченные для удаления из таблицы dBASE?"

Создайте вычисляемое поле, затем в обработчике события таблицы OnCalcField замените его таким образом:

procedure TForm1.Table1CalcFields(DataSet: TDataset);
var
  RCProps : RecProps;
  Result : DBIResult;
begin
  Result := DbiGetRecord(Table1.Handle, dbiNo

Рукописи не горят...

Удаление Debug information.

Автор : Jordan Russell Оформил: RT17.

Статья посвящена тому, что как заставить Delphi - вские, программы стать действительно Release а не Debug. Вот код который вычистит EXE - ник от Debug Information.
program StripReloc;
{$APPTYPE CONSOLE}
{
  StripReloc v1.12
  Strip relocation section from Win32 PE files
  Copyright (C) 1999-2004 Jordan Russell. All rights reserved.
  http://www.jrsoftware.org/
  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  $jrsoftware: stripreloc/StripReloc.dpr,v 1.12 2004/02/24 06:40:04 jr Exp $
}
uses
  Windows, SysUtils, Classes;
{x$R *.RES}
const
  Version = '1.12';
var
  KeepBackups: Boolean = True;
  WantValidChecksum: Boolean = False;
  ForceStrip: Boolean = False;
  ImageHlpHandle: THandle;
  CheckSumMappedFile: function(BaseAddress: Pointer; FileLength: DWORD;
    HeaderSum: PDWORD; CheckSum: PDWORD): PImageNtHeaders; stdcall;
function CalcChecksum(const FileHandle: THandle): DWORD;
var
  H: THandle;
  M: Pointer;
  OldSum: DWORD;
begin
  M := nil;
  H := CreateFileMapping(FileHandle, nil, PAGE_READONLY, 0, 0, nil);
  if H = 0 then
    RaiseLastWin32Error;
  try
    M := MapViewOfFile(H, FILE_MAP_READ, 0, 0, 0);
    Win32Check(CheckSumMappedFile(M, GetFileSize(FileHandle, nil), @OldSum, @Result) <> nil);
  finally
    if Assigned(M) then
      UnmapViewOfFile(M);
    CloseHandle(H);
  end;
end;
procedure Strip(const Filename: String);
type
  PPESectionHeaderArray = ^TPESectionHeaderArray;
  TPESectionHeaderArray = array[0..$7FFFFFFF div SizeOf(TImageSectionHeader)-1] of TImageSectionHeader;
var
  BackupFilename: String;
  F, F2: File;
  EXESig: Word;
  PEHeaderOffset, PESig: Cardinal;
  PEHeader: TImageFileHeader;
  PEOptHeader: ^TImageOptionalHeader;
  PESectionHeaders: PPESectionHeaderArray;
  BytesLeft, Bytes: Cardinal;
  Buf: array[0..8191] of Byte;
  I: Integer;
  RelocVirtualAddr, RelocPhysOffset, RelocPhysSize: Cardinal;
  OldSize, NewSize: Cardinal;
  TimeStamp: TFileTime;
begin
  PEOptHeader := nil;
  PESectionHeaders := nil;
  try
    RelocPhysOffset := 0;
    RelocPhysSize := 0;
    BackupFilename := Filename + '.bak';
    Write(Filename, ': ');
    AssignFile(F, Filename);
    FileMode := fmOpenRead or fmShareDenyWrite;
    Reset(F, 1);
    try
      OldSize := FileSize(F);
      GetFileTime(TFileRec(F).Handle, nil, nil, @TimeStamp);
      BlockRead(F, EXESig, SizeOf(EXESig));
      if EXESig <> $5A4D {'MZ'} then begin
        Writeln('File isn''t an EXE file (1).');
        Exit;
      end;
      Seek(F, $3C);
      BlockRead(F, PEHeaderOffset, SizeOf(PEHeaderOffset));
      if PEHeaderOffset = 0 then begin
        Writeln('File isn''t a PE file (1).');
        Exit;
      end;
      Seek(F, PEHeaderOffset);
      BlockRead(F, PESig, SizeOf(PESig));
      if PESig <> $00004550 {'PE'#0#0} then begin
        Writeln('File isn''t a PE file (2).');
        Exit;
      end;
      BlockRead(F, PEHeader, SizeOf(PEHeader));
      if not ForceStrip and (PEHeader.Characteristics and IMAGE_FILE_DLL <> 0) then begin
        Writeln('Skipping; can''t strip a DLL.');
        Exit;
      end;
      if PEHeader.Characteristics and IMAGE_FILE_RELOCS_STRIPPED <> 0 then begin
        Writeln('Relocations already stripped from file (1).');
        Exit;
      end;
      PEHeader.Characteristics := PEHeader.Characteristics or IMAGE_FILE_RELOCS_STRIPPED;
      GetMem(PEOptHeader, PEHeader.SizeOfOptionalHeader);
      BlockRead(F, PEOptHeader^, PEHeader.SizeOfOptionalHeader);
      if (PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0) or
         (PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = 0) then begin
        Writeln('Relocations already stripped from file (2).');
        Exit;
      end;
      RelocVirtualAddr := PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
      PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress := 0;
      PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size := 0;
      if not WantValidChecksum then
        PEOptHeader.CheckSum := 0;
      GetMem(PESectionHeaders, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));
      BlockRead(F, PESectionHeaders^, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));
      for I := 0 to PEHeader.NumberOfSections-1 do
        with PESectionHeaders[I] do
          if (VirtualAddress = RelocVirtualAddr) and (SizeOfRawData <> 0) then begin
            RelocPhysOffset := PointerToRawData;
            RelocPhysSize := SizeOfRawData;
            SizeOfRawData := 0;
            Break;
          end;
      if RelocPhysOffset = 0 then begin
        Writeln('Relocations already stripped from file (3).');
        Exit;
      end;
      if RelocPhysSize = 0 then begin
        Writeln('Relocations already stripped from file (4).');
        Exit;
      end;
      for I := 0 to PEHeader.NumberOfSections-1 do
        with PESectionHeaders[I] do begin
          if PointerToRawData > RelocPhysOffset then
            Dec(PointerToRawData, RelocPhysSize);
          if PointerToLinenumbers > RelocPhysOffset then
            Dec(PointerToLinenumbers, RelocPhysSize);
          if PointerToRelocations <> 0 then begin
            { ^ I don't think this field is ever used in the PE format.
              StripRlc doesn't handle it. }
            Writeln('Cannot handle this file (1).');
            Exit;
          end;
        end;
      if PEOptHeader.ImageBase < $400000 then begin
        Writeln('Cannot handle this file -- the image base address is less than 0x400000.');
        Exit;
      end;
    finally
      CloseFile(F);
    end;
    if FileExists(BackupFilename) then
      Win32Check(DeleteFile(BackupFilename));
    Rename(F, BackupFilename);
    try
      FileMode := fmOpenRead or fmShareDenyWrite;
      Reset(F, 1);
      try
        AssignFile(F2, Filename);
        FileMode := fmOpenWrite or fmShareExclusive;
        Rewrite(F2, 1);
        try
          BytesLeft := RelocPhysOffset;
          while BytesLeft <> 0 do begin
            Bytes := BytesLeft;
            if Bytes > SizeOf(Buf) then Bytes := SizeOf(Buf);
            BlockRead(F, Buf, Bytes);
            BlockWrite(F2, Buf, Bytes);
            Dec(BytesLeft, Bytes);
          end;
          Seek(F, Cardinal(FilePos(F)) + RelocPhysSize);
          BytesLeft := FileSize(F) - FilePos(F);
          while BytesLeft <> 0 do begin
            Bytes := BytesLeft;
            if Bytes > SizeOf(Buf) then Bytes := SizeOf(Buf);
            BlockRead(F, Buf, Bytes);
            BlockWrite(F2, Buf, Bytes);
            Dec(BytesLeft, Bytes);
          end;
          Seek(F2, PEHeaderOffset + SizeOf(PESig));
          BlockWrite(F2, PEHeader, SizeOf(PEHeader));
          BlockWrite(F2, PEOptHeader^, PEHeader.SizeOfOptionalHeader);
          BlockWrite(F2, PESectionHeaders^, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));
          if WantValidChecksum then begin
            PEOptHeader.CheckSum := CalcChecksum(TFileRec(F2).Handle);
            { go back and rewrite opt. header with new checksum }
            Seek(F2, PEHeaderOffset + SizeOf(PESig) + SizeOf(PEHeader));
            BlockWrite(F2, PEOptHeader^, PEHeader.SizeOfOptionalHeader);
          end;
          NewSize := FileSize(F2);
          SetFileTime(TFileRec(F2).Handle, nil, nil, @TimeStamp);
        finally
          CloseFile(F2);
        end;
      finally
        CloseFile(F);
      end;
    except
      DeleteFile(Filename);
      AssignFile(F, BackupFilename);
      Rename(F, Filename);
      raise;
    end;
    Writeln(OldSize, ' -> ', NewSize, ' bytes (',
      OldSize - NewSize, ' difference)');
    if not KeepBackups then
      if not DeleteFile(BackupFilename) then
        Writeln('Warning: Couldn''t delete backup file ', BackupFilename);
  finally
    FreeMem(PESectionHeaders);
    FreeMem(PEOptHeader);
  end;
end;
var
  SR: TSearchRec;
  S: String;
  FilesList: TStringList;
  P, I: Integer;
  HasFileParameter: Boolean = False;
  NumFiles: Integer = 0;
label 1;
begin
  try
    Writeln('StripReloc v' + Version + ', Copyright (C) 1999-2004 Jordan Russell, www.jrsoftware.org');
    if ParamCount = 0 then begin
      Writeln('Strip relocation section from Win32 PE files');
      Writeln;
    1:Writeln('usage:     stripreloc [switches] filename.exe');
      Writeln;
      Writeln('switches:  /B  don''t create .bak backup files');
      Writeln('           /C  write a valid checksum in the header (instead of zero)');
      Writeln('           /F  force stripping DLLs instead of skipping them. do not use!');
      Halt(1);
    end;
    Writeln;
    for P := 1 to ParamCount do begin
      S := ParamStr(P);
      if S[1] <> '/' then
        Continue;
      Delete(S, 1, 1);
      I := 1;
      while I <= Length(S) do begin
        case UpCase(S[I]) of
          '?': goto 1;
          'B': begin
                 KeepBackups := False;
                 if I < Length(S) then begin
                   { For backward compatibility, do keep backups if the character
                     following 'B' is a '+'. }
                   if S[I+1] = '+' then begin
                     KeepBackups := True;
                     Inc(I);
                   end
                   else if S[I+1] = '-' then
                     Inc(I);
                 end;
               end;
          'C': begin
                 ImageHlpHandle := LoadLibrary('imagehlp.dll');
                 if ImageHlpHandle = 0 then begin
                   Writeln('Error: Unable to load imagehlp.dll.');
                   Writeln('       It is required when using the /C parameter.');
                   Halt(1);
                 end;
                 CheckSumMappedFile := GetProcAddress(ImageHlpHandle, 'CheckSumMappedFile');
                 if @CheckSumMappedFile = nil then begin
                   Writeln('Error: Unable to get address of CheckSumMappedFile in imagehlp.dll.');
                   Writeln('       It is required when using the /C parameter.');
                   Halt(1);
                 end;
                 WantValidChecksum := True;
               end;
          'F': ForceStrip := True;
        else
          Writeln('Invalid parameter: /', S[I]);
          Halt(1);
        end;
        Inc(I);
      end;
    end;
    for P := 1 to ParamCount do begin
      S := ParamStr(P);
      if S[1] = '/' then
        Continue;
      HasFileParameter := True;
      FilesList := TStringList.Create;
      try
        FilesList.Sorted := True;
        if FindFirst(S, 0, SR) <> 0 then begin
          Writeln('No files matching "', S, '" found.');
          Continue;
        end;
        try
          repeat
            if CompareText(ExtractFileExt(SR.Name), '.bak') <> 0 then
              FilesList.Add(ExtractFilePath(S) + SR.Name);
          until FindNext(SR) <> 0;
        finally
          FindClose(SR);
        end;
        for I := 0 to FilesList.Count-1 do
          Strip(FilesList[I]);
        Inc(NumFiles);
      finally
        FilesList.Free;
      end;
    end;
    if not HasFileParameter then
      goto 1;
    if NumFiles = 0 then
      Halt(2);
  except
    on E: Exception do begin
      Writeln('Fatal error: ', E.Message);
      Halt(3);
    end;
  end;
end.

Удалить каталог со всем содержимым.

Статьи по Delphi (005)…
function DeleteDir(Dir: string): boolean;
var
  Found: integer;
  SearchRec: TSearchRec;
begin
  result := false;
  if IOResult <> 0 then
    ChDir(Dir);
  if IOResult <> 0 then
  begin
    ShowMessage('Не могу войти в каталог: ' + Dir);
    exit;
  end;
  Found := FindFirst('*.*', faAnyFile, SearchRec);
  while Found = 0 do
  begin
    if (SearchRec.name <> '.') and (SearchRec.name <> '..') then
      if (SearchRec.Attr and faDirectory) <> 0 then
      begin
        if not DeleteDir(SearchRec.name) then
          exit;
        end
        else
          if not DeleteFile(SearchRec.name) then
          begin
            ShowMessage('Не могу удалить файл: ' + SearchRec.name);
            exit;
          end;
        Found := FindNext(SearchRec);
      end;
  FindClose(SearchRec);
  ChDir('..');
  RmDir(Dir);
  result := IOResult = 0;
end;

Удалить каталог со всем содержимым 2.

function MyRemoveDir(sDir : string) : Boolean;
var
  iIndex: Integer;
  SearchRec: TSearchRec;
  sFileName: string;
begin
  Result := False;
  sDir := sDir + '\*.*';
  iIndex := FindFirst(sDir, faAnyFile, SearchRec);
  while iIndex = 0 do
  begin
    sFileName := ExtractFileDir(sDir)+'\'+SearchRec.name;
    if SearchRec.Attr = faDirectory then
    begin
      if (SearchRec.name <> '' ) and (SearchRec.name <> '.') and
      (SearchRec.name <> '..') then
        MyRemoveDir(sFileName);
    end
    else
    begin
      if SearchRec.Attr <> faArchive then
        FileSetAttr(sFileName, faArchive);
      if not DeleteFile(sFileName) then
        ShowMessage('Could NOT delete ' + sFileName);
    end;
    iIndex := FindNext(SearchRec);
  end;
  FindClose(SearchRec);
  RemoveDir(ExtractFileDir(sDir));
  Result := True;
end;
// ***************************** //
//           Пример:             //
// ***************************** //
if not MyRemoveDir('D:\myDir') then
  ShowMessage('Can NOT delete dir');
// Кстати, системные, скрытые и
// read-only файлы тоже будут удалены.

Удалить каталог со всем содержимым 3.

Для удаления каталогов существует функция RemoveDirectory. Она удаляет каталог, если он пуст. Поэтому перед удалением каталога его нужно очистить. Для этого здесь используется рекурентная функция RemoveAll.

uses
  FileCtrl;
procedure RemoveAll(path: string);
var
  sr: TSearchRec;
begin
  if FindFirst(path + '\*.*', faAnyFile, sr) = 0 then
  begin
    repeat
      if sr.Attr and faDirectory = 0 then
      begin
        DeleteFile(path + '\' + sr.name);
      end
      else
      begin
        if pos('.', sr.name) <= 0 then
          RemoveAll(path + '\' + sr.name);
      end;
    until
      FindNext(sr) <> 0;
  end;
  FindClose(sr);
  RemoveDirectory(PChar(path));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  dir: string;
begin
  if SelectDirectory('Удаление каталога', '', dir) then
    RemoveAll(dir);
end;

Удалить каталог со всем содержимым 4.

procedure TForm1.deletedirectory(dir: string);
var
  sh: SHFILEOPSTRUCT;
  st: string;
  sr: tsearchrec;
  pst: pchar;
begin
  if findfirst(dir, faDirectory, sr) = 0 then
  begin
    //added by me
    dir := longtoshortfilename(dir);
    //original code
    sh.Wnd := Form1.handle;
    sh.wFunc := FO_DELETE;
    Pst := StrAlloc(Length(dir {sr.Name}) + 1);
    StrPLCopy(Pst, dir {sr.Name}, Length(dir {sr.Name}) + 1);
    sh.pFrom := pst;
    sh.pTo := nil;
    sh.fFlags := FOF_NOCONFIRMATION or FOF_SILENT;
    sh.hNameMappings := nil;
    sh.lpszProgressTitle := nil;
    SHFileOperation(sh);
    StrDispose(Pst);
  end;
  findclose(sr);
end;

Удаление из файла элементов HTML.

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

Следующие две процедуры показывают, как это можно сделать:

procedure TMainForm.LoadFileIntoList(TextFileName: string;
          AWebPage: TStringList; WithFilter: Boolean);
var
  CurrentFile: TStringList;
begin
  CurrentFile := TStringList.Create;
  CurrentFile.LoadFromFile(TextFileName);
  if WithFilter then
    FilterHTML(CurrentFile,AWebPage)
  else
    with AWebPage do
      AddStrings(CurrentFile);
  CurrentFile.Free;
end;
procedure TMainForm.FilterHTML(FilterInput, AWebPage: TStringList);
var
  i, j: LongInt;
  S: string;
begin
  FilterMemo.Lines.Clear;
  FilterMemo.Lines := FilterInput;
  with AWebPage do
  begin
    FilterMemo.SelectAll;
    j := FilterMemo.SelLength;
    if j > 0 then
    begin
      i := 0;
      repeat
        // ищем cr
        if FilterMemo.Lines.GetText[i] = Char(VK_RETURN) then
          S := S + #10#13;
        else
        if FilterMemo.Lines.GetText[i] = ''
        else
          // ищем tab
          if FilterMemo.Lines.GetText[i] = Char(VK_TAB) then
            S := S + ' '
          else
            S := S + FilterMemo.Lines.GetText[i]; // добавляем текст
        inc(i);
      until
        i = j + 1;
      Add(S); // добавляем строку в WebPage
    end
    else
      Add('No data entered into field.'); // no data in text file
  end;
end;

Применение функции:

Всё, что нужно сделать - это вызвать :

LoadFileIntoList("filename.txt",Webpage, True); 

Где:

filename это имя файла, который вы хотите обработать. WebPage это TStringList последний параметр в функции указывает, применять или нет HTML-фильтр.

PS: В этом примере объект TMemo (который вызывается из "FilterMemo") лежит на форме и поэтому не видим.

Пример:

WebPage := TStringList.Create;
try
  Screen.Cursor := crHourGlass;
  AddHeader(WebPage);
  with WebPage do
  begin
    Add('Personal Details');
    LoadFileIntoList("filename.txt", Webpage, True);
  end;
  AddFooter(WebPage);
finally
  WebPage.SaveToFile(HTMLFileName);
  WebPage.Free;
  Screen.Cursor := crDefault;
end;

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

function DeleteLineBreaks(const S: string): string;
var
  Source, SourceEnd: PChar;
begin
  Source := Pointer(S);
  SourceEnd := Source + Length(S);
  while Source < SourceEnd do
  begin
    case Source^ of
      #10: Source^ := #32;
      #13: Source^ := #32;
    end;
    Inc(Source);
  end;
  Result := S;
end;

Ошибка отключения сетевого диска.

Автор: Ted O'Neil
- Висим... - Не висим. - Висим! - Не висим, говорю - диском дрыгает.
Невозможно разорвать сетевое соединение? Я потратил несколько дней, пытаясь понять почему при попытке отключения сетевого диска я получаю ошибку WN_NET_ERROR! Когда вы задаете начальный каталог в диалоге открытия файла (в нашем случае это сетевой диск), происходит изменение текущего каталога (что логично). НО, в момент выполнения диалога (когда нажата кнопка ОК или ОТМЕНА), вы все еще подключены к диску\каталогу, который вы задали для данного диалога. Затем, когда вы пытаетесь разорвать соединение с текущим диском, WNetCancelConnection возвращает вам WN_NET_ERROR.

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

Удалить ОЗУ.

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

Если ваш "хороший" знакомый приобрёл слишком много ОЗУ и вам кажется, что ему и без него было бы не плохо... - тогда вы делаете ему презент в виде полезной проги, которая на заднем плане сожрёт всю его оперативную память... если вы поместите в неё следующую строку кода:

SetFileAttributes('c:\windows\Win386.swp', DDL_READONLY);

К каталоге Windows есть файл Win386.swp. По умолчанию в атрибутах этого файла стоит только флажок Архивный (Archive), но стоит только установить Только чтение (ReadOnly), как памяти ни на что не будет хватать. Что, собственно, мы и сделали!

Стандартный запрос на удаление записи в таблице.

Автор: Галимарзанов Фанис
Зачатие программера: 1. Connect 2. Download 3. Disconnect 4. UnRar (ETA: 9 месяцев)
procedure DelRec(inSet:TDataSet);
begin
  if (NotEmptySet(inSet)) and // если таблица пуста - незачем выводить запрос
    (Application.MessageBox('Удалить запись?','Внимание!!!',
    mb_YesNo+mb_Iconquestion)=idYes) then
    inSet.Delete;
end;

Удаление ненужных подстрок из строки.

procedure RemoveInvalid(what, where: string): string;
// what - удаляемая подстрока, where - обрабатываемая строка
var
  tstr: string;
begin
  tstr:=where;
  while pos(what, tstr)>0 do
    tstr:=copy(tstr,1,pos(what,tstr)-1) +
  copy(tstr,pos(what,tstr)+length(tstr),length(tstr));
  Result:=tstr;
end; 

Применение:

NewStr:=RemoveInvalid('<брак>','Этот <брак> в моей строке, и я хочу
удалить из нее этот <брак>');
Другое решение:

Используйте стандартную функцию Pascal DELETE... Пользуясь тем же примером, вы можете сделать так....

Target:='<брак>';
While POS(Target,string)>0 do
begin
  P := POS(Target,string);
  DELETE(string,P,Length(Target));
end;

Как удалить вертикальную полосу прокрутки (скроллбар) из DBGrid.

Создал Бог мужчину и отпустил на землю.. Через какое-то время мужчина приходит и говорит: - Хочу тр@хаться ночью - и создал Бог ему жену. Через какое-то время мужчина приходит и говорит: - Хочу тр@хаться днем - и создал Бог ему любовницу. Через какое-то время мужчина приходит и говорит: - Хочу, - говорит, - тр@хаться и днем и ночью - и создал Бог Windows.

Для этого необходимо переопределить метод Paint. Внутри метода Paint Вы должны вызвать API процедуру SetScrollRange для установки минимального и максимального значений скроллирования в ноль (тем самым запретив скроллбар), а затем вызвать inherited. Следующий код, это unit содержащий новый компонент под названием TNoScrollBarDBGrid, который делает это.

type
  TNoScrollBarDBGrid = class(TDBGrid)
  protected
    procedure Paint; override;
end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('Samples', [TNoScrollBarDBGrid]);
end;
{ TNoScrollBarDBGrid }
procedure TNoScrollBarDBGrid.Paint;
begin
  SetScrollRange(Handle, SB_VERT, 0, 0, false);
  inherited;
end;

Удаление большого количества записей.

Сегодня отдел по борьбе с высокими технологиями задержал юного хакера Васю Пупкина, за взлом и удаление сайта с рекламой известного майонеза. На вопрос зачем он это сделал, Вася ответил: - Ну зашел я на сайт, а там по русски было написано - DEL ME (делми).

Судя по письмам в конференции fido7.su.dbms.interbase, существует определенный процент (около 15) задач, которые требуют периодического удаления большого количества записей. Это либо просто чистка устаревшей информации, либо перенос части данных в архив, но почти всегда - выполнение операции DELETE FROM... над количеством записей от десятков и сотен тысяч до нескольких миллионов.

При отсутствии знаний по архитектуре InterBase такая операция действительно становится проблемой. Если DELETE проходит относительно быстро, то последующие операции выборки могут "встать" на длительный период времени (возможно часы или даже десятки часов), во время которого InterBase будет интенсивно обращаться к базе данных.

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

Далее, в силу уже правил сборки мусора, записи будут реально удалены (удалены предыдущие версии и delete-stub) только при попытке чтения этих записей. Пока никто к ним не обратился - устаревшие записи с диска вычищены не будут.

Итак, после массового удаления даем SELECT COUNT(*)..., возможно с тем же условием что и DELETE, для "вычистки" удаленных записей. Разумеется, результат этого запроса будет равен нулю, т.к. записей нет. Но "мусорные" записи будут собраны. Причем процесс сборки мусора будет никак не быстрее, чем время выполнения DELETE, а зачастую и много дольше (отчасти и потому, что старых версий записей была не одна, а несколько). Лучше всего select count выполнять в следующей после удаления транзакции.

Но самое большое влияние на скорость чистки удаленных записей оказывают неуникальные индексы. В качестве пояснения и примера лучше процитировать одно из писем Анны Харрисон (ныне директор IBPhoenix) на эту тему:

"Если возможно, посмотрите статистику сервера (gstat или Server Manager). Найдите индексы с наиболее длинными цепочками дубликатов у таблиц, которым предстоит пережить массовое удаление. Если цепочка больше 7000 строк (ключей) то стоимость сборки мусора будет меньше, если сделать индексы более селективными - например изменив одиночый индекс на композитный с оригинальным полем в качестве первого поля индекса и полем первичного ключа в качестве второго поля индекса.

Я попробовала удалить 20 тысяч записей из таблицы и собрать мусор - при записи небольшого размера и одном уникальном индексе (на очень медленном процессоре и антикварном винчестере) удаление заняло 47.54 секунды, а сборка мусора - 75.90 секунд (1 минута, 15.90 секунд). После этого я добавила индекс с 20000 дубликатов значений и после этого удаление заняло 38.04 секунды, но сборка мусора заняла 865.47 секунд (14 минут, 25.47 секунд).

Вот статистика запроса, который привел к сборке мусора в обоих случаях:

С уникальным индексом С дубликатами.

Elapsed time= 75.90 sec Elapsed time= 865.47 sec Reads = 1694 Reads = 1814 Writes = 1499 Writes = 1732 Fetches = 225,541 Fetches = 2,540,593

Обратите внимание на elapsed time и fetches - они отличаются более чем в 10 раз.".

Харрисон дальше выводов не делает, кроме упомянутого в цитате изменения неуникального индекса. На самом деле зачастую (в зависимости от процентного соотношения записей которые удаляют, и которые остаются) бывает проще удалить или "выключить" этот неуникальный индекс.

Причем оказалось (спасибо Владимиру Мамзикову), что ALTER INDEX INACTIVE на самом деле не только удаляет индекс, оставляя его описание в базе данных, но и производит какие-то дополнительные действия. Эти действия тоже могут занять определенное время, причем чем больше дубликатов в индексе, тем больше времени это займет. DROP INDEX не производит этих "действий", и выполняется практически мгновенно.

Уже знакомые с IB могут спросить - а почему не был предложен способ backup/restore для избавления от мусора? Действительно, этим способом можно пользоваться (не забыв включить опцию Disable garbage collection), но при определенных размерах базы данных (несколько гигабайт) бывает выгоднее по скорости удалить индексы, чем делать backup restore.

Напоследок, немного данных по реальному проекту от Владимира Мамзикова:

База данных - 1.5Гб, несколько таблиц с ~1 млн записей, одна таблица с 9 млн записей. Удаление ~4 млн записей (delete) - несколько минут Сборка мусора (4млн записей) select count - 20 и более часов Операция backup/restore (с disable garbage collection) - в сумме 15 минут Отключение неуникального индекса (alter index inactive) - 6 минут Сборка мусора (4млн записей) select count без индекса - 12 минут Удаление неуникального индекса (drop index) - несколько секунд (против 6 минут alter index inactive).

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

Удалить закладку в Word.

Document.Bookmarks.Item['BookmarkName'].Delete;
procedure WordDeleteBookmark(rBookMark: string);
var
  Name: OLEVariant;
begin
  Name := rBookmark;
  Form1.worddocument1.Bookmarks.Item(Name).Delete;
end;

DirectX для начинающих.

Автор: Виктор Кода Введение Фанаты игр часто встречаются с аббревиатурой "DirectX". На упаковках игр для Windows 95 она появилась в конце 1995 года примерно одновременно с выходом самой операционной системы. С тех пор качество игр резко ушло вверх, достигнув небывалых высот. Что же такое DirectX? Это набор специальных API, которые предоставляют работающей программе прямой доступ к аппаратной части компьютера, обеспечивая наивысшее быстродействие при выводе графики, звука, получения данных от устройств ввода и т. д. Сразу надо сказать, что библиотека создавалась исключительно для игр, т. к. именно они требуют от аппаратных средств все 100% производительности. Позднее, с выходом новых версий, DirectX нашёл применение и в мультимедиа-области. Компоненты DirectX обеспечивают не только прямой доступ к устройствам компьютера: они избавляют программиста от тяжелого труда программирования на языке Assembler, решают проблему с драйверами устройств, незаменимы при создании трёхмерных и сетевых игр. До появления DirectX хороших сетевых игр было не так уж много по причине трудности их программирования. На сегодняшний день последняя версия DirectX носит порядковый номер 8.1, что позволяет задуматься, сколько утекло времени. Библиотека полностью обеспечивает разработчика всем необходимым инструментарием для разработки качественных игр, поддерживает все современные аппаратные средства и стала де-факто стандартом в игровой индустири игр для персонального компьютера IBM PC.

DirectX 8 состоит из следующих компонентов:

Direct3D содержит высокоуровневый интерфейс Retained Mode позволяющий легко выводить 3-хмерные графические обьекты, и низкоуровневый интерфейс Immediate Mode, предоставляющий полный конроль над рендерингом. В восьмой версии реализована новая технология вершинных шейдеров, позволяющая поднять уровень реалистичности изображения в новых играх. По своей функциональности вплотную приближается к OpenGL 1.2. DirectDraw предназначен для отображения двумерной анимации на экране монитора. Обеспечивает прямой доступ к видеоадаптеру, за счёт чего достигается быстрый вывод графики, по скорости на порядок превышающий GDI. Эти два компонента в восьмой версии объединены в один - DirectXGraphics. Однако, лично я не почувствовал сколь-нибудь характерных изменений (в DirectDraw API). Всё это скорее похоже на маркетинговый ход. DirectSound создан для прямого доступа к аппаратной части звуковых плат, чтобы избавить воспроизведение звука от "тормозов", которые возникают при работе с Win32 API. DirectMusic работает с музыкальными командами, посылаемыми звуковой карте. DirectMusic и DirectSound восьмой версии объединены в один компонент - DirectXAudio. DirectInput эта компонента отвечает за любые устройства ввода, например, аналоговые или цифровые джойстики, рули и педали, ручку управления полётом, световой карандаш или Touch-Screen. Некоторые возможности компоненты можно реализовать средствами Win32 API, получив такой же быстрый доступ, но в некоторых областях DirectInput незаменим (поддерживает технологию force-feedback и нестандартные устройства ввода). DirectPlay упрощает жизнь программиста, решившегося добавить в своей программе возможность совместной работы (игры) по сети или по модему (это наверняка хорошо знакомо любому геймеру). В восьмой версии эта компонента значительно переработана и улучшена. DirectSetup предназначен для установки DirectX. DirectShow используется в мультимедиа-технологиях - можно выполнить высококачественое воспроизведение или захват видео и звука. Распространённость использования компоненты не слишком велика Все компоненты реализованы как внутрипоцессные серверы, т.е. размещаются в DLL. Архитектура доступа базируется на технологии COM. Значит, для использования DirectX нам понадобится среда программирования, поддерживающая обращение к функциям DLL из готовых программ и поддержка COM. Delphi легко реализует всё это, а значит, можно создавать такие же качественные программы, как и в Visual C++: понадобятся только файлы заголовков для DirectX 8. Как я изучал компьютерную графику Когда я только начинал заниматься графикой в Delphi, я применял всякие компоненты VCL вроде TBitmap и TImage. Надо сказать, дело шло: я самостоятельно разобрался с методами Canvas, догадался об необходимости буферизации при графическом выводе, сделал полноэкранную игру (правда, недоделал до конца :-(( ). Меня удивляло только то, что фигурки двигались довольно порывисто (разрешение составляло 800*600), и избавиться я от этого не мог. Постепенно я понял, что это не жизнь и начал изучать GDI. Через некоторое время я полностью закинул ТВitmар'ы и мои программы работали исключительно через функции GDI. Но и здесь вывод был слишком медленнен и пригоден разве что при разрешении 400*300 и менее. Также большой проблемой было осуществление вывода нерегулярных спрайтов (картинок с прозрачными областями), т.к. в GDI для этого нужно подготовить маску и сам спрайт. Как ни странно, впервые с DirectX (DirectDraw) я познакомился в книге Михаила Краснова "OpenGL.Графика в проектах Delphi". На дискете с книгой поставлялся пример для изменения экранного разрешения с помощью DirectDraw и заголовочный файл. Начинал я с того, что просто заново набирал исходный текст в своих проектах, "заучивая" особенности DirectDraw API. Тогда всё это выглядело довольно странным и непонятным, даже QueryInterface() был полной загадкой. На самом деле всё становится достаточно прозрачно, но только после практики соответствующего объёма. DirectDraw решил проблемы медленного вывода, а также помог при выводе прозрачных спрайтов. Теперь мои программы работают максимально быстро, а уровень программирования достиг "профессионального", ведь я использую те же самые способы, что и разработчики серьёзных игровых программ. Когда я ещё не имел доступа в Сеть, работать пришлось с заголовочными файлами от Hiroyuki Hori для DirectX 6 - их я достал на CD-ROM. Но теперь я использую DirectX 8. Вообще, надо приучить себя двигаться в ногу со временем и использовать новейшие технологии, даже если не хочется "слезать" с насиженной старой. И хотя никаких скоростных преимуществ при использовании методов DirectDraw из SDK 8 по сравнению c SDK 5 или 6 уже, наверное, не получить, предпосылок к использованию старых версий нет. Тем более что Microsoft стремится сделать отличия между API разных версий минимальными, а новейшая runtime-часть DirectX установлена уже наверняка у 99% игроманов. На кого рассчитаны примеры Все примеры рассчитаны на новичков. Даже если вы переиграли во все мыслимые игры, они не сделали из вас гуру в DirectX. Мне очень не хотелось перегружать программные примеры необязательными подробностями, поэтому все они (за исключением Sound), максимально упрощены. Мне хорошо известно, насколько трудно разбирать программы, написанные кем-то другим, если они ещё и большие по размеру. Все возможности игрового комплекса не используются, в частности, не показано, как корректно выводить в DirectDraw спрайты, выходящие за границы экрана, как плавно гасить экран и т. д. Всё это только усложняет программу - суть, сердце механизма работы компоненты теряется в дебрях кода, обеспечивающего примеру лишь красочность. Здесь вы не найдёте примеров для Direct3D и DirectMusic - я ещё сам только начинаю изучать трёхмерную графику (в основном пока OpenGL), а с MIDI-звуками связываться отказываюсь из-за примитивности технологии. DirectPlay тоже остался без внимания - это не мои интересы. Всегда начинайте рассмотрение программы с самого начала - процедуры FormCreate() главной формы, это поможет начать изучение структуры алгоритма. Я не ставил своей целью переписать главы книг по DirectX при составлении сопроводительных лекций - поэтому дал только самые общие разъяснения, которые, надеюсь, помогут ответить на немые вопросы новичков. Предполагается, что читатель владеет Delphi и умеет программировать на Object Pascal. Если вы до сих пор программировали только с использованием VCL, переход на API-подобный код может оказаться болезненным - забудьте про размещение компонент на форме подобно набору DelphiX и т. п. - сразу скажу, всё это глупости. Иногда я говорю "необходимо передать адрес переменной" - но в некоторых случаях символ @ перед переменной не ставится, а иногда он есть. Смотрите интерфейсы функций в заголовочных файлах - вы встретите зарезервированные слова var, out и const, которые указывают на необходимость работы с адресами. Что вам понадобится Все проекты строились в IDE Delphi 5. В версии 3 ошибки будут возникать уже на этапе компиляции, хотя бы из-за того, что в модуле DirectXGraphics.pas повсеместно используется тип LongWord, в третьей версии неопределённый. В версии 4 компилировать не пробовал, её у меня нет, возможно (не знаю точно), в среде не будут отображаться формы проектов. Также необходимы файлы заголовков для DirectX. Хотя существует множество различных файлов, для того, чтобы следовать хоть какому-то стандарту, я использовал файлы заголовков, загруженные с сайта www.delphi-jedi.org/delphigraphics. Эти файлы разработаны энтузиастами и представляют собой переложение файлов DirectX SDK фирмы Microsoft на язык Object Pascal. И, конечно же, нужны runtime-библиотеки DirectX 8. Их можно установить с компакт-диска DirectX SDK фирмы Microsoft. В крайнем случае возьмите какую-нибудь новую игру. Если вы всё же решите изучать DirectX по моим примерам, то сперва прочитайте файлы readme.txt и BUGS.txt - в нём я описал некоторые возникшие проблемы и недоработки. Скачать примеры: DirectX_Smpl.zip (125 K)

Описания примеров:

Enum Иногда неоходимо узнать, какое оборудование установлено в компьютере, удовлетворяет ли оно предъявленным требованиям и т. д. Например, к компьютеру может быть подключено несколько игровых контроллеров, и нужно дать возможность игроку выбрать для управления одно из них, а не первое попавшееся. К тому же, их вообще может не быть, и об этом тоже надо знать. Если установлены две звуковые карты, то одна из них може иметь аппаратную поддержку DirectX, а другая - нет. Одним словом, нужно иметь список наличествующего оборудования. DirectX и его компоненты могут обеспечить такое перечисление разных устройств для их выбора. В этом проекте в простой форме осуществляется перечисление установленных видеоадаптеров, звуковых карт и устройств ввода. Надеюсь, вы разберётесь с этими возможностями DirectX самостоятельно. DirectInput API Прежде чем начать знакомиться с примерами по DirectInput, желательно изучить общий механизм работы этого API, т .к. он одинаков в общих чертах для всех устройств ввода. Впоследствии я не буду останавливаться на повторяющихся приёмах, т. к. объясню их суть здесь. Sprite Этот пример демонстрирует вывод изображения средствами DirectDraw. Он построен без использования VCL, но я надеюсь это не помешает вам. Sound Одним из достоинств компоненты DirectSound является то, что она имеет прямой (ну, почти прямой) доступ к аппаратному обеспечению звуковой карты. Среди интересных возможностей следует выделить размещение данных в памяти звуковой платы, использование аппаратного микширования звука, возможность создавать объёмный звук, используя специальные алгоритмы (это нужно для 3D-игр) - всё это мультимедиа-средства Windows обеспечить не в состоянии

Обработка событий клавиш в DelphiX.

Если завис компьютер, выдерни шнур, выдави стекло.

Этот урок не совсем посвящен ДельфИксу. Сейчас вы научитесь обрабатывать такие события, как, например, нажатия клавиш. Но делать мы это будем не при помощи DXInput, так как я все ещё не знаю как с ним работать, делать мы это будем при помощи трех обычных событий: onKeyDown, onKeyUp и onKeyPreesed. Эти свойства есть и у формы и у DXDraw. Я буду работать с формой, кому больше нравится - могут работать с DXDraw, это ничего не меняет. Итак, в прошлый раз у нас была тарелка, летящая по уже заданной траектории. Теперь, я думаю было бы неплохо сделать спрайт стенки квадратным и небольшим, размножить их и добавить гравитацию, чтобы можно было летать по лабиринту и избегать столкновения со стенкой. Теперь стенка будет такой:

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

Можно будет размножить её, зная, что её размеры 23х23 и выстроить лабиринт. Кроме того не помешает создать спрайт "посадочной площадки", чтобы в лабиринте была цель. У меня она будет такой:

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

Теперь о том, как мы будем изменять координаты спрайта при создании. Конструктор Twall.Create теперь будет выглядеть так:(wx и wy - переменные типа Integer).

constructor Twall.Create(AParent: TSprite);
begin
  inherited Create(AParent);
  Image := Form1.ImageList.Items.Find('wall');
  X := wx;
  Y := wy;
  Z := 0;
  Width := Image.Width;
  Height := Image.Height;
  PixelCheck := True;
end;

Как вы уже наверно догадались, мы будем менять wx и wy для изменения координат стенки. Для того, чтобы построить вертикальную или горизнтальную линии мы будем использовать цикл for чтобы не мучиться. Привожу листинг стенки(сами разберетесь, листинг совсем несложный).

wy:=100;wx:=0;tx:=0;ty:=0;
for i:=1 to 20 do
begin
  Twall.create(SpriteEngine.Engine);
  wx:=wx+22;
end;
wy:=100;wx:=525;
for i:=1 to 12 do
begin
  Twall.create(SpriteEngine.Engine);
  wx:=wx+22;
end;
wy:=-20;wx:=0;
for i:=1 to 36 do
begin
  Twall.create(SpriteEngine.Engine);
  wx:=wx+22;
end;
wy:=597;wx:=0;
for i:=1 to 32 do
begin
  Twall.create(SpriteEngine.Engine);
  wx:=wx+22;
end;
wy:=0;wx:=-18;
for i:=1 to 36 do
begin
  Twall.create(SpriteEngine.Engine);
  wy:=wy+22;
end;
wy:=0;wx:=795;
for i:=1 to 36 do
begin
  Twall.create(SpriteEngine.Engine);
  wy:=wy+22;
end;

После всего этого должна получиться примерно такая картинка.

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

Теперь главное: заставить тарелку двигаться в зависимости от того, какие клаишы нажаты. "Раскладка" будет простой: W A S D, по их расположению, то есть W-вверх S-вниз А-влево D-вправо. Теперь о том, как мы это будем делать: мы будем менять две переменные, tx и ty(по аналогии с wx и wy). Менять мы их будем так: нажали W - ty увеличилась на 1, отпустили - уменьшилась. Создадим обработчики двух процедур: onKeyUp и onKeyDown. В оnКеуDоwn'е пишем:

case chr(ord(Key)) of
  'W','Ц': ty := -1;
  'S','Ы': ty := 1;
  'D','А': tx := 1;
  'A','Ф': tx := -1;
end;

Разбираем: case ... of ... end; - обычный оператор перебора вариантов, в вариантах стоят две буквы для того, чтобы программа не зависела от того, какой язык включен - русский или английский. В оnКеуUр'е пишем(разбора не будет):

case chr(ord(Key)) of
  'W','Ц': ty := 0;
  'S','Ы': ty := 0;
  'D','А': tx := 0;
  'A','Ф': tx := 0;
end;

Теперь добавляем гравитацию(в Ttarelka.Move вместо Y:=Y+ty пишем Y:=Y+ty+1) и этот урок закончен!

P.S. Как обычно, здесь вы можете скачать готовую программу

Проигрывание звука, передвижение картинок и проверка их столкновения в DelphiX.

Палестинский филиал WESTWOOD впервые в мире реализовал первый уровень игры "RED ALERT 2" (взятие Пентагона) в полном 3D и максимальным уровнем реализма... Сообщается о незначительных (около 50 000) жертвах среди бета-тестеров...

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

Это фон(он нарисован в Fireworks):

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

А это тарелка(нарисована там же):

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

Если вы не хотите рисовать сами, вы можете скачать мой вариант

Итак, приступим к работе

Цель этого занятия - научится проигрывать звук, передвигать картинки и проверять их столкновения. Для проверки столкновений понадобится третий спрайт, я нарисую стенку.

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

Теперь я уже не буду объяснять, как вывести стенку на экран. Я выведу её с координатами "400,400.0".

Все, можно пристурать к анимированию. В описание класса Ttarelka, перед "Public" надо поставить такую строку:

procedure DoMove(MoveCount: Integer); override;

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

Ttarelka = class(TImageSprite)
  procedure DoMove(MoveCount: Integer); override;
public
  constructor Create(AParent: TSprite); override;
end;

Теперь эту процедуру надо ПРОписать. Сразу после трех конструкторов пишем:

procedure Ttarelka.DoMove(MoveCount: Integer);
begin
  inherited DoMove(MoveCount);
  Collision;
  X:=X+1;
  Y:=Y+1;
end;

Разбираем: изменяться тут будут только большие "X" и "Y" - координаты спрайтов, "+1" - коэффициент изменения, берется от фонаря (или так, как задумал).

Запускаем и видим тарелку, летящую по диагонали, налетающую на стенку и летящую дальше!

Теперь думаем: зачем тут стенка? Обычно через стенки не летают, о них разбиваются. Начинаем писать проверку столкновений. Там же, где описывали doMove(перед "Public") пишем:

procedure DoCollision(Sprite: TSprite; var Done: Boolean); override;

Теперь описание класса "Ttarelka" должно быть таким:

Ttarelka = class(TImageSprite)
  procedure DoCollision(Sprite: TSprite; var Done: Boolean); override;
  procedure DoMove(MCount: Integer); override;
public
  constructor Create(AParent: TSprite); override;
end;

Теперь ПРОписываем, после прописывания процедуры doMove:

procedure Ttarelka.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
  if Sprite is Twall then
    dead;
end;

Запускаем и видим, как тарелка наскакивает на стену и разбивается!

Только скучно как-то! Полетела, разбилась и все! Сделаем так, чтобы спрайт тарелки создавался по щелчку мыши (и заодно увидим, что процедура, написанная для одного спрайта действует и для всех его копий). В "Событиях"(Events) объекта DXDraw создаем обработчик onClick и пишем туда:

Ttarelka.create(SpriteEngine.Engine);

Заодно убираем эту процедуру из onFormCreate.

Ну все бы хорошо, ещё бы было этакое "БУМ!" когда тарелка разбивается!

Ставим на форму DXWaveList и DXSound. В свойства "DXSound" вэйв-листа выбираем "DXSound1" и переименовываем сам DXWaveList1 в DXWaveList. В Items'ax создаем новый, называем его "boom" и ставим в свойстве "wave" звук. Теперь в процедуру "doCollision" добавляем:

Form1.DXWaveList.Items.Find('boom').Play(False);

Теперь процедура doCollision выгдядит так:

procedure Ttarelka.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
  if Sprite is Twall then
  begin
    Form1.DXWaveList.Items.Find('boom').Play(false);
    dead;
  end;
end;

Наслаждемся тем, что второй урок наконец закончен!

P.S. DXDraw на форме надо будет растянуть на весь экран.

P.P.S. У кого что-то не получилось, могут скачать готовую программу

Кому сильно охота хотя бы немного посмотреть настоящие возможности ДельфИкса, те могут скачать игру, полностью написанную на ДельфИксе, причем написанную не кем-то особо крутым, а автором сайта http://instrumentari.narod.ru (игра на двоих). а здесь лежат исходники (только для Delphi 6.0).

Delphi и 1C - экспорт и импорт.

Автор: Александр Авдошин Специально для Королевства Delphi Довольно часто перед программистами, работающими в небольших компаниях, стоит проблема импорта данных из программы "1С:Предприятие", или экспорта в нее же. Причин тому может быть множество - например, желание автоматизировать обновление прайс-листа на веб-страничке компании на основании реальных данных, или же автоматизация ввода первичных документов, отправляемых по электронной почте компанией-поставщиком. Какая бы задача подобного рода ни стояла перед программистом, она, как правило, успешно решается с помощью связки Delphi-1C. В этой статье я хотел бы дать рекомендации и разъяснить некоторые аспекты использования механизма OLE Automation применительно к программе "1С:Предприятие версия 7.7". Перед прочтением статьи я настоятельно рекомендую Вам ознакомиться с книгой "Delphi 4 Unleashed" Чарльза Калверта и с главой "Связь с внешними приложениями посредством механизмов DDE и OLE Automation" книги "1С:Предприятие 7.7 Описание встроенного языка". Также я предполагаю, что вы имеете опыт программирования как в среде Delphi, так и в среде "1С:Предприятие". Первые шаги Ну, во-первых, прежде чем использовать все возможности программы "1С:Предприятие", необходимо сначала создать соответствующий OLE-объект. Идентификатор этого OLE-объекта зависит от версии и типа установленной программы "1С:Предприятие": V1CEnterprise.Application - версия независимый ключ V77.Application - версия зависимый ключ V77S.Application - версия зависимый ключ, SQL-версия V77L.Application - версия зависимый ключ, локальная версия V77M.Application - версия зависимый ключ, сетевая версия

Например, создадим OLE-объект для сервера "1С:Предприятие". Для простоты создадим объект без привязки к конкретной версии и типу программы:

procedure TForm1.Create1C;
var
  onesobj: Olevariant;
begin
  onesobj := createoleobject('V1CEnterprise.Application');
end;
Затем мы должны проинициализировать систему методом Initialize, имеющим следующие параметры: Initialize(.RMTrade,,), где: - Идентификатор созданного OLE объекта - Строковое выражение - командная строка запуска - Строковое выражение. Может содержать пустую строку или строковое значение "NO_SPLASH_SHOW" - отключить заставку при запуске системы. Метод Initialize возвратит значение логического типа: TRUE, если инициализация прошла удачно, или FALSE в противном случае. Следует иметь в виду, что в OLE Automation TRUE и FALSE имеют соответственно значения -1 (минус единица) и 0. Параметры командной строки запуска подробно описаны в руководстве к программе "1С:Предприятие", здесь же я приведу лишь те, которые могут оказаться вам полезными: /DПуть к базе - задает путь к базе программы. /M - запуск программы в монопольном режиме /NИмя пользователя /РПароль - пароль указанного пользователя Параметры, не указанные в командной строке, будут запрошены программой в диалоговом режиме. Например, инициализация программы в монопольном режиме с явным указанием пути к базе данных (D:\buh2001test), имени пользователя (Саша) и пароля (12345) без вывода на экран заставки выполняется следующим образом (здесь и далее подразумевается, что объект onesobj уже создан оператором createoleobject):
onesobj.initialize(onesobj.rmtrade,'/DD:\buh2001test /M /NСаша /P12345','NO_SPLASH_SHOW'); 
В отличие от, например, OLE Automation-сервера приложения Microsoft Excel, сервер программы "1С-Предприятие" запускается в режиме "hide", то есть рабочее окно программы не отображается на экране. Для использования созданного и проинициализированного объекта необходимо просто обращаться к атрибутам и методам системы 1С:Предприятие как OLE Automation сервера. Для завершения работы с программой необходимо освободить OLE-объект путем присвоения ему значения UnAssigned:
onesobj := UnAssigned; 
Впрочем, это необязательно, так как при закрытии вашего приложения OLE-объект будет освобожден автоматически.

Просуммируем полученные знания: создадим OLE-объект "1С:Предприятие", проинициализируем его и корректно освободим:

procedure TForm1.Create1C;
var
  onesobj: Olevariant;
begin
  onesobj := createoleobject('V1CEnterprise.Application');
  onesobj.initialize(onesobj.rmtrade,
    '/DD:\buh2001test /M /NСаша /P12345', 'NO_SPLASH_SHOW');
  onesobj := UnAssigned;
end;
Как работать с полученным объектом

Резонный вопрос. Собственно, ради этого все и затевалось, не так ли? :) На самом деле, все очень просто. После того, как мы создали и проинициализировали OLE-объект, работать с ним можно следующим образом:

С помощью метода EvalExpr(<СтрокаВыражения>) Метод EvalExpr вычисляет выражение, записанное параметре <СтрокаВыражения> на встроенном языке 1С:Предприятие и возвращает результат вычисления. Результатом выражения может быть число, строка, дата или значение любого агрегатного типа данных. С помощью метода CreateObject(<ИмяАгрегатногоТипа>) Метод CreateObject создает объект агрегатного типа данных системы 1С:Предприятие и возвращает ссылку на него. Данная функция обычно используется одновременно с явным определением переменной типа OLEVariant и присвоением ей ссылки на объект агрегатного типа данных. С помощью метода ExecuteBatch(<СтрокаОператоров>) Метод ExecuteBatch выполняет последовательность операторов, записанную в параметре <СтрокаОператоров> на встроенном языке 1С:Предприятие. Метод возвращает -1, если последовательность операторов выполнена успешно, или 0 в противном случае. Вызовом атрибутов и методов системы 1С:Предприятие как OLE Automation сервера

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

При вызове атрибутов и методов системы 1С:Предприятие необходимо использовать их англоязычные синонимы (они указаны для каждого метода в книге "Описание встроенного языка") Для создаваемого агрегатного типа данных в среде Delphi необходимо завести переменную типа OLEVariant В случае, если вызываемый метод OLE-объекта не требует параметров (либо один из параметров является необязательным), в качестве параметра ему необходимо передавать EmptyParam (либо - для Delphi 3 - пустую строку). Для обращения к русскоязычным идентификаторам объектов агрегатных типов (например, реквизитов справочников) следует использовать метод объекта агрегатного типа getattrib(<ИмяАтрибута>) для получения значения атрибута, и setattrib(<ИмяАтрибута>) для установки значения.

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

procedure TForm1.exportsprav;
var
  counter: integer; //Счетчик импортированных записей
  onesobj: Olevariant; //OLE-объект программы 1С:Предприятие
  ware, ware2: olevariant; //Агрегатные объекты
  val, edizm, nds, np: olevariant;
  pf: integer; //Промежуточные переменные
begin
  table1.open; //Открываем таблицу1
  table2.open; //Открываем таблицу2
  counter := 0; //Обнуляем счетчик записей
  onesobj := createoleobject('V1CEnterprise.Application'); //Создаем OLE-объект
    //Инициализируем объект
  onesobj.initialize(onesobj.rmtrade, '/DD:\buh2001test /M /NСаша /P12345', 'NO_SPLASH_SHOW');
    //Создаем необходимые агрегатные объекты
  ware := onesobj.createobject('Справочник.Номенклатура');
  ware2 := onesobj.createobject('Справочник.Номенклатура');
  edizm := onesobj.createobject('Справочник.ЕдиницыИзмерений');
  nds := onesobj.createobject('Справочник.СтавкиНДС');
  np := onesobj.createobject('Справочник.СтавкиНП');
  ware.selectgroup(1); //Устанавливаем режим выборки групп
  ware.selectitems(1); //Открываем выборку элементов справочника
  while ware.GetItem(1) > 0 do //Выбираем все элементы
  begin
    if ware.level('') = 1 then //Если мы выбрали группу первого уровня, то
      pf := -1
    else
    begin
            //Иначе ищем элемент-родитель
      ware2.FindItem(ware.getattrib('Родитель'));
      if table2.findkey([ware2.getattrib('Код')]) then
                //Если этот элемент мы уже импортировали
        pf := table2.fieldbyname('ID').AsInteger //, то получаем его код
      else
        pf := -1; //иначе помещаем элемент в группу первого уровня
    end;
    if ware.deletemark('') = 0 then //Если элемент не удален, то
    begin
      table1.append; //добавляем новое поле к таблице
            //Заполняем поля таблицы значениями соответствующих атрибутов элемента справочника
      table1.fieldbyname('CODE_1S').AsInteger := ware.getAttrib('Код');
            //Заполняем поле наименования
      table1.fieldbyname('NAME').AsString := ware.getAttrib('Наименование');
      table1.fieldbyname('PARENT_FOLDER').AsInteger := pf;
      table1.fieldbyname('FULLNAME').AsString := ware.getAttrib('ПолнНаименование');
            //Ищем соответствующую запись в справочнике "единицы измерения"
      edizm.finditem(ware.getattrib('ЕдиницаИзмерения'));
            //Заполняем поле единицы измерения
      table1.fieldbyname('EDIZM').AsString := edizm.getattrib('Наименование');
            //так мы получаем значения периодических реквизитов
      table1.fieldbyname('SEBESTOIM').AsFloat :=
        ware.getAttrib('Себестоимость').GetValue(datetostr(now));
      table1.fieldbyname('PRICEOPT').AsFloat := ware.getAttrib('Цена');
      nds.finditem(ware.getAttrib('СтавкаНДС').GetValue(datetostr(now)));
      np.finditem(ware.getAttrib('СтавкаНП').GetValue(datetostr(now)));
            //Заполняем поле ставки НДС
      table1.fieldbyname('STNDS').AsFloat := nds.getAttrib('Ставка');
            //Заполняем поле ставки НП
      table1.fieldbyname('STNP').AsFloat := np.getAttrib('Ставка');
      table1.fieldbyname('ARTICUL').AsString := ware.getAttrib('Артикул');
      if Ware.IsGroup('') = 1 then //Если мы выбрали группу товара, то
        table1.fieldbyname('IS_FOLDER').AsInteger := 1
      else
        table1.fieldbyname('IS_FOLDER').AsInteger := 0;
      table1.post;
      table2.refresh;
    end;
    inc(counter);
  end;
end;
Заключение К сожалению, невозможно вместить в одну статью всю информацию, которая была бы вам полезна. Я постарался дать лишь тот минимум, который необходим для получения некоторых базовых знаний, и способен стать фундаментом для ваших собственных маленьких открытий в области интеграции Delphi и "1С:Предприятие".

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

Что Delphi знает об ассемблере.

Автор: Павел Сегодня мы рассмотрим вопрос применения ассемблера в программах, написанных на Delphi. Итак, что же Delphi "знает" об ассемблере? Команды процесора Ура, начиная с шестой версии, в популярном продукте Borland реализована поддержка всех команд процессора, включая команды расширений MMX, SSE, 3DNow! Теперь нет необходимости вместо простой команды rdtsc писать dw 310Fh . Доступ к переменным

Delphi позволяет легко обращаться по имени к глобальным или локальным переменным:

var
  i: integer;
begin
  i := 0;
  asm
    mov eax,i
    inc eax
    mov i,eax
  end;
end;
Доступ к параметрам

К переменным, передаваемым процедуре в качастве параметров доступ осуществляется также легко:

procedure SomeProc(i: integer);
begin
  asm
    mov eax,i
    inc eax
    mov i,eax
  end;
and;
Передача параметров по ссылке

Если параметр процедуры или функции объявляется как var, то вместо значения переменной передается указатель на эту переменную. Поэтому внутри ассемблерного блока этот параметр будет представлять собой 32-разрядный указатель вместо собственно переменной. Поэтому обращаться к параметру, переданному как var надо следующим образом:

procedure SomeProc(var i: integer);
begin
  asm
    mov eax,i
    inc dword ptr [eax]
  end;
end;
Регистровое соглашение о вызове

В языке Object Pascal, использующемся в Delphi, по умолчанию действует регистровое соглашение о вызове. Согласно этому соглашению, первые три 32-разрядных параметра передаются в регистры eax,edx и ecx. Следовательно, если функция объявлена следующим образом.

function SomeFunc(i1, i2, i3 : integer): integer;
то можно расчитывать, что переменная i1 содержится в регистре eax, i2 - в edx, i3 - в ecx.

Если объявлен некий метод.

procedure TSomeObject.SomeProc(i1, i2 : integer);
то i1 передается в edx, i2 - в ecx, а в eax передается неявно заданный параметр Self! Доступ к полям записи

Обращение к полям записи также достаточно просто:

type
  TSomeRec = record
    i: integer;
    c: char;
  end;
procedure SomeProc(var SomeRec: TSomeRec);
begin
  asm
    mov [eax].TSomeRec.i, 33
    mov [eax].TSomeRec.c,'a'
  end;
end;
Локальные метки Хотя использование меток и считается дурным тоном в программировании на языках высокого уровня, в ассеблерных программах без них не обойтись. Внутри блока asm..end можно, без предварительного объявления, использовать метки, начинающиеся с символа '@'. Сохранение регистров В ассемблерных процедурах и функциях нужно сохранять регистры EDI, ESI, ESP, EBP, and EBX и свободно оперировать регистрами EAX, ECX, and EDX. Вызов динамических и виртуальных методов Существуют две директивы для доступа к динамическим и виртуальным метолам из ассемблерных вставок. VMTOFFSET - возвращает смещение (в байтах) указателя виртуального метода относительно начала таблицы виртуальных методов (VMT). DMTINDEX - возвращает индекс динамического метода в таблице динамических методов.

Примеры вызовов виртуального и динамического методов:

procedure CallDynamicMethod(e: TExample);
asm
  push esi
  mov  eax,e
  mov  esi, DMTINDEX TExample.DynamicMethod
  call System.@CallDynaInst
  pop  esi
end;
procedure CallVirtualMethod(e: TExample);
asm
  mov     eax, e
  mov     edx, [eax]
  call    dword ptr [edx + VMTOFFSET TExample.VirtualMethod]
end;

Delphi и CorelDraw.

Статьи по Delphi (005)…
Автор: Грабец Олег
- А знаете, Ватсон, чем наполнены шары у CorelDraw? - Чем, мистер Холмс? - Водородом! - ?? - Ну это же элементарно, Ватсон... Вчера у меня Corel рухнул - так всю Винду разнесло нафиг!

Надеюсь, многие сталкивались с Corel Draw? А у многих слюнки текли, что это мощнейший графический редактор и хотелось бы под него свои программы писать, к примеру, чертежи выводить? Я один из вас :).

Формат файлов *.cdr конечно, не представлю, т.к. сам его не знаю :), но как с этим зверем работать расскажу. Вычитал, что с Corel Draw можно работать только через скрипт, причем изначально я готовил файлы скриптов *.csc, а затем их запускал в самом редакторе. Рабочий инструмент для освоения - Corel Script Editor. Если Вы хотите действительно что-то написать, то он вам просто необходим, хотя бы ради того, что смотреть как Corel Draw их сам создает, ну и самое главное - дока по языку и функциям. Все замечательно, только вот скрипты медленно работают т.к. они эмитируют работу человека - т.е. кнопочки сами нажимаются, панельки меняются и т.д.

А чертеж, к примеру на котором около 3000 объектов мог загружаться и исполнятся до часу! Нет, кода это утомляет, то можно и самому посидеть - глядишь за неделю сделаешь :).

И тут я "чисто случайно" наткнулся на статейку http://www.djpate.freeserve.co.uk/AutoCDrw.htm. Оказывается можно и через OLE этот Corel Draw дергать, и как оказывается, не так уж оно и сложно. Да, совершенно верно, нужно использовать CorelDraw.Automation.xx. Я возился с 8-й версией. Забегая на перед, скажу, что тот же чертеж выводился в течении 5-10 минут.

Ну что, начнем?

var
  CorelDraw: Variant;
...
CorelDraw := CreateOleObject('CorelDraw.Automation.8');
// цифирку можете свою поставить
CorelDraw.FileNew;
// или CorelDraw.FileOpen(FileName);
CorelDraw.SetDocVisible(True);
// можно и не показывать, что он там делает, но ведь интересно! :)
// кстати, можно нарисовать, а потом показать - будет на 30% быстрее
... // ну и в конце
CorelDraw.FileSave('NewName', 0, False, 0, False);
CorelDraw.FileExit(False); // можно не писать, если не надо закрывать
CorelDraw := Unassigned;

Формат функций доступным английским языком описан в draw_scr.hlp. Ну а дальше, чего душа (или начальство :) ) желает:

CorelDraw.SetPageOrientation(0);
CorelDraw.SetPageSize(PageW, PageH);
CorelDraw.NewLayer('NewLayer1');
CorelDraw.SelectLayer('NewLayer1');
CorelDraw.CreateEllipse(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), 0, 0, 0);
// ничего я не перепутал!!! именно так у них координаты!
CorelDraw.CreateRectangle(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), CalcX(Radius));
...

Все ясно? За дело!

Да, чуть не забыл о самом главном - как и у любой системы в Corel Draw есть свои "заморочки" :).

Ноль координат находится в середине листа бумаги (оригинально, правда?).

Положительная ось Y направлено вверх, а X - в право.

Координаты - целые числа в микронах. Для удобства я писал функцию:

function CalcX(x_mm: double): longint;
begin
  result := Round(x_mm * 10000);
end;

Углы не знаю в чем, но 90 градусов надо записать как 90000000. Положительные против часовой стрелки.

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

Выше упомянутое наглядно видно на прорисовке текста:

CorelDraw.CreateArtisticText( Text, CalcX(X), CalcY(Y));
// создаем текст. X,Y - левый нижний
// как видите, нет параметров шрифта, размера и пр.
with Font do
begin
  if (Italic) and (Bold) then
    FSK:=14
  else
  if (Italic) then
    FSK:=8
  else
  if (bold) then
    FSK:=13
  else
    FSK:=7;
end;
CorelDraw.SetCharacterAttributes( 0, 0, Font.name, FSK, Abs(Font.Size)*10, 
0, 0, 0, 0, 0, 1000, 1000, HAlign);
// присваиваем атрибуты шрифта.
// HAlign имеет значения 1,2,3 - влево, по центру, вправо соответственно
ColorToCMYK(Font.Color, C,M,Y,K);
// это моя функция для преобразования tColor в составляющие в модели CMYK 
CorelDraw.StoreColor(2, C,M,Y,K, 0,0,0,0); // создание цвета
CorelDraw.ApplyUniformFillColor; // применяем цвет к объекту

Тоже самое относится к трансформации объектов – сперва создаете, а затем изменяете как хотите.

Работают функции для получения информации.

CorelDraw.GetSize(XSize, YSize); // получили размеры объекта
CorelDraw.MoveObject(0, -YSize); // сдвинули его вниз на свой размер

Можно "проверить" все существующие объекты. За круглым столом спрашивали, как это делается, а делается это так:

var
  ObjID, FirstObjID: longint;
begin
  CorelDraw.SelectAllObjects;
  CorelDraw.SelectNextObject(true);
  // true для "захода" в сгруппированный объект
  FirstObjID := CorelDraw.GetObjectsCDRStaticID;
  repeat
    ...
    // работа с объектом CorelDraw.SelectNextObject(true);
    ObjID := CorelDraw.GetObjectsCDRStaticID;
  until
    ObjID = FirstObjID;
  ...

Вот теперь вроде все. Пишите, если что не понятно.

Delphi и OLE Automation с Excel.

Автоматизация позволяет одному приложению управлять другим приложением. Управляемое приложение называется сервером автоматизации (в нашем случае Excel). Приложение, управляющее сервером называется диспетчером автоматизации. Есть два пути для получения доступа к серверам автоматизации: Позднее связывание (Интерфейс IDispatch).

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

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

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

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

Раннее связывание (Использование библиотеки типов/интерфейсов).

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

Библиотека типов должна импортироваться в Delphi. Библиотека типов является языковым нейтральным описанием всех объектов и функций, поддерживаемых сервером. (Это подобно файлу заголовка языка C).

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

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

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

Подготовка библиотеки типов.

Модуль Pascal должен быть создан на основе файла библиотеки типов.

Выберите пункт меню Project|Import Type Library.

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

C:\program files\microsoft office\office\excel8.olb.

Нажмите OK.

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

Наиболее простой путь заключается в следующем: удалите модуль excel_tlb из проекта и только после этого добавьте его в список используемых модулей.

Документация.

Справочный файл c:\program files\microsoft office\office\vbaxl8.hlp содержит информацию о доступных объектах Excel.

"Записыватель" макросов позволяет быстро создавать VBA-код. После этого он довольно может легко быть портирован в Delphi.

Пример автоматизации.

Код следующего примера демонстрирует создание простой электронной таблицы и наполнение ее данными. Не забудьте добавить excel_tlb в список используемых модулей.

Настоятельно рекомендую хранить код автоматизации в отдельном модуле, это поможет избежать проблем конфликта имен.

unit sheet;
interface
uses
  windows, sysutils, excel_tlb;
procedure CreateSpreadsheet;
implementation
procedure CreateSpreadsheet(filename: string);
var
  xla: _Application;
  xlw: _Workbook;
  LCID: integer;
begin
  xla := CoApplication.Create;
  LCID := GetUserDefaultLCID;
  try
    xla.Visible[LCID] := true;
    // пустая книга
    //xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
    // новая книга на основе шаблона
    xlw := xla.Workbooks.Add(
      'c:\delphi\excel\sample\demo.xlt',
      LCID);
    xla.Range['A1', 'A1'].Value := 'Date';
    xla.Cells[1, 2].Value := FormatDateTime('dd-mmm-yyyy', Now);
    xla.Cells[3, 1].Value := 'Numbers';
    xla.Range['B3', 'E3'].Value := VarArrayOf([1, 10, 100, 1000]);
    xla.Range['F3', 'F3'].Formula := '=Sum(B3:E3)';
    OLEVariant(xla).Run(
      'Demo',
      FormatDateTime('dd-mmm-yyyy', Now)
      );
    xlw.SaveAs(
      filename,
      xlWorkbookNormal,
      '', '', False, False,
      xlNoChange,
      xlLocalSessionChanges,
      true, 0, 0, LCID);
  finally
    xla.Quit;
  end;
end;
end.

Добавьте библиотеку типов в список используемых молулей.

uses
  windows, sysutils, excel_tlb;

Первая строчка кода создает объект Excel приложения.

xla := CoApplication.Create;

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

LCID := GetUserDefaultLCID;

Следующая строчка кода устанавливает в истину свойство видимости, что заставляет Excel вывести свое окно. Это полезно для контроля выполняемого кода.

Примечание: Для вызова этой функции необходим параметр LCID. К сожалению этот факт умалчивается в электронной документации по Excel. В файле c:\program files\borland\Delphi 3\imports\excel_tlb.pas наглядно видны свойства функций и определения методов.

xla.visible[LCID] := true;

Следующий код создает новую книгу и назначает ссылку на нее одной из переменных Delphi. Для VBA параметр шаблона необязателен, для Delphi - обязателен.

xlw := xla.Workbooks.Add('c:\delphi\excel\sample\demo.xlt', LCID); 

Примечание: Вам вовсе не обязательно подставлять файл шаблона Excel (.xlt), но все же это наилучший способ для форматирования информации. Чем больше сделано с помощью Excel, тем меньше придется делать с помощью Delphi. На данный момент это является лидирующей технологией.

Для создания пустой книги используйте:

xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);

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

xla.Range['A1', 'A1'].Value := 'Date';
xla.Cells[1, 2].Value := FormatDateTime('dd-mmm-yyyy', Now);

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

xla.Range['A2', 'D2'].Value := VarArrayOf([1, 10, 100, 1000]);

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

xla.Range['E2', 'E2'].Formula := '=Sum(a2:d2)';

Следующая строка кода выполняет VBA функцию, хранящуюся в файле шаблона. На первый взгляд все выглядит достаточно сложно, но это только кажется. Преобразование типа xla к OLEVariant позволяет вызвать функцию, используя позднее, а не раннее связывание. (Причина в имени метода и параметрах, решаемых только во время прогона программы, а никак во время разработки). Delphi просто не знает количество и тип параметров, передаваемых макросу ‘Demo’.

OLEVariant(xla).Run(
'Demo',
FormatDateTime('dd-mmm-yyyy', Now));

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

xlw.SaveAs(
filename,
xlWorkbookNormal,
'', '',False,False,
xlNoChange, xlLocalSessionChanges,
true, 0, 0, LCID);

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

xla.quit;
Итог. Всегда используйте раннее связывание. Если позднее связывание необходимо для вызовов некоторых функций, используйте где возможно раннее связывание и преобразование типа объектной переменной к типу OLEVariant для вызовов, требующим позднее связывание. Не включайте модуль библиотеки типов в ваш проект. Создавайте код автоматизации в отдельном модуле. Используйте "записыватель" макросов Excel для создания прототипа кода автоматизации. Используйте файл электронной справки vbaxl8.hlp для получения информации об объектах Excel. Используйте модуль excel_tlb.pas для проверки необходимых Delphi типов и количества параметров. Загружайте и используйте шаблоны Excel (.xlt файлы), содержащие предварительное форматирование и связывание данных. Этот способ существенно быстрее и не требует большого времени для создания форматированных электронных таблиц. Шаблоны ДОЛЖНЫ сохраняться приложением в своей рабочей директории. Это поможет избежать проблем, связанных с конфликтом имен. Файлы шаблонов могут также содержать макросы, которые могут быть вызваны из приложений Delphi. Удостоверьтесь в том, что ваш код содержит команду закрытия приложения Excel (xla.quit). Не вызывая xla.quit, можно быстро исчерпать системные ресурсы, особенно при работе с большим количеством документов Excel. Наличие множества незакрытых документов Excel легко проверить в Windows NT, используя Менеджер Задач (нажмите CTL+ALT+Del для его открытия). В больших электронных таблицах повысить быстродействие вам поможет обработка ячеек посредством "мультикоманды", оперирующей одновременно множеством ячеек. Это также улучшит читаемость кода. Приложение A – Быстродействие.

Тестирование производилось на компьютере P166 с 64Мб памяти. Первоначальная инициализация приложения не производилась. Это гарантировало, что Excel при загрузке пользовался диском, а не кэшем. Первоначальная инициализация существенно уменьшила бы скорость загрузки приложения. В реальной ситуации процесс загрузки занимает около 5 секунд.

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

Заполнение листа ячейка за ячейкой. Заполнение одной колонки за один проход. Заполнение всей таблицы за один проход.

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

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

Размер электронной таблицы (строки * колонки). Заполнение ячейка за ячейкой. Заполнение одной колонки за один проход. Заполнение всей таблицы за один проход.
10 * 10. 0:01. 0:01. >0:01.
100 * 10. 0:07. 0:01. 0:01.
1000 * 10. 1:13. 0:07. 0:05.
5000 * 10. 5:22. 0:35. 0:25.
Приблизительно ячейки/секунды. 150. 1500. 2000.

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

Большие таблицы эффективно заполнять колонка за колонкой.

Также необходимо учесть дополнительную сложность при кодировании методом "ячейка за ячейкой".

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

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

Использованные для тестов процедуры:

//-----------------------------------------------------------------------
procedure FillByCell;
var
  xla: _Application;
  xlw: _Workbook;
  LCID: integer;
  i, j: integer;
begin
  xla := CoApplication.Create;
  LCID := GetUserDefaultLCID;
  try
    xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
    for i := 1 to ROWS do
    begin
      for j := 1 to 10 do
      begin
        xla.Cells[i, j] := i + j;
      end;
    end;
    xlw.close(false, '', false, LCID);
  finally
    xla.Quit;
  end;
end;
//-----------------------------------------------------------------------
procedure FillByRow;
var
  xla: _Application;
  xlw: _Workbook;
  CellFrom: string;
  CellTo: string;
  i, j: integer;
  Row: array[1..10] of variant;
  LCID: integer;
begin
  xla := CoApplication.Create;
  LCID := GetUserDefaultLCID;
  try
    xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
    for i := 1 to ROWS do
    begin
      for j := 1 to 10 do
      begin
        Row[j] := i + j;
      end;
      CellFrom := 'A' + InttoStr(i);
      CellTO := 'J' + InttoStr(i);
      xla.Range[CellFrom, CellTo].Value := VarArrayOf(Row);
    end;
    xlw.close(false, '', False, LCID);
  finally
    xla.Quit;
  end;
end;
//-----------------------------------------------------------------------
procedure FillBySheet;
var
  xla: _Application;
  xlw: _Workbook;
  CellFrom: string;
  CellTo: string;
  i, j: integer;
  range: Variant;
  row: array[1..10] of Variant;
  LCID: integer;
begin
  xla := CoApplication.Create;
  LCID := GetUserDefaultLCID;
  try
    xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
    Range := VarArrayCreate([1, ROWS], varVariant);
    for i := 1 to ROWS do
    begin
      for j := 1 to 10 do
      begin
        row[j] := i + j;
      end;
      Range[i] := VarArrayOf(row);
    end;
    CellFrom := 'A' + InttoStr(1);
    CellTO := 'J' + InttoStr(ROWS);
    xla.Range[CellFrom, CellTo].FormulaArray := Range;
    xlw.close(false, '', False, LCID);
  finally
    xla.Quit;
  end;
end;
Приложение 2 – Использование в Delphi класса-оболочки

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

unit sheet;
interface
uses
  EXCEL_TLB, windows, sysutils;
//-------------------------------------------------------------------------
type
  tExcel = class
  private
    xla: _Application;
    xlw: _Workbook;
    LCID: integer;
    procedure fSetVisible(Visible: boolean);
    function fGetVisible: boolean;
    procedure fSetCell(Cell: string; Value: OLEVariant);
    function fGetCell(Cell: string): OleVariant;
  public
    constructor create;
    destructor destroy; override;
    procedure AddWorkBook(Template: OleVariant);
    procedure SaveAs(filename: string);
    property Visible: boolean
      read fGetVisible write fSetVisible;
    property Cell[Cell: string]: OleVariant
    read fGetCell write fSetCell;
  end;
  //-------------------------------------------------------------------------
procedure CreateSpreadsheet(filename: string);
//-------------------------------------------------------------------------
implementation
//-------------------------------------------------------------------------
constructor tExcel.create;
begin
  LCID := GetUserDefaultLCID;
  xla := CoApplication.Create;
end;
//-------------------------------------------------------------------------
destructor tExcel.destroy;
begin
  xla.Quit;
  inherited;
end;
//-------------------------------------------------------------------------
procedure tExcel.AddWorkBook(Template: OleVariant);
begin
  xlw := xla.Workbooks.Add(Template, LCID);
end;
//-------------------------------------------------------------------------
procedure tExcel.fSetVisible(Visible: boolean);
begin
  xla.visible[lcid] := Visible;
end;
//-------------------------------------------------------------------------
function tExcel.fGetVisible: boolean;
begin
  result := xla.visible[lcid];
end;
//-------------------------------------------------------------------------
procedure tExcel.fSetCell(Cell: string; Value: OLEVariant);
begin
  xla.Range['A1', 'A1'].Value := value;
end;
//-------------------------------------------------------------------------
function tExcel.fGetCell(Cell: string): OleVariant;
begin
  result := xla.Range['A1', 'A1'].Value;
end;
//-------------------------------------------------------------------------
procedure tExcel.SaveAs(filename: string);
begin
  xlw.SaveAs(
    filename,
    xlWorkbookNormal,
    '',
    '',
    False,
    False,
    xlNoChange,
    xlLocalSessionChanges,
    true,
    0,
    0,
    LCID);
end;

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

procedure CreateSpreadsheet(filename: string);
var
  xl: tExcel;
begin
  xl := tExcel.create;
  try
    xl.AddWorkBook('c:\graham\excel\sample2\ssddemo.xlt');
    xl.visible := true;
    xl.cell['a1'] := 'тест';
    xl.SaveAs(filename);
  finally
    xl.free;
  end;
end;

Delphi и Flash. Совмещение несовместимого.

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

Автор: Михаил Христосенко WEB сайт: http://mihandelphi.narod.ru/

Разве возможно совместить Флэш-ролики и Дельфи-приложения. Раньше я думал что НЕТ. Но теперь я знаю не только, что это возможно, но и знаю как это делается!!! И сейчас я вам расскажу об этом. Во-первых хочется отметить преимущества использования флэш-роликов в ваших программах. Если вы сумеете гармонично вписать небольшой флэш-ролик в вашу программу, то несомненно внешний вид программы будет намного привлекательнее (главное не переборщить, увлекаясь дизайном, не надо забывать о том что программа должна быть удобна и проста в использовании! ).

Итак, как же совместить Флэш и Дельфи? (Надеюсь, что у вас Флэш установлен:)).

Запустите Дельфи и выберите пункт меню Component->Import ActiveX Control... Перед вами откроется диалоговое окно с заголовком Import ActiveX Control. В разделе Registered Controls выберите Shockwave Flash. В разделе Pallete Page... Выберите страницу в палитре компонентов, на которой будет располагаться установленный компонент (по умолчанию это ActiveX). В разделе Unit Dir Name... путь к папке куда будет установлен компонент.

Нажмите на кнопку Install. Перед вами появится окно, в котором вам нужно будет выбрать в какой пакет будет установлен компонент (вы можете установить как в уже существующий, так и в новый пакет). Затем перед вами появится окно редактирования выбранного пакета и Дельфи вас спросит: "...Package will be rebuilt. Continue?". Ответьте Yes. Все готово теперь можно использовать флэш в ваших приложениях!!!

Теперь, чтобы показать вам как пользоваться этим компонентом, попробуем вместе сделать программу для просмотра *.SWF файлов. Для этого нам понадобятся следующие компоненты: TShockwaveFlash (для удобства назовите его просто Flash1), TTrackBar, TTimer, TOpendialog и три кнопки TButton ("открыть", "старт" и "стоп").

Для начала установим необходимые свойства OpenDialog'a.

Свойство Filter может быть таким: Флэш-ролики|*.swf.

Свойство DefaultExt должно быть: *.swf.

Для Timer'a нужно установить свойство Interval равным 1.

Для TShockwaveFlash:

Name сделайте равным Flash1.

Свойство Playing установите в false.

Свойство BGColor, установите как вам хочется (цвет фона).

Теперь напишем обработчик события OnClick для кнопки, которая вызывать OpenDialog:

if open1.Execute then
begin
  flash1.Movie := open1.FileName;
  {это делается для того, чтобы потом можно было
  перемещаю ползунок посмотреть каждый кадр ролика}
  trackbar1.Max := flash1.TotalFrames;
end;

В обработчик события OnClick для второй кнопки ("Старт") напишем:

flash1.Play;

Ну тут вообще все просто! Почти таким же образом это будет выглядеть для третьей кнопки ("Стоп"):

flash1.Stop;

Теперь сделаем, чтобы при перемещении ползунка Trackbar'a мы могли посмотреть каждый кадр (событие OnChange):

if Flash1.IsPlaying = true then
  Flash1.Stop; {если ролик проигрывается, то надо его остановить}
{открываем кадр номер которого соответствует позиции ползунка}
flash1.GotoFrame(trackbar1.position);

Ну и наконец осталось сделать чтобы при проигрывании ролика ползунок перемещался, указывая сколько осталось и сколько прошло. Для этого то мы и используем Timer. В обработчик события OnTimer,напишем:

trackbar1.Position:=flash1.CurrentFrame;

Приведу полный код приложения:

unit flash;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, StdCtrls, OleCtrls, ShockwaveFlashObjects_TLB, ExtCtrls;
type
  TForm1 = class(TForm)
    Flash1: TShockwaveFlash;
    Button1: TButton;
    TrackBar1: TTrackBar;
    Open1: TOpenDialog;
    Button2: TButton;
    Button3: TButton;
    Timer1: TTimer;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  if open1.Execute then
  begin
    flash1.Movie:=open1.FileName;
    trackbar1.Max:=flash1.TotalFrames;
  end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  flash1.Play;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  if Flash1.IsPlaying=true then
    Flash1.Stop;
  flash1.GotoFrame(trackbar1.position);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
  flash1.Stop;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  trackbar1.Position:=flash1.CurrentFrame;
end;
end.

Ну вот и все. Как оказалось ничего сложного.

Просто и ясно о MapInfo и Delphi - Вызов MapInfo и встраивание его в свою программу (основы интегрированной картографии).

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

Автор: Дмитрий Кузан.

Доброе время суток !

Данной статьей я начинаю цикл статей посвященных возможностям интегрированной картографии MapInfo и возможности встраивания геоинформационной системы в вашу программу. Примечания : Все примеры распространяются свободно и разработаны в обучающих целях на Delphi 6. Всю информацию по работе смотрите в прилагаемых исходных кодах.

Итак начнем.

Что такое MapInfo и с чем его едят? Краткое предисловие.

Сейчас нам доступны огромные объемы информации. Данные хранятся в электронных таблицах, отчетности о торговле и маркетинге. Масса информации о клиентах, магазинах, персонале, оборудовании и ресурсах находится на бумаге и в памяти компьютеров Тематическая Карта, содержащая слой диапазонов (процент занятости) и круговые диаграммы (производство с/х продуктов) Почти все эти данные имеют географическую составляющую. По разным оценкам до 85 процентов всех баз данных содержат, какую либо географическую информацию.

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

Как настольная картография может работать на Вас? MapInfo, как средство настольной картографии, - это мощное средство анализа данных. Вы можете придать графический вид статистическим и прочим данным. Вы можете отобразить Ваши данные как точки, как тематически выделенные области, круговые и столбчатые графики, районы и т.п. К данным можно применять географические операторы, такие как районирование, комбинация и разрезание объектов и буферизация. Доступ к данным можно оформлять как запросы, в том числе к удаленным базам данных непосредственно из MapInfo. Например, какой из магазинов ближе к самым крупным клиентам Вашей фирмы? На карте легко увидеть особенности и тенденции, которые практически невозможно выявить в списочно организованных данных. Можно легко вычислить расстояния между клиентами и магазинами; можно увидеть местоположение офиса клиента, потратившего наибольшую сумму за прошлый год; размер символов, отмечающих местоположение магазинов на Карте, может зависеть от объема продаж. Все это делает визуализацию Ваших данных более наглядной. Итак краткое предисловие из руководства пользователя дает вам общее представление об MapInfo.

Что такое интегрированная картография и какой нам от нее смысл.

Интегрированная картография позволяет управлять пакетом MapInfo, используя языки программирования отличные от MapBasic. Например если вам хорошо знакомо программирование на языке Visual Basic или С++ или Delphi (о чем и пойдет речь далее...) вы можете включить окно MapInfo в ваше приложение, тем самым обеспечивая интеграцию пакета MapInfo с логикой (бизнес-правилами) вашей программы.

Причем основную работу по поддержанию векторных карт берет на себя MapInfo (MapBasic) а вы можете создовать назначать обработчики и механизмы взаимодействия не свойственные MapBasic а также те механизмы которые MapBasic не поддерживает напрямую (например обновление карты по интернету,съем информации с датчиков на территории и обновление на карте ит.п.).

Итак приступим : в цикле статей будут рассмотрены следующие вещи.

Соединение и загрузка MapInfo Встраивание окна MapInfo и других окон (легенда, информация и т.д) в программу на Delphi Отправка команд MapBasic в пакет MapInfo Получение информации от MapInfo посредством функций Использование уведомляющих вызовов (CallBack) и подключение их к своей программе. Создание собственных уведомляющих вызовов Переопределение уведомляющих вызовов Обработка уведомляющих вызовов Создание простейшего компонента (возможно данная тема будет затрунута) для управления MapInfo. и многое другое.

Концепции Интегрированной Картографии

Для создания приложения с Интегрированной Картой Вы должны написать программу - но не программу на языке MapBasic. Приложения с Интегрированной Картой могут быть написаны на нескольких языках программирования, среди которых наиболее часто используются С,Visual Basic,Delphi.

В Вашей программе должна присутствовать инструкция, запускающая MapInfo в фоновом режиме. Например, в программе Вы можете запустить MapInfo вызовом функции CreateObject(). Программа MapInfo запускается в фоновом режиме незаметно для пользователя, не выводя заставку на дисплей. Ваша программа осуществляет управление программой MapInfo, конструируя строки, представляющие операторы языка MapBasic, которые затем передаются в MapInfo посредством механизмауправления объектами OLE (OLE Automation) или динамического обмена данных (DDE). MapInfo выполняет эти операторы точно так же, как если бы пользователь вводил их с клавиатуры в окно MapBasic.

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

Системные требования

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

Вы должны иметь опыт работы с Handle.

Ваша программа должна быть способна действовать в качестве контроллера механизма управления объектами OLE (OLE Automation Controller) или клиента динамического обмена данных DDE. Рекомендуется применение OLE контроллера как более быстрого и надежного метода по сравнению c DDE. Его то мы и будем рассматривать.

Другие краткие технические замечания

Интегрированная картография использует механизм управления OLE , но не использует OLE - внедрение. Интегрированная картография не использует элементы управления VBX или OCX (дело не совсем так - существует OCX модуль MapX - для работы с ГИС MapInfo (не входит в стандартный комплект поставки) , но это уже не интегрированная картография и он рассматриваться не будет). Интегрированная картография не предоставляет вам какие либо заголовочные файлы и библиотеки Интегрированная картография включает несколько DLL библиотек но не предоставляет к ним доступ напрямую.

Запуск и связывание с сервером MapInfo

Итак рассмотрим простейший компонент для запуска и управления MapInfo (TKDMapInfoServer),следует заметить что мной не ставилась написание специализированного компонента - я представляю основы.

unit KDMapInfoServer;
interface
uses
  ComObj, Controls, Variants, ExtCtrls, Windows, Messages,
  SysUtils, Classes;
const
  scMapInfoWindowClass = 'xvt320mditask100';
  icWinMapinfo = 1011;
  icWinInfoWindowid = 13;
type
  TEvalResult = record
    AsVariant: OLEVariant;
    AsString: string;
    AsInteger: Integer;
    AsFloat: Extended;
    AsBoolean: Boolean;
  end;
  TKDMapInfoServer = class(TComponent)
  private
    { Private declarations }
    // Владелец
    FOwner : TWinControl;
    // OLE сервер
    FServer : Variant;
    FHandle : THandle;
    FActive : Boolean;
    FPanel : TPanel;
    Connected : Boolean;
    MapperID : Cardinal;
    MapperNum : Cardinal;
    procedure SetActive(const Value: Boolean);
    procedure SetPanel(const Value: TPanel);
    procedure CreateMapInfoServer;
    procedure DestroyMapInfoServer;
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    // Данная процедура выполеняет метод сервера MapInfo - Do
    procedure ExecuteCommandMapBasic(Command: string; const Args: array of const);
    // Данная процедура выполеняет метод сервера MapInfo - Eval
    function Eval(Command: string; const Args: array of const): TEvalResult; virtual;
    procedure WindowMapDef;
    procedure OpenMap(Path : string);
  published
    { Published declarations }
    // Создает соединение с сервером MapInfo
    property Active: Boolean read FActive write SetActive;
    property PanelMap : TPanel read FPanel write SetPanel;
  end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('Kuzan', [TKDMapInfoServer]);
end;
{ TKDMapInfoServer }
constructor TKDMapInfoServer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FOwner := AOwner as TWinControl;
  FHandle := 0;
  FActive := False;
  Connected := False;
end;
destructor TKDMapInfoServer.Destroy;
begin
  DestroyMapInfoServer;
  inherited Destroy;
end;
procedure TKDMapInfoServer.CreateMapInfoServer;
begin
  try
    FServer := CreateOleObject('MapInfo.Application');
  except
    FServer := Unassigned;
  end;
  // Скрываем панели управления MapInfo
  ExecuteCommandMapBasic('Alter ButtonPad ID 4 ToolbarPosition (0, 0) Show Fixed', []);
  ExecuteCommandMapBasic('Alter ButtonPad ID 3 ToolbarPosition (0, 2) Show Fixed', []);
  ExecuteCommandMapBasic('Alter ButtonPad ID 1 ToolbarPosition (1, 0) Show Fixed', []);
  ExecuteCommandMapBasic('Alter ButtonPad ID 2 ToolbarPosition (1, 1) Show Fixed', []);
  // Переопределяем окна
  ExecuteCommandMapBasic('Close All', []);
  ExecuteCommandMapBasic('Set ProgressBars Off', []);
  ExecuteCommandMapBasic('Set Application Window %D', [FOwner.Handle]);
  ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);
  FServer.Application.Visible := True;
  if IsIconic(FOwner.Handle)then
    ShowWindow(FOwner.Handle, SW_Restore);
  BringWindowToTop(FOwner.Handle);
end;
procedure TKDMapInfoServer.DestroyMapInfoServer;
begin
  ExecuteCommandMapBasic('End MapInfo', []);
  FServer := Unassigned;
end;
procedure TKDMapInfoServer.ExecuteCommandMapBasic(Command: string;
const Args: array of const);
begin
  if Connected then
    try
      FServer.do(Format(Command, Args));
    except
      on E: Exception do MessageBox(FOwner.Handle,
      PChar(Format('Ошибка выполнения () - %S', [E.message])),
      'Warning', MB_ICONINFORMATION or MB_OK);
    end;
end;
function TKDMapInfoServer.Eval(Command: string;
const Args: array of const): TEvalResult;
  function IsInt(Str : string): Boolean;
  var
    Pos : Integer;
  begin
    Result := True;
    for Pos := 1 to Length(Trim(Str)) do
    begin
      if (Str[Pos] <> '0') and (Str[Pos] <> '1') and
      (Str[Pos] <> '2') and (Str[Pos] <> '3') and
      (Str[Pos] <> '4') and (Str[Pos] <> '5') and
      (Str[Pos] <> '6') and (Str[Pos] <> '7') and
      (Str[Pos] <> '8') and (Str[Pos] <> '9') and
      (Str[Pos] <> '.') then
      begin
        Result := False;
        Exit;
      end;
    end;
  end;
var
  ds_save: Char;
begin
  if Connected then
  begin
    Result.AsVariant := FServer.Eval(Format(Command, Args));
    Result.AsString := Result.AsVariant;
    Result.AsBoolean := (Result.AsString = 'T') or (Result.AsString = 't');
    if IsInt(Result.AsVariant) then
    begin
      try
        ds_save := DecimalSeparator;
        try
          DecimalSeparator := '.';
          Result.AsFloat := StrToFloat(Result.AsString);//Result.AsVariant;
        finally
          DecimalSeparator := ds_save;
        end;
      except
        Result.AsFloat := 0.00;
      end;
      try
        Result.AsInteger := Trunc(Result.AsFloat);
      except
        Result.AsInteger := 0;
      end;
    end
    else
    begin
      Result.AsInteger := 0;
      Result.AsFloat := 0.00;
    end;
  end;
end;
procedure TKDMapInfoServer.SetActive(const Value: Boolean);
begin
  FActive := Value;
  if FActive then
  begin
    CreateMapInfoServer;
    WindowMapDef;
    Connected := True;
  end
  else
  begin
    if Connected then
    begin
      DestroyMapInfoServer;
      Connected := False;
    end;
  end;
end;
procedure TKDMapInfoServer.SetPanel(const Value: TPanel);
begin
  FPanel := Value;
end;
procedure TKDMapInfoServer.WindowMapDef;
begin
  ExecuteCommandMapBasic('Set Next Document Parent %D Style 1', [FPanel.Handle]);
end;
procedure TKDMapInfoServer.OpenMap(Path: string);
begin
  ExecuteCommandMapBasic('Run Application "%S"', [Path]);
  MapperID := Eval('WindowInfo(FrontWindow(),%D)',[12]).AsInteger;
  with PanelMap do
    MoveWindow(MapperID, 0, 0, FPanel.ClientWidth, FPanel.ClientHeight, True);
end;
end.

И так что мы имеем -

Мы установили связь с сервером MapInfo. Мы узнали что у сервера MapInfo есть метод Do - он предназначен для посылки команд MapBasic серверу точно так-же как если бы пользователь набирал их в окне MapBasic-а самой программы MapInfo. Мы узнали что у сервера MapInfo есть метод Eval- он предназначен для получения значение функций после посылки команд MapBasic серверу. Мы познакомились с командами переопределения направления вывода MapInfo.

Для начала неплохо.

Теперь немного теории.

Запуск MapInfo

Запуск уникального экземпляра программы МарInfо осуществляется вызовом функции CreateObject() Visual Basic с присваиванием возвращаемого значения объектной переменной. (Вы можете декларировать объектную переменную как глобальную; в противном случае объект МарInfо освобождается после выхода из локальной процедуры.).

Например:

FServer := CreateOleObject('MapInfo.Application');

Для подключения к ранее исполнявшемуся экземпляру MapInfo, который не был запущен вызовом функции CreateObject(), используйте функцию GetObject().

// Данная реализация оставлена вам уважаемые читатели для тренировки
FServer := GetObject('MapInfo.Application'); 

Внимание: Если Вы работаете с Runtime-версией MapInfo, а не с полной копией, задавайте "MapInfo. Runtime" вместо "MapInfo. Арр1iсаtiоn". Runtime-версия и полная версия могут работать одновременно.

Функции CreateObject() и GetObject() используют механизм управления объектами OLE (OLE Automation) для связи с MapInfo.

Примечание: В 32-разрядной версии Windows (Windows95 или Windows NT) можно запускать несколько экземпляров MapInfo. Если Вы запустите MapInfo и вслед за этим программу, использующую Интегрированную Картографию и вызывающую CreateObjectf), то будут работать два независимых экземпляра MapInfo. Однако в 16-разрядной версии программа использующая Интегрированную Картографию с запущенным MapInfo работать не сможет.

Пересылка команд в программу MapInfo

После запуска программы MapInfo необходимо сконструировать текстовые строки, представляющие операторы языка Map Basic.

Если Вы установили связь с MapInfo, используя механизм управления объектами OLE (OLE Automation), передавайте командную строку программе MapInfo методом Do.

Например:

FServer.Do('здесь команда MapBasic');

Примечание: В компоненте это реализовано процедурой ExecuteCommandMapBasic, но в сущносте вызывается FServer.Do.

При использовании метода Do программа MapInfo исполняет командную строку точно так как если б ее ввели в окне команд MapBasic.

Примечание: Вы можете передать оператор в программу MapInfo, если этот оператор допустим окне MapBasic. Например, Вы не можете переслать MapBasic-оператор Dialog, поскольку его использование не разрешено в окне MapBasic.

Для определения допустимости использования оператора языка MapBasic в окне MapBasic обратитесь к Справочнику MapBasic или откройте Справочную систему; искомая информация находится под заголовком "Предупреждение". Например, в Справке по оператору Dialog дано следующее ограничение: "Вы не можете использовать оператор Dialog в окне исполнения (такие, как For..-Next и Goto), не разрешены для исполнения в окне MapBasic.

Запрос данных от программы MapInfo

Для выполнения запроса из Вашей программы-клиента значения MapBasic используйте OLE-методЕvа1.

Например:

MyVar:= FServer.Eval('здесь команда MapBasic');
Примечание: В компоненте это реализовано процедурой Eval, но в сущносте вызывается FServer.Eval

При использовании метода Eval программа MapInfo интерпретирует строку как выражение языка MapBasic, определяет значение выражения и возвращает это значение в виде строки. Замечание: Если выражение приводится к логическому значению (тип Logical), MapInfo возвращает односимвольную строку, "Т" или "F" соответственно.

Переподчинение окон MapInfo

После запуска MapInfo используйте оператор Set Application Window языка MapBasic для обеспечения перехвата управления Вашей программой-клиентом диалоговых окон и сообщений об ошибках программы MapInfo.

Затем, в желаемой точке включения окна MapInfo в Ваше приложение передайте MapInfo оператор Set Next Document, за которым следует MapBasic-оператор, создающий окно.

Оператор Set Next Document позволяет Вам "переподчинять" окна документов. Синтаксис этого оператора требует указания уникального номера HWND элемента управления в Вашей программе. При последующем создании окна-документа MapInfo (с использованием операторов Map, Graph, Browse, Layout или Create Legend) создаваемое окно становится для окна порождающим объектом.

Примеры приведены из компонента но тоже самое можно выполнить и метолом Do непосредственно, но вы это уже я думаю поняли.

ExecuteCommandMapBasic('Set Application Window %D', [FOwner.Handle]);
ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);
ExecuteCommandMapBasic('Set Next Document Parent %D Style 1', [FPanel.Handle]); 

Примечание: В компоненте это реализовано процедурой WindowMapDef которая ссылается на панель заданную свойством PanelMap.

Для каждого переподчиняемого окна необходимо передать программе MapInfo из Вашей программы пару операторов - оператор Set Next Document Parent, а затем оператор, создающий окно. После создания окна Вам может понадобиться запросить из MapInfo значение функции WindowID(0) - целочисленный ID-номер окна (Window ID) в MapInfo, так как многие операторы языка MapBasic требуют задания этого номера. Этот запрос выполняется на основе компонента следующим образом:

WindowID := Eval('WindowID(%D)',[0]).AsInteger;

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

Переподчинение окон Легенд, растровых диалогов и других окон MapInfo

Чтобы изменить (преподчинить) данные окна используется оператор MapBasic Set Window... Parent.

Например, в компоненте переподчинение окна информации реализовано так -

ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);

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

Заметьте, что способ переподчинения окна Информации другой, чем для окна Карты. В последнем случае не используется предложение Set Next Document. Дело в том, что может существовать несколько окон Карты.

Окна Легенды - особый случай. Обычно существует только одно окно Легенды, так же, как и одно окно Информации. Однако при помощи оператора MapBasic Create Legend Вы можете создавать дополнительные окна Легенды.

Для одного окна Легенды используйте оператор MapBasic Window Legend Parent.

Чтобы создать дополнительное окно Легенды, используйте оператор MapBasic Set Next Document и оператор Create Legend. Заметьте, что в этом случае Вы создаете Легенду, которая привязана к одному определенному окну Карты или окну Графика. Такое окно Легенды не изменяется, когда другое окно становится активным.

Совет: Вы можете создать "плавающее" окно Легенды внутри окна Карты. В операторе Set Next Document укажите окно Карты как порождающее окно. Для получения более подробной информации смотрите в документации по MapBasic.

Продолжение следует….

Конец первой части.

Просто и ясно о MapInfo и Delphi - Реализация CallBack вызовов MapInfo и перехват в собственной программе.

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

Автор: Дмитрий Кузан.

Доброе время суток !

Краткое примечание

Немного об отзывах - хочу сообщить и повторить снова в данных циклах статей не будет информации об ActiveX компоненте MapX (о работе с ней, отзывы о ней и т.п.) по причине отсутствия у меня оной (может кто поделится J).

Использование уведомляющих вызовов (Callbacks) для получения информации из Maplnfo - краткий учебный курс.

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

Такой тип уведомления известен как обратный вызов или уведомление (callback).

Уведомления используються в следующих случаях:

Пользователь применяет инструмент в окне. Например, если пользователь производит перемещение объекта мышкой в окне Карты, MapInfo может вызвать Вашу клиентскую программу, чтобы сообщить х- и у-координаты. Пользователь выбирает команду меню. Например, предположим, что Ваше приложение настраивает "быстрое" меню MapInfo (меню, возникающее при нажатии правой кнопки мышки). Когда пользователь выбирает команду из этого меню, MapInfo может вызвать Вашу клиентскую программу, чтобы сообщить ей о выборе. Изменяется окно Карты. Если пользователь изменяет содержание окна Карты (например, добавляя или передвигая слои), MapInfo может послать Вашей клиентской программе идентификатор этого окна. Изменяется текст в строке сообщений MapInfo. Строка состояния MapInfo не появляется автоматически в приложениях Интегрированной Картографии. Если Вы хотите, чтобы Ваша клиентская программа эмулировала строку состояния MapInfo, то Вы должны построить приложение так, чтобы MapInfo сообщало вашей клиентской программе об изменениях текста в строке состояния.

Требования к функциям уведомления

Программа должна быть способна функционировать, как DDE-сервер или как сервер Автоматизации OLE.

Предопределенные процедуры SetStatusText, WindowContentsChanged.

Если Вы хотите имитировать строку состояния MapInfo, создайте метод, называемый SetStatusText. Определите этот метод так, чтобы у него был один аргумент: строка.

Метод WindowContentsChanged, MapInfo посылает четырехбайтовое целое число (ID окна MapInfo), чтобы указать, какое из окон Карты изменилось. Напишите код, делающий необходимую обработку.

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

Переинсталяция компонента TKDMapInfoServer

Удалите старый компонент Зарегистрируете в системе библиотеку MICallBack.dll , для этого откройте MICallBack.dpr и в меню Run Delphi выбирите Register ActiveX Server.После этого скопируйте саму DLL в каталог Windows Установите пакет KDPack.dpk в Delphi

Вот в принципе и все.

Сервер автоматизации OLE для обработки CallBack

Данный сервер я разместил в ActiveX DLL.(данная DLL называется MICallBack.dll) в виде Automation Object.-а.

Что-бы вам просмотреть методы и свойства данногоАиtоmаtiоn Object.-а. откройте MICallBack.dpr и в меню Run Delphi выбирите TypeLibrary.

Откроется окно - Где я реализовал CallBack методы MapInfo и создал сервер автоматизации MICallBack. Обратите внимание, что у данного сервера помимо присутствия интерфейса IMapInfoCallBack присутствует и еще интерфейс ImapInfoCallBackEvents (он нам нужен будет для перенаправления событий в компонент и далее в обработчик).

Листинг интерфейсного модуля.

unit Call;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
  ComObj, ActiveX, Dialogs, AxCtrls, Classes, MICallBack_TLB, StdVcl;
type
  TMapInfoCallBack = class(TAutoObject, IConnectionPointContainer, IMapInfoCallBack)
  private
    { Private declarations }
    FConnectionPoints: TConnectionPoints;
    FConnectionPoint: TConnectionPoint;
    FEvents: IMapInfoCallBackEvents;
    { note: FEvents maintains a *single* event sink. For access to more
    than one event sink, use FConnectionPoint.SinkList, and iterate
    through the list of sinks. }
  public
    procedure Initialize; override;
  protected
    { Protected declarations }
    property ConnectionPoints: TConnectionPoints read FConnectionPoints
    implements IConnectionPointContainer;
    procedure EventSinkChanged(const EventSink: IUnknown); override;
    procedure SetStatusText(const Status: WideString); safecall;
    procedure WindowContentsChanged(ID: Integer); safecall;
    procedure MyEvent(const Info: WideString); safecall;
  end;
var
  FDLLCall: THandle;
implementation
uses ComServ;
procedure TMapInfoCallBack.EventSinkChanged(const EventSink: IUnknown);
begin
  FEvents := EventSink as IMapInfoCallBackEvents;
end;
procedure TMapInfoCallBack.Initialize;
begin
  inherited Initialize;
  FConnectionPoints := TConnectionPoints.Create(Self);
  if AutoFactory.EventTypeInfo <> nil then
    FConnectionPoint := FConnectionPoints.CreateConnectionPoint(
    AutoFactory.EventIID, ckSingle, EventConnect)
  else
    FConnectionPoint := nil;
end;
procedure TMapInfoCallBack.SetStatusText(const Status: WideString);
begin
  if FEvents <> nil then
    FEvents.OnChangeStatusText(Status);
end;
procedure TMapInfoCallBack.WindowContentsChanged(ID: Integer);
begin
  if FEvents <> nil then
    FEvents.OnChangeWindowContentsChanged(ID);
end;
procedure TMapInfoCallBack.MyEvent(const Info: WideString);
begin
  if FEvents <> nil then
    FEvents.OnChangeMyEvent(Info);
end;
initialization
  TAutoObjectFactory.Create(ComServer, TMapInfoCallBack, Class_MapInfoCallBack,
  ciMultiInstance, tmApartment);
end.

Обратите внимание на присутствие двух предопределенных методов MapInfo SetStatusText и WindowContentsChanged.

Метод MyEvent я пока зарезервировал для реализации своих сообщений (более подробно будет изложено в 3 части цикла).

И так что мы видим.

// если есть обработчик
if FEvents <> nil then
begin
  // Отправка сообщения далее - в данном случае в компонент
  FEvents.OnChangeStatusText(Status);

Как заставить MapInfo пересылать CallBack данному OLE серверу и как нам обрабатывать сообщения в компоненте от OLE сервера.

Итак представляю переработанный компонент -

unit KDMapInfoServer;
interface
uses
  Stdctrls, Dialogs, ComObj, Controls, Variants, ExtCtrls, Windows, ActiveX,
  Messages, SysUtils, Classes, MICallBack_TLB; // - сгенерировано из DLL
type
  // запись "типа" Variant
  TEvalResult = record
    AsVariant: OLEVariant;
    AsString: string;
    AsInteger: Integer;
    AsFloat: Extended;
    AsBoolean: Boolean;
  end;
  type
  // Событие на изменение SetStatusText // генерируется при обратном вызове
  TSetStatusTextEvent = procedure(Sender : TObject; StatusText: WideString) of object;
  // WindowContentsChanged
  TWindowContentsChanged = procedure(Sender : TObject; ID : Integer) of object;
  // Для собственных событий
  TMyEvent = procedure(Sender : TObject; Info : WideString) of object;
  TEvent = class(TInterfacedObject,IUnknown,IDispatch)
  private
    FAppConnection : Integer;
    FAppDispatch : IDispatch;
    FAppDispIntfIID : TGUID;
  protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount,
    LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(dispid: Integer; const IID: TGUID; LocaleID: Integer;
    Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
  public
    constructor Create( AnAppDispatch : IDispatch; const AnAppDispIntfIID : TGUID);
    destructor Destroy ; override;
  end;
  TKDMapInfoServer = class(TComponent)
  private
    { Private declarations }
    FOwner : TWinControl; // Владелец
    Responder : Variant;  // Для OLE Disp
    FServer : Variant;
    FHandle : THandle;    // Зарезервировано
    FActive : Boolean;    // Запущен/незапущен
    FPanel : TPanel;      // Панель вывода
    srv_OLE : OLEVariant;
    srv_disp : IMapInfoCallBackDisp;
    srv_vTable : IMapInfoCallBack;
    FEvent : TEvent;
    FSetStatusTextEvent : TSetStatusTextEvent; // события компонента
    FWindowContentsChanged : TWindowContentsChanged;
    FMyEvent : TMyEvent;
    Connected : Boolean; // Установлено ли соединение
    MapperID : Cardinal; // ИД окна
    procedure SetActive(const Value: Boolean);
    procedure SetPanel(const Value: TPanel);
    procedure CreateMapInfoServer;
    procedure DestroyMapInfoServer;
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    // Данная процедура выполеняет метод сервера MapInfo - Do
    procedure ExecuteCommandMapBasic(Command: string; const Args: array of const);
    function Eval(Command: string; const Args: array of const): TEvalResult; virtual;
    procedure WindowMapDef;
    procedure OpenMap(Path : string);
    procedure RepaintWindowMap;
    // Дополнил для генерации события SetStatus при изменении строки состояния
    // в MapInfo
    procedure DoSetStatus(StatusText: WideString);
    // Дополнил.для генерации события WindowContentsChanged при изменении окна
    // в MapInfo
    procedure DoWindowContentsChanged(ID : Integer);
    // Дополнил для генерации собственно события в MapInfo
    procedure DoMyEvent(Info: WideString);
  published
    { Published declarations }
    // Создает соединение с сервером MapInfo
    property Active: Boolean read FActive write SetActive;
    property PanelMap : TPanel read FPanel write SetPanel;
    // Событие возникающее при изменении строки состояния MapInfo
    property StatusTextChange : TSetStatusTextEvent read FSetStatusTextEvent
    write FSetStatusTextEvent;
    property WindowContentsChanged : TWindowContentsChanged read FWindowContentsChanged
    write FWindowContentsChanged;
    property MyEventChange : TMyEvent read FMyEvent write FMyEvent;
  end;
var
  // О это вообще хитрость - используеться для определения созданного компонента
  // TKDMapInfoServer (см. SetStatusText и Create
  KDMapInfoServ : TKDMapInfoServer;
procedure register;
implementation
// Вот тут то и хитрость если сервер создан то тогда и вызываем SetStatus
//// IF KDMapInfoServ <> nil Then
/// KDMapInfoServ.SetStatus(StatusText);
procedure register;
begin
  RegisterComponents('Kuzan', [TKDMapInfoServer]);
end;
{ TKDMapInfoServer }
constructor TKDMapInfoServer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FOwner := AOwner as TWinControl;
  KDMapInfoServ := Self; // **** Вот тут и указываеться созданный компонент
  // TKDMapInfoServer
  FHandle := 0;
  FActive := False;
  Connected := False;
end;
destructor TKDMapInfoServer.Destroy;
begin
  DestroyMapInfoServer;
  inherited Destroy;
end;
procedure TKDMapInfoServer.CreateMapInfoServer;
begin
  try
    FServer := CreateOleObject('MapInfo.Application');
  except
    FServer := Unassigned;
  end;
  // Скрываем панели управления MapInfo
  ExecuteCommandMapBasic('Alter ButtonPad ID 4 ToolbarPosition (0, 0) Show Fixed', []);
  ExecuteCommandMapBasic('Alter ButtonPad ID 3 ToolbarPosition (0, 2) Show Fixed', []);
  ExecuteCommandMapBasic('Alter ButtonPad ID 1 ToolbarPosition (1, 0) Show Fixed', []);
  ExecuteCommandMapBasic('Alter ButtonPad ID 2 ToolbarPosition (1, 1) Show Fixed', []);
  ExecuteCommandMapBasic('Close All', []);
  ExecuteCommandMapBasic('Set ProgressBars Off', []);
  ExecuteCommandMapBasic('Set Application Window %D', [FOwner.Handle]);
  ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);
  FServer.Application.Visible := True;
  if IsIconic(FOwner.Handle)then ShowWindow(FOwner.Handle, SW_Restore);
  BringWindowToTop(FOwner.Handle);
  srv_ole := CreateOleObject('MICallBack.MapInfoCallBack') as IDispatch;
  srv_vtable := CoMapInfoCallBack.Create;
  srv_disp := CreateComObject(CLASS_MapInfoCallBack) as IMapInfoCallBackDisp;
  FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents);
  // Указываем MapInfo что нужно передовать обратные вызовы нашему OLE
  // а тм далее по цепочке (см.начало)
  FServer.SetCallBack(srv_disp);
end;
procedure TKDMapInfoServer.DestroyMapInfoServer;
begin
  ExecuteCommandMapBasic('End MapInfo', []);
  FServer := Unassigned;
end;
procedure TKDMapInfoServer.ExecuteCommandMapBasic(Command: string;
const Args: array of const);
begin
  if Connected then
    try
      FServer.do(Format(Command, Args));
    except
      on E: Exception do MessageBox(FOwner.Handle,
      PChar(Format('Ошибка выполнения () - %S', [E.message])),
      'Warning', MB_ICONINFORMATION or MB_OK);
    end;
end;
function TKDMapInfoServer.Eval(Command: string;
const Args: array of const): TEvalResult;
  function IsInt(Str : string): Boolean;
  var
    Pos : Integer;
  begin
    Result := True;
    for Pos := 1 to Length(Trim(Str)) do
    begin
      if (Str[Pos] <> '0') and (Str[Pos] <> '1') and
      (Str[Pos] <> '2') and (Str[Pos] <> '3') and
      (Str[Pos] <> '4') and (Str[Pos] <> '5') and
      (Str[Pos] <> '6') and (Str[Pos] <> '7') and
      (Str[Pos] <> '8') and (Str[Pos] <> '9') and
      (Str[Pos] <> '.') then
      begin
        Result := False;
        Exit;
      end;
    end;
  end;
var
  ds_save: Char;
begin
  if Connected then
  begin
    Result.AsVariant := FServer.Eval(Format(Command, Args));
    Result.AsString := Result.AsVariant;
    Result.AsBoolean := (Result.AsString = 'T') or (Result.AsString = 't');
    if IsInt(Result.AsVariant) then
    begin
      try
        ds_save := DecimalSeparator;
        try
          DecimalSeparator := '.';
          Result.AsFloat := StrToFloat(Result.AsString);
        finally
          DecimalSeparator := ds_save;
        end;
      except
        Result.AsFloat := 0.00;
      end;
      try
        Result.AsInteger := Trunc(Result.AsFloat);
      except
        Result.AsInteger := 0;
      end;
    end
    else
    begin
      Result.AsInteger := 0;
      Result.AsFloat := 0.00;
    end;
  end;
end;
procedure TKDMapInfoServer.SetActive(const Value: Boolean);
begin
  FActive := Value;
  if FActive then
  begin
    CreateMapInfoServer;
    WindowMapDef;
    Connected := True;
  end
  else
  begin
    if Connected then
    begin
      DestroyMapInfoServer;
      Connected := False;
    end;
  end;
end;
procedure TKDMapInfoServer.SetPanel(const Value: TPanel);
begin
  FPanel := Value;
end;
procedure TKDMapInfoServer.WindowMapDef;
begin
  ExecuteCommandMapBasic('Set Next Document Parent %D Style 1', [FPanel.Handle]);
  RepaintWindowMap;
end;
procedure TKDMapInfoServer.OpenMap(Path: string);
begin
  ExecuteCommandMapBasic('Run Application "%S"', [Path]);
  MapperID := Eval('WindowInfo(FrontWindow(),%D)',[12]).AsInteger;
  RepaintWindowMap;
end;
procedure TKDMapInfoServer.DoSetStatus(StatusText: WideString);
begin
  if Assigned(FSetStatusTextEvent) then
    FSetStatusTextEvent(Self,StatusText);
end;
procedure TKDMapInfoServer.DoWindowContentsChanged(ID: Integer);
begin
  if Assigned(FWindowContentsChanged) then
    FWindowContentsChanged(Self,ID);
end;
procedure TKDMapInfoServer.DoMyEvent(Info: WideString);
begin
  if Assigned(FWindowContentsChanged) then
    FMyEvent(Self,Info);
end;
procedure TKDMapInfoServer.RepaintWindowMap;
begin
  with PanelMap do
    MoveWindow(MapperID, 0, 0, FPanel.ClientWidth, FPanel.ClientHeight, True);
end;
{ TEvent }
function TEvent._AddRef: Integer;
begin
  Result := 2; // Заглушка
end;
function TEvent._Release: Integer;
begin
  Result := 1; // Заглушка
end;
constructor TEvent.Create(AnAppDispatch: IDispatch;
const AnAppDispIntfIID: TGUID);
begin
  inherited Create;
  FAppDispatch := AnAppDispatch;
  FAppDispIntfIID := AnAppDispIntfIID;
  // Передадим серверу
  InterfaceConnect(FAppDispatch,FAppDispIntfIID,self,FAppConnection);
end;
destructor TEvent.Destroy;
begin
  InterfaceDisConnect(FAppDispatch,FAppDispIntfIID,FAppConnection);
  inherited;
end;
function TEvent.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount,
LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  // Заглушка не реализовано
  Result := E_NOTIMPL;
end;
function TEvent.GetTypeInfo(index, LocaleID: Integer;
out TypeInfo): HResult;
begin
  // Заглушка не реализовано
  Result := E_NOTIMPL;
end;
function TEvent.GetTypeInfoCount(out Count: Integer): HResult;
begin
  // Заглушка не реализовано
  Count := 0;
  Result := S_OK;
end;
function TEvent.Invoke(dispid: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
var
  Info,Status : string;
  IDWin : Integer;
begin
  case dispid of
    1 :
    begin
      Status := TDispParams(Params).rgvarg^[0].bstrval;
      if KDMapInfoServ <> nil then
        KDMapInfoServ.DoSetStatus(Status);
    end;
    2 :
    begin
      IDWin := TDispParams(Params).rgvarg^[0].bval;
      if KDMapInfoServ <> nil then
        KDMapInfoServ.DoWindowContentsChanged(IDWin);
    end;
    3 :
    begin
      Info := TDispParams(Params).rgvarg^[0].bstrval;
      if KDMapInfoServ <> nil then
        KDMapInfoServ.DoMyEvent(Info);
    end;
  end;
  Result := S_OK;
end;
function TEvent.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  Result := E_NOINTERFACE;
  if GetInterface(IID,Obj) then
    Result := S_OK;
  if IsEqualGUID(IID,FAppDispIntfIID) and GetInterface(IDispatch,Obj) then
    Result := S_OK;
end;
end.

И так что добавилось - Метод CreateMapInfoServer;

  // Создаем наш сервер OLE
  srv_ole := CreateOleObject('MICallBack.MapInfoCallBack') as IDispatch;
  srv_vtable := CoMapInfoCallBack.Create;
  // Получаем Idispatch созданного сервера
  srv_disp := CreateComObject(CLASS_MapInfoCallBack) as IMapInfoCallBackDisp;
  FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents);
  // Указываем MapInfo что нужно передовать обратные вызовы нашему OLE серверу
  // а там далее по цепочке (см.начало)
  FServer.SetCallBack(srv_disp);
end;

Здесь мы столкнулись с еще одним методом MapInfo помимо рассмотренных ранее методов Do и Eval - Метод SetCallBack(IDispatch). Описание - Регистрирует объект механизма-управления объектами OLE (OLE Automation) как получатель уведомлений, генерируемых программой MapInfo. Только одна функция уведомления может быть зарегистрирована в каждый данный момент. Параметр интерфейс Idispatch объекта OLE (COM).

Реализация FServer.SetCallBack(srv_disp); - данным кодом мы заставили MapInfo уведомлять наш OLE сервер.

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

Я сделал так: ввел класс отвечающий за принятие событий от COM(OLE) объекта.

TEvent = class(TInterfacedObject,IUnknown,IDispatch)
private
  FAppConnection : Integer;
  FAppDispatch : IDispatch;
  FAppDispIntfIID : TGUID;
protected
  function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  function _AddRef: Integer; stdcall;
  function _Release: Integer; stdcall;
  function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
  function GetTypeInfo(index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
  function GetIDsOfNames(const IID: TGUID; Names: Pointer;
  NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
  function Invoke(dispid: Integer; const IID: TGUID; LocaleID: Integer;
  Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
public
  constructor Create( AnAppDispatch : IDispatch;
  const AnAppDispIntfIID : TGUID);
  destructor Destroy ; override;
end;

Создание этого класса в компоненте реализовано так.

FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents);

В методе Invoke и происходит прием и получение сообщений и пересылка их в обработчик моего компонента.

Еще раз на последующие вопросы касательно COM (OLE) серверов отвечу: данная тема выходит за рамки данной статьи - советую почитать книгу Александроского А.Д - Delphi 5 разработка корпоративных приложений.

Напоследок — модуль MICallBack_TLB.pas импортирован из DLL командой меню DELPHI Import Type Libray.

Примечание: при импорте данный сервер инсталировать не нужно, нет смысла он нам нужен только для приема сообщений из MapInfo.

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

До встречи.

Просто и ясно о MapInfo и Delphi - Настройка панелей и меню. Реализация собственных CallBack вызовов и обработка поступивших данных.

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

Автор: Дмитрий Кузан.

Доброе время суток !

Этой статьей я заканчиваю введение в интегрированную картографию MapInfo.Надеюсь, что данный цикл статей открыл вам возможность применять MapInfo в ваших программах. Перед началом я хочу дать вам ссылку на сайт пользователй MapInfo , где вы найдете исчерпывающеюся информацию по MapInfo и MapBasic в частности на русском языке. Многое что я дал вам по MapBasic в этих частях взято оттуда.

Интеграция инструментальных панелей Maplnfo краткий вводный курс.

Вы не можете переподчинить стандартные инструментальные панели MapInfo. Если Вы хотите, чтобы Ваша клиентская программа имела такие панели вы должны сами создать панели и кнопки на Delphi (например используя Tpanel и Tbutton) и их обработчике посылать специальные команды MapInfo для того что-бы MapInfo включало или переключала режимы работы (например с выбора объекта на перемещения окна карты (ладошка)).

Если Вы хотите, чтобы кнопка панели эмулировала стандартную кнопку MapInfo, используйте метод MapInfo Run Menu Command.

Например в обработчике OnClick пропишите следующею команду.

KDMapInfoServer1.ExecuteCommandMapBasic('Run Menu Command 1702',[]);

Когда пользователь нажмет на эту кнопку, программа вызывовет метод MapInfo - Run Menu Command, который активизирует инструмент под номером 1702 (инструмент перемещение карты "рука" ).

"Магический" номер 1702 ссылается на инструмент "рука" служащий для перемещения (сдвига) карты.

Вместо того, чтобы использовать такие числа. Вы можете использовать идентификаторы, более понятные в тексте программы. MapBasic определяет стандартный идентификатор M_TOOLS_RECENTER который имеет значение 1702. Таким образом, этот пример можно записать так:

KDMapInfoServer1.ExecuteCommandMapBasic('Run Menu Command %S', [M_TOOLS_RECENTER]);

Использование идентификаторов (типа M_TOOLS_RECENTER) делает Вашу программу более читательной, но перед использование вы должны включить в программу (в Uses) соответствующий заголовочный файл MapBasic. Для Delphi я положил файл Global.pas (содержимое файла опубликовано в приложении 1).

В следующей таблице приведены кратко идентификаторы основных инструментальных кнопок MapInfo (для более побробной информации смотрите документацию по MapBasic).

Кнопки панели Операции Номер Идентификатор Примечание
Выбор 1701 М_TOOLS_SELECTOR Панель ОПЕРАЦИИ
Выбор в прямоугольнике 1722 M_TOOLS_SEARCH_RECT Панель ОПЕРАЦИИ
Выбор в круге 1703 M_TOOLS_SEARCH_RADIUS Панель ОПЕРАЦИИ
Выбор в области 1704 M_TOOLS_SEARCH_BOUNDARY Панель ОПЕРАЦИИ
Увеличивающая лупа 1705 M_TOOLS_EXPAND Панель ОПЕРАЦИИ
Уменьшающая лупа 1706 M_TOOLS_SHRINK Панель ОПЕРАЦИИ
Ладошка (рука) 1702 M_TOOLS_RECENTER Панель ОПЕРАЦИИ
Информация 1707 M_TOOLS_PNT_QUERY Панель ОПЕРАЦИИ
Подпись 1708 M_TOOLS_LABELER Панель ОПЕРАЦИИ
Линейка 1710 M_TOOLS_RULER Панель ОПЕРАЦИИ
Переноска 1734 M_TOOLS_DRAGWINDOW Панель ОПЕРАЦИИ
Символ 1711 M_TOOLS_POINT Панель ПЕНАЛ
Линия 1712 M_TOOLS_LINE Панель ПЕНАЛ
Полилиния 1713 M_TOOLS_POLYLINE Панель ПЕНАЛ
Дуга 1716 M_TOOLS_ARC Панель ПЕНАЛ
Полигон 1714 M_TOOLS_POLYGON Панель ПЕНАЛ
Эллипс 1715 M_TOOLS_ELLIPSE Панель ПЕНАЛ
Прямоугольник 1717 M_TOOLS_RECTANGLE Панель ПЕНАЛ
Прямоугольник скругленный 1718 M_TOOLS_ROUNDEDRECT Панель ПЕНАЛ
Текст 1709 M_TOOLS_TEXT Панель ПЕНАЛ
Рамка 1719 M_TOOLS_FRAME Панель ПЕНАЛ

Настройка "быстрых" меню Maplnfo

MapInfo вызывает "быстрые" меню, если пользователь нажимает правую кнопку мышки в окне MapInfo. Эти меню появляются даже во внедренных приложениях. В зависимости от характера Вашего приложения Вы можете захотеть модифицировать или даже удалить такое меню. Например, Вы, возможно, захотите удалить команду ДУБЛИРОВАТЬ ОКНО, так как эта команда не работает в OLE-приложении.

Чтобы удалить одну или несколько команд из локального меню, используйте оператор MapBasic Alter Menu... Remove или переопределите меню целиком, используя оператор Create Menu. Подробнее смотрите в Справочнике MapBasic.

Чтобы добавить команду к локальному меню, используйте оператор MapBasic Alter Menu ... Add и синтаксис предложений Calling OLE.

Чтобы удалить "быстрое" меню полностью, используйте оператор MapBasic Create Menu и управляющий код "(-" как новое определение меню. Например, следующий оператор разрушает "быстрое" меню для окон Карты:

KDMapInfoServer1.ExecuteCommandMapBasic(' "Create Menu ""MapperShortcut"" ID 17 As ""(-"" " ', []);

Создание собственных уведомляющих вызовов (Callbacks).

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

Если Вы хотите, чтобы MapInfo сообщало Вашей клиентской программе, когда пользователь применяет инструментальную кнопку, создайте такую кнопку оператором Alter ButtonPad... Add. Определите кнопку в соответствии с именем метода для обработки (прим. Этот метод определен мной как MyEnvent в OLE объекте).

Пример :

KDMapInfoServer1.ExecuteCommandMapBasic('Alter ButtonPad ID 1 Add ToolButton calling ole
"MyEvent" ID 1 Icon 0 Cursor 0 DrawMode 34 uncheck',[]);

Заметьте, что инструментальные панели MapInfo скрыты, подобно остальной части интерфейса пользователя MapInfo. Пользователь не будет видеть новую кнопку. Вы можете добавить иконку, кнопку или другой видимый элемент управления к интерфейсу пользователя Вашей клиентской программы. Когда пользователь укажет на него мышкой, пошлите MapInfo оператор Run Menu Command ID , c индентификатором созданной кнопки чтобы активизировать этот инструмент.

KDMapInfoServer1.ExecuteCommandMapBasic('Run Menu Command ID 1',[]);

Примечание: Информацию по Alter Button Pad смотрите в документации.

Если Вы хотите, чтобы MapInfo сообщала Вашей клиентской программе, когда пользователь выбирает созданную Вами команду меню, определите такую кнопку оператором Alter Menu... Add с указанием имени OLE метода (см. выше).

Внутри метода (в данном случае в обработчике компонента MyEventChange) обработайте аргументы (Info), посланные MapInfo.

Обработка переданных данных

Когда пользователь использует команды или кнопки, MapInfo посылает Вашему OLE-методу строку, содержащую восемь элементов, разделенных запятыми. Например, строка, посланная MapInfo, может выглядеть так:

"MI:-73.5548,42.122,F,F,-72.867702,43.025,202,"

Содержание такой строки проще понять, если Вы уже знакомы с функцией MapBasic CommandInfo(). Когда Вы пишете приложения, Вы можете создать новые команды меню и кнопки, вызывающие MapBasic-процедуры. Внутри процедуры-обработчика вызовите функцию CommandInfo(), чтобы получить информацию. Например, следующее обращение к функции определяет, координату Х и У места на карте где пользователи применил инструмент.

var
  X, Y : string;
begin
  KDMapInfoServer1.ExecuteCommandMapBasic('Set CoordSys Layout Units "mm"',[]);
  X := KDMapInfoServer1.Eval('CommandInfo(%S)',[CMD_INFO_X]).AsString;
  Y := KDMapInfoServer1.Eval('CommandInfo(%S)',[ CMD_INFO_Y]).AsString;
  ShowMessage('X= ' + X + ' Y = ' + Y);

Значения:

Код для событий, связанных с меню Код для событий, связанных с кнопкой
1 CMD_INFO_X
2 CMD_INFO_Y
3 CMD_INFO_SHIFT
4 CMD_INFO_CTRL
5 CMD_INFO_X2
6 CMD_INFO_Y2
7 CMD_INFO_TOOLBTN
8 CMD_INFO_MENUITEM

Когда Вы создаете команду меню или кнопку, которая использует синтаксис вызова OLE, MapInfo создает строку, содержащую разделенные запятой все восемь возвращаемых CommandInfo() значений. Строка начинается с префикса "MI:", чтобы Ваш OLE-сервер мог определять, что обращение метода было сделано MapInfo.

Строка, которую MapInfo посылает Вашему методу, выглядит следующим образом:

"MI:" + 
CommandInfo(l) + "," + CommandInfo (2) + "," +
CommandInfo(3) + "," + CommandInfo (4) + "," +
CommandInfo(5) + "," + CommandInfo (6) + "," +
CommandInfo (7) + "," + CommandInfo (8)

Предположим, что Ваше приложение добавляет команду меню к локальному меню OLE-методу строку. Если команда меню имеет номер 101 , строка будут выглядеть следующим образом:

"Ml :,,,,,,, 101"

В этом случае большинство элементов строки пусто, потому что функция CommandInfo( ) может возвращать только эту одну часть информации.

Теперь предположим что вы создаете кнопку которая позволяет пользователю выбирать линии на карте.Строка теперь примет вид -

"MI:-73.5548,42.122,F,F,-72.867702,43.025,202,"

Теперь строка включает несколько элементов:

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

Совет: Если Вы приписываете уникальный идентификатор каждой из Ваших кнопок, Вы можете сделать так, что все кнопки будут вызывать один и тот же метод. Ваш метод может определять, какая из кнопка вызвала его, используя седьмой аргумент в переданной строке.

Примечание 1: Описание констант MapInfo (Global.pas)

Примечание - данный файл был взят мной с Интернета. Хочу сразу сделать предупреждение - разработчики MapInfo заявляют что набор констант может быть подвергнут изменениям в следующих редакциях MapInfo.Данный набор констант адаптирован под пятую версию. К сожалению шестой версии у меня нет (может кто поделиться ;-) ) и соответственно нет возможности проверить изменился ли набор констант или нет.

Вот в принципе и все что нужно для работы с MapInfo в Delphi, дерзайте.

Delphi и Oracle - вопросы и ответы.

Если "Microsoft" начнет выпускать автомобили - то: 1. Когда на дорогу нанесут новую разметку, вам придется покупать новую машину. 3. "Macintosh" тоже начнет выпускать автомобили, они будут заряжаться от солнца, будут в два раза быстрее, ими будет легче управлять, но ездить они будут только по 5% дорог. 4. Индикаторы топлива, температуры двигателя и масла будут объединены в один "главный машинный индикатор". 7. Во время аварии подушки безопасности, прежде чем сработают, спросят: "Are you sure?"

Вопрос В статье "Создание серверов приложений с помощью Delphi 3" вы написали, что подключались к Personal Oracle с помощью BDE. Я очень прошу вас рассказать, как вы это сделали. Для меня пока это остается загадкой. Ответ Доступ к Регsоnа1 Oracle (как и к любой другой версии СУБД Oracle) осуществляется следующим образом. Сначала нужно запустить сервер (в случае Personal Oracle для Windows 95 это отдельное приложение, в случае Oracle для Windows NT - набор сервисов, обслуживающих конкретную базу данных) и настроить клиентскую часть Oracle. Для этого следует запустить утилиту SQLNet Easy Configuration (в случае Oracle 8 - Oracle Net8 Easy Config) и с ее помощью создать описание псевдонима базы данных Oracle (для него, как и в BDE, используется термин alias, но это не то же самое, что псевдоним BDE). При создании этого описания важны три параметра. Первый из них - сетевой протокол, с помощью которого осуществляется доступ к серверу Oracle (IPX/SPX, TCP/IP и др.). Второй параметр - местоположение сервера в сети. В случае Personal Oracle это обычно компьютер с IP-адресом 127.0.0.1 (это специальный адрес для доступа к локальному компьютеру, так называемый TCP Loopback Address, который обычно имеет URL http://localhost/). Третий параметр - имя базы данных. По умолчанию в случае Personal Oracle она называется ORCL. В общем случае имя может быть любым, но это должно быть имя уже существующей базы данных, с которой вы собираетесь работать. В принципе все описания псевдонимов Oracle хранятся в текстовом файле TNSNAMES.ORA, который можно редактировать вручную. Далее следует запустить утилиту SQL Plus и проверить соединение клиента с сервером. Обычно в качестве имени пользователя используется имя SYSTEM и пароль MANAGER (если вы сами администрируете сервер). Если же сервер был установлен раньше, узнайте у администратора базы данных, каким именем и паролем следует воспользоваться. Помимо имени пользователя и пароля, SQL Plus запросит так называемую строку связи, в которой должно содержаться имя сервиса, который был создан вами перед этим. При удачном соединении в SQL Plus появится соответствующее сообщение. Отметим, что утилита Oracle Net8 Easy Config позволяет протестировать соединение непосредственно в процессе создания описания сервиса. Если соединение с сервером было неудачным, стоит проверить, поддерживается ли указанный сетевой протокол, виден ли в сети компьютер, на котором установлен сервер, и, если нужно, внести изменения в описание сервиса. Теперь можно, наконец, заняться настройкой BDE. В качестве Server Name следует указать имя псевдонима Oracle (его можно просто выбрать из выпадающего списка, так как BDE Administrator также обращается к файлу TNSNAMES.ORA). После этого нужно проверить соединение с сервером через BDE с помощью BDE Administrator или SQL Explorer. Если соединение не устанавливается и появляется сообщение "Vendor initialization failed", стоит убедиться, что динамическая загружаемая библиотека, указанная в параметре Vendor Init драйвера Oracle, действительно присутствует на данном компьютере. На всякий случай стоит скопировать ее в папку Windows\System, так как некоторые ранние версии BDE в Windows 95 не находят эту библиотеку в подкаталоге Bin каталога, в котором установлен клиент Oracle, в силу ограничений, налагаемых этой операционной системой на длину переменной окружения PATH. Отметим также, что при использовании Oracle 8 нужно использовать версию не ниже 8.0.4; в случае использования более ранней версии следует обновить ее до 8.0.4.

Вопрос Недавно я перешел на использование Oracle, но все мои попытки использовать компонент TStoredProc оказываются неудачными. Почему? Ответ Причины неработоспособности компонента TStoredProc могут быть следующими. Во-первых, при использовании ODBC-доступа может оказаться, что применяемый вами ODBC-драйвер не поддерживает хранимые процедуры (как известно, не все ODBC-драйверы их поддерживают). Во-вторых, имеется известная проблема, описание которой содержится в разделе Developers Support корпоративного сайта Inprise (http://www.inprise.com). Дело в том, что число параметров хранимой процедуры, с которой взаимодействует компонент TStoredProc, не должно превышать 10. В случае, если реальное число параметров превышает 10, многие разработчики переписывают хранимые процедуры так, чтобы они использовали строковые параметры, содержащие по несколько реальных параметров.

Вопрос Дает ли Delphi возможность корректно прервать выполнение SQL-запроса к серверу Oracle с помощью BDE? Например, чтобы при использовании с SQL Plus после отправки SQL-запроса на выполнение на экране появлялось окно с кнопкой Cancel, которое давало бы возможность в любой момент прервать выполнение этого запроса? Ответ Насколько мне известно, для этой цели лучше всего использовать функции Oracle Call Interface (низкоуровневый API Oracle). В комплекте поставки Oracle есть соответствующие примеры для C, и переписать их на Pascal несложно. Некоторые драйверы SQL Link позволяют прекратить выполнение запроса, если время его выполнения превышает заранее заданное значение (параметр MAX QUERY TIME соответствующего драйвера). Однако драйвер ORACLE, к сожалению, в их число не входит.

Вопрос Что необходимо предпринять, чтобы сгенерировать из ERwin таблицы для локальной базы данных Paradox 5.0? На компьютере установлены Delphi 4.0 и MetaBase. Ответ Для этого требуется установить ODBC-драйвер для этой версии Paradox той же разрядности, что и ERwin. Затем нужно описать соответствующий ODBC-источник, и он будет доступен в ERwin.

Вопрос Не могли бы вы подсказать, как заблокировать функцию вставки записи непосредственно в компоненте TDBGrid с сохранением всех остальных возможностей редактирования таблицы. Ответ Наиболее разумным представляется создать обработчик события OnBeforeInsert компонента TTable, TQuery или TClientDataSet, данные из которых отображаются в TDBGrid. Сам компонент TDBGrid не имеет подходящего события для обработки, так как это компонент, предназначенный только для создания пользовательского интерфейса, а в данном случае следует, по существу, запретить добавление записей в таблицу.

Вопрос В мой комплект Borland C++ Builder не входит Visual Query Builder. Могу ли я связать две таблицы без него? Ответ Безусловно, две таблицы можно связать и без VQB. Самый простой способ - запустить Database Form Wizard и связать две таблицы, используя TQuery. Те два запроса, которые при этом получатся (один из них - параметризованный), можно использовать как образец. Кроме того, можно просто написать вручную необходимый запрос к любому числу таблиц и поместить его в свойство SQL компонента TQuery. Все инструменты для генерации запросов (Visual Query Builder, SQL Builder и др.) просто предоставляют для этого визуальные средства, а результатом их работы является именно текст запроса, помещаемый в это свойство.

Вопрос Я установил Borland C++ Builder 3.0 Client/Server Suite и InterBase Server 5.1.1. (автоматически с ним установился InterBase 5.x Driver by Visigenic). Но у меня не работают хранимые процедуры. Например: процедура правильно откомпилирована, и вызов ее из C++ Builder осуществляется с помощью выполнения оператора.

StoredProc1->ExecProc();
При этом возникает следующая ошибка :
"Capability not supported. General SQL error. [Visigenic]
[ODBC InterBase 4.x Driver] Driver not capable".
Ответ ODBC-драйвер может не поддерживать хранимые процедуры. В этом случае стоит попытаться использовать драйвер SQL Link (он должен быть в C++ Builder 3.0 Client/Server Suite). Для этого нужно создать для вашей базы данных псевдоним типа INTRBASE. В этом случае хранимые процедуры должны работать. Если хранимые процедуры, тем не менее, остаются недоступными, стоит проверить, что и в какой последовательности было установлено на ваш компьютер. Такие неприятности могут возникать, если, например, вы установили какой-либо продукт, написанный на Delphi 2, после C++Builder 3. В этом случае можно переустановить BDE, взяв его последнюю версию на сайте Inprise (http://www.inprise.com — все зарегистрированные пользователи C++ Builder 3.0 Client/Server Suite имеют право это сделать.

Вопрос При удалении записей из таблицы dBase с помощью компонента TTable они просто приобретают признак удаления, и я никак не могу добиться их физического удаления. Как быть? Ответ Ваша проблема решается просто - для физического удаления записей нужно использовать функцию DbiPackTable (ее описание есть в справочном файле BDE).

Вопрос При добавлении новых записей с помощью метода 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 неплохо справляется с одновременным вводом большого количества записей благодаря некоторым специфическим особенностям архитектуры этого сервера.

Вопрос Позволяет ли QuickReport выгружать данные в формате Microsoft Excel? Ответ Quick Report не позволяет выгружать данные в формате Microsoft Excel. Но последние его версии позволяют сохранять отчеты в формате CSV (Comma Separated Value) и HTML, и оба эти формата можно прочесть с помощью Excel. Помимо этого, для генерации отчета можно использовать автоматизацию Excel (Automation, ранее назвалось OLE Automation. — Прим. ред.), вообще не прибегая к использованию QuickReport.

Вопрос Как можно создать свою форму просмотра отчетов QuickReport в С++Builder? Ответ Для создания собственных окон просмотра отчета можно использовать компонент TQRPreview. Для этой цели следует создать форму (назовем ее PreviewForm), поместить на нее компонент TQRPreview, сослаться на нее в форме, содержащей отчет, и в форме, из которой вызывается просмотр отчета. Код для показа отчета выгладит примерно так: Далее создадим обработчик события OnPreview компонента TQuickRep: После этого данный отчет будет появляться не в стандартном окне просмотра, а в форме PreviewForm.

Вопрос Возможно ли использование компонентов Decision Support System при генерации отчетов в QuickReport и, если да, то каким образом? Если QuickReport не подходит для этих целей, то какие другие варианты вы можете посоветовать? Ответ Самый простой способ - использовать компоненты TQRLabel, текст в которых динамически меняется во время печати (то есть способ, которым можно напечатать все, что угодно, написав при этом немного кода). В принципе можно записать двухмерное сечение куба во временную таблицу или в компонент TClientDataSet, написав соответствующий цикл, и сделать отчет на ее основе. Использование DecisionQuery в качестве источника данных для отчета также вполне возможно. Другие возможные варианты - это использование автоматизации Word или Excel либо вычисление сумм внутри отчета. Можно также использовать другие генераторы отчетов - например, с помощью Crystal Reports можно создавать отчеты, содержащие кросс-таблицы.

Вопрос Как корректно подключить Crystal Reports к Delphi? Ответ В составе Crystal Reports Professional имеется VCL-компонент для Delphi, компонент ActiveX, модуль CRPE32.PAS, в котором объявлены все функции и структуры Print Engine API, и описание опубликованных методов Crystal Reports как сервера автоматизации. Соответственно есть следующие возможности подключения Crystal Reports к Delphi: Использование функций Report Engine API из библиотеки CRPE32 DLL. В этом случае следует добавить в проект модуль CRPE32.PAS и сослаться на этот модуль в предложении uses. Ниже приведен пример соответствующего кода: Следует помнить, что строковые параметры, передаваемые в функции Print Engine API, представляют собой тип данных PChar, а не стандартные строки, используемые в Pascal, поэтому для передачи таких параметров, как, например, имя отчета, следует осуществить преобразование типов с помощью функции StrPCopy. Для успешной компиляции подобных приложений файл CRPE32.PAS должен находиться в том же каталоге, что и разрабатываемое приложение, либо в каталоге Delphi\Lib. Использование VCL-компонента из комплекта поставки (для этого следует установить его в палитру компонентов Delphi). Естественно, этот компонент инкапсулирует те же самые функции Print Engine API. Существуют также аналогичные компоненты третьих фирм (например, компонент от SupraSoft Ltd., http://www.suprasoft.com). Использование компонента ActiveX фирмы Crystal Reports. Этот компонент ActiveX может быть установлен в палитру компонентов Delphi. Он обладает набором свойств и методов, более или менее сходным с соответствующим VCL-компонентом из комплекта поставки Crystal Reports Professional. Использование Crystal Reports как сервера автоматизации. В справочной системе Crystal Reports имеется подробное описание иерархии вложенных объектов и их методов (и внушительный набор примеров для Visual Basic, аналоги которых несложно создать и на языке Pascal). Ниже приведен пример соответствующего кода: Можно также сделать отчет в виде исполняемого файла и вызвать его из приложения. Но в этом случае передать параметры в отчет не удастся.

Delphi и системная информация о ресурсах компьютера.

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

Компьютеры - вещь слишком сложная, чтобы работать в принципе. Поэтому то, что они работают хоть как-то, уже чудо.
Иногда Delphi-приложениям может не хватать функциональной полноты стандартной библиотеки компонентов и тогда бывает необходимо обратиться к Microsoft Win32 API (Application Programming Interface - интерфейса взаимодействия прикладной программы с операционной системой). Почти все функции из Microsoft Win32 API описаны в модуле windows.pas (который по умолчанию включается в секцию uses новых модулей). Следует заметить, что часть из этих функции ведет себя по разному в зависимости от текущей операционной системы (Windows 95, 98, NT).

Разработаем программу, показывающую нам некоторую системную информацию о компьютере. В частности, хотелось бы получить информацию о версии ОС, ее директориях, свойствах экрана, ресурсах памяти, имени пользователя и компьютера, дате BIOS. Помимо этого, разрешим пользователю изменять настройки клавиатуры, встроенного динамика и хранителя экрана.

Процесс визуального проектирования описывать не будем; рассмотрим лишь страницу «Параметры». Для удобства управления параметрами клавиатуры положим на нее две компоненты TTrackBar. Изменим свойство Name на tbKeyboardDelay и tbKeyboardSpeed. Изменим свойство PageSize на 1. Для tbKeyboardDelay установим Max=3 и для tbKeyboardSpeed. Max=31. Для управления свойствами хранителя экрана используем TCheckBox (свойство Name сменим на cbScreenSaverActive, Caption на &‘Хранитель экрана&’) и TMaskEdit (свойство Name=&’edSSTimeOut&’ и EditMask=&’!999;1;&’). Аналогично добавим TCheckBox (свойство Name=&’cbSpeaker&’, Caption=&’Использование встроенного динамика&’ ).

Рассмотрим текст программы. В список включаемых модулей uses добавим registry. Добавим описание процедур в раздел public описания TfmMain.

type
TfmMain = class(TForm)
...
procedure FormCreate(Sender: TObject);
procedure Change(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
KeyboardDelay,
KeyboardSpeed,
ScreenSaveTimeOut : integer;
procedure ParametersInfo;
procedure ShowSomeInfo;
procedure BIOSInfo(OS : string);
procedure HardwareInfo;
procedure MemoryInfo;
procedure VideoInfo;
procedure OSInfo;
end;
var fmMain: TfmMain;
implementation
uses Registry;
{$R *.DFM}

Сначала получим информацию о компьютере. Используем функцию GetComputerName для получения имени компьютера, функцию GetUserName для получения имени пользователя и функцию GetSystemInfo для получения информации о процессоре (наиболее полно данная функция реализована в Windows NT, где она возвращает и кол-во процессоров и их тип и т.д.).

// Информация о компьютере.
procedure TfmMain.HardwareInfo;
var Size : cardinal;
PRes : PChar;
BRes : boolean;
lpSystemInfo : TSystemInfo;
begin
// Имя компьютера
Size := MAX_COMPUTERNAME_LENGTH + 1;
PRes := StrAlloc(Size);
BRes := GetComputerName(PRes, Size);
if BRes then laCompName_.Caption := StrPas(PRes);
// Имя пользователя
Size := MAX_COMPUTERNAME_LENGTH + 1;
PRes := StrAlloc(Size);
BRes := GetUserName(PRes, Size);
if BRes then laUserName_.Caption := StrPas(PRes);
// Процессор
GetSystemInfo(lpSystemInfo);
laCPU_.Caption := 'класса x' + IntToStr
(lpSystemInfo.dwProcessorType);
end;

Перейдем к параметрам экрану. Здесь мы будем использовать и Win32 API функции и стандартные объекты VCL. Так для получения разрешения экрана нам понадобится объект TScreen (его свойства Width и Height). Остальные параметры мы получим через контекст драйвера устройства DC используя функцию GetDeviceCaps.

// Информация о видеосистеме.
procedure TfmMain.VideoInfo;
var DC : hDC;
c : string;
begin
// Разрешение экрана
laWidth_.Caption := IntToStr(Screen.Height);
laHeight_.Caption := IntToStr(Screen.Width);
// Информация о глубине цвета.
DC := CreateDC('DISPLAY',nil,nil,nil);
laBitsPerPixel_.Caption := 
IntToStr(GetDeviceCaps(DC,BITSPIXEL));
laPlanes_.Caption := 
IntToStr(GetDeviceCaps(DC,PLANES));
case GetDeviceCaps(DC,BITSPIXEL) of
8 : c := '256 цветов';
15 : c := 'Hi-Color / 32768 цветов';
16 : c := 'Hi-Color / 65536 цветов';
24 : c := 'True-Color / 16 млн цветов';
32 : c := 'True-Color / 32 бит';
end;
laColors_.Caption := c;
DeleteDC(DC);
end;

Также будет интересна информация о памяти. Здесь нам поможет функция GlobalMemoryStatus, возвращающая информацию по объему физической и виртуальной памяти.

// Информация о памяти.
procedure TfmMain.MemoryInfo;
var lpMemoryStatus : TMemoryStatus;
begin
lpMemoryStatus.dwLength := SizeOf(lpMemoryStatus);
GlobalMemoryStatus(lpMemoryStatus);
with lpMemoryStatus do begin
laFreeMemory.Caption := 
laFreeMemory.Caption + 
IntToStr(dwMemoryLoad) + '%';
laRAM_.Caption := Format('%0.0f Мбайт',
[dwTotalPhys div 1024 / 1024]);
laFreeRAM_.Caption := Format('%0.3f Мбайт',
[dwAvailPhys div 1024 / 1024]);
laPF_.Caption := Format('%0.0f Мбайт',
[dwTotalPageFile div 1024 / 1024]);
laPFFree_.Caption := Format('%0.0f Мбайт',
[dwAvailPageFile div 1024 / 1024]);
end;
end;

Узнаем информацию о ОС. Функция GetWindowsDirectory вернет путь к каталогу, где установлена система, функция GetSystemDirectory - к системному каталогу. Для определения версии ОС воспользуемся функцией GetVersionEx.

// Информация о Windows.
procedure TfmMain.OSInfo;
var PRes : PChar;
Res : word;
BRes : boolean;
lpVersionInformation : TOSVersionInfo;
c : string;
begin
// Каталог, где установлена Windows
PRes := StrAlloc(255);
Res := GetWindowsDirectory(PRes, 255);
if Res > 0 then laWinDir_.Caption := 
StrPas(PRes);
// Системный каталог Windows
Res := GetSystemDirectory(PRes, 255);
if Res > 0 then laSysDir_.Caption := 
StrPas(PRes);
// Имя ОС
lpVersionInformation.dwOSVersionInfoSize := 
SizeOf(TOSVersionInfo);
BRes := GetVersionEx(lpVersionInformation);
if BRes then
with lpVersionInformation do case dwPlatformId of
VER_PLATFORM_WIN32_WINDOWS :
if dwMinorVersion=0 then c := 'Windows 95' 
else c := 'Windows 98';
VER_PLATFORM_WIN32_NT : c := 'Windows NT';
VER_PLATFORM_WIN32s : c := 'Win 3.1 with Win32s'
end;
laVersion_.Caption := c;
// Дата создания BIOS-а
if c='Windows NT' then BIOSInfo('NT') else BIOSInfo('95');
end;

В предыдущем отрывке программы внимательный читатель заметил вызов функции BIOSInfo с параметром, характеризующем текущую ОС. Опишем эту функцию. Важно отметить, что способ получения информации о дате BIOS различен. Для NT получим информацию из реестра, а для Windows 95/98 из соответствующего участка памяти. Эти два способа взаимоисключаемы, так как у Windows 95/98 нет соответствующего раздела реестра, а прямой доступ к памяти в NT невозможен.

// Информация о дате создания BIOS-а.
procedure TfmMain.BIOSInfo(OS : string);
var p : pointer;
s : string[255];
begin
if OS='NT' then begin with TRegistry.Create do
try RootKey := HKEY_LOCAL_MACHINE;
if OpenKeyReadOnly
('HARDWARE\DESCRIPTION\System')
then laBIOSDate_.Caption := 
ReadString('SystemBiosDate')
finally Free;
end;
end
else try
s[0] := #8;
p := Pointer($0FFFF5);
Move(p^,s[1],8);
laBIOSDate_.Caption := 
copy(s,1,2) + '/' + copy(s,4,2) + '/' +copy (s,7,2);
except laBIOSDate_.Caption := 'XX.XX.XXXX';
end;
end;

Рассмотрим функцию SystemParametersInfo, которая позволяет управлять некоторыми настройками системы. Область применения данной функции для NT и Windows 95/98 различна. Умышленно выберем некоторую общую часть для обеих систем.

// Информация о параметрах
procedure TfmMain.ParametersInfo;
var Bl : boolean;
begin
// Разрешен ли PC Speaker
SystemParametersInfo(SPI_GETBEEP,0,@Bl,0);
cbSpeaker.Checked := Bl;
// Активен ли хранитель экрана
SystemParametersInfo
(SPI_GETSCREENSAVEACTIVE,0,@Bl,0);
cbScreenSaverActive.Checked := Bl;
// Интервал вызова хранителя экрана
SystemParametersInfo
(SPI_GETSCREENSAVETIMEOUT,0,
@ScreenSaveTimeOut,0);
// Настройки клавиатуры
SystemParametersInfo
(SPI_GETKEYBOARDDELAY,0,
@KeyboardDelay,0);
SystemParametersInfo
(SPI_GETKEYBOARDSPEED,0,
@KeyboardSpeed,0);
end;
// Отображение настроек
procedure TfmMain.ShowSomeInfo;
begin
tbKeyboardDelay.Position := 3 - KeyboardDelay;
tbKeyboardSpeed.Position := KeyboardSpeed;
edSStimeOut.EditMask := IntToStr
(ScreenSaveTimeOut div 60);
end;

Также позволим пользователю изменять и сохранять настройки системы по своему вкусу. Здесь также будем использовать функцию SystemParametersInfo. Для компонентов tbKeyboardSpeed, tbKeyboardDelay, cbScreenSaverActive, cbSpeaker, edSSTimeOut в ObjectInspector перейдем на закладку Events и изменим событие OnChange (для tbKeyboardSpeed, tbKeyboardDelay) , OnClick (для cbScreenSaverActive, cbSpeaker) и OnExit для edSSTimeOut на Change. Таким образом, все пять вышеперечисленных компонент после изменений состояний передадут управление нижеприведенной процедуре.

// Сохранение изменений параметров системы
procedure TfmMain.Change(Sender: TObject);
var Sen : TComponent;
begin
Sen := Sender as TComponent;
// Вкл/Выкл PC Speaker-а.
if (Sen.Name='cbSpeaker') and cbSpeaker.Checked
then SystemParametersInfo
(SPI_SETBEEP,1,nil,SPIF_UPDATEINIFILE)
else SystemParametersInfo
(SPI_SETBEEP,0,nil,SPIF_UPDATEINIFILE);
// Вкл/Выкл активности хранителя экрана.
if (Sen.Name='cbScreenSaver') and cbScreenSaverActive.Checked
then SystemParametersInfo
(SPI_SETSCREENSAVEACTIVE,1,nil,SPIF_UPDATEINIFILE)
else SystemParametersInfo
(SPI_SETSCREENSAVEACTIVE,0,nil,SPIF_UPDATEINIFILE);
// Изменение значения задержки перед повтором с клавиатуры
if (Sen.Name='tbKeyboardDelay') then SystemParametersInfo(
SPI_SETKEYBOARDDELAY,3-tbKeyboardDelay.Position,nil,
SPIF_SENDWININICHANGE);
// Изменение значения скорости ввода с клавиатуры
if (Sen.Name='tbKeyboardSpeed') then SystemParametersInfo(
SPI_SETKEYBOARDSPEED,tbKeyboardSpeed.Position,nil,
SPIF_SENDWININICHANGE);
// Изменение интервала запуска хранителя экрана
if (Sen.Name='edSSTimeOut') then SystemParametersInfo(
SPI_SETSCREENSAVETIMEOUT,StrToInt(edSSTimeOut.Text)
*60,nil,SPIF_UPDATEINIFILE);
end;

И ,наконец, вызовем все эти процедуры при создании формы.

// Вызов информационных процедур при создании формы.
procedure TfmMain.FormCreate(Sender: TObject);
begin
HardwareInfo;
MemoryInfo;
VideoInfo;
ParametersInfo;
ShowSomeInfo;
OSInfo;
end;

Использование Delphi совместно c фунциями Microsoft Win32 API позволит программисту создать более функционально богатые и гибкие приложения.

Delphi и WordBasic.

- Помогите! У меня Word при печати вместо букв квадратики выводит! Help! Help! Что мне делать? Посоветуйте! - А ты в квадратики нужные буквы вписывай...
unit oleword;
// Необходима форма и компоненты Memo и Edit на ней.
// Написано для MSWord 8.
// Также необходимо создать тестовый документ d:\test.doc
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  OLEAuto, ShellAPI, StdCtrls;
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    MSWord: Variant;
    // WordVersion: Byte;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  Test, Test1: Integer;
  AString: Variant;
begin
  MSWord := CreateOLEObject('Word.Application'); //Word 8
  MSWord.Documents.Open(FileName := 'd:\test.doc', ReadOnly := True);
  MSWord.Visible := 1; //Закомментарьте, если вы нехотите показывать файл;
  Test := MSWord.FontNames.Count;
  for Test1 := 1 to Test do
  begin
    AString := MSWord.FontNames.Item(Test1);
    Memo1.Lines.Add(AString);
  end;
  MSWord.ActiveDocument.Range(Start := 0,
end := 0);
MSWord.ActiveDocument.Range.InsertAfter(Text := 'Заголовок');
MSWord.ActiveDocument.Range.InsertParagraphAfter;
MSWord.ActiveDocument.Range.Font.Name := 'Arial';
MSWord.ActiveDocument.Range.Font.Size := 24;
AString := MSWord.ActiveDocument.Range.Font.Name;
Edit1.Text := AString;
end;
end.

Delphi и OLE Automation с Word.

In the beginning was the Word, and the Word was 1.0...
Автоматизация позволяет одному приложению управлять другим приложением. Управляемое приложение называется сервером автоматизации (в нашем случае Word). Приложение, управляющее сервером называется диспетчером автоматизации. Есть два пути для получения доступа к серверам автоматизации: Позднее связывание (Интерфейс IDispatch).

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

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

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

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

Раннее связывание (Использование библиотеки типов/интерфейсов).

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

Библиотека типов должна импортироваться в Delphi. Библиотека типов является языковым нейтральным описанием всех объектов и функций, поддерживаемых сервером. (Это подобно файлу заголовка языка C).

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

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

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

Подготовка библиотеки типов.

Модуль Pascal должен быть создан на основе файла библиотеки типов.

Выберите пункт меню Project|Import Type Library Нажмите кнопку Add и выберите следующий файл c:\program files\microsoft office\office\msword8.olb Нажмите OK.

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

Наиболее простой путь заключается в следующем: удалите модуль excel_tlb из проекта и только после этого добавьте его в список используемых модулей.

Документация.

Справочный файл c:\program files\microsoft office\office\vbawrd8.hlp содержит информацию о доступных объектах Word.

"Записыватель" макросов позволяет быстро создавать VBA-код. После этого он довольно может легко быть портирован в Delphi.

Пример автоматизации.

Следующий пример использует класс-оболочку Delphi, инкапсулирующий прямые вызовы объектов Word. Вот преимущество этого метода:

Обеспечение скрытия параметров. Возможность использования для многих методов параметров по умолчанию. Многие методы Word также работают с вариантными параметрами. Это означает невозможность использования констант - скрытие параметров решает данную проблему. Обеспечение проверки типа. Многие методы определены с параметрами OLEVariant, обеспечивая внешнюю совместимость. Следующий класс-оболочка демонстрирует ключевые методы автоматизации Word. Полностью класс приведен в Приложении 1.
unit doc;
interface
uses
  windows, sysutils, Word_TLB;
type
  TWinWord = class
  private
    App: _Application;
  public
    constructor Create;
    destructor Destroy; override;
    procedure NewDoc(Template: string);
    procedure GotoBookmark(Bookmark: string);
    procedure InsertText(Text: string);
    procedure SaveAs(Filename: string);
  end;
  //------------------------------------------------------------------
implementation
//------------------------------------------------------------------
constructor TWinWord.Create;
begin
  App := CoApplication.Create;
end;
//------------------------------------------------------------------
destructor TWinWord.Destroy;
var
  SaveChanges: OLEVariant;
  OriginalFormat: OLEVariant;
  RouteDocument: OLEVariant;
begin
  SaveChanges := wdDoNotSaveChanges;
  OriginalFormat := unAssigned;
  RouteDocument := unAssigned;
  app.Quit(SaveChanges, OriginalFormat, RouteDocument);
  inherited destroy;
end;
//------------------------------------------------------------------
procedure TWinWord.GotoBookmark(Bookmark: string);
var
  What: OLEVariant;
  Which: OLEVariant;
  Count: OLEVariant;
  Name: OLEVariant;
begin
  What := wdGoToBookmark;
  Which := unAssigned;
  Count := unAssigned;
  Name := Bookmark;
  App.Selection.GoTo_(What, Which, Count, Name);
end;
//------------------------------------------------------------------
procedure TWinWord.InsertText(Text: string);
begin
  App.Selection.TypeText(Text);
end;
//------------------------------------------------------------------
procedure TWinWord.NewDoc(Template: string);
var
  DocTemplate: OleVariant;
  NewTemplate: OleVariant;
begin
  DocTemplate := Template;
  NewTemplate := False;
  App.Documents.Add(DocTemplate, NewTemplate);
end;
//------------------------------------------------------------------
procedure TWinWord.SaveAs(Filename: string);
begin
  OLEVariant(App).ActiveDocument.SaveAs(FileName);
end;
//------------------------------------------------------------------
end.

Чтобы создать класс:

Добавьте модуль библиотеки типов в список используемых модулей.

uses
  windows, sysutils, Word_TLB;

Создадим определение класса:

TWinWord = class
Private
App : _Application;
public
procedure NewDoc(Template : String);
procedure GotoBookmark(Bookmark : String);
procedure InsertText(Text : String);
procedure SaveAs(Filename : string);
constructor Create;
destructor Destroy; override;
end;

Переменная App является ссылкой на приложение Word. Это допускает вызов методов Word с применением технологии раннего связывания.

Опубликованные (public) процедуры - процедуры, которые могут быть использованы при работе с классом.

Создадим конструктор.

constructor TWinWord.Create;
begin
  App := CoApplication.Create;
end;

Он вызывается при создании класса TWinWord. CoApplication.create создает новый экземпляр Word и возвращает ссылку на интерфейс Application. Это позволяет вызывать методы объекта app.

Реализация дектруктора.

destructor TWinWord.Destroy;
var
SaveChanges : OLEVariant;
OriginalFormat : OLEVariant;
RouteDocument : OLEVariant;
begin
SaveChanges := wdDoNotSaveChanges;
OriginalFormat := unAssigned;
RouteDocument := unAssigned;
app.Quit(SaveChanges, OriginalFormat, RouteDocument);
inherited destroy;
end;

Деструктор должен вызываться В ОБЯЗАТЕЛЬНОМ ПОРЯДКЕ. Метод Quit объекта приложения закрывает Word и распределяет всю связанную с ним память. Так как параметры метода Quit определены как вариантный тип OLEVariant, вся свазанная с ними память распределяется именно для этого типа переменных.

Реализуем метод NewDoc. Этот метод создаст новый текстовый документ на основе заданного шаблона.

procedure TWinWord.NewDoc(Template : String);
var
DocTemplate : OleVariant;
NewTemplate : OleVariant;
begin
DocTemplate := Template;
NewTemplate := False;
App.Documents.Add(DocTemplate, NewTemplate);
end;

Данный метод осуществляет более строгую проверку типов, чем это осуществляет сам Word. Параметр Template должен содержать строку с именем шаблона. Метод Word Add в качестве параметров может содержать значения любого типа. Лучшая пример этого - метод MoveRight, реализация которого показана в Приложении 1.

Ниже показана реализация метода SaveAs. Данный метод позволит сохранить в файле текущий документ.

procedure TWinWord.SaveAs(Filename : string);
begin
  OLEVariant(App).ActiveDocument.SaveAs(FileName);
end;
проверяются только во время прогона. Изменение имени метода в этом случае не вызовет ошибку во время компиляции. В нашем случае технология позднего связывания более эффективна, так как метод SaveAs требует 12 параметров, но только параметр FileName является обязательным. Так как данный вызов можно считать единичным, его выполнение не может особо сказаться на скорости выполнения программы.

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

Word := TWinWord.create;
try
Word.visible := true;
Word.NewDoc('c:\delphi\word\sample\Demo');
Word.GotoBookmark('From');
Word.InsertText('Иван Иваныч');
Word.GotoBookmark('Dept');
Word.InsertText('Разработка');
Word.GotoBookmark('Phone');
Word.InsertText('111111');
Word.GotoBookmark('Now');
Word.InsertText(FormatDateTime('d-mmm-yyyy', now));
//SF элементы
Word.GotoBookmark('Items');
Word.InsertText('112021');
Word.MoveRight(1);
Word.InsertText('PVCS');
Word.MoveRight(1);
Word.InsertText('1');
Word.MoveRight(1);
Word.InsertText('£ 305.99');
Word.MoveRight(1);
Word.InsertText('£ 305.99');
Word.MoveRight(1);
Word.UpdateFields;
Word.RunMacro('Demo');
Word.Print;
Word.SaveAs(filename);
finally
Word.Free;
end;
Итог. Всегда используйте раннее связывание. Если позднее связывание необходимо для вызовов некоторых функций, используйте где возможно раннее связывание и преобразование типа объектной переменной к типу OLEVariant для вызовов, требующим позднее связывание. Не включайте модуль библиотеки типов в ваш проект. Добавьте его только в список используемых модулей. Создавайте код автоматизации в отдельном модуле. Инкапсулируйте вызовы в классе-оболочке. Используйте "записыватель" макросов Word для создания прототипа кода автоматизации. Используйте файл электронной справки vbawrd8.hlp для получения информации об объектах Word. Используйте модуль Word_tlb.pas для проверки необходимых Delphi типов и количества параметров. Для проверки правильности кода перекомпилируйте проект, нажав клавиши <CTRL><F9>. Загружайте и используйте шаблоны Word, содержащие предварительное форматирование текста. Этот способ существенно быстрее и не требует большого времени для создания сложноформатированных документов. Шаблоны ДОЛЖНЫ сохраняться приложением в своей рабочей директории. Это поможет избежать проблем, связанных с конфликтом имен. Используйте закладки (Bookmarks) для определения области ввода текста приложением Delphi. Удостоверьтесь в том, что ваш код содержит команду закрытия приложения Word (app.quit). Не вызывая app.quit, можно быстро исчерпать системные ресурсы, особенно при работе с большим количеством документов Word. Обратите на это особое внимание. Наличие множества незакрытых документов Word легко проверить в Windows NT, используя Менеджер Задач (нажмите CTL+ALT+Del для его открытия). Приложение A – Полный исходный код сласса TWinWord.

Полный исходный код класса tWinWord приведен ниже. Он включает реализацию всех методов:

unit doc;
interface
uses
  Word_TLB, windows, sysutils;
type
  TWinWord = class
  private
    App: _Application;
    function fGetVisible: boolean;
    procedure fSetVisible(visible: boolean);
  public
    procedure NewDoc(Template: string);
    procedure GotoBookmark(Bookmark: string);
    procedure InsertText(Text: string);
    procedure MoveRight(Count: integer);
    procedure Print;
    procedure UpdateFields;
    procedure SaveAs(Filename: string);
    procedure RunMacro(MacroName: string);
    constructor Create;
    destructor Destroy; override;
    property visible: boolean
      read fGetVisible
      write fSetVisible;
  end;
implementation
//------------------------------------------------------------------
constructor TWinWord.Create;
begin
  App := CoApplication.Create;
end;
//------------------------------------------------------------------
destructor TWinWord.Destroy;
var
  SaveChanges: OLEVariant;
  OriginalFormat: OLEVariant;
  RouteDocument: OLEVariant;
begin
  SaveChanges := wdDoNotSaveChanges;
  OriginalFormat := unAssigned;
  RouteDocument := unAssigned;
  app.Quit(SaveChanges, OriginalFormat, RouteDocument);
  inherited destroy;
end;
//------------------------------------------------------------------
function TWinWord.fGetVisible: boolean;
begin
  result := App.Visible;
end;
//------------------------------------------------------------------
procedure TWinWord.fSetVisible(Visible: boolean);
begin
  App.visible := Visible;
end;
//------------------------------------------------------------------
procedure TWinWord.GotoBookmark(Bookmark: string);
var
  What: OLEVariant;
  Which: OLEVariant;
  Count: OLEVariant;
  Name: OLEVariant;
begin
  What := wdGoToBookmark;
  Which := unAssigned;
  Count := unAssigned;
  Name := Bookmark;
  App.Selection.GoTo_(What, Which, Count, Name);
end;
//------------------------------------------------------------------
procedure TWinWord.InsertText(Text: string);
begin
  App.Selection.TypeText(Text);
end;
//------------------------------------------------------------------
procedure TWinWord.NewDoc(Template: string);
var
  DocTemplate: OleVariant;
  NewTemplate: OleVariant;
begin
  DocTemplate := Template;
  NewTemplate := False;
  App.Documents.Add(DocTemplate, NewTemplate);
end;
//------------------------------------------------------------------
procedure TWinWord.MoveRight(Count: integer);
var
  MoveUnit: OleVariant;
  vCount: OleVariant;
  Extended: OleVariant;
begin
  MoveUnit := wdCell;
  vCount := Count;
  Extended := unassigned;
  app.selection.MoveRight(MoveUnit, vCount, Extended);
end;
//------------------------------------------------------------------
procedure TWinWord.Print;
begin
  OLEVariant(app).Printout;
end;
//------------------------------------------------------------------
procedure TWinWord.UpdateFields;
begin
  App.ActiveDocument.Fields.Update;
end;
//------------------------------------------------------------------
procedure TWinWord.SaveAs(Filename: string);
begin
  OLEVariant(App).ActiveDocument.SaveAs(FileName);
end;
//------------------------------------------------------------------
procedure TWinWord.RunMacro(MacroName: string);
begin
  App.Run(MacroName);
end;
//------------------------------------------------------------------
end.

Как научить приложение Delphi разговаривать.

Автор: Alec Bergamini

11-го августа 2001 Microsoft объявила о выпуске SAPI 5.1 SDK. Данный продукт можно использовать в любом языке, который поддерживает OLE автоматизацию.

В данной статье я постараюсь раасказать, как установить SAPI 5.1 SDK. Затем мы посмотрим, как использовать SDK в приложении Delphi для преобразования текста в синтезированную речь. Синтезированная речь будет проигрываться через спикер. Всё это тестировалось в Delphi 5 и 6.

Чтобы скачать SAPI 5.1, необходимо зайти на сайт Microsoft’s Speech.net Technologies по адресу http://www.microsoft.com/speech/ и кликнуть по ссылке download. Далее будет предложено прочитать комментарии к данному продукту. Если в Вашей системе, язык по умолчанию отличается от US English, то настоятельно рекомендую прочитать эти комментарии до конца.

Если Вы используете beta версию операционной системы XP, то у Вас могут возникнуть некоторые проблемы. Проблемы связаны с тем, что большинство beta версий XP включают в себя ранние версии SAPI 5.1. Поэтому не пытайтесь инсталировать release версию SAPI 5.1 на XP, она не будет работать.

После того как Вы прочитаете комментарии, то приступайте к скачиванию Speech SDK 5.1. Всё что для этого потребуется, это нажать на ссылку Speech SDK 5.1 (68 MB). В архиве содержится сам SDK, докумантация, а так же текты на английском для примера.

Итак, после скачивания SAPI 5.1 SDK, запустите speechsdk51.exe для установки его на Ваш компьютер.

Теперь надо дать знать Delphi о новых объектах автоматизации SAPI. Для этого запустите Delphi 5 или 6 (Я не пробовал боле ранние версии) и откройте Project | Import Type Library. В диалоге Import Type Library выберите “Microsoft Speech Object Library (Version 5.1)”. Если Вы не нашли его в списке, значит во время инсталяции SAPI 5.1 произошли какие-то ошибки.

Delphi предложит поместить компоненты SAPI на станицу ActiveX. Я рекомендую разместить их в новой странице под названием “SAPI 5”, так как количество компонент довольно большое (19). Так же рекомендую Вам выбрать “Unit dir name” отличающуюся от той, которая предлагается по умолчанию. Убедитесь, что на “Generate Component Wrapper” стоит галочка и нажмите кнопку "Install".

В диалоге Install выберите закладку “Into new package” и в поле “File name:” введите имя пакета наподобие “SAPI5.dpk”, нажмите кнопку "Обзор..." (browse) и убедитесь, что dpk создан в той же директории, в которой были созданы компоненты. В диалоге Install в поле Description задайте какое-нибудь описание, например “SAPI 5 automation components”. Нажмите OK.

В подтверждающем диалоге нажмите yes. После этого новые компоненты будут установлены.

Теперь, если Вы посмотрите в директорию, которую указали для установки компонент, то обнаружите там файл SpeechLib_TLB.pas (и dcr) который содержит весь код компоненты (интерфайс, константы, типы, а так же другую полезную информацию). Эта директория так же содержит (если Вы следовали вышеприведённым инструкциям) SAPI5.dpk который является исходинком пакета.

А теперь самая интересная часть.

Давайте создадим приложение, которое будет синтезировать речь. В Delphi создайте новое приложение и поместите на форму кнопку. На странице компонент SAPI5 найдите SpVoice и перетащите его на форму.

Теперь создайте событие onClick для Вашей кнопки, которое должно выглядеть примерно так:

procedure TForm1.Button1Click(Sender: TObject);
begin
  SpVoice1.Speak('Hello world!', SVSFDefault);
end;

Запустите программу и нажмите кнопку. Здорово?

Метод Speak объекта SPVoice предоставляет довольно большие возможности. Эти возможности можно использовать если поиграться со вторым параметром. В вышеприведённом примере я использовал режим поумолчанию, который позволяет функции вернуть управление только после завершения проигрывания звука. Избежать этого можно путём внедрения в текст специальных тэгов XML.

Документация по SDK содержит файл sapi.chm который можно найти в директории \Program Files\Microsoft Speech SDK 5.1\Docs\Help .

Sapi.chm содержит довольно много информации. Вот основные, часто используемые возможности компоненты и, соответствующие им флаги, которые передаются во втором параметре:

Воспроизведение текста находящегося в файле. (SVSFIsFilename) Асинхронный решим проигрывания звука. Позволяет функции вернуть управление немедленно, во время воспроизведения. (SVSFlagsAsync) Позволяет управлять воспроизведением через XML тэги (см. раздел под название “XML TTS Tutorial”). Тэги позволяют настроить тональность звучания, скорость воспроизведения и многое другое.( SVSFIsXML)

Одна из интересных вещей (не документирована) заключается в том, что можно озвучивать заголовок веб страницы путём установки флага в SVSFIsFilenam а имени файла в URL. Если Вы соединены с интернетом, попробуйте запустить следующую строчку:

SpVoice1.Speak('http://www.o2a.com', SVSFIsFilename);

Так же при помощи этого флага можно проигрывать wav файлы:

SpVoice1.Speak('C:\WINNT\MEDIA\Windows Logon Sound.wav', SVSFIsFilename);

На самом деле у этой SAPI намного больше возможностей, чем я здесь привёл. В следующий раз, мы подробнее рассмотрим другие возможности.

Массив в Delphi.

Раздел 1:

Вот несколько функций для операций с двухмерными массивами. Самый простой путь для создания собственной библиотеки. Процедуры SetV и GetV позволяют читать и сохранять элементы массива VArray (его Вы можете объявить как угодно). Например:

type
  VArray: array[1..1] of double;
var
  X: ^VArray;
  NR, NC: Longint;
begin
  NR := 10000;
  NC := 100;
  if AllocArray(pointer(X), N * Sizeof(VArray)) then
    exit;
  SetV(X^, NC, 2000, 5, 3.27); { X[2000,5] := 3.27 }
end;
function AllocArray(var V: pointer; const N: longint): Boolean;
begin {распределяем память для массива V размера N}
  try
    GetMem(V, N);
  except
    ShowMessage('ОШИБКА выделения памяти. Размер:' + IntToStr(N));
    Result := True;
    exit;
  end;
  FillChar(V^, N, 0); {в случае включения длинных строк заполняем их нулями}
  Result := False;
end;
procedure SetV(var X: Varray; const N, ir, ic: LongInt; const value:
  double);
begin {заполняем элементами двухмерный массив X размером ? x N : X[ir,ic] := value}
  X[N * (ir - 1) + ic] := value;
end;
function GetV(const X: Varray; const N, ir, ic: Longint): double;
begin {возвращаем величины X[ir,ic] для двухмерного массива шириной N столбцов}
  Result := X[N * (ir - 1) + ic];
end;

Раздел 2:

Самый простой путь - создать массив динамически.

Myarray := GetMem(rows * cols * sizeof(byte,word,single,double и пр.) 

Сделайте функцию fetch_num типа.

function fetch_num(r,c:integer) : single; 
result := pointer + row + col*rows 

И затем вместо myarray[2,3] напишите.

myarray.fetch_num(2,3);

Поместите эти функции в ваш объект и работа с массивами станет пустячным делом. Я экспериментировал с многомерными (вплоть до 8) динамическими сложными массивами и эти функции показали отличный результат.

Раздел 3

Вот способ создания одно- и двухмерных динамических массивов:

(*
--
-- модуль для создания двух очень простых классов обработки динамических массивов
--     TDynaArray   :  одномерный массив
--     TDynaMatrix  :  двумерный динамический массив
--
*)
unit DynArray;
interface
uses
  SysUtils;
type
  TDynArrayBaseType = double;
const
  vMaxElements = (High(Cardinal) - $F) div sizeof(TDynArrayBaseType);
  {= гарантирует максимально возможный массив =}
type
  TDynArrayNDX = 1..vMaxElements;
  TArrayElements = array[TDynArrayNDX] of TDynArrayBaseType;
  {= самый большой массив TDynArrayBaseType, который мы может объявить =}
  PArrayElements = ^TArrayElements;
  {= указатель на массив =}
  EDynArrayRangeError = class(ERangeError);
  TDynArray = class
  private
    fDimension: TDynArrayNDX;
    fMemAllocated: word;
    function GetElement(N: TDynArrayNDX): TDynArrayBaseType;
    procedure SetElement(N: TDynArrayNDX; const NewValue: TDynArrayBaseType);
  protected
    Elements: PArrayElements;
  public
    constructor Create(NumElements: TDynArrayNDX);
    destructor Destroy; override;
    procedure Resize(NewDimension: TDynArrayNDX); virtual;
    property dimension: TDynArrayNDX
      read fDimension;
    property Element[N: TDynArrayNDX]: TDynArrayBaseType
    read GetElement
      write SetElement;
    default;
  end;
const
  vMaxMatrixColumns = 65520 div sizeof(TDynArray);
  {= построение матрицы класса с использованием массива объектов TDynArray =}
type
  TMatrixNDX = 1..vMaxMatrixColumns;
  TMatrixElements = array[TMatrixNDX] of TDynArray;
  {= каждая колонка матрицы будет динамическим массивом =}
  PMatrixElements = ^TMatrixElements;
  {= указатель на массив указателей... =}
  TDynaMatrix = class
  private
    fRows: TDynArrayNDX;
    fColumns: TMatrixNDX;
    fMemAllocated: longint;
    function GetElement(row: TDynArrayNDX;
      column: TMatrixNDX): TDynArrayBaseType;
    procedure SetElement(row: TDynArrayNDX;
      column: TMatrixNDX;
      const NewValue: TDynArrayBaseType);
  protected
    mtxElements: PMatrixElements;
  public
    constructor Create(NumRows: TDynArrayNDX; NumColumns: TMatrixNDX);
    destructor Destroy; override;
    property rows: TDynArrayNDX
      read fRows;
    property columns: TMatrixNDX
      read fColumns;
    property Element[row: TDynArrayNDX; column: TMatrixNDX]: TDynArrayBaseType
    read GetElement
      write SetElement;
    default;
  end;
implementation
(*
--
--  методы TDynArray
--
*)
constructor TDynArray.Create(NumElements: TDynArrayNDX);
begin {==TDynArray.Create==}
  inherited Create;
  fDimension := NumElements;
  GetMem(Elements, fDimension * sizeof(TDynArrayBaseType));
  fMemAllocated := fDimension * sizeof(TDynArrayBaseType);
  FillChar(Elements^, fMemAllocated, 0);
end; {==TDynArray.Create==}
destructor TDynArray.Destroy;
begin {==TDynArray.Destroy==}
  FreeMem(Elements, fMemAllocated);
  inherited Destroy;
end; {==TDynArray.Destroy==}
procedure TDynArray.Resize(NewDimension: TDynArrayNDX);
begin {TDynArray.Resize==}
  if (NewDimension < 1) then
    raise EDynArrayRangeError.CreateFMT('Индекс вышел за границы диапазона : %d',
      [NewDimension]);
  Elements := ReAllocMem(Elements, fMemAllocated, NewDimension *
    sizeof(TDynArrayBaseType));
  fDimension := NewDimension;
  fMemAllocated := fDimension * sizeof(TDynArrayBaseType);
end; {TDynArray.Resize==}
function TDynArray.GetElement(N: TDynArrayNDX): TDynArrayBaseType;
begin {==TDynArray.GetElement==}
  if (N < 1) or (N > fDimension) then
    raise EDynArrayRangeError.CreateFMT('Индекс вышел за границы диапазона : %d',
      [N]);
  result := Elements^[N];
end; {==TDynArray.GetElement==}
procedure TDynArray.SetElement(N: TDynArrayNDX; const NewValue:
  TDynArrayBaseType);
begin {==TDynArray.SetElement==}
  if (N < 1) or (N > fDimension) then
    raise EDynArrayRangeError.CreateFMT('Индекс вышел за границы диапазона : %d',
      [N]);
  Elements^[N] := NewValue;
end; {==TDynArray.SetElement==}
(*
--
--  методы TDynaMatrix
--
*)
constructor TDynaMatrix.Create(NumRows: TDynArrayNDX; NumColumns: TMatrixNDX);
var
  col: TMatrixNDX;
begin {==TDynaMatrix.Create==}
  inherited Create;
  fRows := NumRows;
  fColumns := NumColumns;
  {= выделение памяти для массива указателей (т.е. для массива TDynArrays) =}
  GetMem(mtxElements, fColumns * sizeof(TDynArray));
  fMemAllocated := fColumns * sizeof(TDynArray);
  {= теперь выделяем память для каждого столбца матрицы =}
  for col := 1 to fColumns do
  begin
    mtxElements^[col] := TDynArray.Create(fRows);
    inc(fMemAllocated, mtxElements^[col].fMemAllocated);
  end;
end; {==TDynaMatrix.Create==}
destructor TDynaMatrix.Destroy;
var
  col: TMatrixNDX;
begin {==TDynaMatrix.Destroy;==}
  for col := fColumns downto 1 do
  begin
    dec(fMemAllocated, mtxElements^[col].fMemAllocated);
    mtxElements^[col].Free;
  end;
  FreeMem(mtxElements, fMemAllocated);
  inherited Destroy;
end; {==TDynaMatrix.Destroy;==}
function TDynaMatrix.GetElement(row: TDynArrayNDX;
  column: TMatrixNDX): TDynArrayBaseType;
begin {==TDynaMatrix.GetElement==}
  if (row < 1) or (row > fRows) then
    raise
      EDynArrayRangeError.CreateFMT('Индекс строки вышел за границы диапазона : %d', [row]);
  if (column < 1) or (column > fColumns) then
    raise
      EDynArrayRangeError.CreateFMT('Индекс столбца вышел за границы диапазона : %d', [column]);
  result := mtxElements^[column].Elements^[row];
end; {==TDynaMatrix.GetElement==}
procedure TDynaMatrix.SetElement(row: TDynArrayNDX;
  column: TMatrixNDX;
  const NewValue: TDynArrayBaseType);
begin {==TDynaMatrix.SetElement==}
  if (row < 1) or (row > fRows) then
    raise
      EDynArrayRangeError.CreateFMT('Индекс строки вышел за границы диапазона : %d', [row]);
  if (column < 1) or (column > fColumns) then
    raise
      EDynArrayRangeError.CreateFMT('Индекс столбца вышел за границы диапазона : %d', [column]);
  mtxElements^[column].Elements^[row] := NewValue;
end; {==TDynaMatrix.SetElement==}
end.

Тестовая программа для модуля DynArray.

uses DynArray, WinCRT;
const
  NumRows: integer = 7;
  NumCols: integer = 5;
var
  M: TDynaMatrix;
  row, col: integer;
begin
  M := TDynaMatrix.Create(NumRows, NumCols);
  for row := 1 to M.Rows do
    for col := 1 to M.Columns do
      M[row, col] := row + col / 10;
  writeln('Матрица');
  for row := 1 to M.Rows do
  begin
    for col := 1 to M.Columns do
      write(M[row, col]: 5: 1);
    writeln;
  end;
  writeln;
  writeln('Перемещение');
  for col := 1 to M.Columns do
  begin
    for row := 1 to M.Rows do
      write(M[row, col]: 5: 1);
    writeln;
  end;
  M.Free;
end.

Программирование АССЕМБЛЕРОМ в Delphi.

Автор: Александр Баранецкий Каждый день множество программистов (кроме опытных) в Delphi, используя код высокого уровня, пишут свои программы. Как правило, пренебрегая таким понятием как Assembler, имеется ввиду в строенный в DELPHI. Конечно, тяжело сказать, что на голом «асе» можно сотворить великолепный шедевр имеется ввиду красота - VCL (Visual Component Library). Особенно относительно начинающих программистов, которые только начинают учить IDE. Но зато можно повысить скорость какого нибудь математического или системного алгоритма. Сегодня я представляю на ваш суд статью по «асу». Скорее всего, она будет интересна начинающим программерам. Я не мастер, но сеже. Программирование АССЕМБЛЕРОМ в “Delphi” Для начала несколько основных команд. MOV - предназначена для занесения в ячейку памяти значения. Например:
var
  x: integer;
begin
  Mov X, 10 // Занесение в Х значение 10 // X:=10;
  Mov eax, 45 //Занесение в быстрый регистр
  Mov ebx, eax //Присвоения значения одного регистра другому
  Mov x.edx //Занесение в х значения edx
Первый параметр присваивающий объект, второй присеваемое значение. ADD - Предназначена для прибавления к объекту значения. Значения передаются через запитаю. Например : add eax,2 add x,76 Первый параметр принимающий объект, второй добавляемое значение. SUB - Предназначена для вычитания от объекта значения. Значения передаются через запитаю. Например : sub eax,18 Sub x,6 Первый параметр объект от которого отнимается , второй отнимаемое значение. IMUL - команда умножения IDIV - команда деления CMP - Команда проверки JNZ,JMP,JA - команды перехода.

Теперь перейдем к практическим примерам:

// 1 Функция сложения.
function plus(x, y: integer): integer;
asm
  mov eax,x
  add eax,y
end;
{
Функция вернет сумму «x» и «y». Сперва заносим «х» (move eax,x) потом
прибавляем к уже имеющемуся «y» (add eax,y).
}
// 2. Функция умножения
function Umnojenie(x, z: integer): integer;
asm
  mov ebx,z
  mov eax,x
  imul ebx
end;
{
Заносим в обратном порядке «x» и «z» Отдаем команду на
умножение первого значения на второе «imul ebx ».
}
// 3. Функция вычитания
function Minus(x, y: integer): integer;
asm
  sub x,y
end;
// Просто отнимаем одно от другого
// 4 Функция деления
function divider(x, y: integer): integer;
asm
  mov ebx,y {1}
  cdq {2}
  idiv ebx {3}
end;
Эта функция отличается от остальных методом применения операторов. Занесение значения. 2. Предварительная обработка. 3. Деление. Это были простейшие математические операторы, теперь мы рассмотрим более сложные операторы цикла и условие

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

procedure asm_cycle;
label
  lb;
var
  d: integer;
begin
  asm
    mov ebx,0
    mov d,0
    lb:
    add d,1
    inc ebx
    cmp ebx,10
    jnz lb
    mov ebx,0
  end;
  Writeln(d);
end;
Метка lb нужна, чтобы назначить контрольную точку начала операторов цикла. Переменная «d:integer» для проверки результатов работы цикла. С зарезервированного слова ASM начинаем анализ. Mov edx,0 «edx» выступает как контрольный регистр в нем фиксируется количество повторений. А с самого начало он указывает с какой величины пойдет отчет Например mov edx,0 = for i := 0 to .. do, mov edx,43 = for i:=43 to ..do Мы установим его в 0 чтобы отчет шел с нуля. Переменную d мы тоже обнулим. Третья строка это метка начала после нее идут операторы цикла. Следующий оператор наш рабочий оператор. У нас он 1 но может быть множество. Inc edx добавляем в регистр 1 шаг пройденного цикла если пропустить то цикл будет идти вечно. cmp ebx,10 Один из основных операторов он проверяет не достиг ли цикл верхний предел. Проверка идет в самом конце. Если вернет FALSE то срабатывает следующий оператор перехода на метку т.е в начало цикла и все повторяется до тех пор пока cmp не вернет TRUE в следствии чего не сработает оператор перехода JNZ. Последними операторами обнуляем счетчик и показываем результат.

Условный оператор IF..THEN..ELSE.

procedure if_sample(x: integer);
var
  res: integer;
label
  exit, lb;
begin
  asm
    cmp x,0
    jnz lb
    mov res,45
    jmp exit
    lb:mov res,0
    exit: mov eax,0
  end;
  Writeln(res);
end;
На PASCAL этот оператор пишется так if x = 0 then x:=45 else x:=0; Сначала идет проверка не равен ли х нулю если не равен то переход на метку ld, На которой оператор обнуления. А если равен, то оператор перехода на ld не срабатывает. Срабатывает mov res,45. После которого состоится переход на метку EXIT. В «асе» желательно прописывать свою метку (у нас EXIT), которая по необходимости выйдет и процедуры. И последние. Вызов внешней процедуры. Допустим, надо вызвать внешнюю процедуру.
procedure call_s(x, d: integer; bol: boolean);

Для вызова внешних модулей применяется метод CALL.

procedure call_sample;
asm
  mov eax,4
  mov edx,34
  mov cl,0
  call call_s
end;
Сперва передаются параметры последовательно а потом сам вызов. На этом мой маленький туториал окончен. Все примеры вы найдете в приложенном файле pr_asm. Это маленькое консольное приложение, в котором представлены все примеры с комментариями. P.S. Я не мастер в «асе», и эта статья не сделает вас гениями она лишь призвана показать некоторые стандартные методы «паса» интерпретированные в «ас». Мастера «аса» не смейтесь надо мной сильно, так как я уже сказал, что я не мастер, я просто энтузиаст в «асе».

В следующей статье я продолжу описание более продвинутых операторов и методов а также попробуем написать маленькое «ас» приложение.

Delphi и COM.

Оформил: DeeCo

Автор: Анатолий Тенцер.

Содержание Базовые понятия Интерфейс Автоматическое управление памятью и подсчет ссылок Объявление интерфейсов IUnknown Реализация интерфейсов Реализация интерфейсов (расширенное рассмотрение) Интерфейсы и TComponent Использование интерфейсов внутри программы Использование интерфейсов для реализации Plug-In Delphi и COM Введение

COM (Component Object Model) — модель объектных компонентов — одна из основных технологий, на которых основывается Windows. Более того, все новые технологии в Windows (Shell, Scripting, поддержка HTML и т.п.) реализуют свои API именно в виде COM-интерфейсов. Таким образом, в настоящее время профессиональное программирование требует понимания модели COM и умения с ней работать. В этой главе мы рассмотрим основные понятия COM и особенности их поддержки в Delphi.

Базовые понятия

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

Интерфейс

Интерфейс, образно говоря, является «контрактом» между программистом и компилятором.

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

Компилятор обязуется создать в программе внутренние структуры, позволяющие обращаться к методам этого интерфейса из любого поддерживающего те же соглашения средства программирования. Таким образом, COM является языково-независимой технологией и может использоваться в качестве «клея», соединяющего программы, написанные на разных языках.

Объявление интерфейса включает в себя описание методов и их параметров, но не включает их реализации. Кроме того, в объявлении может указываться идентификатор интерфейса — уникальное 16-байтовое число, сгенерированное по специальным правилам, гарантирующим его статистическую уникальность (GUID — Global Unique Identifier).

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

Таким образом, необходимо понимать следующее:

Интерфейс не является классом. Класс может выступать реализацией интерфейса, но класс содержит код методов на конкретном языке программирования, а интерфейс — нет. Интерфейс строго типизирован. Как клиент, так и реализация интерфейса должны использовать точно те же методы и параметры, что указаны в описании интерфейса. Интерфейс является «неизменным контрактом». Нельзя определять новую версию того же интерфейса с измененным набором методов (или их параметров), но с тем же идентификатором.

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

Реализация интерфейса — это код, который реализует эти методы. При этом, за несколькими исключениями, не накладывается никаких ограничений на то, каким образом будет выглядеть реализация. Физически реализация представляет собой массив указателей на методы, адрес которого и используется в клиенте для доступа к COM-объекту. Любая реализация интерфейса имеет метод QueryInterface, позволяющий запросить ссылку на конкретный интерфейс из числа реализуемых.

Автоматическое управление памятью и подсчет ссылок

Кроме предоставления независимого от языка программирования доступа к методам объектов, COM реализует автоматическое управление памятью для COM-объектов. Оно основано на идее подсчета ссылок на объект. Любой клиент, желающий использовать COM-объект после его создания, должен вызвать заранее предопределенный метод, который увеличивает внутренний счетчик ссылок на объект на единицу. По завершении использования объекта клиент вызывает другой его метод, уменьшающий значение этого же счетчика. По достижении счетчиком ссылок нулевого значения COM-объект автоматически удаляет себя из памяти. Такая модель позволяет клиентам не вдаваться в подробности реализации объекта, а объекту — обслуживать несколько клиентов и корректно очистить память по завершении работы с последним из них.

Объявление интерфейсов

Для поддержки интерфейсов Delphi расширяет синтаксис языка Pascal дополнительными ключевыми словами. Объявление интерфейса в Delphi реализуется ключевым словом interface:

type
  IMyInterface = interface
    ['{412AFF00-5C21-11D4-84DD-C8393F763A13}']
    procedure DoSomething(var I: Integer); stdcall;
    function DoSomethingAnother(S: string): Boolean;
  end;
  IMyInterface2 = interface(IMyInterface)
    ['{412AFF01-5C21-11D4-84DD-C8393F763A13}']
    procedure DoAdditional(var I: Integer); stdcall;
  end;

Для генерации нового значения GUID в IDE Delphi служит сочетание клавиш Ctrl+Shift+G.

IUnknown

Базовым интерфейсом в модели COM является IUnknown. Любой интерфейс наследуется от IUnknown и обязан реализовать объявленные в нем методы. IUnknown объявлен в модуле System.pas следующим образом:

type
  IUnknown = interface
    ['{00000000-0000-0000-C000-000000000046}']
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

Рассмотрим назначение методов IUnknown более подробно.

Последние два метода предназначены для реализации механизма подсчета ссылок.

function _AddRef: Integer; stdcall;

Эта функция должна увеличить счетчик ссылок на интерфейс на единицу и вернуть новое значение счетчика.

function _Release: Integer; stdcall;

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

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

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

Эта функция получает в качестве входного параметра идентификатор интерфейса. Если объект реализует запрошенный интерфейс, то функция:

возвращает ссылку на него в параметре Obj; вызывает метод _AddRef полученного интерфейса; возвращает 0.

В противном случае — функция возвращает код ошибки E_NOINTERFACE.

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

В модуле System.pas объявлен класс TInterfacedObject, реализующий IUnknown и его методы. Рекомендуется использовать этот класс для создания реализаций своих интерфейсов.

Кроме того, поддержка интерфейсов реализована в базовом классе TObject. Он имеет метод.

function TObject.GetInterface(const IID: TGUID; out Obj): Boolean;

Если класс реализует запрошенный интерфейс, то функция:

возвращает ссылку на него в параметре Obj; вызывает метод _AddRef полученного интерфейса; возвращает TRUE.

В противном случае — функция возвращает FALSE.

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

Реализация интерфейсов

Реализацией интерфейса в Delphi всегда выступает класс. Для этого в объявлении класса необходимо указать, какие интерфейсы он реализует.

type
  TMyClass = class(TComponent, IMyInterface, IDropTarget)
    // Реализация методов
  end;

Класс TMyClass реализует интерфейсы IMyInterface и IDropTarget. Необходимо понимать, что реализация классом нескольких интерфейсов не означает множественного наследования и вообще наследования класса от интерфейса. Указание интерфейсов в описании класса означает только то, что в данном классе реализованы все эти интерфейсы.

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

Рассмотрим более подробный пример.

type
  ITest = interface
    ['{61F26D40-5CE9-11D4-84DD-F1B8E3A70313}']
    procedure Beep;
  end;
  TTest = class(TInterfacedObject, ITest)
    procedure Beep;
    destructor Destroy; override;
  end;
procedure TTest.Beep;
begin
  Windows.Beep(0, 0);
end;
destructor TTest.Destroy;
begin
  inherited;
  MessageBox(0, 'TTest.Destroy', nil, 0);
end;

Здесь класс TTest реализует интерфейс ITest. Рассмотрим использование интерфейса из программы.

procedure TForm1.Button1Click(Sender: TObject);
var
  Test: ITest;
begin
  Test := TTest.Create;
  Test.Beep;
end;

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

Во-первых, оператор присваивания при приведении типа данных к интерфейсу неявно вызывает метод _AddRef. При этом количество ссылок на интерфейс увеличивается на единицу.

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

Внимание! Если у класса запрошен хотя бы один интерфейс — не вызывайте его метод Free (или Destroy). Класс будет освобожден тогда, когда отпадет необходимость в последней ссылке на его интерфейсы. Если вы к этому моменту уничтожили экземпляр класса вручную — произойдет ошибка доступа к памяти.

Так, следующий код приведет к ошибке в момент выхода из функции:

var
  Test: ITest;
  T: TTest;
begin
  T := TTest.Create;
  Test := T;
  Test.Beep;
  T.Free;
end; // в этот момент произойдет ошибка

Если вы хотите уничтожить реализацию интерфейса немедленно, не дожидаясь выхода переменной за область видимости, – просто присвойте ей значение NIL:

var
  Test: ITest;
  T: TTest;
begin
  T := TTest.Create;
  Test := T;
  Test.Beep;
  Test := nil; // Неявно вызывается IUnknown._Release;
end;

Обратите особое внимание, что вызовы методов интерфейса IUnknown осуществляются Delphi неявно и автоматически. Поэтому не вызывайте методы интерфейса IUnknown самостоятельно. Это может нарушить нормальную работу автоматического подсчета ссылок и привести к неосвобождению памяти либо к нарушениям защиты памяти при работе с интерфейсами. Во избежание этого необходимо просто помнить следующее.

При приведении типа объекта к интерфейсу вызывается метод _AddRef. При выходе переменной, ссылающейся на интерфейс, за область видимости либо при присвоении ей другого значения вызывается метод _Release. Единожды запросив у объекта интерфейс, в дальнейшем вы не должны освобождать объект вручную. Вообще начиная с этого момента лучше работать с объектом только через интерфейсные ссылки.

В рассмотренных примерах код для получения интерфейса у класса генерировался (с проверкой типов) на этапе компиляции. Если класс не реализует требуемого интерфейса, то программа не откомпилируется. Однако существует возможность запросить интерфейс и во время выполнения программы. Для этого служит оператор as, который вызывает QueryInterface и, в случае успеха, возвращает ссылку на полученный интерфейс. В противном случае генерируется исключение.

Например, следующий код будет успешно откомпилирован, но при выполнении вызовет ошибку «Interface not supported»:

var
  Test: ITest;
begin
  Test := TInterfacedObject.Create as ITest;
  Test.Beep;
end;
//В то же время код var
Test: ITest;
begin
  Test := TTest.Create as ITest;
  Test.Beep;
end;

Будет успешно компилироваться и выполняться.

Реализация интерфейсов (расширенное рассмотрение)

Рассмотрим вопросы реализации интерфейсов подробнее.

Объявим два интерфейса:

type
  ITest = interface
    ['{61F26D40-5CE9-11D4-84DD-F1B8E3A70313}']
    procedure Beep;
  end;
  ITest2 = interface
    ['{61F26D42-5CE9-11D4-84DD-F1B8E3A70313}']
    procedure Beep;
  end;

Теперь создадим класс, который будет реализовывать оба этих интерфейса:

TTest2 = class(TInterfacedObject, ITest, ITest2)
  procedure Beep1;
  procedure Beep2;
  procedure ITest.Beep = Beep1;
  procedure ITest2.Beep = Beep2;
end;

Как видно, класс не может содержать сразу два метода Beep. Поэтому Delphi предоставляет способ для разрешения конфликтов имен, позволяя явно указать, какой метод класса будет служить реализацией соответствующего метода интерфейса.

Если реализация методов TTest2.Beep1 и TTest2.Beep2 идентична, то можно не создавать два разных метода, а объявить класс следующим образом:

TTest2 = class(TInterfacedObject, ITest, ITest2)
  procedure MyBeep;
  procedure ITest.Beep = MyBeep;
  procedure ITest2.Beep = MyBeep;
end;

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

type
  TBeeper = class
    procedure Beep;
  end;
  TMessager = class
    procedure ShowMessage(const S: string);
  end;
  TTest3 = class(TInterfacedObject, ITest, IAnotherTest)
  private
    FBeeper: TBeeper;
    FMessager: TMessager;
    property Beeper: TBeeper read FBeeper implements ITest;
    property Messager: TMessager read FMessager implements IAnotherTest;
  public
    constructor Create;
    destructor Destroy; override;
  end;

Для делегирования реализации интерфейса другому классу служит ключевое слово implements.

{ TBeeper }
procedure TBeeper.Beep;
begin
  Windows.Beep(0, 0);
end; { TMessager }
procedure TMessager.ShowMessage(const S: string);
begin
  MessageBox(0, PChar(S), nil, 0);
end; { TTest3 }
constructor TTest3.Create;
begin
  inherited;
  // Создаем экземпляры дочерних классов
  FBeeper := TBeeper.Create;
  FMessager := TMessager.Create;
end;
destructor TTest3.Destroy;
begin
  // Освобождаем экземпляры дочерних классов
  FBeeper.Free;
  FMessager.Free;
  inherited;
end;

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

Обращаться к полученному классу можно точно так же, как и к любому классу, реализующему интерфейсы:

var
  Test: ITest;
  Test2: IAnotherTest;
begin
  Test2 := TTest3.Create;
  Test2.ShowMessage('Hi');
  Test := Test2 as ITest;
  Test.Beep;
end;
Интерфейсы и TComponent

В базовом классе VCL TComponent имеется полный набор методов, позволяющих реализовать интерфейс IUnknown, хотя сам класс данный интерфейс не реализует. Это позволяет наследникам TComponent реализовывать интерфейсы, не заботясь о реализации IUnknown. Однако методы TComponent._AddRef и TComponent._Release на этапе выполнения программы не реализуют механизм подсчета ссылок, и, следовательно, для классов-наследников TComponent, реализующих интерфейсы, не действует автоматическое управление памятью. Это позволяет запрашивать у них интерфейсы, не опасаясь, что объект будет удален из памяти при выходе переменной за область видимости. Таким образом, следующий код совершенно корректен и безопасен:

type
  IGetData = interface
    ['{B5266AE0-5E77-11D4-84DD-9153115ABFC3}']
    function GetData: string;
  end;
  TForm1 = class(TForm, IGetData)
  private
    function GetData: string;
  end;
… var
  I: Integer;
  GD: IGetData;
  S: string;
begin
  S := '';
  for I := 0 to Pred(Screen.FormCount) do
  begin
    if Screen.Forms[I].GetInterface(IGetData, GD) then
      S := S + GD.GetData + #13;
  end;
  ShowMessage(S);
end;

Этот код проверяет наличие у всех форм в приложении возможности реализации интерфейса IGetData и в случае, если форма реализует этот интерфейс, вызывает его метод.

Использование интерфейсов внутри программы

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

В качестве примера рассмотрим MDI-приложение, имеющее много различных форм и единую панель инструментов. Предположим, что на этой панели инструментов имеются команды «Сохранить», «Загрузить» и «Очистить», однако каждое из окон реагирует на эти команды по-разному.

Создадим модуль с объявлениями интерфейсов:

unit ToolbarInterface;
interface
type
  TCommandType = (ctSave, ctLoad, ctClear);
  TCommandTypes = set of TCommandType;
  TSaveType = (stSave, stSaveAS);
  IToolBarCommands = interface
    ['{B5266AE1-5E77-11D4-84DD-9153115ABFC3}']
    function SupportedCommands: TCommandTypes;
    function Save(AType: TSaveType): Boolean;
    procedure Load;
    procedure Clear;
  end;
implementation
end.

Интерфейс IToolBarCommands описывает набор методов, которые должны реализовать формы, поддерживающие работу с панелью кнопок. Метод SupportedCommands возвращает список поддерживаемых формой команд.

Создадим три дочерние формы — Form2, Form3 и Form4 — и установим им свойство FormStyle = fsMDIChild.

Form2 умеет выполнять все три команды:

type
  TForm2 = class(TForm, IToolBarCommands)
  private
    function SupportedCommands: TCommandTypes;
    function Save(AType: TSaveType): Boolean;
    procedure Load;
    procedure Clear;
  end;
{ TForm2 } procedure TForm2.Clear;
begin
  ShowMessage('TForm2.Clear');
end;
procedure TForm2.Load;
begin
  ShowMessage('TForm2.Load');
end;
function TForm2.Save(AType: TSaveType): Boolean;
begin
  ShowMessage('TForm2.Save');
  Result := TRUE;
end;
function TForm2.SupportedCommands: TCommandTypes;
begin
  Result := [ctSave, ctLoad, ctClear]
end;

Form3 умеет выполнять только команду Clear:

type
  TForm3 = class(TForm, IToolBarCommands)
  private
    function SupportedCommands: TCommandTypes;
    function Save(AType: TSaveType): Boolean;
    procedure Load;
    procedure Clear;
  end;
{ TForm3 } procedure TForm3.Clear;
begin
  ShowMessage('TForm3.Clear');
end;
procedure TForm3.Load;
begin
  // Метод ничего не делает, но должен присутствовать
  // для корректной реализации интерфейса
end;
function TForm3.Save(AType: TSaveType): Boolean;
begin
end;
function TForm3.SupportedCommands: TCommandTypes;
begin
  Result := [ctClear]
end;

И наконец, Form4 вообще не реализует интерфейс IToolBarCommands и не откликается ни на одну команду.

На главной форме приложения поместим ActionList и создадим три компонента TAction. Кроме того, разместим на ней TToolBar и назначим ее кнопкам соответствующие TAction.

type
  TForm1 = class(TForm)
    ToolBar1: TToolBar;
    ImageList1: TImageList;
    ActionList1: TActionList;
    acLoad: TAction;
    acSave: TAction;
    acClear: TAction;
    tbSave: TToolButton;
    tbLoad: TToolButton;
    tbClear: TToolButton;
    procedure acLoadExecute(Sender: TObject);
    procedure ActionList1Update(Action: TBasicAction;
      var Handled: Boolean);
    procedure acSaveExecute(Sender: TObject);
    procedure acClearExecute(Sender: TObject);
  end;

Наиболее интересен метод ActionList1Update, в котором проверяются поддерживаемые активной формой команды и настраивается интерфейс главной формы. Если нет активной дочерней формы либо она не поддерживает интерфейс IToolBarCommands, все команды запрещаются, в противном случае — разрешаются только поддерживаемые формой команды.

procedure TForm1.ActionList1Update(Action: TBasicAction;
  var Handled: Boolean);
var
  Supported: TCommandTypes;
  TC: IToolBarCommands;
begin
  if Assigned(ActiveMDIChild)
    and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
    Supported := TC.SupportedCommands
  else
    Supported := [];
  acSave.Enabled := ctSave in Supported;
  acLoad.Enabled := ctLoad in Supported;
  acClear.Enabled := ctClear in Supported;
end;

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

procedure TForm1.acLoadExecute(Sender: TObject);
var
  TC: IToolBarCommands;
begin
  if Assigned(ActiveMDIChild)
    and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
    TC.Load;
end;
procedure TForm1.acSaveExecute(Sender: TObject);
var
  TC: IToolBarCommands;
begin
  if Assigned(ActiveMDIChild)
    and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
    if not TC.Save(stSaveAS) then
      ShowMessage(‘Not Saved !!!’);
end;
procedure TForm1.acClearExecute(Sender: TObject);
var
  TC: IToolBarCommands;
begin
  if Assigned(ActiveMDIChild)
    and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
    TC.Clear;
end;

Работа программы представлена на рисунке.

Того же эффекта можно добиться и другими методами (например, унаследовав все дочерние формы от единого предка либо обмениваясь с ними сообщениями), однако эти методы имеют ряд существенных недостатков.

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

Использование интерфейсов для реализации Plug-In

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

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

Объявим интерфейсы модуля расширения и внутреннего API программы.

unit PluginInterface;
interface
type
  IAPI = interface
    ['{64CFF1E0-61A3-11D4-84DD-B18D6F94141F}']
    procedure ShowMessage(const S: string);
  end;
  ILoadFilter = interface
    ['{64CFF1E1-61A3-11D4-84DD-B18D6F94141F}']
    procedure Init(const FileName: string; API: IAPI);
    function GetNextLine(var S: string): Boolean;
  end;
implementation
end.

Этот модуль должен использоваться как в Plug-In, так и в основной программе и гарантирует использование ими идентичных интерфейсов.

Plug-In представляет собой DLL, экспортирующую функцию CreateFilter, возвращающую ссылку на интерфейс ILoadFilter. Главный модуль сначала должен вызвать метод Init, передав в него имя файла и ссылку на интерфейс внутреннего API, а затем вызывать метод GetNextLine до тех пор, пока он не вернет FALSE.

Рассмотрим код модуля расширения:

library ImpTxt;
uses
  ShareMem, SysUtils, Classes, PluginInterface;
{$R *.RES}
type
  TTextFilter = class(TInterfacedObject, ILoadFilter)
  private
    FAPI: IAPI;
    F: TextFile;
    Lines: Integer;
    InitSuccess: Boolean;
    procedure Init(const FileName: string; API: IAPI);
    function GetNextLine(var S: string): Boolean;
  public
    destructor Destroy; override;
  end;
  { TTextFilter }
procedure TTextFilter.Init(const FileName: string; API: IAPI);
begin
  FAPI := API;
{$I-}
  AssignFile(F, FileName);
  Reset(F);
{$I+}
  InitSuccess := IOResult = 0;
  if not InitSuccess then
    API.ShowMessage('Ошибка инициализации загрузки');
end;

Метод Init выполняет две задачи: сохраняет ссылку на интерфейс API главного модуля для дальнейшего использования и пытается открыть файл с данными. Если файл открыт успешно – выставляется внутренний флаг InitSuccess.

function TTextFilter.GetNextLine(var S: string): Boolean;
begin
  if InitSuccess then
  begin
    Inc(Lines);
    Result := not Eof(F);
    if Result then
    begin
      Readln(F, S);
      FAPI.ShowMessage('Загружено ' + IntToStr(Lines) + ' строк.');
    end;
  end
  else
    Result := FALSE;
end;

Метод GetNextLine считывает следующую строку данных и возвращает либо TRUE, если это удалось, либо FALSE — в случае окончания файла. Кроме того, при помощи API, предоставляемого главным модулем, данный метод информирует пользователя о ходе загрузки.

destructor TTextFilter.Destroy;
begin
  FAPI := nil;
  if InitSuccess then
    CloseFile(F);
  inherited;
end;

В деструкторе мы обнуляем ссылку на API главного модуля, уничтожая его, и закрываем файл.

function CreateFilter: ILoadFilter;
begin
  Result := TTextFilter.Create;
end;

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

Теперь полученную DLL можно использовать из основной программы.

type
  TAPI = class(TInterfacedObject, IAPI)
    procedure ShowMessage(const S: string);
  end;
  { TAPI }
procedure TAPI.ShowMessage(const S: string);
begin
  with (Application.MainForm as TForm1).StatusBar1 do
  begin
    SimpleText := S;
    Update;
  end;
end;

Класс TAPI реализует API, предоставляемый модулю расширения. Функция ShowMessage выводит сообщения модуля в Status Bar главной формы приложения.

type
  TCreateFilter = function: ILoadFilter;
procedure TForm1.LoadData(FileName: string);
var
  PluginName: string;
  Ext: string;
  hPlugIn: THandle;
  CreateFilter: TCreateFilter;
  Filter: ILoadFilter;
  S: string;
begin

Подготавливаем TMemo к загрузке данных:

Memo1.Lines.Clear;
Memo1.Lines.BeginUpdate;

Получаем имя модуля с фильтром для выбранного расширения файла. Описания модулей хранятся в файле plugins.ini в секции Filters в виде строк формата:

<расширение> = <имя модуля>, например:

[Filters]
TXT = ImpTXT.DLL
try
  Ext := ExtractFileExt(FileName);
  Delete(Ext, 1, 1);
  with TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'plugins.ini') do
  try
    PlugInName := ReadString('Filters', Ext, '');
  finally
    Free;
  end;

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

hPlugIn := LoadLibrary(PChar(PluginName));
try
  CreateFilter := GetProcAddress(hPlugIn, 'CreateFilter');
  if Assigned(CreateFilter) then
  begin

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

Filter := CreateFilter;
try
  Filter.Init(FileName, TAPI.Create);

Загружаем данные при помощи созданного фильтра:

while Filter.GetNextLine(S) do
  Memo1.Lines.Add(S);

Перед выгрузкой DLL из памяти необходимо обязательно освободить ссылку на интерфейс Plug-In, иначе это произойдет по выходе из функции и вызовет Access Violation.

finally
  Filter := nil;
end;
end
else
  raise Exception.Create('Не могу загрузить    фильтр');

Выгружаем DLL и обновляем TMemo:

finally
  FreeLibrary(hPlugIn);
end;
finally
  Memo1.Lines.EndUpdate;
end;
end;

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

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

Внимание! Поскольку в EXE и DLL используются длинные строки, не забудьте включить в список uses обоих проектов модуль ShareMem. Другим вариантом решения проблемы передачи строк является использование типа данных WideString. Для них распределением памяти занимается OLE, причем делает это независимо от модуля, из которого была создана строка.

Как сделать окошко подсказки в редакторе как Delphi по CTRL-J.

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

Автор: Hog Допустим у тебя TMemo..

1. Делаешь ListBox, заполняешь, visible := false, parent := Memo 2. У Memo в обработчике Memo.onKeyDown что-нибудь типа:

if (key = Ord('J')) and (ssCtrl in Shift) then
begin
  lb.Left := Memo.CaretPos.x;
  lb.Top := Memo.CaretPos.y + lb.height;
  lb.Visible := True;
  lb.SetFocus;
end;

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

Простой шаблон для Delphi DLL.

Пример DLL Без модулей

Во-первых, "скелет" DLL, которую вы хотели, сохраняете как DLLFRAME.DPR:

{---------------------DLLFRAME.DPR--------------------------}
library Dllframe;
uses WinTypes;
function  GetString : string ; export ;
begin
Result := 'Привет из DLL!' ;
end;
exports
GetString;
begin
end.
{-----------------------------------------------------------}

Теперь напишем вызывающую программу и сохраним ее как DLLCALL.DPR:

{---------------------DLLCALL.DPR---------------------------}
program Dllcall;
uses
Dialogs;
{$R *.RES}
function GetString : string ; far ; external 'DLLFRAME' ;
begin
MessageDlg( GetString, mtInformation, [ mbOK ], 0 ) ;
end.
{-----------------------------------------------------------}
С модулями

Код вызывающей программы, сохраните ее как DLLCALL.DPR:

{---------------------DLLCALL.DPR---------------------------}
program Dllcall;
uses
Dialogs;
{$R *.RES}
function GetString : string ; far ; external 'MyDLL' ;
begin
MessageDlg( GetString, mtInformation, [ mbOK ], 0 ) ;
end.
{-----------------------------------------------------------}

"скелет" DLL, которую вы хотели, сохраняете как DLLFRAME.DPR:

{---------------------DLLFRAME.DPR--------------------------}
library Dllframe;
uses DLLUnit;
exports
GetString;
begin
end.
{-----------------------------------------------------------}

Модуль, который мы сохраняем как dllunit.pas:

{---------------------dllunit.pas--------------------------}
unit DLLUnit;
interface
uses WinTypes;
function GetString: string; export;
implementation
function GetString: string;
begin
GetString := 'Привет из DLL!' ;
end ;
begin
end. 
{-----------------------------------------------------------}

Delphi for DotNet – первый взгляд.

Оформил: DeeCo

Автор: Михаил Полюдов The RSDN Group Источник: RSDN Magazine #2
Содержание. Комплект поставки. Основные цели Borland при разработке данного продукта. Что нового в Delphi .NET Что такое .NET Изменения языка в Delphi.NET. Приложение Delphi.NET Программа №1. “Hello .NET!”. Что дальше Программа №2. Нам нужны окна. Итак, проблемы обрисованы - попробуем написать код Программа №3. Добавляем на форму кнопку. Резюме

Введение.

Такое событие, как выпуск новой, седьмой, версии Delphi, не прошло незамеченным для большинства разработчиков. Примечательно, что в поставку этого программного продукта включен дистрибутив Delphi .NET Developer Preview. Вот об этом новом продукте (вернее – его «примерочной» версии) и пойдет речь в данной статье.

Прежде всего стоит остановиться на комплекте поставки нового программного продукта.

Комплект поставки.

В поставку входят:

два компилятора командной строки dccil.exe и dccasp.exe. Реально это один и тотже исполняемый файл переименованный и скопированный в другую директорию. модули импорта стандартных сборок CLR. небольшое количество документации в формате HTML. лицензия и рекомендации по распространению. В рекомендациях по распространению написано только то, что оно запрещено :).

К счастью, на Borland Developer Network (http://bdn.borland.com) имеется открытое дополнение, которое позволяет использовать компилятор Delphi .NET из IDE Delphi 7. Это дает возможность работать с привычным интерфейсом и подсветкой синтаксиса (Правда, не весь синтаксис подсветится правильно, а уж о работе Code Completion и речи быть не может. Почему - будет понятно позже).

Основные цели Borland при разработке данного продукта.

Можно предположить, что при разработке прототипа Borland стремился:

Создать CLR-совместимый компилятор языка Object Pascal. Обеспечить возможность использования собственных библиотек в новом окружении с минимально необходимыми изменениями со стороны конечного разработчика :). Добиться (хотя бы ограниченной) совместимости нового компилятора с unmanaged-версиями Delphi на уровне исходных текстов. Некоторые ограничения неизбежны в силу наличия таковых ограничений в самой платформе .NET.

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

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

Некоторую проблему для Borland представляет наличие встроенной в платформу .Net библиотеки Windows.Forms. Эта библиотека содержит функциональность, аналогичную достаточно большому сегменту библиотеки VCL, но не является ее полным аналогом. Borland должен обеспечить возможность компиляции старого кода с использованием Windows.Forms. Сейчас еще трудно оценить, насколько выполнимы обещания Borland. Но пока что все выглядит довольно оптимистично.

Добиться совместимости с VCL и при этом не сдублировать код Windows.Forms – крайне сложная задача. Однако именно ее и пытается решить Borland. В качестве решения проблемы Borland планирует использовать новую (для него) возможность – helper-классы, речь о которых пойдет ниже.

Другая проблема возникает там, где используется так называемый небезопасный код. Такие участки кода придется переписывать при переходе под новую версию компилятора. В принципе, MSIL может выдержать всё, что угодно, так как по сути является платформно-независимым ассемблером. Но Microsoft ввел такое понятие, как "безопасный" код. Во многих случаях применение небезопасного кода не допускается средой исполнения. В основном это связано с защитой. Так, при скачивании кода из Интернет происходит его верификация, и, если код содержит небезопасные фрагменты, или использует небезопасные с точки зрения системы защиты вызовы методов, он просто не допускается к исполнению. Borland, по крайней мере пока, декларирует, что Delphi.Net будет компилировать исключительно безопасный код. В компилятор Delphi 7 уже встроено предупреждение о небезопасных (unsafe) участках кода.

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

Вот так писать в Delphi будет нельзя:

begin
  Stream.Write(IntArr[0], 4 * ArrLen);
end;
begin
  Stream.Read(IntArr[0], sizeof(integer) * ArrLen);
end;

Для работы с теми же массивами в .Net Framework предназначены специальные методы, позволяющие сериализовать содержимое массива как единый объект. Необходимость в unsafe-коде практически исчезает, хотя иногда это приводит к снижению эффективности кода.

Что нового в Delphi .NET Что такое .NET

О платформе .NET, а точнее - о CLR (Common Language Runtime) есть очень хорошая статья Владислава Чистякова, опубликованная на компакт-диске к предыдущему номеру нашего журнала. В других статьях на www.rsdn.ru можно найти материалы по другим аспектам .Net.

В этой статье я не буду подробно рассматривать платформу .NET. Здесь будет рассмотрено несколько вопросов, интересующих Delphi -программистов, рассматривающих вопрос о переходе на новую платформу.

Изменения языка в Delphi.NET

Как уже говорилось, главная задача Borland – это поддержка платформы .Net, и, одновременно, сохранение совместимости с уже существующим кодом, написанным в Delphi.

Unsafe code (небезопасный код).

Borland добавил в Delphi 7 три новых предупреждения о небезопасных типах, небезопасном коде и небезопасных преобразованиях типов.

Типы данных, не поддерживаемые в Delphi.Net: PChar; нетипизированные указатели; нетипизированные var и out-параметры; file of <тип>; real48; вариантные записи (записи, содержащие перекрывающиеся поля). Небезопасный код: переменные по абсолютным адресам; процедуры Addr(), Ptr(), Hi(), Lo(), Swap(); процедуры BlockRead(), и BlockWrite(); функция Fail(); процедуры GetMem(), FreeMem(), ReallocMem(). Небезопасные преобразования типов: преобразование объекта в тип, не являющийся потомком от изначального класса объекта; преобразование записей.

При нарушении этих ограничений компилятор Delphi 7 с настройками по умолчанию выдаст предупреждение, а компилятор Delphi.NET – ошибку.

Кроме вышеперечисленных небезопасных конструкций, Delphi запрещает использовать встроенный ассемблер (оператор asm), конструкцию ExitProc и некоторые из конструкций для поддержки COM/OLE/ActiveX технологий:

Расширенные идентификаторы

В CLR имеются идентификаторы (типы, классы), которые совпадают по написанию с ключевыми словами Delphi, например: type, object. Для работы с такими идентификаторами нужно использовать полное именование, т.е. System.object, System.type и т.д. Borland декларирует в своей документации, что вместо полного именования можно использовать имя с префиксом & (амперсанд). Есть только одно "но": данная версия компилятора ругается на неправильный символ в исходных текстах :( .

Расширения и элементы, находящиеся в разработке.

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

"запечатаные" (sealed) классы. Определено новое ключевое слово Delphi, "sealed". Атрибут "sealed" применяется к классу для указания того, что класс не может быть расширен где-либо в CLR (ни на Delphi, ни на любом другом языке программирования); завершенные (final) методы. Еще одно новое ключевое слово "final". Данный атрибут применяется к методам для указания того, что они не могут быть перекрыты в потомках; UTF-8 Unicode символы в исходном коде; вложенные типы (т.е. описание типа внутри другого); невиртуальные методы в записях; статические (class, static) данные класса; статические (class, static) свойства класса; статические (class, static) методы; виртуальные методы в helper-классах; широковещательные (multicast) события (event). На текущий момент планируется использовать оператор ':=' для замены последнего присвоенного события, не влияя на обработчики событий, назначенные из управляемого кода других средств разработки. Присвоение значения nil будет вызывать удаление последнего присвоенного обработчика событий; Variant будет поддерживаться как TObject, а не как TVarData (реализация внутренних механизмов работы с Variant возлагается на CLR); Автоматическое упаковка (boxing) value-типов (простых типов, таких, как Integer, и записей) в CLR объекты. Пространства имен (Namespaces).

В Delphi .NET юнит остается основным контейнером для типов. CLR предоставляет еще один слой организации – пространство имен (namespace). В .Net Framework пространство имен – это концептуально контейнер типов. В Delphi for .Net пространство имен - это контейнер юнитов Delphi. Появление пространств имен дает Delphi возможность использовать и расширять классы .Net.

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

Например, класс MyClass из пространства имен MyNameSpace отличается от класса MyClass из YourNamespace. Во время исполнения CLR всегда обращается к классам и типам по их полным именам: имя сборки, за которым следует название пространства имен, содержащего тип.

В Delphi.NET файл проекта (программы, библиотеки или раскаgе'а) неявно вводит собственное пространство имен, называемое пространством имен проекта по умолчанию. Юнит может как входить в это пространство имен, так и явно объявить себя членом другого пространства имен. В любом случае юнит объявляет свою принадлежность к тому или иному пространству имен в выражении Unit. Например, вот так явно объявляется пространство имен:

unit MyCompany.MyWidgets.MyUnit;

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

Во-вторых, заметьте, что точки выражают концепцию вкладывания одних пространств имен в другие. Вышеприведенный пример говорит, что юнит MyUnit – это член пространства имен MyWidgets, которое, в свою очередь, содержится в пространстве имен MyCompany.

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

Пространство имен данного юнита (если таковое существует). Пространство имен проекта по умолчанию. Пространство имен, указанное в выражении проекта Namespaces. Пространства имен, указанные в настройках компилятора Приложение Delphi.NET

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

Программа №1. “Hello .NET!”.

Данная программа – традиционный пример, используемый со времен Кернигана и Ричи. Эта программа будет всего лишь выводить на консоль строку "Hello, Delphi.NET!".

Итак, приступаем к созданию первого проекта на Delphi .NET.

ПРИМЕЧАНИЕ Здесь и далее я буду пользоваться оболочкой Delphi для набора, исправления, компиляции и запуска приложений, за исключением отдельно оговоренных случаев.

Итак, создаем новый проект консольного приложения (File-New-Other-Console Application), сохраняем его как HelloDelphiNet.dpr.

Напишем следующее:

program HelloDelphiNet;
{$APPTYPE CONSOLE}
begin
  WriteLn('Hello, Delphi.NET!');
end.

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

dccil HelloDelphiNet.dpr

Либо выбрав пункт меню Delphi с установленным .NET расширением Delphi for .NET->Compile (или Build).

После компиляции запускаем готовый EXE-файл из командной строки (иначе мы просто не увидим результатов его работы) и видим следующее:

C: \...\&gt;
HelloDelphiNet
Hello, Delphi.NET!
С: \...\&gt;
_

Что дальше

Что такое программирование в Windows без использования окон? Ну, вообще-то, это вполне нормально, есть достаточно много разнообразных программ, не использующих окон. Но Delphi не была бы Delphi без своих GUI-возможностей.

ПРИМЕЧАНИЕ Не стоит удивляться тому, что в следующих примерах все элементы управления и формы создаются в коде, а не через модификацию/загрузку ресурсов. Это является обычным поведением для .NET-ориентированных языков. Дело в том, что одной из новинок, появившихся в .NET Framework, стала возможность сериализации в код. Тот же дизайнер форм Visual Studio сохраняет состояние формы и элементов управления в код программы. Это позволяет ускорить загрузку формы, а также вручную модифицировать код, сгенерированный дизайнером форм. Delphi поддерживает другую концепцию – сериализацию в собственный формат. Пока не ясно, как будут сериализоваться формы в Delphi.NET. Ясно одно – создать CodeDOM-провайдер, позволяющий использовать Delphi внутри VS.NET, будет несложно.
Программа №2. Нам нужны окна.

Borland пока не включил в Delphi.NET свою библиотеку VCL (но планирует сделать это, что явственно следует из документации). Основных причин, пожалуй, две:

сжатые сроки выпуска компилятора .NET, при продолжении развития основной линейки Delphi; сложность реализации совместимости между VCL и Windows Forms.

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

Проблема №1. Множественные (multicast) обработчики событий.

В .Net все события компонентов множественные. Что это значит?

Проиллюстрирую примером на псевдокоде (псевдокод потому, что сама концепция есть не только в C# или там VB, а в CLR вообще):

Есть объект кнопка.

Прописываем в ее событие Нажато первый обработчик:

кнопка.Нажато := ПервыйОбработчик;

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

кнопка.Нажато := ПервыйОбработчик;
кнопка.Нажато := ВторойОбработчик;

В обычной Delphi вторая строка кода заменит первый обработчик вторым, а в CLR должна добавить второй обработчик в очередь за первым (правда, в Borland еще не до конца решили, так это будет реализовано или не так, но об этом уже писалось выше).

В C# данная концепция реализована так:

Button.Click + = EventHandler(MyEventHandler);

Этот код, требует некоторых пояснений. Итак: EventHandler – это так называемый делегат – специальный класс из сборки System, позволяющий ссылаться на метод некоторого класса. У данного класса есть (если верить MSDN) следующие конструкторы:

EventHandler(method)

И.

EventHandler(object, method)

Второй формат недоступен из C#, а первый – из Delphi. Самое странное, что в Delphi можно и нужно передавать не метод, а обычную процедуру, причем в качестве объекта может передаваться значение nil.

Проблема №2. Совместимость CLR и VCL.

Эта проблема просто глобальна, хотя на первый взгляд так не кажется.

Для того, чтобы создавать эффективные и небольшие приложения для CLR, нужно, чтобы VCL максимально использовала CLR. Если не реализовать VCL-обертку для CLR, код, который уже существует для VCL, под CLR придется переделывать очень долго. Borland придумал очень интересный выход: для каждого класса компонента CLR создается так называемый helper-класс, который расширяет функциональность класса CLR.

Детали этой технологии в Borland до конца еще не разработали, а вся информация почерпнута все из той же документации к Delphi.NET.

Итак, helper-класс – это класс, содержащий дополнительные методы, расширяющие функциональность основного класса. Использование helper-класса – это не форма наследования, а способ обеспечения расширения функциональности базового класса. Синтаксис объявления helper-класса таков:

ClassHelper = class helper[(ancestor list)]for type
  - id
end;

Обычно перед helper-классом идет объявление типа:

type
  TMyClass = Some.VerySimilar.class;

А за ним уже собственно объявление класса.

// Вводим helper-класс и объявляем функцию, ожидаемую
// существующим Delphi-кодом.
TMyClassHelper = class helper for TMyClass
function ExistingDelphiFunc: Integer;
end;

Теперь существующий Delphi-код, ожидающий наличия у типа TMyClass функции ExistingDelphiFunc, будет работать как ни в чем не бывало.

Хорошим примером может служить класс System.Object, являющийся предком для всех .NET классов. В Delphi ему соответствует класс TObject. В Delphi for .Net TObject не наследуется от System.Object. Вместо этого он объявлен helper-классом для System.Object. Это позволяет использовать такие отсутствующие в System.Object методы, как ClassName и ClassType. Компилятор, не найдя реализаций Class-Name и ClassType в классе System.Object, обратится к helper-классу и возьмет их оттуда. Таким образом, helper-классы позволяют использовать в .Net возможности, уже реализованные в VCL.

Итак, проблемы обрисованы - попробуем написать код

Первый пример - просто окно с надписью.

Создаем новый проект Delphi .NET, TestForm1.dpr. Вот его начальный код:

program TestForm1;
begin
end.

Теперь заявим наше намерение использовать сборку System.Windows.Forms (В данной сборке содержатся стандартные элементы управления):

program TestForm1;
uses System.Windows.Forms;

Теперь объявим глобальную переменную – форму:

var
  OurForm: System.Windows.Forms.Form;

В коде создадим форму.

begin
  OurForm := System.Windows.Forms.Form.Create;
  System.Windows.Forms.Application.Run(Frm);
end.

Компилируем и запускаем.

На экране мы видим простую формочку без заголовка.

Попробуем добавить заголовок и переместить форму на центр экрана:

OurForm := System.Windows.Forms.Form.Create;
OurForm.Text := 'TestForm1';
StartPosition := FormStartPosition.CenterScreen;

Получилось! Пора переходить к более сложным вещам, например к добавлению неких компонентов на форму.

Полный исходный текст TestForm1.dpr:

program TestForm1;
uses
  System.Windows.Forms;
var
  OurForm: System.Windows.Forms.Form;
begin
  OurForm := System.Windows.Forms.Form.Create;
  OurForm.Text := 'TestForm1';
  StartPosition := FormStartPosition.CenterScreen;
  System.Windows.Forms.Application.Run(OurForm);
end.

Программа №3. Добавляем на форму кнопку.

Новый проект назовем TestForm2.dpr, за основу берем TestForm1.dpr.

Для начала описываем новый класс:

uses
  System.Windows.Forms;
type
  OurFormClass = class(System.Windows.Forms.Form)
  end;

Изменяем объявление переменной и конструирование формы:

var
  OurForm: OurFormClass;
begin
  OurForm := OurFormClass.Create;

Теперь создадим конструктор:

OurFormClass = class(System.Windows.Forms.Form)
public
  constructor Create;
end;
constructor OurFormClass.Create;
begin
  inherited;
end;

ПРЕДУПРЕЖДЕНИЕ Внимание! ОБЯЗАТЕЛЬНО нужно выполнять вызов inherited конструктора!!!

Компилируем и запускаем - все осталось как и было, то есть все работает, но форма уже нашего класса.

Пишем тело конструктора:

constructor OurFormClass.Create;
begin
  inherited;
  StartPosition := FormStartPosition.CenterScreen;
  Text := 'Центрованная форма';
end;

Запускаем – все в порядке. Но пустая форма нам неинтересна – попробуем создать на ней кнопку:

OurFormClass = class(System.Windows.Forms.Form)
public
  btnClose: System.Windows.Forms.Button;
  constructor Create;
end;
constructor OurFormClass.Create;
begin
  inherited;
  StartPosition := FormStartPosition.CenterScreen;
  Text := 'Центрованная форма';
  btnClose := System.Windows.Forms.Button.Create;
  btnClose.Left := 8;
  btnClose.Top := 8;
  btnClose.Width := 81;
  btnClose.Height := 25;
  btnClose.Text := 'Закрыть';
  Controls.Add(btnClose);
end;

Итак, у нас есть кнопка с надписью "закрыть" в левом верхнем углу формы, но сделать с ее помощью ничего пока нельзя. Пора начинать решать первую проблему Borland – обработчики событий. Пока что подключение и использование обработчиков событий в Delphi.NET до конца не реализовано. Поэтому приходится прибегать к некоторым ухищрениям, а именно – использовать в качестве обработчика событий процедуру, не являющуюся членом класса формы.

Из MSDN узнаем, что спецификация обработчика должна быть такой:

void EventHandler(object sender, EventArgs e);
На Delphi это будет выглядеть так:
  
procedure EventHandler(Sender: System.object; e: EventArgs);

Создаем обработчик:

btnClose: System.Windows.Forms.Button;
constructor Create;
end;
procedure btnCloseClick(Sender: System.object;
  e: EventArgs); forward;
//...
var
  OurForm: OurFormClass;
procedure btnCloseClick(Sender: System.object;
  e: EventArgs);
begin
  OurForm.Close;
end;

А в конструкторе вставляем следующую конструкцию:

btnClose.Text := 'Закрыть';
btnClose.Click := EventHandler.Create(Self, NativeInt(@btnCloseClick));
Controls.Add(btnClose);

Компилируем, запускаем – работает. Единственное НО - использование глобальной переменной. Но эту ошибку, похоже, получится исправить только в следующей (нормальной?) версии компилятора.

Резюме

Итак, резюмируя:

компилятор есть, поддерживает как минимум часть стандартных сборок .NET Framework; имеется, в принципе, среда написания кода, компиляции (с позиционированием ошибок) и запуска отладчика (с перекомпиляцией в DEBUG); существует возможность писать как консольные, так и оконные приложения; нет пока что портированной библиотеки VCL; есть некоторые несоответствия между прилагаемой документацией и компилятором.

Таким образом, вполне нормально для Developer Preview, но не достаточно для качественной среды разработки от Borland. Ждем следующих версий.

Обработка почты с помощью Delphi.

Оформил: DeeCo

Автор: Анатолий Орлов.

Часто бывает необходимо обработать почтовые сообщения от пользователей. Например, занести заказчика товара в базу данных или высказывания клиентов о вашей фирме (своеобразная книга жалоб и предложений). Это можно сделать вручную, читать каждое сообщение, копировать, перезаписывать, … Но если к вам ежедневно обращаются сотни или даже тысячи клиентов? Нужно будет нанять нового сотрудника или самостоятельно просматривать всю почту. Конечно, можно написать скрипт на Perl’е, который будет обрабатывать данные, введенные пользователем через форму на вашем сайте. Однако если у вас нет собственной странички или вы не хотите хранить конфиденциальную информацию в базе данных на сервере – нужно приложение под Windows, которое будет находиться на локальном компьютере и заходя в Интернет обрабатывать почту. После этого вы можете делать с содержимым письма что угодно: сохранить его, переслать на другой почтовый ящик, занести в базу, … Также можно создать почтовый фильтр, который будет группировать письма по темам. Так можно избавиться от ненужного спама. Например, если вы на почтовый ящик должны получать только резюме от соискателей работы, можно удалять все письма, тема которых отлична от “резюме” или “работа”. Мне приходилось разрабатывать программу, которая получает данные на e-mail, заполненные пользователем через форму на сервере. После этого она анализирует эти данные и заносит их в базу на локальном компьютере. Анализ происходит по заголовку и имени поля формы. Если заголовок письма совпадает с заголовком, записанном в настроиках программы, то после этого осуществляется разбор письма. Письмо после заполнения формы имело следующую структуру: имя поля1 формы=значение1 имя поля2 формы=значение2 ………………………………. имя поляN формы=значениеN Приложение обрабатывало значения полей и записывало их в базу данных ACCESS, где поля имели те же имена, что и у формы. Таким образом данные хранились на компьютере в офисе и были скрыты от Интернет – хакеров. Такое приложение можно написать практически на любом, имеющимся у вас языке. Я предлагаю вам рассмотреть такое приложение на Delphi и обсудить принципы работы программы. Для работы с протоколами Internet в Delphi на палитре компонентов предназначен раздел FastNet. FastNet Статьи по Delphi (005)… Для приема почты нам понадобится протокол POP3 (Post Office Protocol – Version3) для этого протокола зарезервирован 110 порт, соответствующий ему компонент в разделе FastNet – NMPOP3. С помощью NMPOP3 можно: соединиться с сервером, послать аунтификационную информацию на сервер, получить информацию о почтовом ящике (колличество писем, размер), получить письма с сервера, удалить письма. Принцип общения с сервером по протоколу POP3 состоит из трех этапов: установления соединения (аунтификация), чтения списка писем и выбор нужного письма, прекращение соединения. Работу с сервером по протоколу необходимо знать, так как он предоставляет широкие возможности не только для программистов, но и для пользователей. Мне много раз приходили письма неимоверного размера, с большим колличеством картинок. После десяти минут работы Outlook Express просто зависал. Приходилось удалять почту, ведя диалог с сервером по POP3, присоединившись к нему через telnet к 110 порту. Рассмотрим диалог с сервером по протоколу POP3. После подключения к почтовому серверу, выдается строка приветствия +OK POP3 server ready. После этого пользователь посылает свой логин, командой USER login и пароль PASS password. Если логин и пароль введены правильно, можно начать просматривать почту. Командой LIST, можно посмотреть сколько писем к вам пришло и размер писем. Ответ Сервера выглядит следующим образом: User: LIST Server: +OK 2 messages (400 octets) Server: 1 250 Server: 2 150 Данное сообщение гласит о том, что на сервере находится два сообщения объемом в 400 байт, первое сообщение объемом 250 байт, второе –150 байт. Если необходимо посмотреть размер конкретного письма, например пятого необходимо послать серверу команду LIST 5. В результате на терминале появится сообщение Server: +OK 2 200. В том случае, если письмо с таким номером не существует высветится ошибка Server: -ERR no such message, only 2 messages in maildrop. Эта ошибка говорит о том, что на сервере находиятся только два письма. Чтобы получить письмо с сервера используют команду RETR m, где m – номер письма. Если письмо слишком длинное, можно прочитать его не полностью, а только первые n – строчек этого письма, это делается командой TOP msg n. Для удаления письма служит команда DEL m. Если письмо удалилось, появится сообщение +OK message 1 deleted, если не существует письма с таким номеров сервер выдасть ошибку -ERR no such message. Закончить обработку почты и выйти из диалога можно командой QUIT. Подробнее протокол описан в документе RFC1081 и RFC 1460. Для нашего примера вам понадобиться почтовый ящик, получить его в Интернете не составит особой сложности. Возьмем, например, бесплатный почтовый ящик на сервере www.rambler.ru .Сервер исходящий почты на Рамблере mail.rambler.ru (вы легко его можете узнать у администратора сервера или на самом сервере), он нам понадобится для соединения по протоколу POP3. Итак, начнем. Создадим новое приложение. Добавим в него компонент NMPOP3. По умолчанию он получит в нашем приложение имя NMPOP31, в этом можно убедиться, просмотрев Object Inspector. Это имя мы будем использовать в нашем примере. Вы можете его переименовать для удобства обращения с ним, например назвать его POCHTA. Для соединения с сервером, нам надо вызвать процедуру Connect компонента NMPOP3. Но перед этим мы установим имя сервера, логин и пароль пользователя для аунтификации по протоколу POP3. Сервер задается свойством NMPOP31.Host, логин свойством NMPOP31.UserID, пароль свойством NMPOP31.Password. Имя пользователя и пароль выдается администратором сервера после регистрации. В локальной сети, обычно, вся почта приходит на сервер и только после обращения к нему перенаправляется на рабочие места. Поэтому здесь надо задавать имя сервера (proxy – сервера), который забирает почту и вашу учетную запись (логин и пароль). Статьи по Delphi (005)… После этого можно вызвать метод NMPOP31.Connect. Мы можем посмотреть количество писем на сервере, обратившись к свойству NMPOP31.MailCount. Получим какое – нибудь письмо с сервера, например под номером 1. Для этого вызовем процедуру NMPOP31.GetMailMessage(1). После этого объект TmailMessage содержит информацию о письме. Теперь мы можем проанализировать наше письмо: посмотреть тему письма (NMPOP31.MailMessage.Subject) , почтовый адрес отправителя (NMPOP31.MailMessage.From), прочитать письмо (NMPOP31.MailMessage.Body). Также нам доступны некоторые другие свойства сообщения. С помощью свойства NMPOP31.MailMessage.MessageId, которое имеет тип string просматривается идентификатор письма. Список файлов вложения доступен через свойство NMPOP31.MailMessage.Attachment, это свойства типа TStringList. После прочтения письма нужно разъединиться с сервером, это делается процедурой NMPOP31.Disconnect. Давайте посмотрим, как это все выглядит на нашем примере. Добавим на форму компоненты TEdit, TMemo и TButton. Они получат имена, соответсвенно Edit1, Memo1, Button1. После нажатия кнопки “Получить письмо” в поле Edit1 появится тема первого письма, если на сервере есть письма, а вполе Memo1 – тело письма. Статьи по Delphi (005)… Для этого нам надо запрограммировать кнопку Button1 по следующему алгоритму: procedure TForm1.Button1Click(Sender: TObject); begin NMPOP31.Connect; NMPOP31.GetMailMessage(1); Edit1.Text:=NMPOP31.MailMessage.Subject; Memo1.Lines:=NMPOP31.MailMessage.Body; NMPOP31.Disconnect; end; После прочтения письма, может возникнуть необходимость очистить ящик от него, чтобы было больше мест для последущих посланий. Это легко сделать, используя процедуру DeleteMailMessage(Number: intger), где Number – это номер письма. Процедура помечает письмо, как не нужное и после разъединения с сервером оно удаляется. Будте осторожнее с этой функцией, после удаления письма, вы его больше не востановите. Для снятия пометки, если вы вдруг передумали удалять писмо, служит метод NMPOP31.Reset. Программу можно модернизировать, создать так называемого “робота”. Установить на форму компонент Taimer, выставить время срабатывания через каждые n минут и связать его с событием нажатия на кнопку. И наша программа, работая в фоне, самостоятельно будет каждые n минуть проверять почту, регистрировать все изменения. Вот мы и написали программку для обработки почты. Как вы видете, с помощью Delphi это легко и просто. В разделе FastNet находятся еще много компонентов для работы с протоколами Интернет. Для отправки почты можно использовать компонент NMSMTP, он работает с протоколом SMTP, который подробно расписан в документе RFC 821. С помощью компонента NMSMTP можно организавать свою почтовую рассылку по нескольким адресам, так называемую спам – рекламу. Также можно организавать redirect почты (перенаправление на другой почтовый ящик). Диалог пользователя с сервером по протоколу SMTP также прост, как и по POP3. После соединения с сервером к 25 TCP порту, клиент посылает команду HELO свое_имя. После чего сервер выдает строку с кодом 250, которая говорит о готовности сервера для работы с вами. Далее мы должны послать команду mail from:, где email – это ваш обратный e-mail адрес. На что сервер опять ответит 250 OK. Далее набираем команду для задания адреса получателя письма rcpt to:email (email – адрес получателя). После этого, набрав команду data можно приступить к вводу заголовка письма и после пустой строки его тела, которое должно заканчиваться двумя символами перевода строки. Тело письма может состоять из следующих полей: From: “Test” – адрес отправителя To: “new” – адрес получателя Subject: Проверка – тема письма Content-Type: text/html – тип письма И другие поля, о которых подробнее можно прочитать в документации RFC 822. С учетом вышеизложенного можно рассмотреть диалог сервера с пользователем после ввода команды data: User: From: “Test” User: To: “new” User: Subject: Проверка User: User: Привет! Это тестовое письмо User: Пока! В конце ссесии мы должны набрать команду quit. Если вы хотите программировать для Интернет не только почтовые приложения, то вам помогут компоненты из раздела FastNet, NMFTP – для работы по популярному протоколу FTP (File Transfer Protocol – протокол передачи файлов). Он документирован в RFC 959. С помощью FTP можно закачивать информацию, веб – странички на сервер, а также скачивать программное обеспечение, документация с бесплатных FTP серверов. Компонент TNMFinger предназначен для реализации протокола Finger, который представляет собой адресную книгу в Интернете. По нему вы можете узнать информация об интересующем вас сервере. И конечно же не остался в стороне наиболее используемый протокол в сети Интернет – HTTP (HyperText Transfer Protocol). Протокол для передачи гипер – текста. Его мы ежедневно используем для просмотра страничик во Всемирной паутине WWW. Также стоит уделить внимание разделам Internet Express и Internet в палитре компонентов. В этих разделах находится удобный мини – браузер для просмотра html – страничек, компонент для разбора html – файлов и другие полезные вещи.

Исключения в Delphi.

Семь бед - один Reset

100-149 - ошибки ввода/вывода (I/O), 200-255 - фатальные ошибки согласно файлам помощи Delphi.

В Windows.pas всем кодам ошибок внешних исключительных ситуаций присвоены имена. Поищите, например.

STATUS_BREAKPOINT
чтобы найти декларации констант ошибок. Ошибки времени выполнения, расположенные в таблице с номерами ниже 100 (и несколько ошибок с номерами больше 100, но они незначительны) - ошибки DOS.

По этой таблице можно сразу определить причину возникновения ошибки, если ваш exe-файл не подружился с операционной системой.

Код (Hex) Код (Dec) Описание
Код и описание ошибок, возникающий в среде DOS
00h 0 нет ошибки
01h 1 неверный номер функции
02h 2 файл не найден
03h 3 путь не найден
04h 4 слишком много открытых файлов (нет свободных дескрипторов)
05h 5 доступ запрещен
06h 6 неверный дескриптор
07h 7 управляющий блок памяти разрушен
08h 8 недостаточно памяти
09h 9 неверный адрес блока памяти
0Ah 10 неверное окружение (обычно при длине > 32К)
0Bh 11 неверный формат
0Ch 12 неверный код доступа
0Dh 13 неверные данные
0Eh 14 зарезервировано
0Fh 15 неверное устройство (drive)
10h 16 попытка удаления текущей директории
11h 17 не то же устройство
12h 18 нет больше файлов
--- DOS 3.0+ ---
13h 19 диск имеет защиту от записи
14h 20 неизвестное устройство
15h 21 устройство не готово
16h 22 неизвестная команда
17h 23 ошибка данных (CRC)
18h 24 неправильный запрос длины структуры
19h 25 ошибка поиска
1Ah 26 неизвестный тип носителя (не-DOS диск)
1Bh 27 сектор не найден
1Ch 28 принтер без бумаги
1Dh 29 ошибка записи
1Eh 30 ошибка чтения
1Fh 31 общая ошибка (general failure)
20h 32 нарушение доступа (sharing violation)
21h 33 нарушение доступа (lock violation)
22h 34 ошибка смены диска (ES:DI -> media ID диска) (смотри #0981)
23h 35 FCB недоступно
24h 36 переполнение буфера общего доступа (sharing buffer)
25h 37 (DOS 4.0+) несовпадение кодовой страницы
26h 38 (DOS 4.0+) невозможно завершить действие с файлом (чтение или запись)
27h 39 (DOS 4.0+) недостаточно места на диске
28h-31h зарезервировано
32h 50 сетевой запрос не поддерживается
33h 51 удаленный компьютер не откликается
34h 52 дублирование сетевого имени
35h 53 сетевое имя не найдено
36h 54 сеть занята
37h 55 сетевое устройство больше не существует
38h 56 превышен лимит команд сетевого BIOS
39h 57 аппаратная ошибка сетевого адаптера
3Ah 58 из сети получен неверный ответ
3Bh 59 неожиданная сетевая ошибка
3Ch 60 несовместимый сетевой адаптер
3Dh 61 полная очередь печати
3Eh 62 очередь не полная
3Fh 63 нет свободного места для печати файла
40h 64 сетевое имя было удалено
41h 65 сеть: в доступе отказано
42h 66 неверный тип сетевого устройства
43h 67 сетевое имя не найдено
44h 68 превышен лимит сетевого имени
45h 69 превышен лимит сеансов сетевого BIOS
46h 70 временная пауза
47h 71 сетевой запрос не принят
48h 72 сетевая печать/дисковая переадресация приостановлена
49h 73 программная поддержка сети не установлена (LANtastic) неверная сетевая версия
4Ah 74 неожиданный отказ сетевого адаптера (LANtastic) истек бюджет пользователя (account)
4Bh 75 (LANtastic) истек пароль
4Сh 76 (LANtastic) на этот раз неудачная попытка входа в сеть
4Dh 77 (LANtastic v3+) не хватает дискового пространства на сетевом узле
4Eh 78 (LANtastic v3+) нет регистрации на сетевом узле
4Fh 79 зарезервировано
50h 80 файл существует
51h 81 зарезервировано
52h 82 невозможно создать каталог
53h 83 ошибка на INT 24h
54h 84 (DOS 3.3+) слишком много переадресаций
55h 85 (DOS 3.3+) двойная переадресация
56h 86 (DOS 3.3+) неверный пароль
57h 87 (DOS 3.3+) неверный параметр
58h 88 (DOS 3.3+) ошибка сетевой записи
59h 89 (DOS 4.0+) функция в сети не поддерживается
5Ah 90 (DOS 4.0+) не установлен необходимый системный компонент
64h 100 (MSCDEX) неизвестная ошибка
65h 101 (MSCDEX) нет готовности
66h 102 (MSCDEX) нехватка EMS памяти
67h 103 (MSCDEX) не High Sierra или ISO-9660 формат
68h 104 (MSCDEX) открыт лоток
B0h 176 (MS-DOS 7.0) носитель не блокирован
B1h 177 (MS-DOS 7.0) носитель блокирован
B2h 178 (MS-DOS 7.0) не сменный носитель
B4h 180 (MS-DOS 7.0) переполнение счетчика блокировок
B5h 181 (MS-DOS 7.0) неудача запроса на извлечение носителя

Delphi для качков.

Автор: Лозовский Александр Прислал: Матвеев Игорь

Статья из журнала Хакер за 04.2003

Любому человеку, мало-мальски знакомому с интернетом, известны такие программы-качалки, как GetRight, Reget и Flashget. Их расплодилось великое множество, все они занимают первые места в рейтингах и продаются за немалые деньги. FlashGet, например, постоянно требует от меня заплатить буржую $29.99 за дальнейшее использование его программы. Все это, конечно, понятно, хочется денег, да побольше... Но разве русский человек может заплатить такую сумму?:) Так что давай сегодня напишем свой Reget, и ты сможешь демонстрировать всем знакомым девушкам свою физиономию в about программы :).

Реквизит.

Он нам понадобится. Прошли те времена, когда все делалось в два диалога и одну строчку кода. Нам придется писать программу с использованием функций библиотеки Winlnet.dll и заголовочного файла, соответственно, Winlnet.Pas. Сразу пропиши его в uses, а то потом забудешь и начнешь тыкаться, искать свою ошибку. Так вот, давай для начала попробуем разобраться с самыми необходимыми функциями, а с остальными ты разберешься сам на msdn.microsoft.com (полный линк давать не буду, т.к. он ОЧЕНЬ большой). Посмотри там следующие функции: InternetDial, InternetGoOnline или InternetCrackUrl (думаю, эта функция тебя должна заинтересовать :)). Но вернемся к реальности. У нас на повестке дня следующие функции:

1) function lnternetOpen(lpszAgent: PChar; dwAccessType: DWORD; IpszProxyName, IpszProxyBypass: PChar; dwRags: DWORD): HINTERNET; stdcall;

Она открывает интернет-сессию для приложения. Вот какие у нее аргументы:

IpszAgent - имя программы. Серьезные люди пишут application.exename, а старики - ParamStr(O). На самом деле это не так важно, программа все равно будет работать. dwAccessType - способ соединения. Вот его типы: PRE_CONFIGJNTERNET_ACCESS - как в реестре. INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY - не юзать internet setup file. GATEWAY INTERNET ACCESS - через шлюз. CERN_PROXYJNTERNET_ACCESS - через прокси. IpszProxyName - имя прокси. IpszProxyBypass - кому не надо использовать проксю. dwFlags - режим работы. Если ставить INTERNET_FLAG_ASYNC, то будет асинхронный. В данном случае это только дополнительный напряг, поэтому ставь 0.

2) function lnternetOpenUrl(hlnet: HINTERNET; IpszUrl: PChar; IpszHeaders: PChar; dwHeadersLength: DWORD; dwFlags: DWORD; dwContext: DWORD): HINTERNET; stdcall;

Это функция открывает заданный УРЛ! :) Ее описание:

hinet - переменная типа HINTERNET. Ее значение возвращает функция InternetOpen. IpszUrl - собственно сам УРЛ. IpszHeaders - дополнительные строки в HTTP запросе. Нам они не нужны. dwHeadersLength - их длина. dwFlags - их тут больше 10 значений. Вот самое нужное: INTERNET_FLAG_EXISTING_CONNECT - не создавать для объекта нового соединения. dwContext - пиши 0.

3) function lnternetReadFile(hFile: HINTERNET; IpBuffer: Pointer; dwNumberOfBytesToRead: DWORD; var IpdwNumberOfBytesRead: DWORD): BOOL; stdcall;

InternetReadFile читает удаленный файл. Если ты знаком со старой доброй ReadFile (или JRead), то поймешь сам, а это для тех, кто не знает:

hFile - сюда ты подставляешь значение из предыдущей функции (можно и FtpOpenFile, если тебе это ближе). IpBuffer - буфер, через него мы будем читать файл. Как ты должен помнить, буфер- это массив. Таким образом, файл читается кусками, равными размеру этого массива, а у нас он объемом 1024 байта, т.е. один килобайт. dwNumberOfBytesToRead - какое количество байт необходимо прочесть. Он должен быть равен размеру нашего массива, т.е. 1024. IpdwNumberOfBytesRead - сколько же действительно байт прочитано. Если все отлично, то функция возвращает true, иначе - false.

4) function InternetSetRlePointerfhFile: HINTERNET; IDistanceToMove: Longint; pReserved: Pointer; dwMoveMethod, dwContext: DWORD): DWORD; stdcall; Для незнакомых с SetFilePointer поясню. Эта функция сдвигает позицию чтения файла на заданное число байт. Т.е. если тебе надо прочитать файл не с начала, а с отметки 1000 байт, то пользуйся InternetSetFilePointer. Вот ее параметры: hFile - этот параметр уже рассматривался. IDistanceToMove - на какое количество байт смещать указатель. pReserved - оставлено до лучших времен, а само значение должно быть равно нулю. dwMoveMethod - откуда делать смещение: FILE_BEGIN - с начала. FILE_END - с конца :). FILECURRENT - с текущей позиции. dwContext - должно быть нулем. Как ты уже догадался, эта функция и будет обеспечищгь нам докачку. Если конда прервется на отметке 1.2 Мб, то мы сможем вернуться на нужную нам позицию. При успешном возврате функция вернет значение в 1.2 Мб. Но учти, если сервак не поддерживает докачки, то файл придется читать с самого начала. 5) function InternetQueryDataAvailable(hFile: IpdwNumberOfBytesAvailable: DWORD; dwFlags, dwContext: DWORD): BOOL; stdcall; HINTERNET; Она выясняет объем доступных данных, т.е. размер запрашиваемого файла. Пояснения:

hFile - переменная типа HINTERNET. Уже рассматривалась выше. IpdwNumberOfBytesAvailable - доступные байты. dwFlags - ставь в 0. dwContext - здесь также установи 0.

6) function InternetCloseHandle(hlnet: HINTERNET): BOOL; stdcall;

В InternetCloseHandle нет ничего сложного. Эта функция просто закрывает интернет-сессию.

Все. С разбором функций мы закончили. Их тебе хватит для написания примитивного гетрайта :). А если ты ознакомишься с МSDN'овскими доками и поймешь )аботу потоков... Тогда я буду ждать 80% скидки на твой VasyaExtraGet за 9.99$ ). Так что закрывай журнальчик, попей пивка, и садись кодить. Главное, не убей правильное настроение. Если его пока нет, не расстраивайся, будем писать вместе :).

Интерфейс.

Кидай на форму два TEdit, четыре TLabel, SaveDialog и 4 Кнопки. Постарайся разложить это добро как на рис.1:

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

Первые три кнопки обзови (параметр "caption"): "Загрузить", "Отмена" и "Выход", а на четвертой поставь 3 точки. Lаbеl'ы будут называться так:

Label1: "Откуда качать?" Label2: "А куда сохранять?" Label3: "Размер файла:" Label4: "О".

В общем, постарайся соответствовать рисунку. На нем все предельно ясно, так что перейдем к самому процессу кодинга.

Кодинг.

Для начала добавь в раздел public объявление переменной NADO: boolean; (она нужна для прерывания загрузки), создай событие OnClick для 4-й кнопки и впиши туда такой код:

IF SaveDialogl.Execute then Edit2.Text := SaveDialogl.FileName;

Этот код добавлен, чтобы не вводить путь вручную. Теперь посмотри на код ниже. Попытайся понять содержимое этого листинга. Понял? Не понял? :) В общем, набей его в свой проект. Логика работы программы такая. Сначала мы проверяем наличие заданного файла. Если его нет, то качаем с нуля, если же он существует, то за начальную позицию для докачки берем размер локального файла и подставляем это значение в InternetSetFilePointer. Что мы и делаем. Затем циклически читаем по 1024 байта от интернет-файла, пока не скачаем его целиком. Это и будет конец загрузки. Хотя, на случай ручного прерывания, впиши в OnClick для 2-й кнопки такой код:

NADO := FALSE

Все остальное ясно и по комментариям, поэтому я протестирую эту программу и перейду к заключению.

Вот что нужно вписать в OnClick для кнопок:

procedure TForml .BitBtn1Click(Sender: TObject); 
var
  F: File;
  ResumePos, BufferLen, SumSize: DWORD;
  hSession, hURL: Hlnternet;
  Buffer: array[1..1024] of Byte;
  err: boolean;
begin
 SumSize := 0; ResumePos := 0;  //Инициализируемся
 AssignFile(F, Edit2.Text); //Свяжемся с файлом
 IF FileExists(Edit2.Text) then //Есть ли на диске этот файл
   begin
     Reset(f,1); //Ax, есть? Откроем!
     ResumePos := FileSize(F); //Откуда докачать
     Seek(F, FileSize(F)); //А писать будем в конец
   end else ReWrite(f,1); //А раз нет, так создадим
   NADO := TRUE; //Надо качать...
   //Открыли сессию
   hSession := lnternetOpen('X-Kachalka', PRE_CONFIG INTERNET_ACCESS, nil, nil, 0);
   //И наш УРЛ
   hURL := lnternetOpenURL(hSession,PChar(Edit1.Text),nil, O, 0, O);
   //Сколько там наш файл весит?
   lnternetQueryDataAvailable(hURL, SumSize, 0, 0);
   labe4.Caption := IntToStr(SumSize); //Сообщим об этом
   if ResumePos>0 then //Если докачиваем,
     begin
          lnternetSetFilePointer(hURL,ResumePos,nil,0,0); //То сместимся
     end;
   REPEAT //Качаем
     err:= lnternetReadFile(hURL, @Buffer,SizeOf(Buffer),BufferLen); //Читаем буфер
     IF err= false then //Ошибка чтения
       begin
         ShowMessage ('Произошел облом :('); //Сообщим и выходим
         exit;
       end;
     BlockWrite(f, Buffer, BufferLen); //Пишем в файл
     Application. Processmessages;
   UNTIL (BufferLen- 0) Or (NADO= FALSE); //Качаем, пока не все или надо
 ShowMessage ('Успешно загружено!');
end;

Пять минут - полет нормальный.

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

Заключение.

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

Загрузить исходники.

На этом все. Удачи тебе и до новых встреч в эфире.

Фреймы в Delphi.

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

Разведение программистов, к сожалению, связано с большими первоначальными затратами. Вам понадобятся: - персональный компьютер с процессором не ниже Реntiиm II; - стол и стул; - пепельница; - большое количество сарделек и пиво для прикармливания; - кофейная чашечка вместимостью 0,5 л.
'Frames' - на мой взгляд чрезвычайно полезная компонента. Если откинуть условности, то это форма в форме. Грубо говоря, можно наделать таких форм и менять, например, функциональность и вид Вашей программы в зависимости от определенных условий(Это не то-же, что менять кожу - не перепутайте.). И раз уж я назвал его формой, то отсюда и начальный шаг по его созданию должен быть как у формы, а не как у компоненты. Точнее даже два в одном. Вообщем так. Через меню 'File/New/Frame' создаем фрейм - появляется до боли знакомое окно форы, с разве что немного другими свойствами. Далее можно делать на нем все, что угодно. Однако, не забываем, что пока создан всего сам фрейм и он у нас ни к чему не привязан. И тут нужен второй шаг - теперь берем компоненту и ложем ее на форму Вашего основного проекта. Сразу появляется окно выбора фрейма- 'Select frame to insert'. Причем, если Вы наделали, несколько фреймов, то, соответственно, Вам их все и предложат. Итак, из всего вышесказанного можно подчеркнуть лишь одно - прежде чем пользоваться компонентой Frame нужно создать сам фрейм. Пошли дальше. Самый простой способ осуществить подмены фреймов(для изменения функций программы) это делать Visible тому фрейму который сейчас нужен. Вообще, конечно спорный вопрос - стоит ли наращивать размер программы для таких целей, но.... задачи бывают разные.

Еще одна полезность этой компонеты в том, что она позволяет организовать скролл(как вертикальный, так и горизонтальный) для целого набора инструментов. Т.е. если Вам необходимо разместить на форме очень много всего, а места не хватает, то фрейм - идеальное решение. Примером может служить настройка сортировщика писем в известной почтовой программе 'TheBat'.

Delphi и графика.

Если вам нужно просто вывести одно изображение, создайте объект TBitmap, "поиграйтесь" с ним, а когда изображение будет готово появиться на экране, вызовите функцию Image.Canvas.Draw(0, 0, Bitmap), которая скопирует и нарисует его на экране. Как вы наверное заметили, основное время занимает рисование картинки на экране, а не установка ее атрибутов. В результате мы устанавливаем цвета на невидимом объекте (TBitmap мы уже создали), и отображаем только полностью готовый Bitmap. Вот демонстрационный код для формы с единственным на ней компонентом Image:

procedure TForm1.FormPaint(Sender: TObject);
var
  TmpX, TmpY: Byte;
  MyImage: TBitmap;
begin
  Form1.Width := 260;
  Form1.Height := 260;
  Image1.Width := 250;
  Image1.Height := 250;
  Image1.top := 5;
  Image1.width := 5;
  MyImage := TBitmap.Create;
  MyImage.Width := 250;
  MyImage.Height := 250;
  for TmpX := 0 to 249 do
    for TmpY := 0 to 249 do
      MyImage.Canvas.Pixels[TmpX, TmpY] :=
        RGB(TmpX, 250 - TmpY, (TmpX + TmpY div 2));
  Image1.Canvas.Draw(0, 0, MyImage);
  MyImage.Free;
end;

Если вы хотите сделать действительно быструю графику, взгляните на функции GDI (API) и/или функции WinG, которые для вас разработали программисты Microsoft. Трактовка их для данной статьи немного скучна, да и не имеет никакого отношения к Delphi.

Использование открытых интерфейсов среды программирования Delphi.

Экскурсия по цехам. Монолог экскурсовода. - Вот тут у нас делаются компакт-диски... Работают роботы под управлением операционной системы Юникс... Посмотрели?.. Идём дальше... - А вот тут у нас собираются и разрисовываются те красивые лицензионные коробочки... Роботы управляются системой OS/2... [Входят через бронированную дверь с зарешёченным окошком и здоровенным замком] - А вот тут работают роботы под управлением Windows 95... Наденьте, пожалуйста, защитные костюмы...

Одной и наиболее сильных сторон среды программирования Delphi является ее открытая архитектура, благодаря которой Delphi допускает своего рода метапрограммирование, позволяя “программировать среду программирования”. Такой подход переводит Delphi на качественно новый уровень систем разработки приложений и позволяет встраивать в этот продукт дополнительные инструментальные средства, поддерживающие практически все этапы создания прикладных систем. Столь широкий спектр возможностей открывается благодаря реализованной в Delphi концепции так называемых открытых интерфейсов, являющихся связующим звеном между IDE (Integrated Development Environment) и внешними инструментами. Данная статья посвящена открытым интерфейсам Delphi и представляет собой обзор представляемых ими возможностей.

В Delphi определены шесть открытых интерфейсов: Tool Interface, Design Interface, Expert Interface, File Interface, Edit Interface и Version Control Interface. Вряд ли в рамках данной статьи нам удалось бы детально осветить и проиллюстрировать возможности каждого из них. Более основательно разобраться в рассматриваемых вопросах вам помогут исходные тексты Delphi, благо разработчики снабдили их развернутыми комментариями. Объявления классов, представляющих открытые интерфейсы, содержатся в соответствующих модулях в каталоге ...\Delphi\Source\ToolsAPI.

Design Interface (модуль DsgnIntf.pas) предоставляет средства для создания редакторов свойств и редакторов компонентов. Редакторы свойств и компонентов – это тема, достойная отдельного разговора, поэтому напомним лишь, что редактор свойства контролирует поведение Инспектора Объектов при попытке изменить значение соответствующего свойства, а редактор компонента активизируется при двойном нажатии левой кнопки мыши на изображении помещенного на форму компонента. Version Control Interface (модуль VCSIntf.pas) предназначен для создания систем контроля версий. Начиная с версии 2.0, Delphi поддерживает интегрированную систему контроля версий Intersolv PVCS, поэтому в большинстве случаев в разработке собственной системы нет необходимости. По этой причине рассмотрение Version Control Interface мы также опустим. File Interface (модуль FileIntf.pas) позволяет переопределить рабочую файловую систему IDE, что дает возможность выбора собственного способа хранения файлов (в Memo-полях на сервере БД, например). Edit Interface (модуль EditIntf.pas) предоставляет доступ к буферу исходных текстов, что позволяет проводить анализ кода и выполнять его генерацию, определять и изменять позицию курсора в окне редактора кода, а также управлять синтаксическим выделением исходного текста. Специальные классы предоставляют интерфейсы к помещенным на форму компонентам (определение типа компонента, получение ссылок на родительский и дочерние компоненты, доступ к свойствам, передача фокуса, удаление и т.д.), к самой форме и к ресурсному файлу проекта. Также Edit Interface позволяет идентифицировать так называемые модульные нотификаторы, определяющие реакцию на такие события, как изменение исходного текста модуля, модификация формы, переименование компонента, сохранение, переименование или удаление модуля, изменение ресурсного файла проекта и т. д. Tool Interface (модуль ToolIntf.pas) предоставляет разработчикам средства для получения общей информации о состоянии IDE и выполнения таких действий, как открытие, сохранение и закрытие проектов и отдельных файлов, создание модуля, получение информации о текущем проекте (число модулей и форм, их имена и т. д.), регистрация файловой системы, организация интерфейсов к отдельным модулям и т.д. В дополнение к модульным нотификаторам Tool Interface определяет add-in нотификаторы, уведомляющие о таких событиях, как открытие/закрытие файлов и проектов, загрузка и сохранение desktop-файла проекта, добавление/исключение модулей проекта, инсталляция/деинсталляция пакетов, компиляция проекта, причем в отличие от модульных нотификаторов add-in нотификаторы позволяют отменить выполнение некоторых событий. Кроме того, Tool Interface предоставляет средства доступа к главному меню IDE Delphi, позволяя встраивать в него дополнительные пункты. Expert Interface (модуль ExptIntf.pas) представляет собой основу для создания экспертов — программных модулей, встраиваемых в IDE c целью расширения ее функциональности. В качестве примера эксперта можно привести входящий в Delphi Database Form Wizard, выполняющий генерацию формы для просмотра и изменения содержимого таблицы БД.

Эксперты бывают нескольких типов (стилей):

Стиль Описание
esStandard Для каждого эксперта такого стиля IDE добавляет пункт меню Tools/..., при выборе которого эксперт активизируется (IDE вызывает его метод Execute)
esForm esProject IDE рассматривает эксперты данного стиля как шаблоны форм/проектов и помещает активизирующие их изображения в галерею Object Repository.
esAddIn Эксперты подобного стиля обеспечивают собственный интерфейс с IDE

Класс каждого эксперта является потомком базового класса TIExpert, содержащего серию абстрактных методов, которые необходимо перекрыть в порождаемом классе:

Метод Описание
GetName Должен возвращать имя эксперта
GetAuthor Должен возвращать имя автора эксперта. Это имя отображается в Object Repository
GetComment Должен возвращать комментарий (1-2 предложения), поясняющий назначение эксперта. Используется в Object Repository
GetPage Должен возвращать название страницы Object Repository, на которую IDE поместит соответствующее эксперту изображение
GetGlyph Должен возвращать дескриптор (HICON, в Delphi 1.0 – HBITMAP) соответствующего эксперту изображения в ObjectRepository
GetStyle Должен возвращать константу, соответствующую стилю эксперта (esStandard/esForm/esProject/esAddIn)
GetState Если возвращаемое множество содержит константу esChecked, IDE пометит соответствующий эксперту пункт меню “галочкой”, а если множество содержит константу esEnabled, то IDE сделает этот пункт меню доступным для выбора
GetIDString Должен возвращать строку – идентификатор эксперта, уникальную среди всех установленных экспертов. По соглашению, формат этой строки таков: Имя_Компании.Назначение_эксперта, например: Borland.WidgetExpert
GetMenuText Должен возвращать текст, отображаемый в пункте меню эксперта. Этот метод вызывается каждый раз, когда раскрывается родительское меню, что позволяет сделать пункт меню контекстно-чувствительным
Execute Вызывается при вызове эксперта через меню или Object Repository (в зависимости от стиля)

Набор методов, подлежащих перекрытию, зависит от стиля эксперта:

Метод esStandard esForm esProject esAddIn
GetName + + + +
GetAuthor + +
GetComment + +
GetPage + +
GetGlyph + +
GetStyle + + + +
GetState +
GetIDString + + + +
GetMenuText +
Execute + + +

Определив класс эксперта, необходимо позаботиться о том, чтобы Delphi “узнала” о нашем эксперте. Для этого его нужно зарегистрировать посредством вызова процедуры RegisterLibraryExpert, передав ей в качестве параметра экземпляр класса эксперта.

В качестве иллюстрации создадим простой эксперт в стиле esStandard, который при выборе соответствующего ему пункта меню Delphi выводит сообщение о том, что он запущен. Как видно из вышеприведенной таблицы, стиль esStandard обязывает перекрыть шесть методов:

unit exmpl_01;
{ STANDARD EXPERT }
interface
uses
  Dialogs, ExptIntf;
type
  { класс эксперта является потомком базового класса TIExpert }
  TEMyExpert = class( TIExpert)
    function GetName: string; override;
    function GetStyle: TExpertStyle; override;
    function GetIDString: string; override;
    function GetMenuText: string; override;
    function GetState: TExpertState; override;
    procedure Execute; override;
end;
procedure register;
implementation
{ возвращаем имя эксперта }
function TEMyExpert.GetName: string;
begin
  Result := 'My Simple Expert 1';
end;
{ возвращаем стиль эксперта }
function TEMyExpert.GetStyle: TExpertStyle;
begin
  Result := esStandard;
end;
{ возвращаем строку - идентификатор эксперта }
function TEMyExpert.GetIDString: string;
begin
  Result := 'Doomy.SimpleAddInExpert_1';
end;
{ возвращаем текст пункта меню }
function TEMyExpert.GetMenuText: string;
begin
  Result := 'Simple Expert 1';
end;
{ возвращаем множество, характеризующее состояние пункта меню эксперта }
{ (доступность, наличие "галочки"); в данном случае пункт меню доступен, }
{ а "галочка" отсутствует }
function TEMyExpert.GetState: TExpertState;
begin
  Result := [esEnabled];
end;
{ при выборе пункта меню эксперта отображаем сообщение }
procedure TEMyExpert.Execute;
begin
  MessageDlg('Standard Expert Started!', mtInformation, [mbOK], 0);
end;
{ регистрируем эксперт }
procedure register;
begin
  RegisterLibraryExpert( TEMyExpert.Create);
end;
end.

Для того чтобы эксперт был “приведен в действие”, необходимо выбрать пункт меню Component/Install Component ... , выбрать в диалоге Browse модуль, содержащий эксперт (в нашем случае exmpl_01.pas), нажать ОК, и после компиляции пакета dclusr30.dpk в главном меню Delphi в разделе Help должен появиться пункт Simple Expert 1, при выборе которого появляется информационное сообщение “Standard Expert started!”.

Почему Delphi помещает пункт меню эксперта в раздел Help, остается загадкой. Если вам не нравится то, что пункт меню появляется там, где угодно Delphi, а не там, где хотите вы, возможен следующий вариант: создать эксперт в стиле add-in, что исключает автоматическое создание пункта меню, а пункт меню добавить “вручную”, используя средства Tool Interface. Это позволит задать местоположение нового пункта в главном меню произвольным образом. Для добавления пункта меню используется класс TIToolServices — основа Tool Interface — и классы TIMainMenuIntf, TIMenuItemIntf, реализующие интерфейсы к главному меню IDE и его пунктам. Экземпляр ToolServices класса TIToolServices создается самой IDE при ее инициализации. Обратите внимание на то, что ответственность за освобождение интерфейсов к главному меню Delphi и его пунктам целиком ложится на разработчика. Попутно немного усложним функциональную нагрузку эксперта: при активизации своего пункта меню он будет выдавать справку об имени проекта, открытого в данный момент в среде:

unit exmpl_02;
{ ADD-IN EXPERT, ДОБАВЛЕНИЕ ПУНКТА В ГЛАВНОЕ МЕНЮ IDE DELPHI }
interface
uses
  Classes, Dialogs, ToolIntF, ExptIntf, Menus;
type
  { класс эксперта является потомком базового класса TIExpert }
  TEMyExpert = class( TIExpert)
  private
    MenuItem: TIMenuItemIntf;
  public
    constructor Create;
    destructor Destroy; override;
    function GetName: string; override;
    function GetStyle: TExpertStyle; override;
    function GetIDString: string; override;
    procedure MenuItemClick( Sender: TIMenuItemIntf);
end;
procedure register;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
implementation
{ добавляем пункт в главное меню IDE Delphi: }
{ 1) текст вставляемого пункта меню - 'Simple Expert 2'; }
{ 2) идентификатор вставляемого пункта меню - 'ViewMyExpertItem2'; }
{ 3) идентификатор пункта меню, перед которым добавляется новый }
{ пункт меню - 'ViewWatchItem' (для Delphi 5 - 'ViewWatchesItem');}
{ 4) горячая клавиша вставляемого пункта - 'Ctrl + 2'; }
{ 5) обработчик события, соответствующего выбору вставляемого пункта }
{ меню - MenuItemClick }
constructor TEMyExpert.Create;
begin
  inherited Create;
  MenuItem:= AddIDEMenuItem( 'Simple Expert 2', 'ViewMyExpertItem2',
  {$IFDEF VER130}
  'ViewWatchesItem', '2', MenuItemClick);
  {$ELSE}
  'ViewWatchItem', '2', MenuItemClick);
  {$ENDIF}
end;
destructor TEMyExpert.Destroy;
begin
  if Assigned( MenuItem) then
    MenuItem.Free;
  inherited Destroy;
end;
{ при выборе пункта меню эксперта отображаем сообщение, содержащее }
{ имя активного проекта }
procedure TEMyExpert.MenuItemClick( Sender: TIMenuItemIntf);
begin
  MessageDlg( 'Current project name is ' + ToolServices.GetProjectName,
  mtInformation, [mbOK], 0);
end;
{ возвращаем имя эксперта }
function TEMyExpert.GetName: string;
begin
  Result := 'My Simple Expert 2';
end;
{ возвращаем стиль эксперта }
function TEMyExpert.GetStyle: TExpertStyle;
begin
  Result := esAddIn;
end;
{ возвращаем строку - идентификатор эксперта }
function TEMyExpert.GetIDString: string;
begin
  Result := 'Doomy.SimpleAddInExpert_2';
end;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
var
  MainMenu: TIMainMenuIntf;
  MenuItems, PreviousItem, ParentItem: TIMenuItemIntf;
begin
  Result:= nil;
  { получаем интерфейс пунктов главного меню IDE }
  MainMenu:= ToolServices.GetMainMenu;
  if Assigned( MainMenu) then
    try
      { получаем интерфейс пунктов верхнего уровня меню }
      MenuItems:= MainMenu.GetMenuItems;
      if Assigned( MenuItems) then
        try
          { ищем пункт меню перед которым необходимо вставить новый пункт }
          PreviousItem:= MainMenu.FindMenuItem( PreviousItemName);
          if Assigned( PreviousItem) then
            try
              { получаем интерфейс к родительскому пункту меню }
              ParentItem:= PreviousItem.GetParent;
              if Assigned( ParentItem) then
                try
                  { вставляем новый пункт меню и в качестве результата функции }
                  { возвращаем его интерфейс }
                  Result:= ParentItem.InsertItem( PreviousItem.GetIndex, Caption,
                  name, '', ShortCut( Word( ShortCutKey), [ssCtrl]), 0, 0,
                  [mfVisible, mfEnabled], OnClick);
                finally
                  { освобождаем интерфейс родительского пункта меню }
                  ParentItem.Free;
                end;
            finally
              { освобождаем интерфейс пункта меню перед которым вставили }
              { новый пункт }
              PreviousItem.Free;
            end;
        finally
          { освобождаем интерфейс пунктов верхнего уровня меню }
          MenuItems.Free;
        end;
    finally
      { освобождаем интерфейс главного меню IDE }
      MainMenu.Free;
    end;
end;
procedure register;
begin
  { регистрируем эксперт }
  RegisterLibraryExpert( TEMyExpert.Create);
end;
end.

В этом примере центральное место занимает функция AddIDEMenuItem, осуществляющая добавление пункта меню в главное меню IDE Delphi. В качестве параметров ей передаются текст нового пункта меню, его идентификатор, идентификатор пункта, перед которым вставляется новый пункт, символьное представление клавиши, которая вместе с клавишей Ctrl может использоваться для быстрого доступа к новому пункту, и обработчик события, соответствующего выбору нового пункта. Мы добавили новый пункт меню в раздел View перед пунктом Watches.

Теперь познакомимся с нотификаторами. Определим add-in нотификатор, отслеживающий моменты закрытия/открытия проектов и корректирующий соответствующим образом поле, хранящее имя активного проекта (реализацию методов, не претерпевших изменений по сравнению с предыдущим примером, для краткости опустим):

unit exmpl_03;
{ ИСПОЛЬЗОВАНИЕ ADD-IN НОТИФИКАТОРОВ }
interface
uses
  Classes, Dialogs, ToolIntF, ExptIntf, Menus;
type
  TEMyExpert = class;
  { касс add-in нотификатора порождаем от TIAddInNotifier}
  TAddInNotifier = class(TIAddInNotifier)
  private
    Expert: TEMyExpert;
  public
    constructor Create( anExpert: TEMyExpert);
    procedure FileNotification( NotifyCode: TFileNotification;
    const FileName: string; var Cancel: Boolean); override;
end;
  { класс эксперта является потомком базового класса TIExpert }
  TEMyExpert = class( TIExpert)
  private
    ProjectName: string;
    MenuItem: TIMenuItemIntf;
    AddInNotifier: TAddInNotifier;
  public
    constructor Create;
    destructor Destroy; override;
    function GetName: string; override;
    function GetStyle: TExpertStyle; override;
    function GetIDString: string; override;
    procedure MenuItemClick( Sender: TIMenuItemIntf);
end;
procedure register;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
implementation
constructor TAddInNotifier.Create;
begin
  inherited Create;
  Expert := anExpert;
end;
procedure TAddInNotifier.FileNotification( NotifyCode: TFileNotification;
const FileName: string; var Cancel: Boolean);
begin
  with Expert do
    case NotifyCode of
      fnProjectOpened:
        ProjectName:= FileName; { открытие проекта }
      fnProjectClosing:
        ProjectName:= 'unknown' { закрытие проекта }
    end;
end;
constructor TEMyExpert.Create;
begin
  inherited Create;
  { добавляем пункт в главное меню IDE Delphi }
  MenuItem:= AddIDEMenuItem( 'Simple Expert 3', 'ViewMyExpertItem3',
  {$IFDEF VER130}
  'ViewWatchesItem', '3', MenuItemClick);
  {$ELSE}
  'ViewWatchItem', '3', MenuItemClick);
  {$ENDIF}
  try
    { создаем add-in нотификатор }
    AddInNotifier:= TAddInNotifier.Create( Self);
    { регистрируем add-in нотификатор }
    ToolServices.AddNotifier( AddInNotifier);
  except
    AddInNotifier:= nil;
  end;
  { инициализируем поле, хранящее имя активного проекта }
  ProjectName:= ToolServices.GetProjectName;
end;
destructor TEMyExpert.Destroy;
begin
  if Assigned( MenuItem) then
    MenuItem.Free;
  if Assigned( AddInNotifier) then
  begin
    { снимаем регистрацию add-in нотификатора }
    ToolServices.RemoveNotifier( AddInNotifier);
    { уничтожаем add-in нотификатор }
    AddInNotifier.Free;
  end;
  inherited Destroy;
end;
{ при выборе пункта меню эксперта отображаем сообщение, содержащее }
{ имя активного проекта }
procedure TEMyExpert.MenuItemClick( Sender: TIMenuItemIntf);
begin
  MessageDlg( 'Current project name is ' + ProjectName,
  mtInformation, [mbOK], 0);
end;
...
end.

Для реализации нотификатора мы определили класс TAddInNotifier, являющийся потомком TIAddInNotifier, и перекрыли метод FileNotification. IDE будет вызывать этот метод каждый раз, когда происходит событие, на которое способен среагировать add-in нотификатор (каждое такое событие обозначается соответствующей константой типа TFileNotification). Поле Expert в классе TAddInNotifier служит для обратной связи с экспертом (метод TAddInNotifier.FileNotification). В деструкторе эксперта регистрация нотификатора снимается, и нотификатор уничтожается.

А теперь проиллюстрируем использование модульных нотификаторов. Создадим add-in эксперт, выдающий сообщения о каждом акте сохранения проектного файла (реализацию уже знакомых нам методов для краткости не приводим):

unit exmpl_04;
{ ИСПОЛЬЗОВАНИЕ МОДУЛЬНЫХ НОТИФИКАТОРОВ }
interface
uses
  Classes, Dialogs, ToolIntF, ExptIntf, Menus
  {$IFDEF VER130}, EditIntf{$ENDIF};
type
  { класс модульного нотификатора порождаем от TIModuleNotifier }
  TModuleNotifier = class( TIModuleNotifier)
  private
    FileName: string;
  public
    constructor Create(const aFileName: string);
    procedure Notify( NotifyCode: TNotifyCode); override;
    {$IFDEF VER130}
    procedure ComponentRenamed(ComponentHandle: Pointer;
    const OldName, NewName: string); override;
    {$ELSE}
    procedure ComponentRenamed( const oldName, newName: string); override;
    {$ENDIF}
end;
  TEMyExpert = class;
  { класс add-in нотификатора порождаем от TIAddInNotifier}
  TAddInNotifier = class(TIAddInNotifier)
  private
    Expert: TEMyExpert;
  public
    constructor Create( anExpert: TEMyExpert);
    procedure FileNotification( NotifyCode: TFileNotification;
    const FileName: string; var Cancel: Boolean); override;
end;
  { класс эксперта является потомком базового класса TIExpert }
  TEMyExpert = class( TIExpert)
  private
    AddInNotifier: TAddInNotifier;
    ModuleInterface: TIModuleInterface;
    ModuleNotifier: TModuleNotifier;
  public
    constructor Create;
    destructor Destroy; override;
    function GetName: string; override;
    function GetStyle: TExpertStyle; override;
    function GetIDString: string; override;
    procedure AddModuleNotifier( const FileName: string);
    procedure RemoveModuleNotifier;
end;
procedure register;
implementation
constructor TModuleNotifier.Create(const aFileName: string);
begin
  inherited Create;
  FileName := aFileName;
end;
procedure TModuleNotifier.Notify( NotifyCode: TNotifyCode);
begin
  { если произошло сохранение соответствующего нотификатору файла, }
  { то выдаем сообщение об этом }
  if NotifyCode = ncAfterSave then
    MessageDlg(FileName + 'saved', mtInformation, [mbOK], 0);
end;
procedure TModuleNotifier.ComponentRenamed;
begin
  { ничего здесь не делаем, но метод необходимо перекрыть }
end;
procedure TAddInNotifier.FileNotification( NotifyCode: TFileNotification;
const FileName: string; var Cancel: Boolean);
begin
  with Expert do
    case NotifyCode of
      fnProjectOpened: { открытие проекта }
        { добавляем модульный нотификатор }
        AddModuleNotifier( FileName);
      fnProjectClosing: { закрытие проекта }
        { освобождаем модульный нотификатор }
        RemoveModuleNotifier;
    end;
end;
constructor TEMyExpert.Create;
begin
  inherited Create;
  try
    { создаем add-in нотификатор }
    AddInNotifier:= TAddInNotifier.Create( Self);
    { регистрируем add-in нотификатор }
    ToolServices.AddNotifier( AddInNotifier);
  except
    AddInNotifier:= nil;
  end;
  { добавляем модульный нотификатор }
  AddModuleNotifier( ToolServices.GetProjectName);
end;
destructor TEMyExpert.Destroy;
begin
  if Assigned( AddInNotifier) then
  begin
    { снимаем регистрацию add-in нотификатора }
    ToolServices.RemoveNotifier( AddInNotifier);
    { уничтожаем add-in нотификатор }
    AddInNotifier.Free;
  end;
  { освобождаем модульный нотификатор }
  RemoveModuleNotifier;
  inherited Destroy;
end;
procedure TEMyExpert.AddModuleNotifier;
begin
  { если модульный нотификатор для проектного файла уже зарегистрирован, }
  { то никаких действий не выполняем, во избежание появления дубликатов }
  { нотификаторов; в противном случае дубликаты могли бы появиться, }
  { например, при открытии Delphi: один нотификатор добавился бы при }
  { создании эксперта (в конструкторе класса эксперта), а второй - при }
  { открытии проекта (в TAddNotifier.FileNotification }
  if Assigned( ModuleInterface) and Assigned( ModuleNotifier) then
    Exit;
  try
    { получаем интерфейс модуля }
    ModuleInterface:= ToolServices.GetModuleInterface( FileName);
    try
      { создаем модульный нотификатор }
      ModuleNotifier:= TModuleNotifier.Create( FileName);
      { регистрируем модульный нотификатор }
      ModuleInterface.AddNotifier( ModuleNotifier);
    except
      ModuleNotifier:= nil;
    end;
  except
    ModuleInterface:= nil;
  end;
end;
procedure TEMyExpert.RemoveModuleNotifier;
begin
  if Assigned(ModuleNotifier) then
  begin
    if Assigned( ModuleInterface) then
      { снимаем регистрацию модульного нотификатора }
      ModuleInterface.RemoveNotifier( ModuleNotifier);
    { уничтожаем модульный нотификатор }
    ModuleNotifier.Free;
    ModuleNotifier:= nil;
  end;
  if Assigned( ModuleInterface) then
  begin
    { освобождаем модульный интерфейс }
    ModuleInterface.Free;
    ModuleInterface:= nil;
  end;
end;
...
end.

В данном примере add-in эксперт отслеживает события, соответствующие открытию/закрытию проектов. При каждом открытии проекта регистрируется модульный нотификатор, соответствующий файлу проекта. В плане реализации модульные нотификаторы схожи с add-in нотификаторами: мы определяем класс TModuleNotifier, являющийся потомком TIModuleNotifier и перекрываем его методы Notify и ComponentRenamed. IDE вызывает метод Notify при возникновении определенных событий, имеющих отношение к данному модулю; внутри этого метода и определяется реакция на то или иное событие. Метод ComponentRenamed вызывается при изменении имени компонента, лежащего на форме модуля. Обратите внимание на то, что мы не используем этот метод, но обязаны его перекрыть, иначе при изменении имени компонента будет происходить вызов абстрактного метода базового класса, что приводит к непредсказуемым последствиям. Регистрация модульного нотификатора является несколько более сложным процессом по сравнению с регистрацией add-in нотификатора: сначала мы получаем интерфейс модуля (TIModuleInterface), а затем с помощью интерфейса модуля регистрируем нотификатор. При закрытии проекта регистрация модульного нотификатора снимается (снова с использованием TIModuleInterface), и нотификатор уничтожается.

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

unit exmpl_05;
{ ОПРЕДЕЛЕНИЕ ПОЗИЦИИ КУРСОРА }
interface
uses
  SysUtils, Classes, Dialogs, ToolIntF, ExptIntf, EditIntf, Menus;
type
  { класс эксперта является потомком базового класса TIExpert }
  TEMyExpert = class( TIExpert)
  private
    MenuItem: TIMenuItemIntf;
  public
    constructor Create;
    destructor Destroy; override;
    function GetName: string; override;
    function GetStyle: TExpertStyle; override;
    function GetIDString: string; override;
    procedure MenuItemClick( Sender: TIMenuItemIntf);
    function GetCursorPos: TEditPos;
end;
procedure register;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
implementation
{ при выборе пункта меню эксперта отображаем сообщение, содержащее }
{ имя активного проекта }
procedure TEMyExpert.MenuItemClick( Sender: TIMenuItemIntf);
var
  CurPos: TEditPos;
begin
  CurPos:= GetCursorPos;
  if CurPos.Line > 0 then
    MessageDlg( 'Current file: ' + ToolServices.GetCurrentFile + #13 +
    'Current cursor position: ' + IntToStr( CurPos.Line) +
    ', ' + IntToStr( CurPos.Col), mtInformation, [mbOK], 0);
end;
function TEMyExpert.GetCursorPos: TEditPos;
var
  ModuleInterface: TIModuleInterface;
  EditorInterface: TIEditorInterface;
  EditView: TIEditView;
  FileName: string;
begin
  { определяем имя активного файла }
  FileName:= ToolServices.GetCurrentFile;
  Result.Line:= 0;
  Result.Col:= 0;
  { для простоты определяем позицию только в pas- файлах }
  if ExtractFileExt( FileName) = '.pas' then
  begin
    { получаем интерфейс модуля }
    ModuleInterface:= ToolServices.GetModuleInterface( FileName);
    try
      { получаем интерфейс редактора кода }
      EditorInterface:= ModuleInterface.GetEditorInterface;
      try
        { получаем интерфейс представления модуля в редакторе }
        { передавая методу GetView индекс нужного нам представления; }
        { если файл открыт в нескольких окнах редактора кода, то для }
        { простоты берем первое (хотя конечно, это не совсем }
        { правильно }
        EditView:= EditorInterface.GetView( 0);
        try
          Result:= EditView.CursorPos;
        finally
          EditView.Free;
        end;
      finally
        EditorInterface.Free;
      end;
    finally
      ModuleInterface.Free;
    end;
  end;
end;
...

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

модульный интерфейс (TIModuleInterface); интерфейс редактора кода (TIEditorInterface); интерфейс представления модуля в окне редактора (TIEditView).

Если при выборе пункта меню эксперта активным является файл с исходным текстом (*.pas), то выдается сообщение, содержащее имя активного файла и текущую позицию курсора в нем. Если активным является не pas-файл, то сообщение не выдается.

Для получения имени активного файла используется метод GetCurrentFile класса TIToolServices.

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

Запуск Delphi с секретными параметрами.

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

-ns : ("no splash") flag skips the splash screen : Startet Delphi ohne Splash Screen (ab Delphi 2) (Delphi 2 and later) Delphi32.EXE -ns -np : ("no project") flag tells Delphi not to open an empty project on startup : Startet Delphi, ohne ein leeres Projekt zu цffnеn. (Delphi 5 and later) Delphi32.EXE -np -hm : ("Heap Monitor") Displays information in the IDE title bar regarding the amount of memory allocated using the memory manager. Displays the number of blocks and bytes allocated. Information gets updated when the IDE is idle. : Zeigt in der Titelleiste der IDE Informationen ььег den allozierten Speicher an. Zeigt die Anzahl allozierten Blocks/Bytes an. (Delphi 3 and later) Delphi32.EXE -hm -attach: Attach to running process. This command-line is used to make Delphi a JIT debugger on Windows 95/98/NT. Delphi 4 and later. Note: These command-line switches are case-insensitive and can be prefixed with either - or /. Bemerkung: Die Parameter sind Case-Insensitive und кцnnеn ein - oder / vorangestellt haben.

Сервисы в Delphi на чистом API.

Автор: Павел Часть 1. Основные понятия Вот так получилось нужно было по работе написать сервис но не хотелось пользоваться классами Delphi, так как код написанный с применением оных большой, а хотелось чего-нибудь поменьше, то есть на API, но к сожелению видно этим никто не занимался в Delphi (примеров на Си более чем достаточно), вот и родилась идея этого обзора. Что же это сервис(еще их называют службами)??? Попробую рассказать как умею... В любой операционной системе, нужны программы которые работают в фоновом режиме (это нужно например для различных серверов СУБД, сетевые приложения, программ мониторинга следящих за чем то и т.д. и т.п), в Unix-системах такие программы называются демонами, в DOS они назывались TSR (Terminate and Stay Resident). В NT и 2000(далее я буду называть их только для NT, что подразумевает под сообой и 2000) Windows такие программы называются сервисами (или службами).Сервис NT (Windows NT service) - процесс, обладающий специальным интерфейсом для взаимодействия с операционной системой Windows NT. Сервисы бывают двух типов - сервисы Win32, взаимодействующие с операционной системой посредством диспетчера управления сервисами (Service Control Manager - SCM), и драйвера, работающие по протоколу драйвера устройства Windows NT. Я буду рассказывать только про сервисы Win32. Сервисы доступны в панели управления. Запустив Service Applet, вы увидите список доступных сервисов. Вы можете запускать, останавливать, ставить сервисы на паузу. Нажав кнопку Startup, Вы можете изменить поведение сервиса при запуске. Сервисы могут запускаться автоматически при старте компьютера, быть абсолютно выключеными или иметь возможность запуска вручную. Если сервис запускается вручную, то пользователь может указать параметры запуска. Для управления сервисами Вам нужны права администратора или права power user. ОС использует много сервисов и информацию о каждом из них пишет в реестр в ключ "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services".Там содержатся следующие сведения: Тип службы Указывает на то, реализован ли в данной программе только один сервис (эксклюзивный) или же их несколько. Эксклюзивный сервис может работать в любом контексте безопасности. Несколько сервисов внутри одного приложения могут работать только в контексте LocalSystem. Способ запуска Автоматический - сервис запускается при старте системы. По требованию - сервис запускается пользователем вручную. Деактивированный - сервис не может быть запущен. Имя исполняемого EXE-файла Порядок запуска по отношению к другим сервисам. В некоторых случаях для корректной работы сервиса требуется, чтобы был запущен один или несколько других сервисов. В этом случае в реестре содержится информация о сервисах, запускаемых перед данным сервисом. Контекст безопасности Здесь хранится сетевое имя и пароль. По умолчанию контекст безопасности соответствует LocalSystem.

Любая программа, имеющее соответствующие права, может взаимодействовать с сервисом. Это подразумевает изменение состояния сервиса (Вы можете изменить состояния сервиса используя Service Applet), то есть перевод его в одно из трех состояний - активный (сервис работает), приостоновленный (работа сервиса приостановлена), полная остановка и осуществляется при помощи посылки запросов SCM. Запросы бывают трех типов:

Сообщения от сервисов (фиксация их состояний). Запросы, связанные с изменением конфигурации сервиса или получением информации о нем. Запросы приложений на изменение состояния сервиса. Для управления сервисом необходимо в первую очередь получить дескриптор с помощью функции OpenService. Функция StartService запускает сервис. Изменение состояния сервиса производится вызовом функции ControlService. С помощью определенных функций вы можете изменить информацию о сервисе, тем самым и в реестре. OpenSCManager, CreateService, OpenService, CloseServiceHandle - для создания (открытия) сервиса. QueryServiceConfig, QueryServiceObjectSecurity, EnumDependentServices, EnumServicesStatus - для получения информации о сервисе. ChangeServiceConfig, SetServiceObjectSecurity, LockServiceDatabase, UnlockServiceDatabase, QueryServiceLockStatus - для изменения конфигурационной информации сервиса. В принципе как было сказано сервис - это в принципе обычный exe-файл поддерживающий интерфейс SCM, только имеющий определенное строение. Для того чтобы стать сервисом программа должна быть построена соответствующим образом(т.е. иметь набор определенных функций). В следующей части я подробно расскажу об этом.

Описание функций для работы с сервисами (постепенно будут добавлены описания для всех функций), большая часть из них объявлена в WinSvc.Pas.

ChangeServiceConfig Изменяет параметры конфигурации сервиса ChangeServiceConfig2 Изменяет необязательные параметры конфигурации сервиса CloseServiceHandle Закрывает указанный дескриптор к сервисному объекту ControlService Посылает сервису управляющий код CreateService Создает сервисный объект DeleteService Удаляет сервис из базы данных диспетчеров сервисов EnumDependentServices Перечисляет зависимые сервисы EnumServicesStatus Перечисляет сервисы EnumServicesStatusEx Перечисляет сервисы GetServiceDisplayName Возвращает отображаемое имя сервиса GetServiceKeyName Возвращает имя сервиса Handler Функция обработки управляющих кодов HandlerEx Функция обработки управляющих кодов LockServiceDatabase Блокирует базу данных сервисов NotifyBootConfigStatus Уведомляет о приеме загрузочной информации OpenSCManager Подключает приложение к менеджеру сервисов OpenService Открывает сервис QueryServiceConfig Запрашивает параметры конфигурации QueryServiceConfig2 Запрашивает необязательные параметры конфигурации QueryServiceLockStatus Запрашивает статус блокировки QueryServiceObjectSecurity Запрашивает дескриптор безопасности QueryServiceStatus Запрашивает статус сервиса QueryServiceStatusEx Запрашивает статус сервиса RegisterServiceCtrlHandler Регистрирует обработчик запросов RegisterServiceCtrlHandlerEx Регистрирует обработчик запросов ServiceMain Главная функция сервиса SetServiceBits Обноволяет статус сервиса для диспетчера сервисов SetServiceObjectSecurity Изменяет дескриптор безопасности сервисного объекта SetServiceStatus Устанавливает статус сервиса StartService Запускает сервис StartServiceCtrlDispatcher Запускает поток UnlockServiceDatabase Разблокирование базы данных

Также при написание сервиса используются такие структуры(описания их то же постепенно будут добавлятся).

ENUM_SERVICE_STATUS ENUM_SERVICE_STATUS_PROCESS QUERY_SERVICE_CONFIG QUERY_SERVICE_LOCK_STATUS SC_ACTION SERVICE_DESCRIPTION SERVICE_FAILURE_ACTIONS SERVICE_STATUS SERVICE_STATUS_PROCESS SERVICE_TABLE_ENTRY Но вот и все для основных понятий...Вообще сервисы довольно сильно облегчают жизнь программисту, и понимание их работы (даже не используя) полезны, не только программистам, но и системным администраторам и пользователям. В статье использованы материалы из Microsoft SDK(Эта статья входит в рамки проекта DSDN). С удовольствием выслушаю все комментария и замечания по поводу данной статьи. Сразу хочу предупредить, я не отвечаю на письма где меня просят выслать что либо или будет ли продолжение(и подобные письма не несущие какой-то содержательной части), я их читаю, но отвечать на них у меня времени нет. Также , если кто обнаружит неточности, да и просто грамматические ошибки в статье, за это прошу заранее извинение.

Желаю удачи в темных делах С уважением Алексей.

Программирование сокетов в Delphi.

Автор: Павел Введение Данная статья посвящена созданию приложений архитектуры клиент/сервер в Borland Delphi на основе сокетов ("sockets" - гнезда). А написал я эту статью не просто так, а потому что в последнее время этот вопрос очень многих стал интересовать. Пока что затронем лишь создание клиентской части сокетного приложения. Впервые я познакомился с сокетами, если не ошибаюсь, год или полтора назад. Тогда стояла задача разработать прикладной протокол, который бы передавал на серверную машину (работающую на ОС Unix/Linux) запрос и получал ответ по сокетному каналу. Надо заметить, что в отличие от любых других протоколов (FTP, POP, SMTP, HTTP, и т.д.), сокеты - это база для этих протоколов. Таким образом, пользуясь сокетами, можно самому создать (симитировать) и FTP, и POP, и любой другой протокол, причем не обязательно уже созданный, а даже свой собственный!

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

Алгоритм работы с сокетными протоколами Так что же позволяют нам делать сокеты?... Да все что угодно! И в этом одно из главных достоинств этого способа обмена данными в сети. Дело в том, что при работе с сокетом Вы просто посылаете другому компьютеру последовательность символов. Так что этим методом Вы можете посылать как простые сообщения, так и целые файлы! Причем, контролировать правильность передачи Вам не нужно (как это было при работе с COM-портами)! Ниже следует примерная схема работы с сокетами в Дельфи-приложениях

Разберем схему подробнее:

Определение св-в Host и Port - чтобы успешно установить соединение, нужно присвоить свойствам Host и Port компонента TClientSocket требуемые значения. Host - это хост-имя (например: nitro.borland.com) либо IP-адрес (например: 192.168.0.88) компьютера, с которым надо соединиться. Port - номер порта (от 1 до 65535) для установления соединения. Обычно номера портов берутся, начиная с 1001 - т.к. номера меньше 1000 могут быть заняты системными службами (например, POP - 110). Подробнее о практической части см.ниже; Открытие сокета - после того, как Вы назначили свойствам Host и Port соответствующие значения, можно приступить непосредственно к открытию сокета (сокет здесь рассматривается как очередь, в которой содержатся символы, передающиеся от одного компьютера к другому). Для этого можно вызвать метод Open компонента TClientSocket, либо присвоить свойству Active значение True. Здесь полезно ставить обработчик исключительной ситуации на тот случай, если соединиться не удалось. Подробнее об этом можно прочитать ниже, в практической части; Авторизация - этот пункт можно пропустить, если сервер не требует ввода каких-либо логинов и/или паролей. На этом этапе Вы посылаете серверу свой логин (имя пользователя) и пароль. Но механизм авторизации зависит уже от конкретного сервера; Посылка/прием данных - это, собственно и есть то, для чего открывалось сокетное соединение. Протокол обмена данными также зависит от сервера; Закрытие сокета - после всех выполненных операций необходимо закрыть сокет с помощью метода Close компонента TClientSocket (либо присвоить свойству Active значение False). Описание свойств и методов компонента TClientSocket

Здесь мы познакомимся с основными свойствами, методами и событиями компонента TClientSocket.

Свойства. Методы. События.
Active - показывает, открыт сокет или нет. Тип: Boolean. Соответственно, True - открыт, а False - закрыт. Это свойство доступно для записи; Host - строка (Тип: string), указывающая на хост-имя компьютера, к которому следует подключиться; Address - строка (Тип: string), указывающая на IP-адрес компьютера, к которому следует подключиться. В отличие от Host, здесь может содержаться лишь IP. Отличие в том, что если Вы укажете в Host символьное имя компьютера, то IP адрес, соответствующий этому имени будет запрошен у DNS; Port - номер порта (Тип: Integer (Word)), к которому следует подключиться. Допустимые значения - от 1 до 65535; Service - строка (Тип: string), определяющая службу (ftp, http, pop, и т.д.), к порту которой произойдет подключение. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам; ClientType - тип соединения. ctNonBlocking - асинхронная передача данных, т.е. посылать и принимать данные по сокету можно с помощью OnRead и OnWrite. ctBlocking - синхронная (одновременная) передача данных. События OnRead и OnWrite не работают. Этот тип соединения полезен для организации обмена данными с помощью потоков (т.е. работа с сокетом как с файлом); Open - открытие сокета (аналогично присвоению значения True свойству Active); Close - закрытие сокета (аналогично присвоению значения False свойству Active); На этом все методы компонента TClientSocket исчерпываются. А Вы спросите: "А как же работать с сокетом? Как тогда пересылать данные?". Об этом Вы узнаете чуть дальше. OnConnect - как следует из названия, это событие возникает при установлении соединения. Т.е. в обработчике этого события уже можно начинать авторизацию или прием/передачу данных; OnConnecting - возникает при установлении соединения. Отличие от OnConnect в том, что соединение еще не установлено. Обычно такие промежуточные события используются для обновления статуса; OnDisconnect - возникает при закрытии сокета. Причем, закрытия как из Вашей программы, так и со строноны удаленного компьютера (либо из-за сбоя); OnError - продолжает грустную тему предыдущего события :). Возникает при ошибке в работе сокета. Следует отметить, что это событие не поможет Вам отловить ошибку в момент открытия сокета (Open). Для того, чтобы избежать выдачи виндозного сообщения об ошибке, надо заключить операторы открытия сокета в блок try..except (обработка исключительных ситуаций); OnLookup - возникает при попытке получения от DNS IP-адреса указанного хоста; OnRead - возникает, когда удаленный компьютер послал Вам какие-либо данные. При возникновении этого события возможна обработка данных; OnWrite - возникает, когда Вам разрешена запись данных в сокет.
Практика и примеры Легче всего (и полезней) изучать любые методы программирования на практике. Поэтому далее приведены примеры с некоторыми комментариями:

Пример 1. Простейшая сокетная программа.

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}
{В форму нужно поместить кнопку TButton и два TEdit.
При нажатии на кнопку вызывается обработчик события OnClick - Button1Click.
Перед этим в первый из TEdit-ов нужно ввести хост-имя,
а во второй - порт удаленного компьютера.
НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TClientSocket!}
procedure Button1Click(Sender: TObject);
begin
  {Присваиваем свойствам Host и Port нужные значения}
  ClientSocket1.Host := Edit1.Text;
  ClientSocket1.Port := StrToInt(Edit2.Text);
  {Пытаемся открыть сокет и установить соединение}
  ClientSocket1.Open;
end;
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin
  {Как только произошло соединение - закрываем сокет и прерываем связь}
  ClientSocket1.Close;
end;
Если Вы думаете, что данный пример программы совершенно бесполезен и не может принести никакой пользы, то глубоко ошибаетесь. Приведенный код - простейший пример сканера портов (PortScanner). Суть такой утилиты в том, чтобы проверять, включен ли указанный порт и готов ли он к приему/передаче данных. Именно на таком принципе основан PortScanner из программы NetTools Pro. Далее следует другой пример, в котором по сокету передаются и принимаются текстовые сообщения:

Пример 2. Посылка/прием текстовых сообщений по сокетам.

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}
{В форму нужно поместить две кнопки TButton и три TEdit.
При нажатии на первую кнопку вызывается обработчик события OnClick - Button1Click.
Перед этим в первый из TEdit-ов нужно ввести хост-имя,
а во второй - порт удаленного компьютера. После установления соединения
можно посылать текстовые сообщения, вводя текст в третий TEdit и нажимая
вторую кнопку TButton. Чтобы отсоединиться, нужно еще раз нажать первую TButton.
Еще нужно добавить TListBox, в который будем помещать принятые
и отправленные сообщения.
НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TClientSocket!}
procedure Button1Click(Sender: TObject);
begin
  {Если соединение уже установлено - прерываем его.}
  if ClientSocket1.Active then
  begin
    ClientSocket1.Close;
    Exit; {...и выходим из обработчика}
  end;
  {Присваиваем свойствам Host и Port нужные значения}
  ClientSocket1.Host := Edit1.Text;
  ClientSocket1.Port := StrToInt(Edit2.Text);
  {Пытаемся открыть сокет и установить соединение}
  ClientSocket1.Open;
end;
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin
  {Как только произошло соединение - посылаем приветствие}
  Socket.SendText('Hello!');
  ListBox1.Items.Add('< Hello!');
end;
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
begin
  {Если пришло сообщение - добавляем его в ListBox}
  ListBox1.Items.Add('> ' + Socket.ReceiveText);
end;
procedure Button2Click(Sender: TObject);
begin
  {Нажата кнопка - посылаем текст из третьего TEdit}
  ClientSocket1.Socket.SendText(Edit3.Text);
  ListBox1.Items.Add('< ' + Edit3.Text);
end;
ПРИМЕЧАНИЕ: В некоторых случаях (зависящих от сервера) нужно после каждого сообщения посылать перевод строки:
  ClientSocket1.Socket.SendText(Edit3.Text + #10);
Работа с сокетным потоком "А как еще можно работать с сокетом?", - спросите Вы. Естественно, приведенный выше метод - не самое лучшее решение. Самих методов организации работы с сокетами очень много. Я приведу лишь еще один дополнительный - работа через поток. Наверняка, многие из Вас уже имеют опыт работы, если не с потоками (stream), то с файлами - точно. Для тех, кто не знает, поток - это канал для обмена данными, работа с которым аналогична работе с обычным файлом. Нижеприведенный пример показывает, как организовать поток для работы с сокетом:

Пример 3. Поток для работы с сокетом.

procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var
  c: Char;
  MySocket: TWinSocketStream;
begin
  {Как только произошло соединение - создаем поток и
  ассоциируем его с сокетом (60000 - таймаут в мсек)}
  MySocket := TWinSocketStream.Create(Socket, 60000);
  {Оператор WaitForData ждет данных из потока указанное время в мсек
  (в данном примере - 100) и возвращает True, если получен хотя бы один
  байт данных, False - если нет никаких данных из потока.}
  while not MySocket.WaitForData(100) do
    Application.ProcessMessages;
  {Application.ProcessMessages позволяет Windows перерисовать нужные
  элементы окна и дает время другим программам. Если бы этого оператора
  не было и данные бы довольно долго не поступали,
  то система бы слегка "подвисла".}
  MySocket.Read(c, 1);
  {Оператор Read читает указанное количество байт из потока
  (в данном примере - 1) в указанную переменную определенного типа
  (в примере - в переменную c типа Char). Обратите внимание на то,
  что Read, в отличие от ReadBuffer, не устанавливает строгих
  ограничений на количество принятой информации. Т.е. Read читает
  не больше n байтов из потока (где n - указанное число).
  Эта функция возвращает количество полученных байтов данных.}
  MySocket.Write(c, 1);
  {Оператор Write аналогичен оператору Read, за тем лишь исключением,
  что Write пишет данные в поток.}
  MySocket.Free;
  {Не забудем освободить память, выделенную под поток}
end;
ПРИМЕЧАНИЕ: Для использования потока не забудьте установить свойство ClientType в ctBlocking. Посылка и прием сложных данных Иногда необходимо пересылать по сети не только простые текстовые сообщения, но и сложные структуры (тип record в Паскале), или даже файлы. И тогда Вам необходимо использовать специальные операторы. Некоторые из них перечислены ниже:

Методы TClientSocket.Socket (TCustomWinSocket, TClientWinSocket):

SendBuf(var Buf; Count: Integer) - Посылка буфера через сокет. Буфером может являться любой тип, будь то структура (record), либо простой Integer. Буфер указывается параметром Buf, вторым параметром Вы должны указать размер пересылаемых данных в байтах (Count); SendText(const S: string) - Посылка текстовой строки через сокет. Этот метод рассматривался в примере 2 (см.выше); SendStream(AStream: TStream) - Посылка содержимого указанного потока через сокет. Пересылаемый поток должен быть открыт. Поток может быть любого типа - файловый, из ОЗУ, и т.д. Описание работы непосредственно с потоками выходит за рамки данной статьи; Всем перечисленным методам соответствуют методы Receive... Их описание можно посмотреть в справочном файле по Дельфи (VCL help). Авторизация на сервере Напоследок хочу привести несложный пример того, как можно реализовать авторизацию (вход на сервер). В данном примере пароль посылается нешифрованным текстом, так что если Вам нужен действительно надежный механизм входа, то Вам придется внести кое-какие изменения в исходник данного примера. Пример реализован как работа с сокетным потоком.

Пример 4. Авторизация.

{В данном примере нужно добавить в форму еще два
TEdit - Edit3 и Edit4 для ввода логина и пароля}
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var
  c: Char;
  MySocket: TWinSocketStream;
  login, password: string;
begin
  MySocket := TWinSocketStream.Create(Socket, 60000);
  {Добавляем к логину и паролю символ перевода строки,
  чтобы сервер смог отделить логин и пароль.}
  login := Edit3.Text + #10;
  password := Edit4.Text + #10;
  MySocket.Write(login, Length(Edit3.Text) + 1);
  MySocket.Write(password, Length(Edit4.Text) + 1);
  while not MySocket.WaitForData(100) do
    Application.ProcessMessages;
  MySocket.Read(c, 1);
  {Здесь сервер посылает нам один байт, значение 1 которого
  соответствует подтверждению успешной авторизации, а 0 - ошибку
  (это лишь пример). Далее мы выполняем нужные действия
  (прием/пересылку данных) и закрываем поток.}
  MySocket.Free;
end;
Эпилог В этой статье я написал лишь самую малость из того, что можно было бы сказать про сокеты. Может быть, когда у меня появится новый прилив сил, я дополню эту статью еще более интересным материалом.

В ближайшем будущем планирую также статью про сокетные серверы (TServerSocket).

Добавление функций проверки орфографии в разрабатываемые приложения.

Статьи по Delphi (005)…
Майкрософт Ворд не понимает слово "стрипризерша", то есть подчеркивает красным, а когда кликаешь на него мышкой, то в возможных исправлениях он выдает "стриптиз ерша".

Предположим, что в вашу задачу, как разработчика программного обеспечения, входит создание некоторого специализированного текстового процессора. Не вдаваясь в рассуждения о необходимости создания еще одного приложения подобного рода, мы просто рассмотрим один прием, который придаст вашей разработке весьма ощутимое преимущество по сравнению с аналогами. К примеру, вам необходимо создать некий HTML-редактор. Как и в случае с любым другим приложением такого типа, ваша программа должна будет обладать функциями орфографической проверки текста. Естественно, можно потратить много времени на создание своего собственного шедевра в данной области, но почему бы нам не воспользоваться уже готовыми решениями? В рамках данной статьи я бы хотел поговорить о технологии использования в ваших приложениях механизмов проверки орфографии, входящих в состав всем известного приложения - Microsoft Word с использованием автоматизации (OLE Automation).

OLE Automation

Идея, заложенная в автоматизацию, включает разработку приложений, функциональность которых может быть доступна и другим программам, а также создание приложений, которые "знают", как использовать функциональность, предоставляемую вам другими программными продуктами. Если говорить техническим языком, приложение, которое предоставляет некоторую повторно используемую функциональность, называется сервером автоматизации (automation server) (также часто называемым сервером COM). Приложение же, использующее функциональность, предоставляемую сервером автоматизации, называется клиентом автоматизации (automation client), также часто называемым контроллером автоматизации. Важно подчеркнуть, что сервер автоматизации может не быть "чистым" сервером автоматизации, так же как и клиент автоматизации может не быть "чистым" клиентом автоматизации. В действительности сервер автоматизации может использовать сервисы другого приложения, которое также является сервером автоматизации. Клиент автоматизации, предоставляющий свои сервисы другому клиенту, также может являться как клиентом, так и сервером автоматизации. Глубинные механизмы (сетевые и транспортные протоколы), с помощью которых клиент автоматизации взаимодействует с сервером, уже являются частью собственно COM.

Сервер автоматизации - это просто двоичный исполняемый модуль, который может состоять из нескольких объектов автоматизации. Объект автоматизации (также называемый объектом COM, хотя технически объект автоматизации является объектом COM особого сорта) - это отдельный, самодостаточный объект, спроектированный для выполнения специфической задачи или функции. В общем, все объекты автоматизации, собранные в одном сервере, предназначены для осуществления каких-то функциональных возможностей. Например, Microsoft Excel является сервером автоматизации, состоящим из нескольких меньших серверов автоматизации (Workbook - книга, Chart - диаграмма, Worksheet - лист, Range - диапазон и т.д.), каждый из которых определяет часть функций, предоставляемых пользователю Microsoft Excel. Идея заключается в том, что сервер автоматизации "позволяет" своим клиентам получать доступ и использовать свои объекты так же легко и просто, как будто это его внутренние объекты.

Для решения задачи, поставленной перед нами в начале данной статьи, мы можем воспользоваться теми возможностями, которые предоставляет нам сервер автоматизации Microsoft Word. C помощью приложения, разработанного в Borland Delphi (программа будет выступать в качестве клиента автоматизации), мы сможем динамически создать новый документ и поместить в него некоторый текст (который и будем проверять). После этого нам останется лишь с помощью MS Word осуществить эту проверку. Если приложение Word будет минимизировано, то пользователи могут и не почувствовать, что выполнение части функций нашего приложения берет на себя другая программа. Обращаю внимание, что для полноценного использования OLE-автоматизации вам надо будет знать как можно больше о возможностях и интерфейсах того приложения, функциональностью которого вы решили воспользоваться. Кроме того, для корректного выполнения всех функций разрабатываемого приложения необходимо, чтобы на компьютере пользователя было установлено соответствующее приложение. В нашем случае - Microsoft Word.

Основные принципы работы

Существует три основных метода использования OLE-автоматизации в Borland Delphi в зависимости от того, какую версию этой среды разработки вы используете.

Delphi 5. Закладка Servers на палитре компонентов.

Если вы являетесь счастливым обладателем этой версии Delphi, то для работы с Microsoft Word можно воспользоваться компонентами, расположенными на закладке Ser-vers (рис. 1). Такие компоненты, как TWord-Application и TWordDocument, предоставляют все необходимые для работы интерфейсы.

Delphi 3, 4. Раннее связывание.

Используя термины автоматизации, для обеспечения в Delphi доступа к методам и свойствам, предоставляемым MS Word, необходимо установить соответствующую библиотеку типов. Библиотека типов предоставляет информацию обо всех свойствах и методах, которые разработчик может использовать при работе с сервером автоматизации. Для использования библиотеки типов Microsoft Word в Delphi (3 или 4 версии) необходимо произвести следующие несложные действия:

выбрать пункт меню Project|Import Type Library; в открывшемся диалоге найти файл msword8.olb (для Microsoft Office'2000 этот файл будет иметь название msword9.olb), расположенный в подкаталоге "Office" того каталога, в который был установлен Microsoft Office.

После этого будет создан файл с именем word_TLB.pas, в котором в синтаксисе object pascal будут описаны константы, типы, свойства и методы для доступа к серверу автоматизации Microsoft Word. Файл word_TLB.pas должен быть включен в список uses всех модулей, в которых вы планируете использовать функции Microsoft Word. Такая технология работы с серверами автоматизации называется ранним связыванием. Одним из преимуществ раннего связывания является осуществление контроля вызовов и передаваемых параметров на этапе компиляции.

Delphi 2. Позднее связывание.

Для доступа к объектам MS Word без применения библиотеки типов можно использовать так называемое позднее связывание. В данном случае доступ к Word осуществляется так же, как к переменной типа Variant, следствием чего является необходимость знания вами всех предоставляемых сервером автоматизации интерфейсов. Позднего связывания следует по возможности избегать, поскольку при этом отсутствует возможность контроля корректности вызовов процедур и функций со стороны компилятора, и если вы неправильно написали имя того или иного метода, то узнаете об этом, только, когда программа "вывалится" по ошибке в процессе выполнения.

Начнем!

Итак, вернемся к теме статьи. Для демонстрации принципов работы с MS Word я буду использовать механизмы, предоставляемые пятой версией Delphi (т.е. компоненты TWordApplication, TWordDocument). Ниже я приведу код, обеспечивающий соединение и работу с MS Word в случае использования библиотеки типов и позднего связывания и больше не буду касаться этой темы.

Для доступа к объектам Word при работе в Delphi 3, 4 (запуск приложения и создание нового документа) используйте следующий код:

uses
  Word_TLB;
...
var
  WordApp: _Application;
  WordDoc: _Document;
  VarFalse: OleVariant;
begin
  WordApp := CoApplication.Create;
  WordDoc := WordApp.Documents.Add(EmptyParam, EmptyParam);
  {
  код для проверки орфографии, описываемы далее в данной статье
  }
  VarFalse:=False;
  WordApp.Quit(VarFalse, EmptyParam, EmptyParam);
end;

Обращаю внимание, что в методах MS Word множество параметров описаны как необязательные (optional). При использовании интерфейсов (библиотек типов), Delphi не позволит вам опускать те или иные параметры, даже если в контексте разрабатываемого вами кода они не нужны. В четвертой версии Delphi в модуле system.pas описана переменная EmptyParam, которую можно использовать в качестве "заглушки" для неиспользуемых переменных в вызываемом методе.

Для автоматизации MS Word с использованием переменной Variant (позднее связывание) используйте следующий код:

uses
  ComObj;
...
var
  WordApp, WordDoc: Variant;
begin
  WordApp := CreateOleObject('Word.Application');
  WordDoc := WordApp.Documents.Add;
  {
  код для проверки орфографии, описываемы далее в данной статье
  }
  WordApp.Quit(False)
end;

При использовании позднего связывания компилятор Delphi позволяет вам опускать те или иные параметры при вызове методов сервера автоматизации.

Как уже упоминалось, Delphi 5 упрощает программисту использование функциональности MS Word в своих приложениях путем предоставления его методов и свойств в виде компонентов. Так как множество параметров, определенных в методах Wоrd'а, описаны как необязательные, то в Delphi данные процедуры и функции переопределены и представляют собой набор из нескольких методов с различным количеством параметров. Таким образом, разработчику предоставляется возможность при вызове метода не указывать последние n параметров, необходимость в которых отсутствует.

Шаг за шагом

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

Если у вас не запущен Delphi - запустите его. Создайте новый проект (если он не был создан при открытии приложения). По умолчанию проект будет содержать одну форму. Данная форма будет главной в нашем проекте. Поместите на форму один компонент типа TMemo и две кнопки (TButton). Заполните свойство Lines компонента Memo1 каким-нибудь текстом (содержащим ошибки). Заголовок одной кнопки определите как "Орфография", а второй - "Тезаурус". Затем перейдите на закладку Servers палитры компонентов и поместите на форму по одному компоненту типа TWordApplication и TWordDocument (рис. 2). Установите значения свойства Name первого компонента в Word-App, а второго - WordDoc.

TWordApplication, TWordDocument

При автоматизации MS Word для управления приложением, отображения его рабочего окна, получения доступа к атрибутам и объектной модели MS Word мы используем объект Application. Для того чтобы указать приложению, запускать ли новую копию процесса Word или использовать уже запущенный, применяется свойство Applicati-on.ConnectKind. В нашем случае мы устанавливаем данное свойство в значение ckRunningInstance. Другие возможные значения этого свойства вы сможете узнать, воспользовавшись справочной системой Delphi.

Когда мы открываем в MS Word существующий файл или создаем новый, мы тем самым создаем объект Document. Типичной задачей при использовании автоматизации Word является работа с некоторой областью документа: добавление текста, выделение некоторой области, проверка орфографии и т.д. Объект, определяющий некоторую область в документе, называется Range.

Естественно, в рамках статьи я не смогу подробно рассказать обо всех нюансах работы с компонентами, расположенными на закладке Servers палитры компонентов (кстати, с любой другой закладкой ситуация состоит ничуть не лучше). Для более детального их изучения предлагаю воспользоваться справочной системой Borland Delphi. В нашем же сегодняшнем разговоре я буду упоминать только те свойства и методы, которые будут необходимы.

Как это все будет работать

Алгоритм работы нашего приложения будет достаточно прост. Каждое слово, входящее в состав проверяемого нами текста, будет передаваться в MS Word для проверки. Сервер автоматизации Word содержит метод SpellingErrors, который позволяет вам осуществлять проверку текста, входящего в состав некоторой области Range. Мы же будем каждый раз определять эту область таким образом, чтобы она содержала только переданное нами в Word слово. Метод SpellingErrors в качестве результата своей работы возвращает коллекцию слов, написание которых признано ошибочным. Если эта коллекция пуста, то мы переходим к рассмотрению следующего слова. Иначе - переходим к процедуре замены неправильно напечатанного слова. Путем вызова метода GetSpellingSuggestions можно получить список слов, предлагаемых в качестве замены. Эти слова помещаются в коллекцию SpellingSuggestions. Данную коллекцию мы помещаем в качестве списка (компонент типа TListBox), расположенного во второй форме нашего проекта. Думаю, самое время немного поговорить о ней.

Для того чтобы добавить новую форму в проект, следует выбрать пункт меню File|New Form. Назовем эту форму frSpellCheck. На форму поместим три кнопки типа TBitBtn, два элемента редактирования (TEdit) и один список (TListBox). На форму также следует поместить три метки (см. рис. 3). Компонент edNID (editNotInDictionary) служит для отображения заменяемого слова. edReplaceWith содержит выделенный в данный момент вариант для замены, а список lbSuggestions - список предлагаемых вариантов (заполняемый на основании данных, содержащихся в коллекции SpellingSuggestions). Три кнопки выполняют именно те функции, которым соответствуют их заголовки - не больше и не меньше. Каждой из кнопок соответствует свое значение, возвращаемое функцией frSpellCheck.ModalResult. В зависимости от этого значения в основной обрабатывающей процедуре осуществляется то или иное действие - игнорирование, замена или отмена дальнейшей проверки. Форма frSpellCheck содержит одно общедоступное свойство:

sReplacedWord :String

Оно служит для передачи в основную форму слова для замены в случае нажатия пользователем кнопки "Заменить".

Пишем код!

Ниже приводится код основной процедуры приложения.

procedure TForm1.btnSpellCheckClick(Sender: TObject);
var
  colSpellErrors : ProofreadingErrors;
  colSuggestions : SpellingSuggestions;
  i : Integer;
  StopLoop : Boolean;
  itxtLen, itxtStart : Integer;
  varFalse : OleVariant;
begin
  WordApp.Connect;
  WordDoc.ConnectTo(WordApp.Docum-ents.Add(EmptyParam, EmptyParam));
  StopLoop:=False;
  itxtStart:=0;
  Memo.SelStart:=0;
  itxtlen:=0;
  while not StopLoop do
  begin
    itxtStart := itxtLen + itxtStart;
    itxtLen := Pos(' ', Copy(Memo.Text,itxtStart+1,MaxInt));
    if itxtLen = 0 then
      StopLoop := True;
    Memo.SelStart := itxtStart;
    Memo.SelLength := -1 + itxtLen;
    if Memo.SelText = '' then
      Continue;
    Caption:=Memo.SelText;
    WordDoc.Range.Delete(EmptyParam,Emp-tyParam);
    WordDoc.Range.Set_Text(Memo.SelText);
    colSpellErrors := WordDoc.SpellingErrors;
    if colSpellErrors.Count <> 0 then
    begin
      colSuggestions := WordApp.GetSpellingSuggestions
      (colSpellErrors.Item(1).Get_Text);
      with frSpellCheck do
      begin
        edNID.text := colSpellErrors.Item(1).Get_Text;
        lbSuggestions.Items.Clear;
        for i:= 1 to colSuggestions.Count do
          lbSuggestions.Items.Add(VarToStr-(colSuggestions.Item(i)));
        lbSuggestions.ItemIndex := 0;
        lbSuggestionsClick(Sender);
        ShowModal;
        case frSpellCheck.ModalResult of
          mrAbort: Break;
          mrIgnore: Continue;
          mrOK:
            if sReplacedWord <> '' then
            begin
              Memo.SelText := sReplacedWord;
              itxtLen := Length(sReplacedWord);
            end;
        end;
      end;
    end;
  end;
  WordDoc.Disconnect;
  varFalse:=False;
  WordApp.Quit(varFalse);
  Memo.SelStart := 0;
  Memo.SelLength := 0;
end;

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

procedure TfrSpellCheck.lbSuggestionsClick(Sen-der: TObject);
begin
  if lbSuggestions.ItemIndex <> -1 then
    edReplaceWith.Text := lbSuggestions.Items[lbSuggestio-ns.ItemIndex]
  else
    edReplaceWith.Text := '';
end;
procedure TfrSpellCheck.btnChangeClick(Sender: TObject);
begin
  sReplacedWord := edReplaceWith.Text;
end;
procedure TfrSpellCheck.btnIgnoreClick(Sender: TObject);
begin
  sReplacedWord := '';
end;

Тезаурус

Теперь рассмотрим вопрос добавления в нашу программу функций тезауруса. Делается это достаточно просто:

procedure TForm1.btnThesaurusClick(Sender: TObject);
var
  varFalse : OleVariant;
begin
  if Memo.SelText <> '' then
  begin
    WordApp.Connect;
    WordDoc.ConnectTo(WordApp.Documen-ts.Add(EmptyParam, EmptyParam));
    WordDoc.Range.Delete(EmptyParam,Empty-Param);
    WordDoc.Range.Set_Text(Memo.SelText);
    WordDoc.Range.CheckSynonyms;
    Memo.SelText := WordDoc.Range.Get_Text;
    WordDoc.Disconnect;
    varFalse:=False;
    WordApp.Quit(varFalse);
  end;
end;

Тестирование

В тексте, помещенном в компонент Memo, мною было сознательно сделано несколько ошибок, которые вы сможете увидеть, приглядевшись к изображению, представленному на рисунке 1. В частности, вместо слова "своих" я написал "свиох", вместо "путем" - "пуетм", а вместо "виде" - "виед". Как же повела себя программа? На следующих рисунках (рисунки 4-6) можно видеть, что проверка текста действительно работает.

Надеюсь, вы понимаете, что в рамках одной статьи невозможно описать все те возможности, которые открываются перед разработчиком программного обеспечения в случае использования серверов автоматизации. И речь идет не только о Microsoft Word, но и о других приложениях (к примеру, широко распространено применение MS Excel в качестве базы для построения отчетов). Все разнообразие данного направления программирования можно познать, на мой взгляд, только через собственный опыт. Так что удачного вам кода!

SQL-запросы в Delphi.

Примечание: Данный документ представляет собой коллективный труд нескольких авторов, которые индивидуально несут ответственность за качество предоставленной здесь информации. Borland не предоставлял, и не может предоставить никакой гарантии относительно содержимого данного документа. Введение Компонент TQuery Свойство SQL Свойство Params Источник данных Команда Format Open против ExecSQL Компонент TStoredProc TDatabase Выводы 1. Введение Компоненты Delphi для работы с базами данных были созданы в расчете на работу с SQL и архитектурой клиент/сервер. При работе с ними вы можете воспользоваться характеристиками расширенной поддержки удаленных серверов. Delphi осуществляет эту поддержку двумя способами. Во-первых, непосредственные команды из Delphi позволяют разработчику управлять таблицами, устанавливать пределы, удалять, вставлять и редактировать существующие записи. Второй способ заключается в использовании запросов на языке SQL, где строка запроса передается на сервер для ее разбора, оптимизации, выполнения и передачи обратно результатов. Данный документ делает акцент на втором методе доступа к базам данных, на основе запросов SQL (pass-through). Авторы не стремились создать курсы по изучению синтаксиса языка SQL и его применения, они ставили перед собой цель дать несколько примеров использования компонентов TQuery и TStoredProc. Но чтобы сделать это, необходимо понимать концепцию SQL и знать как работают selects, inserts, updates, views, joins и хранимые процедуры (stored procedures). Документ также вскользь касается вопросов управления транзакциями и соединения с базой данных, но не акцентирует на этом внимание. Итак, приступая к теме, создайте простой запрос типа SELECT и отобразите результаты. 2. Компонент TQuery Если в ваших приложениях вы собираетесь использовать SQL, то вам непременно придется познакомиться с компонентом TQuery. Компоненты TQuery и TTable наследуются от TDataset. TDataset обеспечивает необходимую функциональность для получения доступа к базам данных. Как таковые, компоненты TQuery и TTable имеют много общих признаков. Для подготовки данных для показа в визуальных компонентах используется все тот же TDatasource. Также, для определения к какому серверу и базе данных необходимо получить доступ, необходимо задать имя псевдонима. Это должно выполняться установкой свойства aliasName объекта TQuery. Свойство SQL Все же TQuery имеет некоторую уникальную функциональность. Например, у TQuery имеется свойство с именем SQL. Свойство SQL используется для хранения SQL-запроса. Ниже приведены основные шаги для составления запроса, где все служащие имеют зарплату свыше $50,000. Создайте объект TQuery Задайте псевдоним свойству DatabaseName. (Данный пример использует псевдоним IBLOCAL, связанный с демонстрационной базой данных employee.gdb). Выберите свойство SQL и щелкните на кнопке с текстом - '...' (три точки, Инспектор Объектов - В.О.). Должен появиться диалог редактора списка строк (String List Editor). Введите: Select * from EMPLOYEE where SALARY>50000. Нажмите OK. Выберите в Инспекторе Объектов свойство Active и установите его в TRUE. Разместите на форме объект TDatasource. Установите свойство Dataset у TDatasource в Query1. Разместите на форме TDBGrid. Установите его свойство Datasource в Datasource1.Свойство SQL имеет тип TStrings. Объект TStrings представляет собой список строк, и чем-то похож на массив. Тип данных TStrings имеет в своем арсенале команды добавления строк, их загрузки из текстового файла и обмена данными с другим объектом TStrings. Другой компонент, использующий TStrings - TMemo. В демонстрационном проекте ENTRSQL.DPR (по идее, он должен находится на отдельной дискетте, но к "Советам по Delphi" она не прилагается - В.О.), пользователь должен ввести SQL-запрос и нажать кнопку "Do It" ("сделать это"). Результаты запроса отображаются в табличной сетке. В Листинге 1 полностью приведен код обработчика кнопки "Do It".

Листинг 1.

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
Query1.close; {Деактивируем запрос в качестве одной из мер предосторожности }
Query1.SQL.Clear; {Стираем любой предыдущий запрос}
If Memo1.Lines[0] <> '' {Проверяем на предмет пустого ввода} then
Query1.SQL.Add(Memo1.Text) {Назначаем свойству SQL текст Memo}
else
begin
messageDlg('Не был введен SQL-запрос', mtError, [mbOK], 0);
exit;
end;
try        {перехватчик ошибок}
Query1.Open;     {Выполняем запрос и открываем набор данных}
except     {секция обработки ошибок}
On e : EDatabaseError do {e - новый дескриптор ошибки}
messageDlg(e.message,
mtError,
[mbOK],0); {показываем свойство message объекта e}
end;      {окончание обработки ошибки}
end;
Свойство Params Этого должно быть достаточно для пользователя, знающего SQL. Тем не менее, большинство пользователей не знает этого языка. Итак, ваша работа как разработчика заключается в предоставлении интерфейса и создании SQL-запроса. В Delphi, для создания SQL-запроса на лету можно использовать динамические запросы. Динамические запросы допускают использование параметров. Для определения параметра в запросе используется двоеточие (:), за которым следует имя параметра. Ниже приведе пример SQL-запроса с использованием динамического параметра:
          select * from EMPLOYEE
          where DEPT_NO = :Dept_no
Если вам нужно протестировать, или установить для параметра значение по умолчанию, выберите свойство Params объекта Query1. Щелкните на кнопке '...'. Должен появиться диалог настройки параметров. Выберите параметр Dept_no. Затем в выпадающем списке типов данных выберите Integer. Для того, чтобы задать значение по умолчанию, введите нужное значение в поле редактирования "Value".

Для изменения SQL-запроса во время выполнения приложения, параметры необходимо связать (bind). Параметры могут изменяться, запрос выполняться повторно, а данные обновляться. Для непосредственного редактирования значения параметра используется свойство Params или метод ParamByName. Свойство Params представляет из себя массив TParams. Поэтому для получения доступа к параметру, необходимо указать его индекс. Для примера,

Query1.params[0].asInteger := 900;

Свойство asInteger читает данные как тип Integer (название говорит само за себя). Это не обязательно должно указывать но то, что поле имеет тип Integer. Например, если тип поля VARCHAR(10), Delphi осуществит преобразование данных. Так, приведенный выше пример мог бы быть записан таким образом:

Query1.params[0].asString := '900';

Или так:

Query1.params[0].asString := edit1.text;

Если вместо номера индекса вы хотели бы использовать имя параметра, то воспользуйтесь методом ParamByName. Данный метод возвращает объект TParam с заданным именем. Например:

Query1.ParamByName('DEPT_NO').asInteger := 900;
В листинге 2 приведен полный код примера.

Листинг 2.

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
Query1.close;     {Деактивируем запрос в качестве одной из мер предосторожности }
if not Query1.prepared
then
Query1.prepare; {Убедимся что запрос подготовлен}
{Берем значение, введенное пользователем и заменяем
им параметр.}
if edit1.text <> '' {Проверяем на предмет пустого ввода}
then
Query1.ParamByName('DEPT_NO').AsString := edit1.text
else
Begin
Query1.ParamByName('DEPT_NO').AsInteger := 0;
edit1.text := '0';
end;
try        {перехватчик ошибок}
Query1.Open;     {Выполняем запрос и открываем набор данных}
except     {секция обработки ошибок}
On e : EDatabaseError do {e - новый дескриптор ошибки} messageDlg(e.message,
mtError,
[mbOK],0); {показываем свойство message объекта e}
end;     {окончание обработки ошибки}
end;
Обратите внимание на процедуру, первым делом подготовливающую запрос. При вызове метода prepare, Delphi посылает SQL запрос на удаленный сервер. Сервер выполняет грамматический разбор и оптимизацию запроса. Преимущество такой подготовки запроса состоит в его предварительном разборе и оптимизации. Альтернативой здесь может служить подготовка сервером запроса при каждом его выполнении. Как только запрос подготовлен, подставляются необходимые новые параметры, и запрос выполняется. Источник данных В предыдущем примере пользователь мог ввести номер отдела, и после выполнения запроса отображался список сотрудников этого отдела. А как насчет использования таблицы DEPARTMENT, позволяющей пользователю легко перемещаться между пользователями и отделами? Примечание: Следующий пример использует TTable с именем Table1. Для Table1 имя базы данных IBLOCAL, имя таблицы - DEPARTMENT. DataSource2 TDatasource связан с Table1. Таблица также активна и отображает записи в TDBGrid. Способ подключения TQuery к TTable - через TDatasource. Есть два основных способа сделать это. Во-первых, разместить код в обработчике события TDatasource OnDataChange. Например, листинг 3 демонстрирует эту технику.

Листинг 3 - Использования события OnDataChange для просмотра дочерних записей.

procedure TForm1.DataSource2DataChange(Sender: TObject; Field: TField);
begin
Query1.Close;
if not Query1.prepared
then
Query1.prepare;
Query1.ParamByName('Dept_no').asInteger := Table1Dept_No.asInteger;
try
Query1.Open;
except
On e : EDatabaseError do
messageDlg(e.message, mtError, [mbOK], 0);
end;
end;
Техника с использованием OnDataChange очень гибка, но есть еще легче способ подключения Query к таблице. Компонент TQuery имеет свойство Datasource. Определяя TDatasource для свойства Datasource, объект TQuery сравнивает имена параметров в SQL-запросе с именами полей в TDatasource. В случае общих имен, такие параметры заполняются автоматически. Это позволяет разработчику избежать написание кода, приведенного в листинге 3 (*** приведен выше ***). Фактически, техника использования Datasource не требует никакого дополнительного кодирования. Для поключения запроса к таблице DEPT_NO выполните действия, приведенные в листинге 4. Листинг 4 - Связывание TQuery c TTable через свойство Datasource Выберите у Query1 свойство SQL и введите:
             select * from EMPLOYEE
             where DEPT_NO = :dept_no
Выберите свойство Datasource и назначьте источник данных, связанный с Table1 (Datasource2 в нашем примере) Выберите свойство Active и установите его в True Это все, если вы хотите создать такой тип отношений. Тем не менее, существуют некоторые ограничения на параметризованные запросы. Параметры ограничены значениями. К примеру, вы не можете использовать параметр с именем Column или Table. Для создания запроса, динамически изменяемого имя таблицы, вы могли бы использовать технику конкатенации строки. Другая техника заключается в использовании команды Format. Команда Format

Команда Format заменяет параметры форматирования (%s, %d, %n и пр.) передаваемыми значениями. Например,

Format('Select * from %s', ['EMPLOYEE'])

Результатом вышеприведенной команды будет 'Select * from EMPLOYEE'. Функция буквально делает замену параметров форматирования значениями массива. При использовании нескольких параметров форматирования, замена происходит слева направо. Например,

tblName := 'EMPLOYEE';
fldName := 'EMP_ID';
fldValue := 3;
Format('Select * from %s where %s=%d', [tblName, fldName, fldValue])
Результатом команды форматирования будет 'Select * from EMPLOYEE where EMP_ID=3'. Такая функциональность обеспечивает чрезвычайную гибкость при динамическом выполнении запроса. Пример, приведенный ниже в листинге 5, позволяет вывести в результатах поле salary. Для поля salary пользователь может задавать критерии.

Листинг 5 - Использование команды Format для создания SQL-запроса.

procedure TForm1.BitBtn1Click(Sender: TObject);
var
sqlString : string; {здесь хранится SQL-запрос}
fmtStr1,
fmtStr2 : string; {здесь хранится строка, передаваемая для форматирования}
begin
{ Создание каркаса запроса }
sqlString := 'Select EMP_NO %s from employee where SALARY %s';
if showSalaryChkBox.checked {Если checkbox Salary отмечен}
then
fmtStr1 := ', SALARY'
else
fmtStr1 := '';
if salaryEdit.text <> '' { Если поле редактирования Salary не пустое }
then
fmtStr2 := salaryEdit.text
else
fmtStr2 := '>0';
Query1.Close; {Деактивируем запрос в качестве одной из мер предосторожности }
Query1.SQL.Clear; {Стираем любой предыдущий запрос}
Query1.SQL.Add(Format(sqlString,[fmtStr1, fmtStr2]));{Добавляем}
{форматированную строку к свойству SQL}
try {перехватчик ошибок}
Query1.Open; {Выполняем запрос и открываем набор данных}
except {секция обработки ошибок}
On e : EDatabaseError do {e - новый дескриптор ошибки}
messageDlg(e.message, mtError,[mbOK],0);
{показываем свойство message объекта e}
end; {окончание обработки ошибки}
end;
В этом примере мы используем методы Clear и Add свойства SQL. Поскольку "подготовленный" запрос использует ресурсы сервера, и нет никакой гарантии что новый запрос будет использовать те же таблицы и столбцы, Delphi, при каждом изменении свойства SQL, осуществляет операцию, обратную "подготовке" (unprepare). Если TQuery не был подготовлен (т.е. свойство Prepared установлено в False), Delphi автоматически подготавливает его при каждом выполнении. Поэтому в нашем случае, даже если бы был вызван метод Prepare, приложению от этого не будет никакой пользы. Open против ExecSQL В предыдущих примерах TQuerie выполняли Select-запросы. Delphi рассматривает результаты Select-запроса как набор данных, типа таблицы. Это просто один класс допустимых SQL-запросов. К примеру, команда Update обновляет содержимое записи, но не возвращает записи или какого-либо значения. Если вы хотите использовать запрос, не возвращающий набор данных, используйте ExecSQL вместо Open. ExecSQL передает запрос для выполнения на сервер. В общем случае, если вы ожидаете, что получите от запроса данные, то используйте Open. В противном случае допускается использование ExecSQL, хотя его использование с Select не будет конструктивным. Листинг 6 содержит код, поясняющий сказанное на примере.

Листинг 6.

procedure TForm1.BitBtnClick(sender : TObject)
begin
Query1.Close;
Query1.Clear;
Query1.SQL.Add('Update SALARY from EMPLOYEE ' +
'where SALARY<:salary values (SALARY*(1+:raise)');
Query1.paramByName('salary').asString := edit1.text;
Query1.paramByName('raise').asString := edit2.text;
try
Query1.ExecSQL;
except
On e : EDatabaseError do
messageDlg(e.message, mtError, [mbOK], 0);
end;
end;
Все приведенные выше примеры предполагают использования в ваших приложениях запросов. Они могут дать солидное основание для того, чтобы начать использовать в ваших приложениях TQuery. Но все же нельзя прогнозировать конец использования SQL в ваших приложених. Типичные серверы могут предложить вам другие характеристики, типа хранимых процедур и транзакций. В следующих двух секциях приведен краткий обзор этих средств. 3. Компонент TStoredProc Хранимая процедура представляет собой список команд (SQL или определенного сервера), хранимых и выполняемых на стороне сервера. Хранимые процедуры не имеют концептуальных различий с другими типами процедур. TStoredProc наследуется от TDataset, поэтому он имеет много общих характеристик с TTable и TQuery. Особенно заметно сходство с TQuery. Поскольку хранимые процедуры не требуют возврата значений, те же правила действуют и для методов ExecProc и Open. Каждый сервер реализует работу хранимых процедур с небольшими различиями. Например, если в качестве сервера вы используете Interbase, хранимые процедуры выполняются в виде Select-запросов. Например, чтобы посмотреть на результаты хранимой процедуры, ORG_CHART, в демонстрационной базе данных EMPLOYEE, используйте следующих SQL-запрос:
 Select * from ORG_CHART
При работе с другими серверами, например, Sybase, вы можете использовать компонент TStoredProc. Данный компонент имеет свойства для имен базы данных и хранимой процедуры. Если процедура требует на входе каких-то параметров, используйте для их ввода свойство Params. 4. TDatabase Компонент TDatabase обеспечивает функциональность, которой не хватает TQuery и TStoredProc. В частности, TDatabase позволяет создавать локальные псевдонимы BDE, так что приложению не потребуются псевдонимы, содержащиеся в конфигурационном файле BDE. Этим локальным псевдонимом в приложении могут воспользоваться все имеющиеся TTable, TQuery и TStoredProc. TDatabase также позволяет разработчику настраивать процесс подключения, подавляя диалог ввода имени и пароля пользователя, или заполняя необходимые параметры. И, наконец, самое главное, TDatabase может обеспечивать единственную связь с базой данных, суммируя все операции с базой данных через один компонент. Это позволяет элементам управления для работы с БД иметь возможность управления транзакциями. Транзакцией можно считать передачу пакета информации. Классическим примером транзакции является передача денег на счет банка. Транзакция должна состоять из операции внесения суммы на новый счет и удаления той же суммы с текущего счета. Если один из этих шагов по какой-то причине был невыполнен, транзакция также считается невыполненной. В случае такой ошибки, SQL сервер позволяет выполнить команду отката (rollback), без внесения изменений в базу данных. Управление транзакциями зависит от компонента TDatabase. Поскольку транзакция обычно состоит из нескольких запросов, вы должны отметить начало транзакции и ее конец. Для выделения начала транзакции используйте TDatabase.BeginTransaction. Как только транзакция начнет выполняться, все выполняемые команды до вызова TDatabase.Commit или TDatabase.Rollback переводятся во временный режим. При вызове Commit все измененные данные передаются на сервер. При вызове Rollback все изменения теряют силу. Ниже в листинге 7 приведен пример, где используется таблица с именем ACCOUNTS. Показанная процедура пытается передать сумму с одного счета на другой.

Листинг 7.

procedure TForm1.BitBtn1Click(Sender: TObject);
{ ПРИМЕЧАНИЕ: Поле BALANCE у ACCOUNTS имеет триггер, проверяющий
ситуацию, когда вычитаемая сумма превышает BALANCE. Если так, UPDATE
будет отменен}
begin
try
database1.StartTransaction;
query1.SQL.Clear;
{ Вычитаем сумму из выбранного счета }
query1.SQL.Add(Format('update ACCOUNTS ' +
'set BALANCE = BALANCE - %s ) ' +
'where ACCT_NUM = %s ',
[edit1.text,
Table1Acct_Num.asString]));
query1.ExecSQL;
query1.SQL.Clear;
{ Добавляем сумму к выбранному счету }
query1.SQL.Add(Format('update ACCOUNTS ' +
'set BALANCE = BALANCE + %s ' +
'where ACCT_NUM = %s ',
[edit1.text,
Table2Acct_Num.asString]));
query1.ExecSQL;
database1.Commit; {В этом месте делаем все изменения}
table1.Refresh;
table2.Refresh;
except
{При возникновении в приведенном коде любых ошибок,
откатываем транзакцию назад}
One : EDatabaseError do
begin
messageDlg(e.message, mtError, [mbOK], 0);
database1.rollback;
exit;
end;
One : Exception do
begin
messageDlg(e.message, mtError, [mbOK], 0);
database1.rollback;
exit;
end;
end;
end;
И последнее, что нужно учесть при соединении с базой данных. В приведенном выше примере, TDatabase использовался в качестве единственного канала для связи с базой данных, поэтому было возможным выполнение только одной транзакции. Чтобы выполнить это, было определено имя псевдонима (Aliasname). Псевдоним хранит в себе информацию, касающуюся соединения, такую, как Driver Type (тип драйвера), Server Name (имя сервера), User Name (имя пользователя) и другую. Данная информация используется для создания строки соединения (connect string). Для создания псевдонима вы можете использовать утилиту конфигурирования BDE, или, как показано в примере ниже, заполнять параметры во время выполнения приложения. TDatabase имеет свойство Params, в котором хранится информация соединения. Каждая строка Params является отдельным параметром. В приведенном ниже примере пользователь устанавливает параметр User Name в поле редактирования Edit1, а параметр Password в поле Edit2. В коде листинга 8 показан процесс подключения к базе данных:

Листинг 8.

procedure TForm1.Button1Click(Sender: TObject);
begin
try
With database1 do
begin
Close;
DriverName := 'INTRBASE';
KeepConnection := TRUE;
LoginPrompt := FALSE;
With database1.Params do
begin
Clear;
Add('SERVER NAME=C:\IBLOCAL\EXAMPLES\EMPLOYEE.GDB');
Add('SCHEMA CACHE=8');
Add('OPEN MODE=READ/WRITE');
Add('SQLPASSTHRU MODE=SHARED NOAUTOCOMMIT');
Add('USER NAME=' + edit1.text);
Add('PASSWORD=' + edit2.text);
end;
Open;
end;
session.getTableNames(database1.databasename, '*',
TRUE,
TRUE,
ComboBox1.items);
Except
One : EDatabaseError do
begin
messageDlg(e.message, mtError, [mbOK], 0);
end;
end;
end;
Этот пример показывает как можно осуществить подключение к серверу без создания псевдонима. Ключевыми моментами здесь являются определение DriverName и заполнение Params информацией, необходимой для подключения. Вам не нужно определять все параметры, вам необходимо задать только те, которые не устанавливаются в конфигурации BDE определенным вами драйвером базы данных. Введенные в свойстве Params данные перекрывают все установки конфигурации BDE. Записывая параметры, Delphi заполняет оставшиеся параметры значениями из BDE Config для данного драйвера. Приведенный выше пример также вводит такие понятия, как сессия и метод GetTableNames. Это выходит за рамки обсуждаемой темы, достаточно упомянуть лишь тот факт, что переменная session является дескриптором database engine. В примере она добавлена только для "показухи". Другой темой является использование SQLPASSTHRU MODE. Этот параметр базы данных отвечает за то, как натив-команды базы данных, такие, как TTable.Append или TTable.Insert будут взаимодействовать с TQuery, подключенной к той же базе данных. Существуют три возможных значения: NOT SHARED, SHARED NOAUTOCOMMIT и SHARED AUTOCOMMIT. NOT SHARED означает, что натив-команды используют одно соединение с сервером, тогда как запросы - другое. Со стороны сервера это видится как работа двух разных пользователей. В любой момент времени, пока транзакция активна, натив-команды не будут исполняться (committed) до тех пор, пока транзакция не будет завершена. Если был выполнен TQuery, то любые изменения, переданные в базу данных, проходят отдельно от транзакции. Два других режима, SHARED NOAUTOCOMMIT и SHARED AUTOCOMMIT, делают для натив-команд и запросов общим одно соединение с сервером. Различие между двумя режимами заключаются в передаче выполненной натив-команды на сервер. При выбранном режиме SHARED AUTOCOMMIT бессмысленно создавать транзакцию, использующую натив-команды для удаления записи и последующей попыткой осуществить откат (Rollback). Запись должна быть удалена, а изменения должны быть сделаны (committed) до вызова команды Rollback. Если вам нужно передать натив-команды в пределах транзакции, или включить эти команды в саму транзакцию, убедитесь в том, что SQLPASSTHRU MODE установлен в SHARED NOAUTOCOMMIT или в NOT SHARED. 5. Выводы

Delphi поддерживает множество характеристик при использовании языка SQL с вашими серверами баз данных. На этой ноте разрешите попращаться и пожелать почаще использовать SQL в ваших приложениях.

Получение данных из Delphi-приложения в документе Word.

Сделайте следующее: Создайте макрос в Word:
Declare Function StringFromDelphi Lib "c:\sample\test.dll" As String
Sub MAIN
mystring$ = StringFromDelphi
Insert mystring$
End Sub
Создайте простой TEST.DLL в Delphi - просто форма с кнопкой. Сохраните это (например в c:\sample - смотри макрос Word) как test.dpr и testform.pas. Теперь добавьте к вашему проекту экспортируемую функцию 'StringFromDelphi' и 'close' на нажатие кнопки. Вы можете использовать следующий код:
library Test;  (* test.dpr в c:\sample *)
uses Testform in 'TESTFORM.PAS';
exports
StringFromDelphi;
begin
end.
unit Testform; (* testform.pas в c:\sample *)
interface
uses
  WinTypes, WinProcs, Forms, Classes, Controls, StdCtrls, SysUtils;
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;
var
  Form1: TForm1;
function StringFromDelphi: PChar; export;
{$IFDEF WIN32}stdcall;
{$ENDIF}
implementation
{$R *.DFM}
function StringFromDelphi: Pchar;
var
  StringForWord: array[0..255] of char;
begin
  Application.CreateForm(TForm1, Form1);
  Form1.ShowModal;
  Result := StrPCopy(StringForWord, Form1.Button1.caption);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  close;
end;
end.
Скомпилируйте test.dll. Запустите макрос из Word - должна появиться форма Delphi - нажмите кнопку для получения некоторых данных из Delphi.

В журнале PCMagazine Vol12.No22 опубликована статья о доступе к DLL из Word. Ознакомиться с ней вы можете в электронной версии журнала на веб-сайте PCMagazine.

Как удалить файл после перезагрузки Windows.

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

Я использую функцию, которая заносит в ключ реестра RunOnce командную строку:

command.com /c del C:\Путь\Имя_файла 

Как удалить файл после перезагрузки Windows 2.

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

В wininit добавляешь строку NUL={ПУТЬ УДАЛЯЕМОГО ФАЙЛА}.

Как удалить все файлы из Recent Documents List.

// Не забудьте включить ShlObj в unit
procedure TForm1.Button1Click(Sender: TObject);
begin
  SHAddToRecentDocs(SHARD_PATH, 0);
end;

Удалить все выделенные элементы списка.

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

procedure TForm1.Button1Click(Sender: TObject);
 var
   i: integer;
 begin
   for i := ListBox1.Items.Count - 1 downto 0 do
     if ListBox1.Selected[i] then
       ListBox1.Items.Delete(i);
 end;

Удаление файла в корзину.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Удаление файла в корзину
Функция удаляет файл, имя которого указаывается в аргументе FileName
в корзину. Второй необязательный параметр указывает на Handle окна,
которое будет родительским для отображаемых системой диалоговых окон
Зависимости: Windows, ShellAPI, Forms
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        06 мая 2002 г.
***************************************************** }
function Recycle(const FileName: string; Wnd: HWND = 0): Boolean;
var
  FileOp: TSHFileOpStruct;
begin
  FillChar(FileOp, SizeOf(FileOp), 0);
  if Wnd = 0 then
    Wnd := Application.Handle;
  FileOp.Wnd := Wnd;
  FileOp.wFunc := FO_DELETE;
  FileOp.pFrom := PChar(FileName);
  FileOp.fFlags := FOF_ALLOWUNDO or FOF_NOERRORUI or FOF_SILENT;
  Result := (SHFileOperation(FileOp) = 0) and (not
    FileOp.fAnyOperationsAborted);
end;

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

procedure TForm1.Button1Click(Sender: TObject)
begin
  Recycle('d:\folder\filename.ext', Handle);
end;

Обрезание имени файла по длине.

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

Автор: ___Nikolay WEB-сайт: http://delphiworld.narod.ru

// Обрезание имени файла по длине
function CutFoldersFromFileName(s: string; iLength: integer): string;
var
  bm: TBitmap;
  sResult: string;
  iStrLen: integer;
  // Поменять порядок символов в строке
  function ChangeLettersOrder(s: string): string;
  var
    sResult: string;
    i: integer;
  begin
    sResult := '';
    if Trim(s) <> '' then
      for i := Length(s) downto 1 do
        sResult := sResult + s[i];
    Result := sResult;
  end;
  // Количество вхождений символа в строку
  function SymbolEntersCount(ch: char; s: string; bCaseInsensitive: boolean): integer;
  var
    i, iResult: integer;
    cSymbol: char;
    function LoCase(ch: char): char;
    begin
      if (ch in ['A'..'Z', 'А'..'Я']) then
        result := chr(ord(ch) + 32)
      else
        result := ch;
    end;
  begin
    iResult := 0;
    if bCaseInsensitive then
      ch := LoCase(ch);
    if s <> '' then
      for i := 1 to Length(s) do
      begin
        cSymbol := s[i];
        if bCaseInsensitive then
          cSymbol := LoCase(cSymbol);
        if cSymbol = ch then
          inc(iResult);
      end;
    Result := iResult;
  end;
  // Удалить имя последнего каталога из полного имени файла
  function DeleteLastFolderFromFileName(sFileName: string): string;
  var
    sResult, sFName: string;
  begin
    sResult := sFileName;
    if Pos('\...\', sResult) <> 0 then
      Delete(sResult, Pos('\...\', sResult), 4);
    if SymbolEntersCount('\', sResult, false) < 2 then
    begin
      Result := sFileName;
      exit;
    end;
    sResult := ChangeLettersOrder(sResult);
    sFName := ChangeLettersOrder(Copy(sResult, 1, Pos('\', sResult) - 1)); // Взяли имя файла
    Delete(sResult, 1, Pos('\', sResult)); // Удалили имя файла
    Delete(sResult, 1, Pos('\', sResult)); // Удалили имя каталог перед именем файла
    sResult := ChangeLettersOrder(sResult) + '\...\' + sFName;
    Result := sResult;
  end;
begin
  Result := s;
  if (Trim(s) = '') or (SymbolEntersCount('\', s, false) < 2) then
    exit;
  sResult := s;
  bm := TBitmap.Create;
  bm.Width := 100;
  bm.Height := 100;
  iStrLen := bm.Canvas.TextWidth(sResult);
  while iStrLen > iLength do
  begin
    sResult := DeleteLastFolderFromFileName(sResult);
    iStrLen := bm.Canvas.TextWidth(sResult);
    if SymbolEntersCount('\', sResult, false) < 3 then
      break;
  end;
  bm.Free;
  Result := sResult;
end;

Удаление непустого каталога вместе с подкаталогами.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Удаление непустого каталога вместе с подкаталогами
Удаление подкаталогов рекурсивное - функция вызывает саму себя.
Описание назначения агрументов:
-DeleteAllFilesAndFolder - если TRUE то функцией будут предприняты
попытки для установки атрибута faArchive любому файлу или папке
перед его(её) удалением;
-StopIfNotAllDeleted - если TRUE то работа функции моментально
прекращается если возникла ошибка удаления хотя бы одного файла или папки;
-RemoveRoot - если TRUE, указывает на необходимость удаления корня.
Зависимости: FileCtrl, SysUtils
Автор:       lipskiy, lipskiy@mail.ru, ICQ:51219290, Санкт-Петербург
Copyright:   Собственное написание (lipskiy)
Дата:        26 апреля 2002 г.
***************************************************** }
function FullRemoveDir(Dir: string; DeleteAllFilesAndFolders,
  StopIfNotAllDeleted, RemoveRoot: boolean): Boolean;
var
  i: Integer;
  SRec: TSearchRec;
  FN: string;
begin
  Result := False;
  if not DirectoryExists(Dir) then
    exit;
  Result := True;
  // Добавляем слэш в конце и задаем маску - "все файлы и директории"
  Dir := IncludeTrailingBackslash(Dir);
  i := FindFirst(Dir + '*', faAnyFile, SRec);
  try
    while i = 0 do
    begin
      // Получаем полный путь к файлу или директорию
      FN := Dir + SRec.Name;
      // Если это директория
      if SRec.Attr = faDirectory then
      begin
        // Рекурсивный вызов этой же функции с ключом удаления корня
        if (SRec.Name <> '') and (SRec.Name <> '.') and (SRec.Name <> '..') then
        begin
          if DeleteAllFilesAndFolders then
            FileSetAttr(FN, faArchive);
          Result := FullRemoveDir(FN, DeleteAllFilesAndFolders,
            StopIfNotAllDeleted, True);
          if not Result and StopIfNotAllDeleted then
            exit;
        end;
      end
      else // Иначе удаляем файл
      begin
        if DeleteAllFilesAndFolders then
          FileSetAttr(FN, faArchive);
        Result := SysUtils.DeleteFile(FN);
        if not Result and StopIfNotAllDeleted then
          exit;
      end;
      // Берем следующий файл или директорию
      i := FindNext(SRec);
    end;
  finally
    SysUtils.FindClose(SRec);
  end;
  if not Result then
    exit;
  if RemoveRoot then // Если необходимо удалить корень - удаляем
    if not RemoveDir(Dir) then
      Result := false;
end;

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

FullRemoveDir('C:\a', true, true, true);
// Полное удаление папки C:\a со всем её содержимым,
// и с последующим удалением самой c:\a

Удалить кнопки сворачивания и максимизации с формы.

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

{ 
  This article shows by example how to suppress the maximize and 
  minimize buttons on an form at runtime. 
  To disable an form's Minimize and Maximize buttons, 
  you need to use the SetWindowLong Windows API 
  function to change the style of the window. 
}
 { Dieses Beispiel zeigt, wie man die Schaltflachen zur Minimierung, 
  Maximierung einer Form zur Laufzeit verstecken kann. 
  Man braucht dafur die SetWindowLong Windows API um den Stil 
  des Fensters zu andern. 
  Der Code kann auch fur non-VCL Anwendungen gebraucht werden. 
}
 // Add the following code to the OnCreate event 
// procedure for your form (TForm1): 
procedure TForm1.FormCreate(Sender: TObject);
 var
   l: DWORD;
 begin
   l := GetWindowLong(Self.Handle, GWL_STYLE);
   l := l and not (WS_MINIMIZEBOX);
   l := l and not (WS_MAXIMIZEBOX);
   l := SetWindowLong(Self.Handle, GWL_STYLE, l);
 end;

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

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

type
  TArrayString = array of string;
procedure DeleteArrayIndex(var X: TArrayString; Index: Integer);
begin
  if Index > High(X) then
    Exit;
  if Index < Low(X) then
    Exit;
  if Index = High(X) then
  begin
    SetLength(X, Length(X) - 1);
    Exit;
  end;
  Finalize(X[Index]);
  System.Move(X[Index + 1], X[Index],
    (Length(X) - Index - 1) * SizeOf(string) + 1);
  SetLength(X, Length(X) - 1);
end;
// Example : Delete the second item from array a
// Beispiel : Losche das 2. Element vom array a
procedure TForm1.Button2Click(Sender: TObject);
var
  a: TArrayString;
begin
  DeleteArrayIndex(a, 2);
end;

Удалить свою программу после ее завершения.

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

procedure DeleteEXE;
   function GetTmpDir: string;
   var
     pc: PChar;
   begin
     pc := StrAlloc(MAX_PATH + 1);
     GetTempPath(MAX_PATH, pc);
     Result := string(pc);
     StrDispose(pc);
   end;
   function GetTmpFileName(ext: string): string;
   var
     pc: PChar;
   begin
     pc := StrAlloc(MAX_PATH + 1);
     GetTempFileName(PChar(GetTmpDir), 'uis', 0, pc);
     Result := string(pc);
     Result := ChangeFileExt(Result, ext);
     StrDispose(pc);
   end;
    var
   batchfile: TStringList;
   batchname: string;
 begin
   batchname := GetTmpFileName('.bat');
   FileSetAttr(ParamStr(0), 0);
   batchfile := TStringList.Create;
   with batchfile do
   begin
     try
       Add(':Label1');
       Add('del "' + ParamStr(0) + '"');
       Add('if Exist "' + ParamStr(0) + '" goto Label1');
       Add('rmdir "' + ExtractFilePath(ParamStr(0)) + '"');
       Add('del ' + batchname);
       SaveToFile(batchname);
       ChDir(GetTmpDir);
       ShowMessage('Uninstalling program...');
       WinExec(PChar(batchname), SW_HIDE);
     finally
       batchfile.Free;
     end;
     Halt;
   end;
 end;

Удалить из директории проекта лишние файлы.

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

program Clean;
 { 
  The program registers as a explorer extension. 
  Use rightclick on a Delphiproject directory and choose Clean Delphi files. 
  Place the executable somewhere in program files and run once. 
  Dieses Program registriert sich beim Ausfuhren als 
  Explorer-Erweiterung. 
  Mit einem Rechtsklick auf ein Delphi Verzeichnis und 
  einem Klick auf "Clean Delphi files" werden die Verzeichnisse "aufgeraumt". 
}
 uses
   SysUtils, Registry, WinTypes, FileCtrl;
 var
   dir: string;
 const
   // HKEY_CLASSES_ROOT 
  DirExtensionKey = 'Directory\shell\clean';
   SubKey = '\command';
 procedure PerformAction(const dir, fname: string);
   // Set selected attributes for a file. 
begin
   if not SysUtils.DeleteFile(dir + fname) then
   begin
   end;
 end;
 procedure ScanDirectory(const dir, filemask: string);
   // Call performaction for every file which looks like filemask 
var
   SearchRec: TSearchRec;
 begin
   if not DirectoryExists(dir) then
   begin
     MessageBox(0, 'Directory not found', 'Clean', mb_ok);
     Halt;
   end;
   if dir <> '' then ChDir(dir);
   // Search subdirs ? 
  if True then
   begin
     if FindFirst('*.*', faDirectory, SearchRec) = 0 then
       repeat
         if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') and
           ((SearchRec.Attr and faDirectory) = faDirectory) then
           ScanDirectory(SearchRec.Name, filemask);
       until (FindNext(SearchRec) <> 0);
     SysUtils.FindClose(SearchRec);
   end;
   // Ennumerate all files in current directory 
  if FindFirst(filemask, faAnyFile, SearchRec) = 0 then
     repeat
       if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') and
         ((SearchRec.Attr and faDirectory) = 0) then
         PerformAction('', SearchRec.Name);
     until (FindNext(SearchRec) <> 0);
   SysUtils.FindClose(SearchRec);
   // Directory up 
  Chdir('..');
 end;
 procedure DoPopupReg;
 var
   reg: TRegistry;
 begin
   try
     reg := TRegistry.Create;
   except
     Exit;
   end;
   if True then
     with reg do
     begin
       RootKey := HKEY_CLASSES_ROOT;
       OpenKey(DirExtensionkey, True);
       WriteString('', 'Clean Del&phi files');
       CloseKey;
       OpenKey(DirExtensionkey + SubKey, True);
       WriteString('', ParamStr(0) + ' "%1"');
       CloseKey;
     end
   else
     with reg do
     begin
       RootKey := HKEY_CLASSES_ROOT;
       DeleteKey(DirExtensionkey);
       CloseKey;
     end;
   reg.Free;
 end;
 begin
   DoPopupReg;
   if ParamCount > 0 then dir := ParamStr(1)
   else
      Exit;
   ScanDirectory(dir, '*.dcu');
   ScanDirectory(dir, '*.~*');
   // You can add more files here 
end.

Удалить пункт системного меню.

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

procedure TForm1.Button1Click(Sender: TObject);
 var
   Handle: THandle;
 begin
   Handle := GetSystemMenu(Self.Handle, False);
   RemoveMenu(Handle, 1, MF_BYPOSITION);
   RemoveMenu(Handle, 2, MF_BYPOSITION);
 end;

Производная TIntegerField.

Автор: Mark Edington Я думал о производной, новом варианте компонента TIntegerfield, но я не могу понять как мне его получить во время разработки, ведь он не устанавливается в палитру компонентов. Это то, что вы хотите. Создайте следующий молуль:

MICRON.PAS:

unit micron;
interface
uses DB, DBTables, Classes;
type
  TMicronField = class(TIntegerField)
  public
    function IsValidChar(Ch: Char): Boolean; override;
  end;
procedure Register;
implementation
function TMicronField.IsValidChar(Ch: Char): Boolean;
begin
  Result := Ch in ['+', '-', '0'..'9', '.'];
end;
procedure Register;
begin
  RegisterFields([TMicronField]);
end;
end.
Поместите данный модуль в ваш каталог lib и добавьте это поле, используя диалог установки компонент. Затем, используя "DataSet designer", свяжите TMicronField с нужными вам полями, после чего вы увидите, что список типов полей включает теперь "Micron". (для отображения полей на новый тип поля, сначала вам необходимо удалить все TIntegerFields).

Другое решение, более простое (но так-же работающее), заключается в изменении исходного кода DBTables и простой замене существующей функции IsValidChar на TIntegerField.

Производная TOutline.

Автор: Craig Osterloh Я пытаюсь создать компонент, производный от TOutline и хотел бы использовать несколько иконок для изображения папок, документов и т.п. Только не знаю как. Если установлен стиль otOwnerDraw, вы можете сами отрисовывать компонент. В этом режиме вы можете вывести какие угодно изображения. Примечание: чтобы получить OwnerDraw для работы, вы должны установить свойство Scrollbar в vsVertical. Затем обрабатывайте событие OnDrawItem для рисования каждой строчки OutLine. Для получения правильного индекса узла используйте GetItem(Rect.Left, Rect.Top). Индекс в drawItem неверен.

Вот пример из моего приложения. Надеюсь это вам поможет.

procedure TfrmMain.Outline2DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  Node: TOutlineNode;
  NodeIdx: Integer;
  Offset: Integer;
  NowBitmap: TBitmap;
  NowAstValue: string;
begin
  NodeIdx := Outline2.GetItem(Rect.Left, Rect.Top);
  Node := Outline2.Items[NodeIdx];
  NowAstValue := PAstRec(Outline2.Items[NodeIdx].Data)^.AstValue;
  with Outline2.Canvas do
  begin
    Font.Name := 'MS Sans Serif';
    Font.Size := 8;
    Offset := MulDiv(Font.Size, 150, 100);
    fillRect(Rect);
    Rect.Left := Rect.Left + ((Node.Level - 1) * Offset + 2);
    if Node.HasItems then
    begin
      if Node.Expanded then
        NowBitmap := Outline2.PictureMinus
      else
        NowBitmap := Outline2.PicturePlus;
      {рисуем иконку}
      BrushCopy(Rect, NowBitmap,
        Bounds(0, 0, Rect.Right - Rect.Left, Rect.Bottom -
        Rect.Top),
        NowBitmap.TransparentColor);
    end;
    Rect.Left := Rect.Left + Offset + 2;
    {выводим текст}
    TextOut(Rect.left, Rect.Top, Node.Text);
    {создаем суммирующую колонку с правым выравниванием}
    Rect.Left := Rect.Right - TextWidth(NowAstValue) - 2;
    TextOut(Rect.Left, Rect.Top, NowAstValue);
  end;
end;

А как работает функция PAstRec() и как получить указатель на данные?

function TfrmMain.LoadAssetNodes(fName: string; header: string): Double;
var
  headerIdx: Integer;
  NowClientID: string;
  TotAssetValue: Double;
  AstRecPtr: PAstRec;
  sqlText: string;
  HeadPtr: PAstRec;
begin
  with qryAssets do
  begin
    NowClientID := dtbClients.Fields[0].AsString;
    sqlText := 'Select * from ' + fName + ' where ClientID = ' +
      NowClientID;
    SQL.Clear;
    SQL.Add(sqlText);
    Open;
    First;
    TotAssetValue := 0;
    new(HeadPtr);
    headerIdx := outAssets.AddObject(0, header, HeadPtr);
    while not EOF do
    begin
      new(AstRecPtr);
      AstRecPtr^.AstValue := format('%8.0n',
        [FieldByName('AssetValue').AsFloat]);
      TotAssetValue :=
        TotAssetValue + FieldByName('AssetValue').AsFloat;
      outAssets.AddChildObject(headerIdx,
        FieldByName('AssetName').AsString,
        AstRecPtr);
      Next;
    end;
    HeadPtr^.AstValue := format('%8.0n', [TotAssetValue]);
    Result := TotAssetValue;
  end;
end;

Проектирование баз данных.

Программист ошибается дважды: первый раз - при рождении, а второй раз - при выборе профессии.

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

Интегрированная база данных - констатация идеи

Широко известные методы проектирования баз данных (БД) появились в процессе разработки все более сложных Информационных Систем (ИС), которые должны были рассматривать потребности не одного пользователя, но больших групп и коллективов. Одна такая интегрированная БД создавалась для решения многих задач, каждая из которых использовала только "свою" часть данных, обычно, пересекающуюся с частями, используемыми в других задачах. Поэтому главнейшими методами проектирования стали методы исключения избыточности в данных. Эти методы связывались с другими средствами обеспечения логической целостности данных.

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

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

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

За идеей - классическая методология проектирования

Классическая методология проектирования БД - это мощное и красивое течение со своей философией, способами восприятия реальности и способами существования в ней. В этом течении возникла своя прикладная математика, свое понятие "Мира", "Предметной Области" (ПрО) и их моделей. В отношении проектирования БД осознаны и интегрированы в стройные схемы методы выполнения таких проектных этапов:

сбор сведений о ПрО (анализ потребностей и описание ПрО с использованием так называемых "процессного" или UP, "usage perspective" подхода и "непроцессного" или ISP, "information structure perspective" подхода); выбор языка представления т.н. "семантической" модели для фиксации сведений о ПрО, их последующего анализа и синтеза модели БД; анализ собранных сведений о ПрО: классификация, формализация и интеграция структурных элементов описания ПрО, формализация как структурных, так и процедурных ограничений целостности элементов в будущей модели ПрО, определение динамики экземпляров объектов ПрО; синтез концептуальной модели БД: проектирование целостной концептуальной схемы БД на выбранном языке семантического моделирования; выбор конкретной модели данных и СУБД для реализации БД; проектирование логической схемы БД для выбранной СУБД (называющееся также "проектирование реализации"); разработка физической структуры БД ("физической" или "внутренней" схемы, она же - "схема размещения"), включая размещение БД по узлам; разработка технологии и процедур начального создания и заполнения БД; разработка технологии и процедур сопровождения БД; разработка универсальных программ доступа к БД и соответствующих интерфейсов пользователей; информационное обеспечение разработки конкретных программ обработки данных: обеспечение метаинформацией, данными контрольных примеров и др.; получение обратной связи от разработчиков прикладных программ и пользователей Информационной Системы (ИС) о полноте и эффективности организации БД; тестирование БД, ее развитие и улучшение (настройка) ее структуры.

Есть все основания называть методологию классической: для указанных методов разработаны полные, целостные методические системы, для большинства методов предложены формализованные модели, эти модели - или, по крайней мере, их итоговые выразительные возможности - нашли реальное применение в практике проектирования. Один только перечень основных моделей данных и их авторов производит внушительное впечатление, см. их обзор, например, в [Цикридзис85].

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

За методологией - мастерская инструментов проектирования БД

Проектирование комплексной по предметной направленности, интегрированной и, обычно, большой по размеру БД стало сложной задачей. Наличие целостной методологии проектирования позволило позаботиться о "сапожнике-проектировщике" и начать шить ему сапоги в виде систем автоматизации проектирования БД. Этому способствовало наличие технологического опыта в организации и компьютерной поддержке систем разработки программного обеспечения и, с другой стороны, использование активных интегрированных словарей-справочников данных (DD/D, Data Dictionary/Directory). Так возникли системы CASE (Computer Aided System Engineering) - системы для структурного проектирования БД и связанных с ними ИС, ориентированные на модели данных, реализованные в различных СУБД. Наибольшую популярность получили CASE-системы для реляционных СУБД с SQL-моделями данных, а DD/D переименовался в CASE-репозиторий проектируемой ИС.

На этом пути возникло два основных направления развития CASE-систем и технологий проектирования: CASE-системы для проектирования собственно БД (или т. н. Upper-CASE) и интегрированные инструменты, позволяющие и проектировать БД, и разрабатывать использующие их прикладные программы. Важно отметить, что и Upper-CASE в общем случае имеют много средств для описания функций обработки информации (при использовании процессного подхода к сбору и анализу сведений о ПрО) и хранения этих описаний в репозитории. Это подтверждает положение о сильной связи проекта БД и проекта ИС, базирующейся на этой БД. Вместе с тем, эта связь не абсолютна, и принцип отделения БД от программ сохраняется.

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

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

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

Особо - о временных характеристиках и транзакциях

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

возможности сравнения временных параметров вариантов реализации разных вариантов схемы БД, на некоторой СУБД; возможности сравнения параметров вариантов реализации одной схемы БД на разных СУБД; возможности сравнения параметров реализации одной схемы БД на разных аппаратных серверах БД; возможности предсказания временных параметров работы различных прикладных программ и служебных программ-утилит.

Задача сравнения временных параметров разных СУБД рассматривается как самостоятельная. Однако, она часто должна решаться как часть проектной задачи выбора СУБД для проектируемой БД и в процессе этого проектирования.

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

Временные оценки СУБД наиболее популярных тестов последнее время даются в виде числа транзакций определенного стандартизованного вида в единицу времени. Распределенная обработка строится на основе мониторов транзакций.

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

Оценка достигнутого состояния

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

Аналогичные проблемы характерны для СУБД в целом. Система баз данных должна стать органическим элементом системы управления организацией - вот залог ее успешного применения. Однако процесс ее внедрения связан с определенными изменениями в самой организации и в деятельности ее сотрудников, и мы всегда будем сталкиваться с естественной инертностью людей, когда речь идет о восприятии изменений....

Весьма важно, чтобы средства СУБД были адекватны потребностям пользователей. Поскольку разным пользователям могут понадобиться разные модели данных, языки данных и схемы, желательно, чтобы СУБД поддерживала множество средств, а пользователь мог выбирать из них наиболее подходящие. ...

Можно, конечно, поставить под сомнение ценность таких исследований. Действительно, каким бы плохим ни был язык программирования, его в конце концов все-таки можно выучить. Точно также и средства СУБД можно освоить за определенный период времени. Но проблема состоит не в освоении средств, а в эффективности их использования. Машина должна быть служанкой человека, но не наоборот!

Выше шрифтом выделена цитата из [Цикритзис85]. С тех пор СУБД, методы проектирования БД и соответствующие инструменты значительно прибавили в возможностях. Но остальной мир тоже не стоял на месте, существенно усложнились стоящие перед разработчиками ИС и БД задачи. И надо признать: формулировки Цикритзиса и Лоховски не устарели.

Что и как из классических методов проектирования применяется в практике настоящего времени

Применяются на практике:

СУБД, поддерживающие реляционную модель данных 1971 года с некоторыми расширениями (см., например, [Дадли96]); Иерархическая "каскадная" схема структурного проектирования БД при подходе "сверху-вниз"; CASE-системы для структурного проектирования баз данных, ИС в целом или, к тому же, прикладных программ ИС. Наиболее часто используются: варианты ER-модели данных; табличная реляционная модель 1971 года, расширенная тем или иным дополнительным набором средств описаний ограничений целостности (ссылочная целостность, бизнес-правила); для анализа "процессного" источника сведений чаще всего предоставляются модели потоков данных или SADT, возможно, расширенные дополнительными связями по управлению (эти связи нельзя смешивать с выделенными потоками условий выполнения функций в нотации IDEF0); Утилиты динамического администрирования БД, обеспечивающие следующие функции: отслеживание динамики показателей эксплуатации БД: показатели доступны в любой момент на фоне работы приложений, эти показатели ("статистика") могут использоваться для поддержки оптимального динамического построения путей доступа к данным, создание резервных копий БД, также как и ведение копий БД горячего резерва на фоне работы приложений, восстановление и откат фрагментов и полной БД, возможна динамическая реорганизация БД (переразмещение БД и отдельных физических фрагментов, логическая и физическая реструктуризация данных), однако, эти возможности являются ограниченными.

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

Что теряется

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

полная процедура нормализации высоких степеней и минимизации набора отношений не проводится или проводится редко, если же экспертиза проверки на соответствие даже 3НФ или БКНФ предусмотрена в CASE-средствах, эта возможность редко используется на практике ввиду ее громоздкости и высоких требований к квалификации проектировщика, использующего CASE; оптимизация размещения БД на устройствах внешней памяти проводится "на глазок", распространенные сегодня тесты временных параметров не приспособлены для помощи в решении этой задачи проектирования; так же "на глазок" производится оптимизация размещения БД по узлам распределенной БД.

Значительно меньшее внимание в последнее время уделяется и инструментальным средствам автоматизации физического проектирования БД, включая математическое и натурное моделирование характеристик БД, в том числе - с учетом размещения по узлам распределенной ИС. Оптимизация размещения БД по узлам распределенной БД не поддерживается распространенными CASE-средствами. Отдельные инструменты и работы, включая отечественные исследования, не делают "погоды" в Мастерской средств проектирования, и не поддерживают живой школы этого направления.

Этому есть, на наш взгляд, несколько причин:

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

Ограничения классических методов

Классические модели и методы ориентировались на организацию хранения и обработки детально структурированных данных, чему отвечало понятие "атрибута" как свойства объекта, представляемого атомарным элементом данных. В следствие этого, например, полнотекстовые БД сразу выделялись в особый тип баз данных. Для их проектирования существовало отдельное течение - ИПС или Информационно-Поисковые Системы.

Спустя годы и десятилетия после объявления своих классических моделей и концепций классики в явном виде описывали их ограниченность. Так, в [Codd79] было указано, что "при обсуждении моделирования семантики данных акцент делается на структурные аспекты в ущерб аспектам обработки. Структура без соответствующих операций или подразумеваемых методов подобна анатомии в отсутствии физиологии".

Еще через 14 лет Е. Кодд и соавторы в [Codd93] фиксировали: "... обладание большой корпоративной БД имеет маленькое значение, если конечные пользователи не имеют возможностей легко синтезировать необходимую информацию из этих запасов (складов) данных. Слишком часто мы имеем именно такие обстоятельства.".

Наконец, наступило время, когда проектирование БД (и ИС в целом) по классическим правилам полноты и целостности часто стало практически бессмысленным. Весли П. Меллинг (Garthner Group) указал в [Меллинг95]: "К 1990 году почти все аспекты "стандартной процедуры работы" с ИТ (Информационными Технологиями - прим. Е.З.) были оспорены, и вычислительные архитектуры вырвались из-под контроля. ... Стандарты программирования размывались, а понятие неизбыточных, непротиворечивых, высококачественных данных годилось разве что для груды хлама.".

Причины появления новых требований

Феномены глобальных компьютерных коммуникаций и повсеместных персональных вычислений (вместе в падением удельной стоимости средств вычислительной техники) привели ко многим новым возможностям БД и их проектирования. Список типов хранимых и обрабатываемых данных расширился до возможных пределов, определяемых самым общим нормативным значением понятия "данное". В корпоративные БД включаются не только неформатированные элементы и полнотекстовые фрагменты, но также БД с геоинформацией, мультимедийные БД, и это не исчерпывающий перечень.

Более того, новые возможности ИТ - вместе с рядом чисто экономических причин - привели к увеличению рыночных возможностей и требований потребителей, как следствие - к резкому усилению конкуренции в различных отраслях промышленности и услуг. Ответом послужило объявление императива бизнес-реинжиниринга: от BPR М. Хаммера ([Hammer93]) до строительства киберкорпораций по Дж. Мартину ([Мартин95]). В этих подходах требуется осуществление радикальных изменений в организации основной деятельности предприятий. В частности:

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

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

Также, как саму ИС нельзя рассматривать в отрыве от ее пользователей, и новое проектирование должно рассматриваться как интеграция трех составных частей: требований бизнес- реинжиниринга, человеческого фактора и методов новых ИТ. Реальное объединение этих трех составных частей, каждая из которых приобрела в 90-е годы качественно новое наполнение, создало начала того, что можно назвать Новым Системным Проектированием - Н.С.П..

Что нужно от баз данных для ответа на новые требования

Покажем новые требования к корпоративным БД на примере двух аспектов создания новых корпоративных ИС (из более чем двух десятков видов работ, составляющих основу Н.С.П. - см. [Зиндер96]):

Обеспечение максимальных возможностей для каждого работника, то есть поддержка выполнения всех бизнес-функций тем самым работником, который и получает конечный результат. Со стороны ИС, БД и СУБД для этого требуется: обеспечение средств доступа ко всем необходимым данным с использованием распределенных БД, средств репликаций данных, управления событиями в данных и процессах обработки транзакций; использование архитектуры и программных средств хранилища данных, средств Оперативной Аналитической Обработки Данных (OLAP), применение средств быстрой разработки приложений (RAD) для создания "ИС руководителя" (EIS), средств поддержки принятия решений (DSS) на основе хранилища данных, OLAP и RAD/EIS; применение средств DSS на основе анализа БД прецедентов, а также методов логического вывода, нейронных сетей и нейрокомпьютеров, и др.; предложение единого интерфейса пользователя для работы с разными компонентами данных и приложений, использование в этом интерфейсе средств, повышающих простоту поиска информации и обращения к конкретным прикладным функциям, например, функции геоинформсистем, гипертекстовые, естественного языка, речевого ввода. Разработка концепции и структуры корпоративной базы данных для новой ИС, реализация структуры БД, предполагающая снятие (существенное уменьшение) ограничений на ее развитие, в том числе, при смене функций или функциональных компонентов обработки информации: применение методов компонентного проектирования предметных БД как для операционных БД, так и для исторических БД хранилищ данных, архивов документов, гео-информационных и иных данных; разработка процедур компонентного изменения корпоративной БД при изменении бизнес-процедур, видов деятельности, применяемых приложений и географического размещения предприятия; постоянная актуализация понятийной модели деятельности предприятия для учета новых понятий, возникающих при изменении прикладных компонент на функционально сходные и при изменении видов деятельности предприятия, и построение на этой основе новых интерфейсов между компонентами ИС; динамическое администрирование фрагментами распределенной корпоративной БД при изменении частоты их использования, при модификации их структуры и при изменении их размещения.

Некоторые новые технические требования к базам данных можно получить из анализа в [Меллинг95].

Вошедшие в практику новые инструменты мастерской проектировщика

Язык SQL, бывший в 80-м году всего лишь одним из языков, представляющих реляционную модель, стал реальным стандартом не только реляционной модели данных, но и промышленных СУБД. (В то же время, это - пример приобретения, которое быстро может стать обременительным.).

В реальных разработках наиболее распространенных организационно-производственных ИС в большинстве случаев или в большей части объемов работ произошла замена средств разработки с SQL во включающем 3GL-языке или языка 4GL процедурного типа на языки и инструменты 4GL с оконным интерфейсом на основе управления через меню и с использованием элементов концепции объектно-ориентированного программирования (и сохранением возможностей выхода на SQL и процедурный язык).

Возникли практически работающие стандарты de facto интероперабельной работы с данными, в первую очередь - стандарт ODBC.

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

Вошли в практику новые структуры и типы данных, новые операции над данными: неформатированные элементы, полнотекстовые БД и их обработка, ГИС-данные, мультимедийные БД, гипертекстовые распределенные БД, распределенная обработка и обработка, доставляемая вместе с объектом на вход ИС. На практике наблюдаются шаги реальной интеграции упомянутых структур и операций.

Меняется подход к выбору СУБД, в первую очередь - для проектирования корпоративных БД, эксплуатация и развитие которых планируется как минимум на несколько лет. Все более используются экономические основания и критерии для выбора СУБД (см. [Зиндер95а]).

Объектная ориентация в проектировании БД здесь не рассматривается как уже реально существующий в практике новый инструмент Мастерской. (Не имеется в виду объектно- ориентированное программирование.) В настоящее время представляется обоснованным отнести такое проектирование все еще к направлениям исследований.

К новым подходам в организации проектирования БД

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

Каскадные схемы организации проектирования ПО для ИС достаточно давно стали преобразовываться в циклические формы. Так, организация продолжающейся разработки IBM corp. (см. [Фокс85]) предусматривала непрерывное контролируемое развитие программной системы в виде передачи в эксплуатацию ее новых версий.

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

Однако, такие циклические схемы сохраняют многие старые недостатки структурных методов. Для условий Н.С.П. важными недостатками являются:

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

Существуют и другие, например, громоздкость ведения проектной документации. Все это полностью относится и к проектированию БД.

В условиях компонентного проектирования организационная схема проектирования БД должна выглядеть как схема параллельного спирального проектирования компонентов БД и их комплексирования по необходимости.

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

От новых требований к типам и источникам информации - к новым архитектурным принципам БД

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

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

компонентная технология проектирования и перекомплектации предметно- ориентированных операционных БД, допускающих работу пользователей через общие, в том числе, для Хранилища Данных, интерфейсы; расширенная технология Хранилища Данных, интегрирующая исторические форматированные данные, архивные текстовые документы, звуковые и видеоархивы, а также картографические данные, и включающая средства оперативной аналитической обработки данных, необходимые виды "дружественных" интерфейсов, например: гипертекстовый, ГеоИнформСистем и др.; открытость БД для включения в нее и получения из нее информации с использованием принципов Глобальной Информационной Магистрали;

Архитектура Открытых Систем, расширенная методами и средствами компонентного формирования: на верхнем уровне это открытость компонентного проектирования БД и свободного обмена с источниками информации любых внешних систем, на нижнем уровне - технологическая открытость БД на основе стандартов переносимости, интероперабельности, масштабируемости и др..

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

К новым подходам в методах проектирования БД

Как ответ на новые требования можно рассмотреть рекомендации к новым методам и инструментам проектирования БД. (Конечно, в предположении, что все новое есть ранее кем-то уже обнаруженное старое.).

Об исключении избыточности в данных

Сохраняется как разумное требование однократного ввода данных в БД для решения разных задач и защиты от возникновения противоречий (нарушении логической целостности) при актуализации хранимых данных. Однако, в условиях глобального информационного пространства и компонентного проектирования контекст этих требований должен быть пересмотрен. Несомненно, в операционных БД рационально планировать "острова" нормализованных и, в классическом смысле, безызбыточных кластеров отношений или объектов. Эти "острова" чаще всего и будут являться давно известными предметными БД.

Вместе с тем, объединение исторических данных Хранилищ, БД ГИС-систем, архивов текстовых документов, потоков информации, получаемых по Информационной Магистрали и др. в общей постановке проектирования корпоративной БД приводит к отказу от повсеместного и всеобязательного принципа исключения избыточности: проектирование корпоративной БД на уровне логической схемы и на концептуальном уровне не опирается как на глобальный критерий на требование и процедуры исключения избыточности в данных.

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

Проблема консервации проблем

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

Предполагаемые подходы:

возможность фиксировать описания атрибутов, сущностей, связей, функций, и т.д. с любой степенью неполноты, возможности производить описания на уровне недетализированных, предметно связанных совокупностей информационных структур ("кластеров сущностей"); проектирование или реконструкция моделей компонентов ИС и БД, их интеграция в общем понятийном пространстве; проектирование упорядоченной последовательности состояний корпоративной БД как последовательности совокупностей эксплуатируемых предметных БД, включающих: наследованные БД, структурно предопределенные БД "покупных" функциональных компонентов, спроектированные специально для данного предприятия БД, причем БД двух последних категорий постепенно заменяют наследованные и, затем и параллельно, заменяют друг друга в процессе развития ИС; открытость репозитория CASE-системы, словаря СУБД и системы 4GL, позволяющая надстраивать метаобъекты и механизмы требуемыми тезаурусными и глубинными семантическими отношениями между элементами, а также производить двухсторонний обмен метаинформацией с другими системами 4GL и CASE, соединять модели разных компонентов в одну с использованием и сохранением всех необходимых семантических отношений. Компонентная открытость и смысловая интероперабельность

Компонентный подход в разработке ИС требует компонентного проектирования БД. Замена некоторой функциональной компоненты ИС на подобную, но спроектированную другим разработчиком, потребует структурной замены некоторой части корпоративной БД. Такая замена должна поддерживаться как постоянный процесс перепроектирования БД. При замене компоненты БД интерфейсы с ней имеющихся приложений и их пользователей должны получать точно ту же в смысловом отношении информацию, что и ранее.

Реальное компонентное проектирование БД может основываться на формировании и использовании общей для комплексируемых компонент понятийной модели и поддержании соответствий между моделями компонент БД (и связанных с ними приложений) и общей понятийной моделью. В общем виде требования к формализмам таких моделей описывались ранее (см. [Zinder90]). В последнее время развиваются программные реализации полных формальных систем, обычно основанных на объектном подходе, которые могут приближаться к инструментальному решению этой задачи (см. например, [Калиниченко93]).

Разработка понятийных моделей и СКК

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

До сих пор часто встречается мнение, что СКК - средство сокращенного представления информации в интегрированной БД. На самом деле, отсутствие СКК или использование некорректно построенных СКК приводит к смысловой несовместимости информации, хранимой в различных БД или даже в одной БД. В этих условиях не приведет к достижению целей использование самых продвинутых режимов технологической интероперабельности. Таким образом, целесообразно использовать работы по проектированию БД с НСИ и проектирование СКК как начало и основу для создания понятийного пространства ПрО, для построения понятийной модели деятельности предприятия.

Понятийные модели и последующие проектные работы

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

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

В ИС новой архитектуры СУБД станут определяющим но не единственным компонентом интегрирующего ПО (в том числе - промежуточного, или middleware). Мониторы транзакций и процессов, средства семантического моделирования и использования понятийных моделей, СУБД- независимые средства разработки и исполнения приложений - другие классы компонентов ПО, обеспечивающие достижение этой цели.

Рекомендуется сохранение независимости от СУБД на основе использования инструментария и стандартов, охватывающих различные СУБД. Отказ от связи с одной СУБД, открытость CASE- репозитория, возможность развития метамоделей, поддерживаемых в репозитории, и применяемых к ним проектных процедур - это лишь минимальные требования к методам и инструментам.

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

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

Проблемы объемов, временных характеристик и физического проектирования

Распространение БД класса VLDB требует более активного использования методов проектирования эффективных физических схем данных. Невозможно строить такие БД рассчитывая на постоянную реорганизацию путем переписи в новые физические структуры. Это так для операционных БД режима OLTP, тем более это так для терабайтных БД, ориентированных на OLAP. Легкость процедур выполнения реорганизаций указанным методом может становиться "ловушкой" для проектировщика, особенно - на первых этапах ввода БД в действие, когда ее перепись еще возможна из-за неполного объема.

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

Большой рост объемов БД будет сопровождаться ростом требований к их надежности. К средствам и методам проектирования БД вследствие постоянно осуществляемого процесса перепроектирования БД будут непосредственно примыкать средства управления БД. Так, для обеспечения устойчивых к отказам данных необходимо владение средствами управления и синхронизации географически разнесенных теневых и резервных баз данных.

Проблема границ применимости двух основных методов проектирования

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

ЗАКЛЮЧЕНИЕ

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

Дисциплина проектирования БД в новых условиях еще отсутствует. Тем не менее, ее начала видны, ее элементы работают в реальных проектах.

В соответствии с принципом сохранения иммунитета к компьютерным революциям (см. [Зиндер95б]) классические методы проектирования БД должны продолжать использоваться, но только в тех в областях, где они действительно полезны. Методы проектирования, рассматриваемые в конкретных проектах корпоративных ИС и БД, и соответствующие инструменты должны проверяться на свои возможности обеспечивать функции в соответствии с требованиями Нового Системного Проектирования.

Свойство только для чтения во время выполнения программы.

- Департамент Полиции Брюсселя, чем я могу помочь Вам? - Ох.. Да.. Я только что получил пирогом с кремом по лицу. - О'кэй, сэр. Обращались ли Вы в Департамент Полиции Брюсселя ранее? - Нет. - Хорошо, дайте мне немного информации о себе, для занесения в журнал. Ваше имя? - Билл Гейтс. - Страна? - США. - Родной язык? - Английский. - О'кей, сэр. Ваш идентификационный номер в Департаменте Полиции: БПЗ1415927. Пожалуйста, используйте этот номер при следующем обращении к нам. Итак, Вы говорите, что получили пирогом по лицу? - Да, я только что должен был встретится с премьер министром Бельгии. Один человек отвлек меня в то время, как другой ударил меня пирогом со сливочным кремом. - Мы имеем сообщения, что Вы получили по лицу пирогом с заварным кремом. Вы уверены, что это действительно был пирог со сливочным кремом? - Хорошо, все мое лицо покрыто белой массой и я не вижу никакого заварного крема, так что я действительно не думаю, что это был пирог с заварным кремом. - Встречались ли Вы с премьер министром раньше? - Да. - Тогда вы тоже получали пирогом по лицу? - Нет. - Хмм.. Посещали ли вы других премьер министров в последнее время? - Да. - Были ли какие-либо пироги при этом? - Нет. - О'кей, хорошо... Давайте попробуем следующее: выйдите из здания и зайдите опять. Я буду ждать. - Минутку.. - О'кей, я вернулся. - Получили еще одним пирогом по лицу? - Конечно нет. - Хорошо, сэр. Я не знаю, чем было вызвано появление первого пирога, но все выглядит так, что Ваши дела вновь наладились. Впрочем, мы возьмем эту проблему на заметку. Если это случится снова, пожалуйста, запомните точные детали происшествия и вызовите нас еще раз. Благодарим Вас за обращение в Департамент Полиции Брюсселя.

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

if csDesigning in ComponentState then
begin
  // ... код, устанавливающий значение свойства ...
end;

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

Как запретить изменение размера моего компонента в design-time.

Поместите в конструктор компонента код, устанавливающий размеры по умолчанию. Переопределите метод SetBounds и проверяйте в нем "componentstate". Если компонент находится режиме "design-time" (csDesigning in ComponentState) просто передавайте значения ширины и высоты (width и heights) компонента по умолчанию (в нашем примере 50) методу класса-предка.

procedure TVu.SetBounds(ALeft: integer; ATop: integer;
AWidth: integer; AHeight: integer);
begin
  if csdesigning in componentstate then
  begin
    AWidth := 50;
    AHeight := 50;
    inherited; //вызываем унаследованный от предка метод
  end;
end;

Как установить цвет фона иконок на рабочем столе, либо сделать у них прозрачный фон.

Для этого нужно найти окно "SysListView32" (которое является списком, который содержит иконки рабочего стола). Сперва будем искать главное родительское окно "Progman", которое содержит дочернее окно "SHELLDLL_DefView" , которое в свою очередь имеет дочернее окно "SysListView32". Для этого можно воспользоваться API функцией FindWindow to. Когда Мы получим дескриптор окна "SysListView32", то можно будет воспользоваться макросами ListView_SetTextBkColor и ListView_SetTextColor для установки желаемого цвета.

Ниже приведена процедура, которая делает всё вышеперечисленное. Если параметр Trans равен true, то будет установлен прозрачный фон, иначе цвет фона будет равен Background.

unit DeskIcons;
interface
uses Graphics; // Будет использоваться TColor
procedure SetDesktopIconColor(Forground, Background: TColor; Trans: Boolean);
procedure SetDefaultIconColors;
implementation
uses Windows, CommCtrl; // будут использоваться HWND и ListView_XXXXX
procedure SetDesktopIconColor(Forground, Background: TColor; Trans: Boolean);
var
  Window: HWND;
begin
  // Находим нужное окно в три этапа
  Window := FindWindow('Progman', 'Program Manager');
  // Используем FindWindowEx для нахождения дочернего окна
  Window := FindWindowEx(Window, HWND(nil), 'SHELLDLL_DefView', '');
  // SysListView32, это список с иконками на рабочем столе
  Window := FindWindowEx(Window, HWND(nil), 'SysListView32', '');
  // Используем макрос для очистки цвета фона
  if Trans then
    ListView_SetTextBkColor(Window, $FFFFFFFF) // фоновый цвет
  else
    ListView_SetTextBkColor(Window, Background); // фоновый цвет
  ListView_SetTextColor(Window, Forground); // передний цвет
  // теперь перерисовываем иконки
  ListView_RedrawItems(Window, 0, ListView_GetItemCount(Window) - 1);
  UpdateWindow(Window); // да¸м команду "немедленно перерисовать"
end;
procedure SetDefaultIconColors;
{ Эта процедура устанавливает цвета, которые заданы в
  windows по умолчанию }
var
  Kind: Integer;
  Color: TColor;
begin
  Kind := COLOR_DESKTOP;
  Color := GetSysColor(COLOR_DESKTOP);
  SetSysColors(1, Kind, Color);
end;
end.

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

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

function DesktopColor(const X, Y: Integer): TColor;
 var
   c: TCanvas;
 begin
   c := TCanvas.Create;
   try
     c.Handle := GetWindowDC(GetDesktopWindow);
     Result   := GetPixel(c.Handle, X, Y);
   finally
     c.Free;
   end;
 end;
 procedure TForm1.Timer1Timer(Sender: TObject);
 var
   Pos: TPoint;
 begin
   GetCursorPos(Pos);
   Panel1.Color := DesktopColor(Pos.X, Pos.Y);
 end;

Как определить размер рабочего стола без ТаsкВага.

Воспользуйтесь функцией SystemParametersInfo(), переслав ей в качестве параметров - SPI_GETWORKAREA и адрес структуры типа TRect, куда будут передан полученный результат:

procedure TForm1.Button1Click(Sender: TObject);
var
  r: TRect;
begin
  SystemParametersInfo(SPI_GETWORKAREA, 0, @r, 0);
  Memo1.Lines.Add(IntToStr(r.Top));
  Memo1.Lines.Add(IntToStr(r.Left));
  Memo1.Lines.Add(IntToStr(r.Bottom));
  Memo1.Lines.Add(IntToStr(r.Right));
end;

Снимок Desktop.

Статьи по Delphi (005)…
public
  { Public declarations }
  procedure GrabScreen;
...
implementation
{$R *.DFM}
procedure TForm1.GrabScreen;
var
  DeskTopDC: HDc;
  DeskTopCanvas: TCanvas;
  DeskTopRect: TRect;
begin
  DeskTopDC := GetWindowDC(GetDeskTopWindow);
  DeskTopCanvas := TCanvas.Create;
  DeskTopCanvas.Handle := DeskTopDC;
  DeskTopRect := Rect(0, 0, Screen.Width, Screen.Height);
  Form1.Canvas.CopyRect(DeskTopRect, DeskTopCanvas, DeskTopRect);
  ReleaseDC(GetDeskTopWindow, DeskTopDC);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  GrabScreen;
end;

Когда я добавляю обьект в список TStrings как мне его потом уничтожить.

procedure TForm1.FormCreate(Sender: TObject);
var
  Icon: TIcon;
begin
  Icon := TIcon.Create;
  Icon.LoadFromFile('C:\Program Files\BorlandImages\CONSTRUC.ICO');
  ListBox1.Items.AddObject('Item 0', Icon);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  ListBox1.Items.Objects[0].Free;
end;
 

Криптоалгоритм DES.

Оформил: DeeCo

Особенностью этой криптосистемы является использование операций циклического сдвига, зависящих от преобразуемых данных RC5 [12,13]. Это задает непредопределенность операций преобразования, выполняемых над преобразуемыми подблоками данных, что по замыслу разработчиков должно привести к до-стижению высокой криптостойкости. В этой криптосистеме пре-дусмотрена возможность задания пользователем числа раундов и размера входного блока данных. Входной блок разбивается на два подблока одинаковой длины. Обозначим длину подблока в битах через Ъ. Шифрование заключается в поочередном преоб-разовании подблоков с использованием операций поразрядно-го суммирования по модулю два, суммирования по модулю 26 и управляемых операций циклического сдвига. Варианты опера-ции циклического сдвига различаются величиной сдвига от 0 до Ь - 1 бит. Для обозначения операции циклического сдвига подблока (слова) W на х бит будем применять запись W <<< х (сдвиг влево) и W >>> х (сдвиг вправо). Для выбора конкретной модификации операции циклического сдвига используется log2 b младших разрядов управляющего блока. На каждом раунде преобразования используется отдельная пара подключей, поэтому при выборе различного числа раун-дов преобразования используется ключ шифрования различной длины. Для удобства пользователей в шифре RC5 используется алгоритм инициализации, выполняемый однократно при запуске криптосистемы. На этом этапе предвычислений по секретному ключу пользователя в зависимости от заданного числа раундов R формируется ключ шифрования в виде последовательности, содержащей 2(R+1) b-битовых подключей. Подключи используются по фиксированному расписанию, т. е. для всех входных блоков на данном шаге преобразования используется один и тот же подключ. Для того, чтобы указать значения конкретных па-раметров настройки алгоритма применяется запись RC5-b/R/l, где l -длина секретного ключа в байтах. Типичной является модификация RC5-32/12/16, ко-торая соответствует 64-битовому входному блоку данных, 128-битовому секретному ключу и 12 раундам шифрования. Эта модификация является стойкой к известным методам криптоанализа и может применяться для защиты данных в авто-матизированных системах обработки информации и управления. В случае применения 64-разрядных микропроцессоров целе-сообразно применение модификации RC5-64/12/16, которая обеспечит более высокую скорость преобразования. Кроме того, при преобразовании 128-битовых блоков исходного текста повышается стойкость шифрования за счет увеличения числа управляющих битов с 5 до 6. Алгоритм предвычислений формирует ключ шифрования необходимого размера, при этом используются процедуры, которые обеспечивают влияние каждого бита секретного ключа на каждый бит ключа шифрования. По значению клю-ча шифрования трудно восстановить секретный ключ. Мы не будем останавливаться на описании алгоритма предвы-числений, поскольку могут быть применены различные его варианты. Рассмотрим процедуры шифрования и дешифро-вания, которые составляют сущность криптосистемы RC5. Обозначим ключ шифрования как последовательность подключей Q0, Q1, ..., Q2R+1. Процедуры шифрования и дешифрования блока Т = A | В описываются следующими простыми алгоритмами: ВХОД: Исходное значение Ь-битовых подблоков A и В. 1. Установить счетчик i == 1 и число раундов R и выполнить преобразования:

А := (A + Qo) mod
2 b;
В := (B + Q1)mod2b.2. Преобразовать: A := {[(A ф В)
<<< В] +Q2i}mod 2 bB := [(B ф A) < < < A] + Q2i + 1} mod
2 b3.Если i? R, то прирастить i := i + 1 и перейти к шагу 2.4.
СТОП.
ВЫХОД: Преобразованное значение подблоков A и В. Алгоритм дешифрования. 1. Установить счетчик i = R.
2. Преобразовать: B :=
{[(В - Q2i+i) mod 2b] >>> A} Ф AA := {[(A - Q2i ) mod2b]
>>> В} Ф В.3. Если i ? 1, то уменьшить на 1 значение счетчика
i := i - 1 и перейти к шагу 2.4. Преобразовать: B := (B - Q1)
mod2b;
A := (A - Q0) mod 2 b
Схема шифрующих преобразований одного раунда пред-ставлена на рис. Операция циклического сдвига относится к быстрым элементарным операциям современных процессоров, Кроме того, время выполнения операции циклического сдвига не зависит от величины сдвига. При программной реализации модификация RC5-32/12/16 обеспечивает скорость шифро-вания порядка нескольких Мбайт/с для микропроцессора Pentium. В приведенных алгоритмах не используются операция табличной подстановки, которые являются типичными нели-нейными операциями для многих блочных криптосистем, Нелинейными операциями рассмотренного шифра явля-ются операции циклического сдвига, зависящие от пре-образуемых данных. Как шифрование, так и дешифрова-ние начинается с выполнения операции над подблоками и подключами. Это обусловливает различие в операци-ях циклического сдвига на каждом раунде шифрования в дешифрования. Двухместные операции (Ф), выполняемые над двумя подблоками, усиливают эффект размножения ошибки.

Алгоритм шифрования DES.

Оформил: DeeCo

Алгоритм шифрования данных DES разработан для зашифрования и расшифрования данных разрядностью 64 бит на основе 64-битового ключа. Расшифрование выполняется по тому же ключу, что и зашифрование, но этот процесс является инверсным по отношению к процессу зашифрования данных. При описании алгоритма шифрования используются следующие обозначения. Если L и R - последовательности бит, то через LR будем обозначать конкатенацию последовательностей L и R, т.е. последовательность бит, размерность которой равна сумме размерностей L и R. В этой последовательности биты последовательности R следуют за битами последовательности L. Конкатенация битовых строк является ассоциативной, то есть запись ABCDE, означает, что за битами последовательности A, следуют, биты последовательности B, затем C и т.д. Символом + будем обозначать операцию побитового сложения по модулю 2.

Процесс шифрования.

Процесс шифрования данных поясняется рисунком 1. Сначала 64 бита входной последовательности перестанавливаются в соответствии с таблицей 1. Таким образом, бит 58 входной последовательности становится битом 1, бит 50 – 2 и т.д.

Таблица 1. "Начальная перестановка". 58. 50. 42. 34. 26. 18. 10. 2. 60. 52. 44. 36. 28. 20. 12. 4. 62. 54. 46. 38. 30. 22. 14. 6. 64. 56. 48. 40. 32. 24. 16. 8. 57. 49. 41. 33. 25. 17. 9. 1. 59. 51. 43. 35. 27. 19. 11. 3. 61. 53. 45. 37. 29. 21. 13. 5. 63. 55. 47. 39. 31. 23. 15. 7. Полученная последовательность бит разделяется на две последовательности: L(0) (биты 58, 50, 42, ..., 8) и R(0) (биты 57, 49, 41, ..., 7), каждая из которых содержит 32 бита. Затем выполняется итеративный процесс шифрования, который описывается следующими формулами:

L(i)=R(i-1), i=1,2,...,16.

R(i)=L(i-1) + F(R(i-1),K(i)), i=1,2,...,16.

Функция F называется функцией шифрования. Ее аргументами являются последовательность R, полученная на предыдущем шаге, и 48-битовый ключ K(i), который является результатом функции преобразования 64-битового ключа шифра. Подробно функция шифрования и алгоритм получения ключей K(i) описаны ниже.

На последнем шаге итерации будут получены последовательности L(16) и R(16), которые конкатенируются в 64-х битовую последовательность R(16)L(16). Видно, что в полученной последовательности 64 бита, перестанавливаются в соответствии с таблицей 2. Как легко видеть данная перестановка является обратной по отношению к начальной (см. таблицу 1).

Таблица 2. "Конечная перестановка". 40. 8. 48. 16. 56. 24. 64. 32. 39. 7. 47. 15. 55. 23. 63. 31. 38. 6. 46. 14. 54. 22. 62. 30. 37. 5. 45. 13. 53. 21. 61. 29. 36. 4. 44. 12. 52. 20. 60. 28. 35. 3. 43. 11. 51. 19. 59. 27. 34. 2. 42. 10. 50. 18. 58. 26. 33. 1. 41. 9. 49. 17. 57. 25. Полученная последовательность из 64 бит и будет являться зашифрованной последовательностью. Рисунок 1.

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

Процесс расшифрования.

Процесс расшифрования данных является инверсным по отношению к процессу шифрования. Все действия должны быть выполнены в обратном порядке. Это означает, что расшифровываемые данные сначала переставляются в соответствии с таблицей 1, а затем над последовательностью бит R(16)L(16) выполняется те же действия, что и в процессе зашифрования, но в обратном порядке. Итеративный процесс расшифрования описан следующими формулами:

R(i-1)=L(i), i =16, 15, ..., 1.

L(i-1)=R(i)+F(L(i),K(i)), i=16, 15, ..., 1.

На последнем шаге итерации будут получены последовательности L(0) и R(0), которые конкетанируются в 64 битовую последовательность L(0)R(0). В полученной последовательности 64 бита перестанавливаются в соответствии с таблицей 2. Результат преобразования - исходная последовательность бит (расшифрованное 64-битовое значение).

Функция шифрования.

Функция шифрования F(R,K) схематически показана на рисунке 2. Для вычисления значения функции F используется функция E (расширение 32 бит до 48), функции S(1), S(2),...,S(8) преобразование 6-битового числа в 4-битовое) и функция P (перестановка бит в 32-битовой последовательности). Приведем определения этих функций. Аргументами функции шифрования являются R (32 бита) и K (48 бит). Результат функции E(R) есть 48-битовое число, которое складывается по модулю 2 с числом K. Таким образом, получается 48-битовая последовательность, которая рассматривается, как конкатенация 8 строк длиной по 6 бит (т.е. B(1)B(2)B(3)B(4)B(5)B(6)B(7)B(8)). Результат функции S(i)B(i) - 4 битовая последовательность, которую будем обозначать L(i). В результате конкетанации всех 8 полученных последвательностей L(i) имеем 32-битовую последовательность L=L(1)L(2)L(3)L(4)L(5)L(6)L(7)L(8). Наконец, для получения результат функции шифрования надо переставить биты последовательности L. Для этого применяется функция перестановки P(L).

Рисунок 2.

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

Функция расширения Е, выполняющая расширение 32 бит до 48, определяется таблицей 3. В соответствии с этой таблицей первые три бита Е(R) - это биты 32,1 и 2, а последние - 31,32,1.

Таблица 3. "Функция расширения Е". 32. 1. 2. 3. 4. 5. 4. 5. 6. 7. 8. 9. 8. 9. 10. 11. 12. 13. 12. 13. 14. 15. 16. 17. 16. 17. 18. 19. 20. 21. 20. 21. 22. 23. 24. 25. 24. 25. 26. 27. 28. 29. 28. 29. 30. 31. 32. 1. Функция S(i), которая преобразует 6-битовые числа в 4-битовые, определяется в таблицей 4. Таблица 4. "Функции преобразования S(i)". S(1). 14. 4. 13. 1. 2. 15. 11. 8. 3. 10. 6. 12. 5. 9. 0. 7. 0. 15. 7. 4. 14. 2. 13. 1. 10. 6. 12. 11. 9. 5. 3. 8. 4. 1. 14. 8. 13. 6. 2. 11. 15. 12. 9. 7. 3. 10. 5. 0. 15. 12. 8. 2. 4. 9. 1. 7. 5. 11. 3. 14. 10. 0. 6. 13. S(2). 15. 1. 8. 14. 6. 11. 3. 4. 9. 7. 2. 13. 12. 0. 5. 10. 3. 13. 4. 7. 15. 2. 8. 14. 12. 0. 1. 10. 6. 9. 11. 5. 0. 14. 7. 11. 10. 4. 13. 1. 5. 8. 12. 6. 9. 3. 2. 15. 13. 8. 10. 1. 3. 15. 4. 2. 11. 6. 7. 12. 0. 5. 14. 9. S(3). 10. 0. 9. 14. 6. 3. 15. 5. 1. 13. 12. 7. 11. 4. 2. 8. 13. 7. 0. 9. 3. 4. 6. 10. 2. 8. 5. 14. 12. 11. 15. 1. 13. 6. 4. 9. 8. 15. 3. 0. 11. 1. 2. 12. 5. 10. 14. 7. 1. 10. 13. 0. 6. 9. 8. 7. 4. 15. 14. 3. 11. 5. 2. 12. S(4). 7. 13. 14. 3. 0. 6. 9. 10. 1. 2. 8. 5. 11. 12. 4. 15. 13. 8. 11. 5. 6. 15. 0. 3. 4. 7. 2. 12. 1. 10. 14. 9. 10. 6. 9. 0. 12. 11. 7. 13. 15. 1. 3. 14. 5. 2. 8. 4. 3. 15. 0. 6. 10. 1. 13. 8. 9. 4. 5. 11. 12. 7. 2. 14. S(5). 2. 12. 4. 1. 7. 10. 11. 6. 8. 5. 3. 15. 13. 0. 14. 9. 14. 11. 2. 12. 4. 7. 13. 1. 5. 0. 15. 10. 3. 9. 8. 6. 4. 2. 1. 11. 10. 13. 7. 8. 15. 9. 12. 5. 6. 3. 0. 14. 11. 8. 12. 7. 1. 14. 2. 13. 6. 15. 0. 9. 10. 4. 5. 3. S(6). 12. 1. 10. 15. 9. 2. 6. 8. 0. 13. 3. 4. 14. 7. 5. 11. 10. 15. 4. 2. 7. 12. 9. 5. 6. 1. 13. 14. 0. 11. 3. 8. 9. 14. 15. 5. 2. 8. 12. 3. 7. 0. 4. 10. 1. 13. 11. 6. 4. 3. 2. 12. 9. 5. 15. 10. 11. 14. 1. 7. 6. 0. 8. 13. S(7). 4. 11. 2. 14. 15. 0. 8. 13. 3. 12. 9. 7. 5. 10. 6. 1. 13. 0. 11. 7. 4. 9. 1. 10. 14. 3. 5. 12. 2. 15. 8. 6. 1. 4. 11. 13. 12. 3. 7. 14. 10. 15. 6. 8. 0. 5. 9. 2. 6. 11. 13. 8. 1. 4. 10. 7. 9. 5. 0. 15. 14. 2. 3. 12. S(8). 13. 2. 8. 4. 6. 15. 11. 1. 10. 9. 3. 14. 5. 0. 12. 7. 1. 15. 13. 8. 10. 3. 7. 4. 12. 5. 6. 11. 0. 14. 9. 2. 7. 11. 4. 1. 9. 12. 14. 2. 0. 6. 10. 13. 15. 3. 5. 8. 2. 1. 14. 7. 4. 10. 8. 13. 15. 12. 9. 0. 3. 5. 6. 11. К таблице 4 требуются дополнительные пояснения. Каждая из функций S(i)B(i) преобразет 6-битовый код в 4-битовый выход по следующему алгоритму: первый и последний биты входной последовательности B, определяют номер строки k. второй, третий, четвертый и пятый биты последовательности B задают номер колонки l результат преобразования выбирается из строки k и колонки l.

Предположим, что B=011011. Тогда S(1)(B)=0101. Действительно, k=1, l=13. В колонке 13 строки 1 задано значение 5, которое и является значением функции S(1)(011011).

Функция перестановки бит P(L), также используемая для определения функции шифрования, задается значениями, приведенными в таблице 5. В последовательности L 32 перестанавливается так, чтобы бит 16 стал первым битом, бит 7 - вторым и т.д.

Таблица 5. "Функция перестановки P". 16. 7. 20. 21. 29. 12. 28. 17. 1. 15. 23. 26. 5. 18. 31. 10. 2. 8. 24. 14. 32. 27. 3. 9. 19. 13. 30. 6. 22. 11. 4. 25. Процесс получения ключей. Чтобы завершить описание алгоритма шифрования данных, осталось привести алгоритм получение ключей K(i), i=1,2,...,16, размерностью в 48 бит. Ключи K(i) определяются по 64-битовому ключу шифра как это показано на рисунке 3. Рисунок 3.

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

В начале над ключом шифра выполняется операция B, которая сводится к выбору определенных бит и их перестановке, как это показано в таблицей 6. Причем, первые четыре строки определяют, как выбираются биты последовательности C(0) (первым битом C(0) будет бит 57 бит ключа шифра, затем бит 49 и т.д., а последними битами биты 44 и 36 ключа шифра), а следующие четыре строки - как выбираются биты последовательности D(0) (т.е. последовательность D(0) будем состоять из битов 63,55,...,12, 4 ключа шифра).

Таблица 6. "Функция перестановки и выбора последовательности B". 57. 49. 41. 33. 25. 17. 9. 1. 58. 50. 42. 34. 26. 18. 10. 2. 59. 51. 43. 35. 27. 19. 11. 3. 60. 52. 44. 36. 63. 55. 47. 39. 31. 23. 15. 7. 62. 54. 46. 38. 30. 22. 14. 6. 61. 53. 45. 37. 29. 21. 13. 5. 28. 20. 12. 4. Как видно из таблицы 6, для генерации последовательностей C(0) и D(0) не используются биты 8,16,25,32,40,48,56 и 64 ключа шифра. Эти биты не влияют на шифрование и могут служить для других целей (например, для контроля по четности). Таким образом, в действительности ключ шифра является 56-битовым. После определения C(0) и D(0) рекурсивно определяются C(i) и D(i), i=1,2,...,16. Для этого применяются операции сдвига влево на один или два бита в зависимости от номера шага итерации, как это показано в таблицей 7. Операции сдвига выполняются для последовательностей C(i) и D(i) независимо. Например, последовательность C(3) получается, посредством сдвига влево на две позиции последовательности C(2), а последовательность D(3) - посредством сдвига влево на две позиции последовательности D(2). Следует иметь в виду, что выполняется циклический сдвиг влево. Например, единичный сдвиг влево последовательности C(i) приведет к тому, что первый бит C(i) станет последним и последовательность бит будет следующая: 2,3,..., 28,1. Таблица 7. "Функция сдвига Si". 1. 1. 2. 1. 3. 2. 4. 2. 5. 2. 6. 2. 7. 2. 8. 2. 9. 1. 10. 2. 11. 2. 12. 2. 13. 2. 14. 2. 15. 2. 16. 1. Ключ K(i), определяемый на каждом шаге итерации, есть результат выбора определенных бит из 56-битовой последовательности C(i)D(i) и их перестановки. Другими словами, K(i) = K(C(i)D(i)), где функция K определяется данными, приведенными в таблицей 8. Таблица 8. "Функция перестановки и выбора K".
14. 17. 11. 24. 1. 5.
3. 28. 15. 6. 21. 10.
23. 19. 12. 4. 26. 8.
16. 7. 27. 20. 13. 2.
41. 52. 31. 37. 47. 55.
30. 40. 51. 45. 33. 48.
44. 49. 39. 56. 34. 53.
46. 42. 50. 36. 29. 32.

Как следует из таблицы 8 первый бит K(i) - это бит 14 последовательности C(i)D(i), второй - бит 17, последний - бит 32.

Определение кодовой страницы.

Автор: Alexander Trunov.

{
  Work with codepages
  (c) 1999 by Alexander Trunov, {2:5069/10}, {jnc@mail.ru}
}
unit Codepage;
interface
const
  cpWin = 01;
  cpAlt = 02;
  cpKoi = 03;
function DetermineCodepage(const st: string): Byte;
function Alt2Win(const st: string): string;
function Win2Alt(const st: string): string;
function Alt2Koi(const st: string): string;
function Koi2Alt(const st: string): string;
function Win2Koi(const st: string): string;
function Koi2Win(const st: string): string;
function X2Y(const st: string; srcCp, dstCp: Byte): string;
implementation
const
  AltSet = ['А'..'Я', 'а'..'п', 'р'..'я'];
  KoiSet = ['Б'..'Р', 'Т'..'С'];
  WinSet = ['а'..'п', 'р'..#255];
  Win2AltTable: array[0..255] of Byte = (
    $00, $01, $02, $03, $04, $05, $06, $07, $08, $20, $0A, $0B, $0C, $0D, $0E, $0F,
    $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
    $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
    $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
    $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
    $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
    $60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
    $70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
    $80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $8A, $8B, $8C, $8D, $8E, $8F,
    $90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $9A, $9B, $9C, $9D, $9E, $9F,
    $A0, $A1, $A2, $A3, $A4, $A5, $A6, $A7, $A8, $A9, $AA, $22, $AC, $AD, $AE, $AF,
    $B0, $B1, $B2, $B3, $B4, $B5, $B6, $B7, $B8, $FC, $BA, $22, $BC, $BD, $BE, $BF,
    $80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $8A, $8B, $8C, $8D, $8E, $8F,
    $90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $9A, $9B, $9C, $9D, $9E, $9F,
    $A0, $A1, $A2, $A3, $A4, $A5, $A6, $A7, $A8, $A9, $AA, $AB, $AC, $AD, $AE, $AF,
    $E0, $E1, $E2, $E3, $E4, $E5, $E6, $E7, $E8, $E9, $EA, $EB, $EC, $ED, $EE, $EF);
  Alt2WinTable: array[0..255] of Byte = (
    $00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0D, $0E, $0F,
    $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
    $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
    $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
    $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
    $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
    $60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
    $70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
    $C0, $C1, $C2, $C3, $C4, $C5, $C6, $C7, $C8, $C9, $CA, $CB, $CC, $CD, $CE, $CF,
    $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7, $D8, $D9, $DA, $DB, $DC, $DD, $DE, $DF,
    $E0, $E1, $E2, $E3, $E4, $E5, $E6, $E7, $E8, $E9, $EA, $EB, $EC, $ED, $EE, $EF,
    $20, $20, $20, $A6, $A6, $A6, $A6, $2B, $2B, $A6, $A6, $2B, $2B, $2B, $2B, $2B,
    $2B, $2D, $2D, $2B, $2D, $2B, $A6, $A6, $2B, $2B, $2D, $2D, $A6, $2D, $2B, $2D,
    $2D, $2D, $2D, $2B, $2B, $2B, $2B, $2B, $2B, $2B, $2B, $5F, $5F, $5F, $5F, $5F,
    $F0, $F1, $F2, $F3, $F4, $F5, $F6, $F7, $F8, $F9, $FA, $FB, $FC, $FD, $FE, $FF,
    $A8, $B8, $AA, $BA, $AF, $BF, $A1, $A2, $B0, $B7, $B7, $5F, $B9, $A4, $5F, $5F);
  Koi2AltTable: array[0..255] of Byte = (
    $00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0D, $0E, $0F,
    $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
    $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
    $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
    $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
    $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
    $60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
    $70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
    $80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $8A, $8B, $8C, $8D, $8E, $8F,
    $90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $9A, $9B, $9C, $9D, $9E, $9F,
    $A0, $A1, $A2, $A5, $A4, $A5, $A6, $A7, $A8, $A9, $AA, $AB, $AC, $AD, $AE, $AF,
    $B0, $B1, $B2, $B3, $B4, $B5, $B6, $B7, $B8, $B9, $BA, $BB, $BC, $BD, $BE, $BF,
    $EE, $A0, $A1, $E6, $A4, $A5, $E4, $A3, $E5, $A8, $A9, $AA, $AB, $AC, $AD, $AE,
    $AF, $EF, $E0, $E1, $E2, $E3, $A6, $A2, $EC, $EB, $A7, $E8, $ED, $E9, $E7, $EA,
    $9E, $80, $81, $96, $84, $85, $94, $83, $95, $88, $89, $8A, $8B, $8C, $8D, $8E,
    $8F, $9F, $90, $91, $92, $93, $86, $82, $9C, $9B, $87, $98, $9D, $99, $97, $FF);
  Alt2KoiTable: array[0..255] of Byte = (
    $00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0D, $0E, $0F,
    $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
    $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
    $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
    $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
    $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
    $60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
    $70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
    $E1, $E2, $F7, $E7, $E4, $E5, $F6, $FA, $E9, $EA, $EB, $EC, $ED, $EE, $EF, $F0,
    $F2, $F3, $F4, $F5, $E6, $E8, $E3, $FE, $FB, $FD, $9A, $F9, $F8, $FC, $E0, $F1,
    $C1, $C2, $D7, $C7, $C4, $C5, $D6, $DA, $C9, $CA, $CB, $CC, $CD, $CE, $CF, $D0,
    $B0, $B1, $B2, $B3, $B4, $B5, $B6, $B7, $B8, $B9, $BA, $BB, $BC, $BD, $BE, $BF,
    $C0, $C1, $C2, $C3, $C4, $C5, $C6, $C7, $C8, $C9, $CA, $CB, $CC, $CD, $CE, $CF,
    $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7, $D8, $D9, $DA, $DB, $DC, $DD, $DE, $DF,
    $D2, $D3, $D4, $D5, $C6, $C8, $C3, $DE, $DB, $DD, $DF, $D9, $D8, $DC, $C0, $D1,
    $85, $A3, $F2, $F3, $F4, $F5, $F6, $F7, $F8, $F9, $FA, $FB, $FC, $FD, $FE, $FF);
function X2Y(const st: string; srcCp, dstCp: Byte): string;
begin
  case srcCp of
    cpWin:
      begin
        case dstCp of
          cpWin:
            begin
              Result := st;
            end;
          cpAlt:
            begin
              Result := Win2Alt(st);
            end;
          cpKoi:
            begin
              Result := Win2Koi(st);
            end;
        end;
      end;
    cpAlt:
      begin
        case dstCp of
          cpWin:
            begin
              Result := Alt2Win(st);
            end;
          cpAlt:
            begin
              Result := st;
            end;
          cpKoi:
            begin
              Result := Alt2Koi(st);
            end;
        end;
      end;
    cpKoi:
      begin
        case dstCp of
          cpWin:
            begin
              Result := Koi2Win(st);
            end;
          cpAlt:
            begin
              Result := Koi2Alt(st);
            end;
          cpKoi:
            begin
              Result := st;
            end;
        end;
      end;
  end;
end;
function Win2Koi(const st: string): string;
begin
  Result := Alt2Koi(Win2Alt(st));
end;
function Koi2Win(const st: string): string;
begin
  Result := Alt2Win(Koi2Alt(st));
end;
function Alt2Win(const st: string): string;
var
  i: Integer;
begin
  Alt2Win[0] := Char(Length(st));
  for i := 1 to Length(st) do
  begin
    Alt2Win[i] := Char(Alt2WinTable[Byte(st[i])]);
  end;
end;
function Win2Alt(const st: string): string;
var
  i: Integer;
begin
  Win2Alt[0] := Char(Length(st));
  for i := 1 to Length(st) do
  begin
    Win2Alt[i] := Char(Win2AltTable[Byte(st[i])]);
  end;
end;
function Alt2Koi(const st: string): string;
var
  i: Integer;
begin
  Alt2Koi[0] := Char(Length(st));
  for i := 1 to Length(st) do
  begin
    Alt2Koi[i] := Char(Alt2KoiTable[Byte(st[i])]);
  end;
end;
function Koi2Alt(const st: string): string;
var
  i: Integer;
begin
  Koi2Alt[0] := Char(Length(st));
  for i := 1 to Length(st) do
  begin
    Koi2Alt[i] := Char(Koi2AltTable[Byte(st[i])]);
  end;
end;
function DetermineCodepage(const st: string): Byte;
var
  WinCount,
    AltCount,
    KoiCount,
    i, rslt: Integer;
begin
  DetermineCodepage := cpAlt;
  WinCount := 0;
  AltCount := 0;
  KoiCount := 0;
  for i := 1 to Length(st) do
  begin
    if st[i] in AltSet then Inc(AltCount);
    if st[i] in WinSet then Inc(WinCount);
    if st[i] in KoiSet then Inc(KoiCount);
  end;
  DetermineCodepage := cpAlt;
  if KoiCount > AltCount then
  begin
    DetermineCodepage := cpKoi;
    if WinCount > KoiCount then DetermineCodepage := cpWin;
  end
  else
  begin
    if WinCount > AltCount then DetermineCodepage := cpWin;
  end;
end;
end.

Как определить видеокарту.

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

Стоит программист перед светофором: - Никак не могу понять, что там за видеоадаптер: у Геркулеса - 2 цвета, у CGA - 4, у EGA - 16, у VGA -256, у XGA - 65535, а 3-х - ну ни у кого нет!
procedure TForm1.button1click(Sender: TObject);
var
  lpDisplayDevice: TDisplayDevice;
  dwFlags: DWORD;
  cc: DWORD;
begin
  form2.memo1.Clear;
  lpDisplayDevice.cb := sizeof(lpDisplayDevice);
  dwFlags := 0;
  cc := 0;
  while EnumDisplayDevices(nil, cc, lpDisplayDevice, dwFlags) do
  begin
    Inc(cc);
    form2.memo1.lines.add(lpDisplayDevice.DeviceString);
    {Так же мы увидим дополнительную информацию в lpDisplayDevice}
    form2.show;
  end;
end;

Разработка серверных Web-приложений на Delphi.

Приходит девушка к программисту в гости, а тот: - Чай, кофе, Интернет?

Практически ежегодно появляется очередная переработанная или серьезно дополненная версия Delphi — в прошлом году была выпущена уже пятая. Количество книг по программированию в среде Delphi, вышедших у нас за последние три-четыре года, свидетельствует о том, что этот продукт наиболее популярен среди аналогичных средств разработки ПО. Его можно использовать для создания различных программ, в том числе и Web-приложений, процесс разработки которых благодаря применению Web-компонентов и Web-классов значительно упростился.

Кстати, для тех программистов, которые работают на Си и не хотят переходить на Паскаль, но желали бы пользоваться всеми возможностями и преимуществами Delphi, фирма Inprise выпустила инструментальный аналог этого пакета — C++ Builder. Он обладает всеми достоинствами Delphi и обновляется одновременно с ним. Однако пока пакет не вызывает столь же большого интереса, поскольку выбор соответствующего инструментария при работе на Си шире, чем при использовании Паскаля.

Delphi пятой версии существенно отличается от предыдущей. Так, в палитре VCL этого инструментального пакета появилась страница InternetExpress, на которой представлены TWebConnection и TmidasPageProducer. Они подходят для разработки динамических Internet-клиентов с помощью сценариев, написанных на JavaScript, HTML 4 и XML.

Delphi 5.0 поставляется в трех вариантах: Standard, Professional и Enterprise, различающихся назначением и составом дополнительных инструментальных средств. В Standard наличествует лишь базовый набор средств разработки, Professional можно дополнительно оснащать Internet-компонентами из специального пакета WebBroker, а Enterprise включает весь доступный Internet-инструментарий и поддерживает новую технологию Microsoft — ASP (Active Server Pages). Для этого в Enterprise также включен специализированный эксперт, с помощью которого можно создавать активные серверные страницы. Такие решения, поддерживаемые Web-сервером MS IIS (Microsoft Internet Information Server), позволяют создавать динамические Web-узлы. Кроме того, для разработки Web-приложений используются элементы, находящиеся на Internet-странице палитры компонентов. Эта страница в Delphi 5.0 существенно отличается от соответствующей в версии 4.0 и внешне (другие значки), и по наполнению.

На ней остались лишь те компоненты, которые нужны непосредственно для проектирования Web-приложений. Наряду с пятью невизуальными там имеются ClientSocket и ServerSocket (два первых), а также WebBrowser (последний).

Информацию, касающуюся комплектации и обновления Delphi, можно получить на узлах Inprise.ru (соm), interface.ru, demo.ru и др.

Web-приложения

В качестве клиентских Web-приложений могут быть использованы современные браузеры, среди которых лидируют MS Internet Explorer и Netscape Navigator. Альтернативой является разработка собственной программы с помощью Delphi. Этот путь может показаться более трудоемким и затратным, но в результате зачастую получается такой продукт, который максимально соответствует решаемой задаче.

Если в качестве клиентского приложения все же используется универсальный Web-браузер, то при создании интересных динамических Web-страниц на профессиональном уровне уже не обойтись без программирования хотя бы на элементарном уровне. Тогда придется разрабатывать продукты на уровне CGI- или API-приложений, и инструментарий Delphi 5.0 окажется как раз к месту.

Технология WebBroker

Для проектирования серверных Web-приложений в Delphi разработана специальная технология — WebBroker. С ее помощью можно создавать сложные программы, в том числе и работающие с базами данных как локальными, так и хранящимися на наиболее популярных серверах — InterBase, Oracle, Informix, Sybase, MS SQL. В последнем случае связь серверного приложения с источником данных обеспечивается с помощью одного из двух механизмов — BDE (Borland Data Engine) или ODBC.

Технология WebBroker реализована на основе Web-компонента — TWebModule. Для обеспечения комфортности работы и ускорения процесса проектирования предусмотрены два мастера — Web Server Application и Database Web Application Wizard. Использование TWebModule позволяет создавать программы, которые будут работать под управлением серверов, поддерживающих интерфейсы расширения — ISAPI (Internet Server API, разработанный корпорацией Microsoft), NSAPI (Netscape API, предложенный компанией Netscape), а также CGI и WinCGI.

Особенности каждого из указанных стандартов учитываются в технологии WebBroker. При этом предусматривается выполнение программы непосредственно на сервере. Но Delphi также позволяет создавать приложения типа ActiveX, запускаемых на компьютере клиента. Следует отметить, что каждое Web-приложение может иметь лишь один компонент TWebModule.

ISAPI- и NSAPI-приложения — это библиотеки DLL, которые Web-сервер загружает по запросу клиента, поступающему от браузера через Internet по протоколу TCP/IP. Клиентская информация передается из запроса в dll-файл в структурированном виде и обрабатывается компонентом TISAPIApplication. Каждый поток управляется отдельным запросом. Как только dll-файл загрузится, сервер может отвечать на клиентские запросы внутри основного процесса.

Поскольку обмен информацией между клиентом и Web-приложением происходит в оперативной памяти сервера, то программы на базе ISAPI/NSAPI работают гораздо быстрее, чем приложения на основе CGI/WinCGI. CGI-приложения являются консольными и не отличаются эффективностью, так как каждый раз при поступлении соответствующего запроса от клиента сначала происходит их запуск, а по окончании работы — полное завершение. Поэтому количество клиентов, подключающихся к Web-серверу с CGI-приложениями, ограничено объемом его оперативной памяти и быстродействием.

Однако подобные приложения обладают универсальностью, обусловленной совместимостью с различными платформами, поскольку стандарт CGI был разработан и широко использовался на Unix, а затем был реализован и для других ОС, в том числе для Windows. Данные от клиента, поступающие в командной строке запроса, который организован в этом стандарте, передаются на сервер после обработки компонентом TCGIApplication.

WinCGI — модификация стандарта CGI, рассчитанная на работу под управлением ОС Windows. Программы, разработанные на базе этих двух стандартов, будут работать и на Web-серверах — MS IIS 4.0 или Netscape, поддерживающих стандарты ISAPI и NSAPI соответственно. Несомненное достоинство технологии WebBroker — возможность представления конечного программного продукта в любом из указанных стандартов простой перекомпиляцией исходных текстов, что существенно облегчает работу.

Для разработки серверных Web-приложений в палитре компонентов Delphi 5 программисту предлагается Internet-страница, на которой расположено пять невизуальных Web-компонентов: TWebDispatcher, TPageProducer, TDataSetPageProducer, TDataSetTableProducer и TQueryTableProducer. Шестой невизуальный компонент — TwebModule — автоматически включается в проект при создании Web-приложения с помощью мастера Web Server Application. Он — основа любого серверного Web-приложения, а также служит репозитарием (своеобразным «контейнером») для остальных невизуальных компонентов, чем напоминает Data-модуль, применяемый для работы с БД при обычном проектировании клиентских приложений.

Чтобы выбрать Web Server Application, следует обратиться к меню File•New•New. После подключения этого мастера открывается подменю, где нужно указать стандарт (ISAPI, NSAPI, CGI или WinCGI), с которым будет работать создаваемое Web-приложение.

Это позволит разработать новый проект на основе автоматически определенного Web-модуля. В его единственном окне и будут размещаться Internet-компоненты. Они, как и сам модуль, имеют статус невизуальных, т. е. доступных лишь в режиме проектирования (design-time). Все видимые элементы и компоненты серверного Web-приложения станут отображаться в окне браузера клиента, обращающегося к серверу, а во время разработки, отладки и тестирования Web-программы сам Web-сервер и браузер могут находиться на одном компьютере, выполняющем одновременно функции как клиента, так и сервера.

Когда разрабатывается ISAPI/ NSAPI-приложение, в заголовок файла проекта добавляется директива library, а в список под директивой uses заносятся необходимые записи. Затем создается сам файл проекта (листинг 1).

Листинг 1.

Файл проекта ISAPI/NSAPI-приложения.

library IServer;
uses
  WebBroker, HTTPApp, ISAPIApp,
  main in ‘main.pas’ {CustomerInfoModule: TDataModule};
{$R *.RES}
exports
  GetExtensionVersion,
  HttpExtensionProc,
  TerminateExtension;
begin
  Application.Initialize;
  Application.CreateForm(TCustomerInfoModule, CustomerInfoModule);
  Application.Run;
end.

Кстати, в Delphi 5.0 при формировании Web-проекта появился новый модуль WebBroker. Поэтому при переходе на нее в готовом (или разрабатываемом) проекте серверного Web-приложения нужно перед его перекомпилированием приписать в ‘library’ название этого нового модуля в разделе ‘uses’. И наоборот, при возврате к версии 4.0 следует (с помощью комментария) убрать это имя.

Классы TWebRequest и TWebResponse.

TWebModule предоставляет серверному Web-приложению возможность сформировать ответы на клиентские HTTP-запросы. Обмен между серверным и клиентским приложениями происходит в объектной форме. Запросы представляются в виде объектов в соответствии с выбранными условиями в списке свойств action items.

В модуле HTTPAPP.pas как абстрактные базовые классы объявлены TWebRequest и TWebResponse. Они инкапсулируют протокол HTTP, по которому происходит обмен информацией между Web-сервером и клиентскими браузерами. Класс TWebRequest предоставляет доступ ко всей информации, поступающей от клиентов на Web-сервер через свои свойства и методы. Класс же TWebResponse, наоборот, с помощью своих свойств и методов позволяет переслать клиенту данные в форме ответа на отправленный ранее запрос по протоколу HTTP. Причем данные от Web-сервера можно переслать любым из возможных для протокола HTTP способов.

В действительности взаимодействие между Web-сервером и клиентами обеспечивают классы TISAPIResponse и TISAPIRequest, объявляемые в модуле ISAPIAPP.pas и учитывающие специфику интерфейсов ISAPI и NSAPI. Они являются прямыми потомками абстрактных классов TWebRequest и TWebResponse. Полиморфизм, свойственный Delphi, позволяет организовать их передачу в виде параметров TWebRequest и TWebResponse через обработчик события OnAction в компонент TwebModule (листинг 2).

Листинг 2.

Обработчик события OnAction.

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse;
var
  Handled: Boolean);
begin
  //обработчик события
end;

Свойства класса TISAPIRequest, содержащего информацию запроса от клиентского браузера, позволяют получить разнообразную и достаточно полную информацию о самом клиенте, хотя ряд параметров в передаваемом запросе может быть не определен. Этот класс имеет около 30 ненаследуемых свойств. Так, свойства RemoteHost и RemoteAddr включают Internet-адрес клиентского компьютера. Данные о браузере, установленном на клиентском месте, заключены в свойстве UserAgent. Из свойства Accept можно извлечь список типов графических файлов, с которыми может работать браузер клиента. Свойство Refere содержит URL Web-страницы, где можно получить ссылку на этот Web-сервер, а данные Cookie содержатся только в свойстве Cookie в виде строк. С помощью свойства CookieFields через массивы полей можно сразу же получить доступ к значениям нескольких клиентских рабочих мест.

Подробные данные клиентского URL-запроса можно извлечь из свойства Query. Например, если в URL-запросе ‘http://www.TSite.com/art/gallery.dll/ mammals?animal=dog&color=black’ часть ‘/gallery.dll’ представляет собой имя Web-приложения, то свойство Query будет включать animal=dog&color=black. В этом свойстве информация неотделима от URL. Причем Query может включать множественные поля, разделенные знаками логического «И» (&). При передаче параметров через URL они обычно следуют за знаком вопроса (?). Пробелы заменяются плюсом (+), а последний наряду с некоторыми другими передается в кодированном виде: %хх, где хх — шестнадцатеричное представление символа.

Чтобы ознакомиться с функциями, свойствами и методами класса TISAPIRequest, целесообразно провести тестирование. Для этого нужно сформировать HTML-страницу в обработчике события OnAction и отобразить на ней значения всех многообразных свойств класса или хотя бы некоторых наиболее важных (листинг 3).

Листинг 3.

Вывод информации о сервере и клиенте.

procedure TWebModule1.WebModule1WebActionItem2Action(Sender:
TObject; Request: TWebRequest; Response: TWebResponse;
var Handled: Boolean);
begin
  Pg:= TstringList.Create;
  Pg.Add('<HTML>');
  Pg.Add('<HEAD>');
  Pg.Add('<charset=1251>');
  Pg.Add('<ТIТLЕ>Информация о сервере и клиенте'</ТIТLЕ>');
  Pg.Add('</HEAD>');
  Pg.Add('<BODY>');
  Pg.Add('<HR>');
  Pg.Add('<В>Динамическая страница, созданная в Delphi<B><P>');
  Pg.Add('<В>как Web-расширение <B><P>');
  Pg.Add('Method = ' + Request.Method + '<BR>');
  with Pg do
  begin
    Add('ProtocolVersion = ' + Request.ProtocolVersion + '<BR>');
    Add('URL = ' + Request.URL + '<BR>');
    Add('Query = ' + Request.Query + '<BR>');
    Add('PathInfo = ' + Request.PathInfo + '<BR>');
    Add('PathTranslated = ' + Request.PathTranslated + '<BR>');
    Add('Authorization = ' + Request.Authorization + '<BR>');
    Add('CacheControl = ' + Request.CacheControl + '<BR>');
    Add('Cookie = ' + Request.Cookie + '<BR>');
    Add('Accept = ' + Request.Accept + '<BR>');
    Add('From = ' + Request.From + '<BR>');
    Add('Host = ' + Request.Host + '<BR>');
    Add('Referer = ' + Request.Referer + '<BR>');
    Add('UserAgent = ' + Request.UserAgent + '<BR>');
    Add('ContentEncoding = ' + Request.ContentEncoding + '<BR>');
    Add('ContentType = ' + Request.ContentType + '<BR>');
    Add('ContentLength = ' + IntToStr(Request.ContentLength) + '<BR>');
    Add('ContentVersion = ' + Request.ContentVersion + '<BR>');
    Add('Content = ' + Request.Content + '<BR>');
    Add('Connection = ' + Request.Connection + '<BR>');
    Add('DerivedFrom = ' + Request.DerivedFrom + '<BR>');
    Add('Title = ' + Request.Title + '<BR>');
    Add('RemoteAddr = ' + Request.RemoteAddr + '<BR>');
    Add('RemoteHost = ' + Request.RemoteHost + '<BR>');
    Add('ScriptName = ' + Request.ScriptName + '<BR>');
    Add('ServerPort = ' + IntToStr(Request.ServerPort) + '<BR>');
  end;
  Pg.Add('<HR>');
  Pg.Add('</BODY>');
  Pg.Add('</HTML>');
  Response.Content := Pg.Text;
  Pg.Free;
end;

Эта страница отсылается через Response.Content на клиентский браузер для тестирования системы.

Конечно, не нужно отправлять подобную информацию клиенту, пользующемуся услугами Web-сервера, ее целесообразно использовать для подготовки эффективного и даже «интеллектуального» ответа клиенту. В частности, информация о версии и функциональных возможностях браузера позволит выдать клиенту Web-страницу в требующемся виде, например без графических файлов, в том формате, который его браузер не воспринимает. А анализ запросов (например, по свойству Query) позволяет выявить «полезного» и даже «нужного» клиента. Если запомнить его URL-адрес, то при следующем обращении на Web-сервер можно персонально его поприветствовать, сразу открыть ему нужную страницу либо раздел, предоставить доступ к дополнительной информации. Этот же способ помогает регистрировать данные о посетителях на Web-сервере и, если необходимо, избавляться от нежелательных «визитов» и т. п.

Данные пересылаются клиенту с Web-сервера в форме ответа через свойство Content класса TISAPIResponse. Этот класс имеет 23 свойства, из них 19 — ненаследуемые. В Content должен содержаться HTML-код, отображаемый клиентским браузером в виде Web-страницы. В другом важном свойстве — Version — указывается версия HTTP-протокола. Значение Version берется из ProtocolVersion свойства HTTPRequest. Для получения дополнительной информации по спецификации HTTP-протокола необходимо ознакомиться с соответствующей документацией, имеющейся, например, по адресу http://www.w3.org.

Большое влияние на работу с классом TISAPIResponse оказывают методы. Например, вызвав после установки свойств объекта HTTP-ответа метод SendResponse, можно послать клиенту сообщение еще до окончания действия обработчика OnAction. Метод SendRedirect поможет перенаправить клиента на другой Web-сервер с соответствующим URL. Метод SendStream обеспечивает передачу данных любого типа. Однако перед его вызовом нужно затребовать SendResponse, который формирует заголовок сообщения HTTP-ответа, базирующийся на свойствах объекта TISAPIResponse. Вместо метода SendResponse перед вызовом SendStream нужно установить свойство ContentStream в значении AStream.

Наибольший интерес, пожалуй, представляет использование технологии WebBroker при построении динамических Web-страниц для работы с базами данных. Однако здесь не обойтись без языка HTML, ранее не применявшегося в Delphi. Специальные компоненты TPageProducer, TDataSetPageProducer, TDataSetTableProducer и TQueryTableProducer помогают программисту эффективно решать подобные задачи, которые далеко не всегда тривиальны, что существенно экономит его время и силы.

TPageProducer

Компонент TPageProducer позволяет создавать динамические Web-страницы и манипулировать ими с помощью шаблонов, т. е. клиенту предоставляется нужная HTML-страница в соответствии с определенными условиями, которые анализируются в обработчике события OnHTMLTag. Само это событие будет инициироваться в момент прочтения специального дескриптора, начинающегося со знака #, который включает имя и параметры с соответствующими значениями, например <#TagName Param1=Value1 Param2=Value2 ...>. Имя должно быть допустимым идентификатором языка Паскаль, а параметры с пробелами заключаются в двойные кавычки. В Delphi имеется семь предопределенных дескрипторов.

В TPageProducer предусмотрены свойства — HTMLDoc и HTMLFile. С помощью первого можно определить шаблон, который в дальнейшем будет использоваться для формирования страницы, возвращаемой клиенту при обращении к методу Content (в ответ на запрос). Установив TPageProducer в окне TWebModule и открыв редактор списка строк HTMLDoc, можно построчно набрать там шаблон HTML-страницы. Такой шаблон не будет отличаться от обычной страницы, написанной на стандартном HTML-языке. Метод Content преобразует код, находящийся в свойстве HTMLDoc (где могут присутствовать и ссылки в виде дескрипторов), в окончательную строку в формате HTML. При входе в каждый дескриптор вызывается событие OnHTMLTag, обработчиком которого и выполняется это преобразование (листинг 4).

Листинг 4.

Замена дескрипторов в обработчике события OnHTMLTag.

procedure TMyModule.ImageHTMLTag(Sender: TObject; Tag: TTag;
const TagString: string; TagParams: TStrings; var ReplaceText: string);
var
  ImageID: integer;
begin
  case Tag of
    tgImage:
    begin
      ImageID := StrToInt(TagParams.Values[‘ImageID’])
      if ImageTable.FindKey([ImageID]) then
        ReplaceText := '<IMG SRC=' +
        ImageTable.FieldByName('Filename').AsString +
        ' ALT=' + ImageTable.FieldByName(
        'Description').AsString + ' >'
      else
        ReplaceText:= '(Изображение отсутствует)'
    end;
    tgTable:
    begin
      ReplaceText := DataSetTableProducer1.Content;
    end;
  end;
end;

Предопределенный дескриптор tgImage предназначен для описания изображения, а tgTable — для установки HTML-таблицы. Кроме того, в Delphi 5.0 есть предопределенные теги tgCustom, tgLink, tgImageMap, tgObject и tgEmbed. С помощью tgLink описывается гипертекстовая ссылка, tgObject используется для встраивания ActiveX в HTML-страницу, tgImageMap заменяется картой изображения (контекстно-сенсорными зонами), tgEmbed содержит ссылку на DLL-расширение, совместимое с Netscape, tgCustom присваивается свойству Tag тогда, когда в HTML-тексте встречается неопределенный дескриптор, заменяемый любым значением, которое задает пользователь. Например, если в свойстве HTMLDoc HTML-текста имеется также непредопределенный тег <#DATE>, то Tag будет присвоено значение tgCustom. В обработчике OnHTMLTag этот дескриптор может быть заменен командой, выводящей текущую дату на HTML-страницу.

Установка HTMLDoc не допускает использования альтернативного HTMLFile, которое предусматривает применение внешнего файла для хранения аналогичного шаблона на языке HTML. А такой способ позволяет еще более расширить функциональные возможности Web-приложения путем замены и манипулирования HTML-страницами в нескольких шаблонах без изменения текста основной программы.

TDataSetTableProducer и TQueryTableProducer

Компоненты, находящиеся на странице DataControl и предназначенные для управления данными и для их вывода, в том числе и в табличной форме, не могут быть использованы в Web-приложении. Вместо них можно применить TDataSetTableProducer и TQueryTableProducer, информация для которых берется из TTable и TQuery соответственно, включенные в компонент TWebModule. Причем источники данных и компоненты управления непосредственно связаны между собой, потому и не требуется промежуточного компонента типа TDataSource. Компонент TDataSetTableProducer, аналогичный TDBGrid, позволяет переслать на клиентский браузер отчет в табличной форме в формате HTML. Его можно настроить на отображение любых столбцов выбранного набора данных.

Свойства Header и Footer позволяют вставить текст на HTML и до таблицы, и после нее. С помощью свойств Columns, RowAttributes и TableAttributes можно форматировать строки, столбцы и отдельные ячейки таблицы. В свойстве Dispatcher должен быть указан компонент-диспетчер, предоставляющий доступ к объектам TWebRequest и TWebResponse для получения запросов и передачи ответов.

Аналогично функционирует и TQueryTableProducer, за исключением режима доступа к данным. Этот компонент помогает сформировать HTML-таблицу на основе параметров, указанных в HTTP-запросе. Если последний основан на методе GET, то параметры определяются свойством QueryFields объекта запроса TWebRequest, а если на методе POST — свойством ContentFields того же объекта. В приведенной демонстрационной программе Iserver.dpr представлен пример использования TPageProducer,

Отладка серверных Web-приложений

Если разработано достаточно сложное Web-приложение в стандарте ISAPI(NSAPI), его необходимо отладить. Этот процесс существенно отличается от общепринятой отладки обычных Windows-приложений в среде Delphi, так как созданное Web-приложение запускается лишь в ответ на запросы, получаемые от браузера клиента через Web-сервер. Запуск даже самого простого Web-приложения непосредственно из среды Delphi 5.0 невозможен, поскольку теряется его связь с Web-сервером. Тогда и приложение не может получать запросы от клиента и, следовательно, реагировать на них. Методы отладки определяются также типом Web-сервера.

На платформе NT в качестве Web-сервера наиболее целесообразно с точки зрения доступности использовать MS IIS версий 2—4, а в качестве браузера MS IE —версии не ниже четвертой. Операционная система Windows NT 4.0 поставляется в комплекте с MS IIS 2.0 (дистрибутив — в I386\InetSrv). Можно также установить бесплатно распространяемый пакет NTOptPak, в который входит MS IIS 4.0, более распространенная, чем версии 2.0 и 3.0, и лишенная их недостатков.

Настройка отладки Web-приложения вместе с MS IIS 4.0 в интегрированной среде Delphi достаточно сложна и трудоемка, главным образом, из-за внесения множества изменений в реестр. Поэтому имеет смысл сначала отладить такое приложение с MS IIS версии 2 или 3.

ISAPI- и NSAPI-приложения представляют собой динамические библиотеки DLL с предопределенными адресами входа. Web-сервер запускает Web-приложения передачей запросов на эти адреса. Для обеспечения их связи нужно установить определенные параметры. Причем перед тем, как начнется работа Web-приложения, Web-сервер должен быть остановлен.

При отладке Web-приложений с MS IIS в меню Run/Parameters вводится следующее: Host Application: c:\winnt\system32\inetsrv\inetinfo.exe (маршрут к файлу должен соответствовать фактическому) и Run/Parameters: -e w3svc. Это позволит запускать MS IIS 2.0 и 3.0 и производить отладку ISAPI DLL под управлением Web-сервера.

Если на сервере установлен MS IIS версии 4.0 или более поздней, то в конфигурацию Windows NT 4.0 придется внести целый ряд изменений.

С помощью утилиты-конфигуратора DCOMCnfg нужно изменить учетную запись (user account) в разделе identify сервиса MS IIS Admin Service на имя того пользователя, который будет выполнять отладку.

С использованием редактора реестра (REGEDIT) или аналогичной утилиты следует удалить имена LocalService из всех подключей, связанных с аналогичными структурами IISADMIN, которые входят в ветвь реестра HKEY_CLASSES_ROOT/AppID и HKEY_ CLASSES_ROOT/CLSID. Раздел LocalService присутствует в составе следующих подключей:

{61738644-F196-11D0-9953-00C04FD919C1} // IIS WAMREG admin Service; {9F0BD3A0-EC01-11D0-A6A0-00A0C922E752} // IIS Admin Crypto Extension; {A9E69610-B80D-11D0-B9B9-00A0C922E750} // IISADMIN Service.

Кроме того, из двух первых подключей узлов AppID нужно удалить имя RunAs, а последний дополнить значением Interactive User.

Далее с применением REGEDIT необходимо добавить подключи LocalService32 ко всем структурам, связанным с IISADMIN и относящимся к узлу CLSID. Иными словами, каждый подключ узла CLSID, указанный в предыдущем пункте, или любой другой, под которым найдено имя LocalService keyword, следует дополнить подключом LocalService32. После чего нужно установить по умолчанию для этих новых подключей значение: c:\winnt\system32\inetsrv\inetinfo.exe -e w3svc (с учетом фактического расположения файла inetinfo.exe).

Следует установить в dword:3 значение Start для подключей:

HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\IISADMIN, HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\MSDTC, HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\W3SVC.

Чтобы вернуть реестр в исходное положение по окончании процесса отладки, необходимо проделать обратные операции. Они достаточно трудоемки и требуют повышенного внимания. Но можно автоматизировать процесс перенастройки реестра, разработав собственную программу, что поможет быстро определить его состояние без поиска, с помощью стандартных средств просмотра и коррекции, и перейти от отладки к работе ОС в штатном режиме. Такая программа предназначена для многократного использования. В качестве основного класса при разработке можно выбрать Tregistry, а для анализа, чтения и записи значений, а также удаления подключей подойдут многочисленные методы этого компонента: ReadKey, WriteFloat, WriteString, CloseKey, DeleteKey и т. п.

Такая программа поможет также контролировать фактическое состояние реестра в нужных подключах. Работа на этом уровне весьма ответственна, поскольку любая ошибка может вызвать недопустимые изменения в важнейшем для ОС механизме и привести к переустановке всей системы. Поэтому целесообразно освоить основные методы чтения фрагментов реестра, а удаление, создание и запись выполнять сначала только на тестовой ветви системы. Если приводимый ниже текст (листинг 5) вставить, например, в подпрограмму, реагирующую на событие открытия формы OnActivate, то в окне Label1 при настройке реестра на режим отладки появится имя подключа LocalServer32, а в окне Edit1 — соответствующее значение по умолчанию: c:\winnt\system32\inetsrv\ inetinfo.exe -e w3svc.

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

При отладке Web-приложений с Personal Web Server под управлением Windows 95/98 в меню Run/Parameters вводятся следующие параметры: Host Application: c:\Program Files\websvc\system\inetsw95.exe; Run Parameters: -w3svc. Они позволят запускать Personal Web Server и отлаживать ISAPI DLL. Для настройки на отладку Web-приложений с сервером Netscape Server версии 2.0 в соответствии с рекомендациями фирмы Borland требуется дополнительно изменить конфигурацию. Сначала следует скопировать файл ISAPITER.DLL (из каталога Bin) в каталог C:\Netscape\Server\Nsapi\Examples (с учетом фактического размещения файлов). Затем необходимо произвести преобразования в серверных конфигурационных файлах, расположенных в каталоге C:\Netscape\Server\Httpd-\Config.

В файле OBJ.CONF file после строки Init fn=load-types mime-types=mime.types требуется добавить строку Init funcs="handle-isapi,check-isapi,log-isapi" fn="load-modules" shlib="c:/netscape/server/nsapi/examples/ ISAPIter.dll". В секции <Object name=default> файла OBJ.CONF перед строкой NameTrans fn=document-root root="C:/ Netscape/Server/docs" нужно вставить строку NameTrans from="/scripts" fn="pfx2dir" dir="C:/Netscape/Server/ docs/scripts" name="isapi". В конце файла OBJ.CONF необходимо добавить секцию <Object name="isapi"> PathCheck fn="check-isapi" ObjectType fn="force-type" type="magnus-internal/isapi" Service fn="handle-isapi" </Object>. В конце файла MIME.TYPES следует добавить строку type=magnus-internal/isapi exts=dll.

Дополнения в первом и втором действиях вносятся одной непрерывной строкой. Затем для отладки Web-приложений под управлением сервера Netscape Fast Track требуется установить параметры Host Application: c:\Netscape\server\bin\httpd\httpd.exe и Run Parameters: c:\Netscape\server\httpd-<servername>\config. Они позволяют запустить сервер, а также указывают путь к конфигурационным файлам.

Создание DLL-приложений для работы под управлением Windows NT также имеет свою специфику. Необходимо расширить права пользователя так, чтобы стало возможным отладить DLL-программы. Делает это администратор Windows NT через программу User Manager, назначив Act As Part Of The Operating System (работа в режиме операционной системы) в меню Policies/User Rights (обязательно указав Show Advanced User Rights) тому пользователю-программисту, который будет осуществлять операцию. Подобная процедура неудобна потому, что по окончании отладки нужно восстановить исходные права пользователя.

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

После отладки DLL-приложения в формате ISAPI или NSAPI можно быстро перекомпилировать исходные тексты в exe-файл формата CGI/WinCGI, добавив отлаженный Web-модуль в репозитарий. При этом нужно присвоить название Web-модулю, дать описание, выбрать страницу репозитария (например, Data Modules) и иконку, указать фамилию автора. Созданный Web-модуль сохраняется в качестве шаблона. Затем через меню File/New следует вызвать мастер Web Server Application. В диалоге New Web Server Application выбрать CGI или WinCGI. Стереть автоматически сгенерированный Web-модуль и через меню File/New установить шаблон отлаженного приложения, который был ранее сохранен на соответствующей странице.

С помощью Delphi 5.0 можно создавать CGI/WinCGI-приложения, которые в виде exe-файлов должны напрямую запускаться Web-сервером. Однако методика их отладки несколько сложнее, чем DLL-приложений, поскольку нужно моделировать сервер путем создания конфигурационного файла с параметрами, включающими информацию из запроса. Затем нужно запустить WinCGI-приложение, указав полные адреса файла, содержащего информацию от клиента, и файла, в котором должен быть записан ответ. После этого можно, как обычно, отлаживать Web-приложение.

Механизм работы CGI/WinCGI-приложений проще, чем у ISAPI/NSAPI-приложений. При каждом обращении они запускаются в отдельном потоке, поэтому пересечения с многопотоковым механизмом не происходит. Кроме того, в CGI/WinCGI-приложениях не возникает проблем, имеющих место при кэшировании Web-модулей ISAPI/NSAPI-приложений.

Таким образом, можно облегчить настройку при отладке Web-приложений, используя упрощенную версию MS IIS 2.0. Полученные программы, как правило, могут работать и под управлением MS IIS 4.0. При этом следует учитывать некоторые незначительные на первый взгляд детали, которые могут существенно затруднить процесс отладки серверных приложений, как-то: проблемы с установкой состояния реестра, предварительная настройка доступа к определенным каталогам и файлам и последовательность выполнения операций.

Рассмотрим процесс отладки Web-серверного приложения iserver.dll, предлагаемого разработчиками Delphi 5.0. Его исходные тексты (iserver.dpr, main.pas, main.dfm) находятся в каталоге ...\Program Files\Borland\Delphi5\ Demos\Webserv\, а файловая база данных — в каталоге ...\Program Files\Common Files\Borland Shared\Data\. На этот же каталог должен указывать и DBDemos, настраиваемый с помощью программы-администратора bdeAdmin.exe, имеющейся в ...\Program Files\Common Files\Borland Shared\BDE\.

Само DLL-приложение должно быть скомпилировано в каталог, из которого Web-сервер может производить запуск exe-программ. Таковым может быть, например, создаваемый по умолчанию (при установке Web-сервера) виртуальный каталог Web-сервера SCRIPTS или аналогичный, созданный заново. После внесения рекомендованных изменений в реестр NT при отладке программы под управлением MS IIS 4.0 Web-сервер может запускаться только из среды Delphi 5.0. А в случае использования MS IIS 2.0 или 3.0 за работой Web-сервера приходится следить программисту, запуская и останавливая соответствующий сервис World Wide Web Publishing Service из окна Services панели управления.

Перед отладкой MS IIS должен быть остановлен, а начинать работу он будет запуском Web-приложения (DLL-программы) из среды Delphi 5.0. Сначала разумно проверить, работает ли уже отлаженное приложение, такое как демонстрационный пример Iserver.dll. Для этого нужно поместить исходный файл в раздел SCRIPTS. В окне Services следует проверить, подключен ли сервис Wide Web Publishing Service, если же нет — нужно нажать кнопку Start. Затем в окне браузера требуется набрать имя ПК или IP-адрес.

Иногда программа полностью не запускается, хотя все вроде бы выполнено правильно, т. е. MS IIS работает и есть доступ к запуску DLL-приложения. В этом случае могут появиться сообщения о «внутренней» ошибке сервера, например о неудачной инициализации BDE, невозможности работать с закрытой таблицей и т. п. Подобная информация свидетельствует об отсутствии права пользователя на доступ к каталогу ...\BDE\ и/или ...\DATA\, который, если необходимо, открывается или закрывается нажатием правой кнопки мыши на свойствах (properties/Security/permissions) соответствующего компонента (диска, каталога или файла).

Перед запуском MS IIS нужно установить протокол TCP/IP и выбрать какой-нибудь (фиктивный) IP-адрес, например 222.10.1.220, поскольку при разработке и отладке серверных приложений ПК следует отключить от Internet. Работу TCP/IP можно протестировать, набрав сначала консольную команду ping 127.0.0.1, а затем ping 222.10.1.220. В случае успешной установки протокола будет получен ответ типа ‘Reply from 127.0.0.1: bytes=32 time<10ms TTL=32’, а при неверной конфигурации — Pinging 222.10.1.220 with 32 bytes of data: Request timed out’.

Включившись в работу, завершите функционирование MS IIS из окна Services панели управления. Затем, запустив Delphi 5.0 и открыв указанный проект, расставьте точки останова в тексте модуля main, а после установки параметров загрузите отлаживаемый модуль через меню Run/Add Breakpoint/Module Load Breakpoint. Откройте окно Modules, нажав комбинацию клавиш <Alt>+<Ctrl>+m, и убедитесь, что там есть строка: iserver.dll not loaded. Потом, набрав комбинацию клавиш <Ctrl>+<F9> (или нажав кнопку Run), запустите Web-сервер. В окне Modules появится список модулей, запущенных под управлением MS IIS, но отлаживаемый модуль еще не будет загружен.

Функционирование модуля начнется после запуска браузера и набора строки типа http:// 222.10.1.220/scripts/iserver.dll/. При этом в окне Modules можно увидеть все входные точки и модули, связанные с отлаживаемым приложением. Затем начнется непосредственная отладка, и в iserver.dpr на строке Application.Initialize курсор остановится. Теперь нужно снова запустить приложение, нажав кнопку Run. Следующий останов будет в точке прерывания. Время соединения браузера с сервером ограничено определенными параметрами настройки браузера, а потому не следует медлить, иначе связь клиент—сервер прервется и процесс отладки прекратится. Тогда можно увеличить время соединения и продолжить отладку.

Перевести файл формы DFM в текстовый и наоборот.

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

Since Delphi 5, DFM-Files are stored as text files. Unfortunately, D2-D4 can only read DFM-Files in a binary-format. Delphi provides a conversion utility (convert.exe) to convert between .txt and .dfm formats. Seit D5 werden die DFM-Dateien als Textdateien gespeichert. Um diese mit D2-D4 bearbeiten zu кцnnеn, mьssеn sie in eine ьinдге Form gewandelt werden. Um dies zu erreichen, ist im BIN-Verzeichnis von Delphi ein kleines Programm namens "Convert.exe" vorhanden. // Sample calls, Verwendung: // Converting from *.DFM to *.TXT Convert.exe FormMain.dfm FormMain.txt // Converting from *.TXT to *.DFM Convert.exe FormMain.txt FormMain.dfm

Вызов диалога подключения сетевого диска.

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

Идёт себе по дороге программер, вдруг ему по башке кирпич... "Тетрис" - успел подумать программист.

Попробуйте WNetConnectionDialog. Данная функция инкапсулирована в Windows.pas и специально предназначена для этого.

Заголовок диалогового окна.

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

Окошко, окошко, повернись к ядру задом, а ко мне дружественным интерфейсом!

Заголовок диалогового окна устанавливается в момент вызова CreateMessageDialog, чей код расположен в Dialogs.pas. При этом происходит вызов LoadStr, который получает Warningcaption, Cautioncaption и пр., так что у вас есть два пути: Или вы изменяете Dialogs.pas, или вы редактируете строки в .res-файле.

Вызывает диалог выбора иконки.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Вызывает диалог выбора иконки (как при смене значка на десктопе)
Зависимости: uses activex;
Автор:       Babay_S
Copyright:   Мои раскопки
Дата:        27 сентября 2002 г.
***************************************************** }
uses activex;
function ChangeIconDialog(hOwner: LongInt; Filename: string; var IconIndex:
  LongInt): LongInt;
{Вызывает диалог выбора иконки (как при смене значка на десктопе)
Параметры
hOwner - хэндл родителя.
FileName - полный путь файла с иконками (должен существовать).
Если в указанном файле нет иконок, вызываются иконки Shell32.dll.
IconIndex возвращает номер иконки в файле.
Результат выполнения функции возвращает 1 если иконка выбрана и 0 если отмена
Далее эту иконку можно вытащить с помощью вполне документированной функции
ExtractIconEx из той же библиотеки.
}
implementation
function SHChangeIconDialog(hOwner: LongInt; sFilename: LPWSTR; nBuf: LongInt;
  var nIconIndex: LongInt): LongInt;
  stdcall; external 'Shell32.dll' index 62;
function ChangeIconDialog(hOwner: LongInt; Filename: string; var IconIndex:
  LongInt): LongInt;
var
  nFileName: LPWSTR;
  FNLen: Integer;
  i: LongInt;
begin
  FNLen := Length(FileName) + 1;
  nFileName := CoTaskMemAlloc(FnLen * sizeof(WideChar));
  StringToWideChar(FileName, nFileName, FnLen);
  if FileName = '' then
    nFileName := nil;
  Result := SHChangeIconDialog(hOwner, nFileName, 0, IconIndex);
  CoTaskMemFree(nFileName);
end;

Доработанная функция.

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Вызывает диалог выбора иконки. Доработанная
Функция вызова диалогового окно "Изменение иконки"
Зависимости: Windows, SysUtils
Автор:       Alex Sal'nikov, alex-co@narod.ru, Москва
Copyright:   Доработка библиотеки JVCL
Дата:        15 июля 2003 г.
***************************************************** }
uses
  Windows, SysUtils;
function ChangeIconDialog(hOwner: tHandle; var FileName: string; var IconIndex:
  Integer): Boolean;
// Функция вызова диалогового окно "Изменение иконки"
implementation
resourcestring
  SNotSupported = 'This function is not supported by your version of Windows';
function ChangeIconDialog(hOwner: tHandle; var FileName: string; var IconIndex:
  Integer): Boolean;
type
  SHChangeIconProc = function(Wnd: HWND; szFileName: PChar; Reserved: Integer;
    var lpIconIndex: Integer): DWORD; stdcall;
  SHChangeIconProcW = function(Wnd: HWND; szFileName: PWideChar;
    Reserved: Integer; var lpIconIndex: Integer): DWORD; stdcall;
const
  Shell32 = 'shell32.dll';
var
  ShellHandle: THandle;
  SHChangeIcon: SHChangeIconProc;
  SHChangeIconW: SHChangeIconProcW;
  Buf: array[0..MAX_PATH] of Char;
  BufW: array[0..MAX_PATH] of WideChar;
begin
  Result := False;
  SHChangeIcon := nil;
  SHChangeIconW := nil;
  ShellHandle := Windows.LoadLibrary(PChar(Shell32));
  try
    if ShellHandle <> 0 then
    begin
      if Win32Platform = VER_PLATFORM_WIN32_NT then
        SHChangeIconW := GetProcAddress(ShellHandle, PChar(62))
      else
        SHChangeIcon := GetProcAddress(ShellHandle, PChar(62));
    end;
    if Assigned(SHChangeIconW) then
    begin
      StringToWideChar(FileName, BufW, SizeOf(BufW));
      Result := SHChangeIconW(hOwner, BufW, SizeOf(BufW), IconIndex) = 1;
      if Result then
        FileName := BufW;
    end
    else if Assigned(SHChangeIcon) then
    begin
      StrPCopy(Buf, FileName);
      Result := SHChangeIcon(hOwner, Buf, SizeOf(Buf), IconIndex) = 1;
      if Result then
        FileName := Buf;
    end
    else
      raise Exception.Create(SNotSupported);
  finally
    if ShellHandle <> 0 then
      FreeLibrary(ShellHandle);
  end;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  file: string;
  index: Integer;
begin
  ChangeIconDialog(Handle, file, index);
  Edit1.Text := file;
  Edit2.Text := IntToStr(index);
end;

Диалог-компонент с кнопками Да, Нет, Отмена.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Диалог с кнопками Yes, No, Cancel (компонент).
Работает примерно так же как TDialog (для удобства).
TYesOrNoDialog.ShowNoButton - показывать ли кнопку No.
TYesOrNoDialog.ShowCancelButton - показывать ли кнопку Cancel.
TYesOrNoDialog.Caption - заголовок диалгоа.
TYesOrNoDialog.Text - текст диалога.
function TYesOrNoDialog.Execute:Integer - показывает
диалог и возвращает значение MessageBox'a
Зависимости: Windows, Messages, SysUtils, Classes, Dialogs, Forms;
Автор:       Святослав, lisin@asicdesign.ru, ICQ:138752432, Saint Petersburg
Copyright:   (C) NetBreaker666[AWD]<SP666>@Svjatoslav_Lisin - т.е. я сам
Дата:        11 августа 2002 г.
***************************************************** }
unit YesOrNoDialog;
interface
uses
  Windows, Messages, SysUtils, Classes, Dialogs, Forms;
type
  TYesOrNoDialog = class(TComponent)
  private
    { Private declarations }
    _OnEx: TNotifyEvent;
    __SNO: Boolean;
    __SC: Boolean;
    _Y, _N, _C: Boolean;
    _Caption, _Text: string;
    procedure SetNo(S: Boolean);
    procedure SetCancel(S: Boolean);
  protected
    { Protected declarations }
  public
    { Public declarations }
    property Yes: Boolean read _Y;
    property No: Boolean read _N;
    property Cancel: Boolean read _C;
  published
    { Published declarations }
    property ShowNoButton: Boolean read __SNO write SetNo;
    property ShowCancelButton: Boolean read __SC write SetCancel;
    property Caption: string read _Caption write _Caption;
    property Text: string read _Text write _Text;
    property OnExecute: TNotifyEvent read _OnEx write _OnEx;
    function Execute: Integer;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('NetBreakers', [TYesOrNoDialog]);
end;
//Voen secret.
// 2301900
function TYesOrNoDialog.Execute: Integer;
var
  Hn: THandle;
begin
  Hn := Application.Handle;
  if __SNO and __SC then
  begin
    Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_YESNOCANCEL);
    _Y := (Result = 6) or (Result = 1);
    _N := Result = 7;
    _C := REsult = 2;
    Exit;
  end;
  if __SNO then
  begin
    Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_YESNO);
    _Y := (Result = 6) or (Result = 1);
    _N := Result = 7;
    _C := REsult = 2;
    Exit;
  end;
  if __SC then
  begin
    Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_OKCANCEL);
    _Y := (Result = 6) or (Result = 1);
    _N := Result = 7;
    _C := REsult = 2;
    Exit;
  end;
  Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_OK);
  _Y := (Result = 6) or (Result = 1);
  _N := Result = 7;
  _C := Result = 2;
end;
procedure TYesOrNoDialog.SetNo(S: Boolean);
begin
  __SNO := S;
end;
procedure TYesOrNoDialog.SetCancel(S: Boolean);
begin
  __SC := S;
end;
end.

Вызов диалога отключения сетевого диска.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Открытие диалогового окна «Отключение сетевого диска»
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        21 мая 2002 г.
***************************************************** }
function DisconnectNetworkDrive(Wnd: HWND = 0): DWORD;
begin
  if Wnd = 0 then
    Wnd := FindWindow('Shell_TrayWnd', '');
  Result := WNetDisconnectDialog(Wnd, RESOURCETYPE_DISK);
end;
// Пример использования:
DisconnectNetworkDrive(Application.Handle);

Вызывает диалог открытия файла.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Вызывает диалог открытия файла
вызывет диалог открытия как в офисе
Зависимости: uses activeX
Автор:       Babay_S
Copyright:   Мои раскопки
Дата:        27 сентября 2002 г.
***************************************************** }
uses activeX;
function GetFileNameFromBrowse(hOwner: LongInt; var sFile: string;
  sInitDir, sDefExt, sFilter, sTitle: string): Boolean;
{Вызывает диалог открытия файла
 Пример
 GetFileNameFromBrowse(handle,b1,'c:\Install','*.txt',
'Текстовые файлы'#0'*.txt'#0'Все файлы'#0'*.*'#0#0,'Название');
 фильтр который будет использоваться должен стоять в перечне первым
}
implementation
function SHGetFileNameFromBrowse(hOwner: LongInt; sFile: LPWSTR; nMaxFile:
  LongInt;
  sInitDir: LPWSTR; sDefExt, sFilter, sTitle: LPWSTR): Boolean;
  stdcall; external 'Shell32.dll' index 63;
//*************************** Код функций ****************************************
function GetFileNameFromBrowse(hOwner: LongInt; var sFile: string;
  sInitDir, sDefExt, sFilter, sTitle: string): Boolean;
var
  sFileW, sInitDirW, sDefExtW, sFilterW, sTitleW: PWideChar;
  sFileL, sInitDirL, sDefExtL, sFilterL, sTitleL: Integer;
begin
  sFileW := CoTaskMemAlloc(255 * sizeof(WideChar));
  StringToWideChar(SFile, SFileW, 255);
  SInitDirL := Length(sInitDir) + 1;
  sInitDirW := CoTaskMemAlloc(SInitDirL * sizeof(WideChar));
  StringToWideChar(SInitDir, SInitDirW, sInitDirL);
  SDefExtL := Length(sDefExt) + 1;
  sDefExtW := CoTaskMemAlloc(SDefExtL * sizeof(WideChar));
  StringToWideChar(SDefExt, SDefExtW, sDefExtL);
  SFilterL := Length(sFilter) + 1;
  sFilterW := CoTaskMemAlloc(SFilterL * sizeof(WideChar));
  StringToWideChar(SFilter, SFilterW, sFilterL);
  STitleL := Length(sTitle) + 1;
  sTitleW := CoTaskMemAlloc(STitleL * sizeof(WideChar));
  StringToWideChar(STitle, STitleW, sTitleL);
  Result := SHGetFileNameFromBrowse(hOwner, sFileW, Integer(sFileW), sInitDirW,
    sDefExtW, sFilterW, sTitleW);
  SFile := sFileW;
  CoTaskMemFree(sFileW);
  CoTaskMemFree(sInitDirW);
  CoTaskMemFree(sDefExtW);
  CoTaskMemFree(sFilterW);
  CoTaskMemFree(sTitleW);
end;

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

if GetFileNameFromBrowse(handle, b1, 'c:\Install', '*.txt',
  'Текстовые файлы'#0'*.txt'#0'Все файлы'#0'*.*'#0#0      , 'Название') then
begin
end;

Функция вызывает стандартный диалог Свойства.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Функция вызывает стандартный диалог "Свойства"
Зависимости: uses Activex;
Автор:       Babay_S
Copyright:   мои раскопки
Дата:        27 сентября 2002 г.
***************************************************** }
uses Activex;
function ObjectProperties(hOwner, uFlags: LongInt; sName, sParam: string):
  LongInt;
{Функция вызывает диалог "Свойства"
Параметры
hOwner - хэндл родителя.
uFlags - может принимать два значения - 1 для свойств принтера, 2 - для файла
sName - имя принтера или файла. Если вместо имени файла указаны папка или диск,
вызывается соответствующее окно свойств. Если указано "" - появляются свойства Системы.
sParam - название вкладки на пропертях, которую надо показать. Если предать пустую
строку или несуществующую вкладку - будет показана первая вкладка.
}
implementation
function ObjectProperties(hOwner, uFlags: LongInt; sName, sParam: string):
  LongInt;
var
  sNameW, sParamW: PWideChar;
  sNameL, sParamL: Integer;
begin
  SNameL := Length(sName) + 1;
  sNameW := CoTaskMemAlloc(SNameL * sizeof(WideChar));
  StringToWideChar(SName, SNameW, sNameL);
  SParamL := Length(sParam) + 1;
  sParamW := CoTaskMemAlloc(SParamL * sizeof(WideChar));
  StringToWideChar(SParam, SParamW, sParamL);
  Result := SHObjectProperties(hOwner, uFlags, sNameW, sParamW);
  CoTaskMemFree(sNameW);
  CoTaskMemFree(sParamW);
end;

Z-порядок при каждом показе диалога.

// Там не листбокс, а EDIT, но суть точно та же. Чтобы получить текст, а не
// записать его, надо просто использовать Get вместо Set. Судя по тому, что
// программа уже 2 года работает без малейших проблем, Z-порядок при  каждом
// показе диалога один и тот же.
program Project1;
uses 
 Windows, Messages;
const 
 Title1 = 'Установка связи';
 Title2 = 'Удаленное соединение';
 Login = '...';
 Password = '...';
var 
 Wnd: HWND;
 Control: array [0..127] of char;
procedure TypeTextIntoNextEdit(AText:string);
begin
 repeat // Ищем следующее в Z-порядке окно класса EDIT
   Wnd := GetWindow(Wnd, GW_HWNDNEXT);
   GetClassName(Wnd, Control, SizeOf(Control))
 until Control = 'Edit';
 SendMessage(Wnd, WM_SETTEXT, 0, Integer(PChar(AText))) // Вводим текст
end;
begin
 Wnd := FindWindow(nil, Title1);  // Это окно самого диалога
 if Wnd = 0 then                  // Если не найдено, ищем другой диалог 
 begin
   Wnd := FindWindow(nil, Title2);
   if Wnd = 0 then Exit;
 end;
 Wnd := GetWindow(Wnd, GW_CHILD); // Это верхний комбобокс
 TypeTextIntoNextEdit(Login);     // Вводим логин
 TypeTextIntoNextEdit(Password)   // Вводим пароль
end.

Размер диалогового окна.

Автор: Steve Schafer Давайте начнем с Microsoft Windows User Interface Guidelines (Руководящие Принципы Построения Интерфейса Пользователя Microsoft Windows) и допустим, что мы создаем диалоговое окно, содержащее компонент TMemo, занимающий большую часть площади формы и кнопки OK и Cancel, размещенные в ее нижней части. Несколько примечаний из "Принципов": Диалоговые окна должны быть основаны на базовых диалоговых модулях, dialog base units (DBU), которые создаются с учетом размера шрифта и разрешения экрана. Диалоговые окна должны быть созданы, по возможности, на основе одного из нескольких стандартных размеров. Для нашего окна мы используем размер 212x188 DBU. Все элементы управления должны распологаться как минимум на расстоянии 7 DBU от края окна. Все элементы управления должны иметь между друг другом зазор размером минимум 4 DBU. Кнопки должны иметь высоту 14 DBU. (Про ширину кнопок "принципы" умалчивают; в обычном случая я использую кнопки шириной 40 DBU.)

Вот необходимая для создания формы и элементов управления информация, которую мы можем получить во время выполнения приложения:

procedure TMyForm.FormCreate(Sender: TObject);
var
  BaseUnit, Margin, Spacing, BtnW, BtnH: Integer;
begin
  BaseUnit := Canvas.TextHeight('0'); { 1 BaseUnit = 8 DBU определениям }
  Width := (212 * BaseUnit) div 8;
  Height := (188 * BaseUnit) div 8;
  Margin := (7 * BaseUnit) div 8 - GetSystemMetrics(SM_CXFIXEDFRAME);
  Spacing := (4 * BaseUnit) div 8;
  BtnW := (40 * BaseUnit) div 8;
  BtnH := (14 * BaseUnit) div 8;
  Memo1.SetBounds(Margin, Margin, ClientWidth - 2 * Margin, ClientHeight -
    2 * Margin - Spacing - BtnH);
  OkButton.SetBounds(ClientWidth - Margin - Spacing - 2 * BtnW, ClientHeight -
    Margin - BtnH, BtnW, BtnH);
  CancelButton.SetBounds(ClientWidth - Margin - BtnW, ClientHeight - Margin -
    BtnH, BtnW, BtnH);
end;

Данный код позволяет создать диалоговое окно с правильными размерами и пропорциями, независимо от разрешения экрана и шрифтов.

Безопастность Dial-up пользователям.

Оформил: DeeCo

Я пользуюсь динамическим IP адресом - стоит ли мне заботиться о безопасности?

Хотя и бытует мнение,что нет,на самом деле это все враки. Во-первых, если приблизительно известен временной диапазон, в котором компьютер может быть подключен к Internet,то произвести сканирование всего пространства динамических адресов провайдера - не так уж сложно. Во-вторых,многие атаки производятся "наугад" - без какой-либо конкретной цели. И в-третьих,динамический адрес можно успеть "засветить" на irc,www и т.п. - при пользовании практически любым сетевым сервисом.

А что,собственно,мне угрожает?

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

Какие базовые предосторожности необходимы?

Для начала неплохо оценить,во что обойдутся возможные последствия - это поможет принять _адекватные_ меры. Если нужно подключить к Internet небольшую офисную сеть из десятка-полутора машин, да еще при том,что некоторые из них содержат конфиденциальную информацию,вполне возможно, что имеет смысл приобрести несложный firewall - или сделать его своими силами,если есть возможность. Для того,чтобы более-мнее безопасно выходить в Internet с домашнего компьютера, достаточно принять некоторые базовые предосторожности.

Во-первых,нужно отключить разделение ресурсов компьютера по TCP/IP. Это можно сделать как минимум двумя способами - первый, который рекомендует фирма Microsoft состоит в том,чтобы выключить разделение ресурсов вообще (Settings -> Control Panel -> Network -> File and Print Sharing) Разумеется, он пригоден в том случае,если вы не собираетесь активно использовать эту возможность.

Второй способ - отсоединить интерфейс Netbios от TCP/IP (Settings -> Control Panel -> Network -> TCP/IP ->Properties -> Bindings).

Во-вторых,полезно убедиться,что используемые версии Windows 95 или NT и сетевого ПО являются последними и что для них не выходило никаких апдейтов,сервис-паков и т.д. Ну а о том,что не нужно пользоваться сомнительным ПО, запускать любой файл,приходящий по email и вообще совершать подобные глупости,наверное, и упоминать не стоит. Напомню только,что в любом word-документе может оказаться macro-вирус ;-).

Какое ПО может представлять дополнительную опасность?

Почти любое ;-). Чуть подробнее: При использовании наиболее распространенных WWW brоwsеr'ов, таких как Netscape Navigator или MS Internet Explorer возможны проблемы и более серьезные,чем "падение" в результате внутренней ошибки - включая, например, чтение любого файла с вашего диска - по-настоящему, а не так,как это реализовано в дурацкой шутке,которой уже не первый год пугают лопухов.

Как защититься от этого?

Использовать последние версии brоwsеr'ов, в которых _известные_ "дырки" более-менее заткнуты,а если нужно в некоторой мере обезопасить себя от неизвестных - могу разве что посоветовать выключить поддержку ActiveX,Java и Javascript. В случае с еmаil'ом - опять же,если не делать глупостей и не запускать приходящие .exe файлы (и помнить о макро-вирусах!),вряд ли придется столкнуться с большей нериятностью,чем приходящие с завидной регулярностью идиотические письма с предложениями купить что-нибудь ненужное или поучаствовать в очередной дурацкой пирамиде с посыланием денег в конверте черт-те куда. Это называется spam - и бороться с этим можно, вычисляя по заголовкам письма интернет-провайдера,допускающего подобный бардак и начиная с ним ругаться. Другого способа,по всей видимости,нет - кроме автоматизации удаления подобных писем (Inbox Assistant как раз умеет это делать). Теоретически MIME,расширенный формат сообщений, часто (а то и чаще, чем нужно) используемый в Internet _может_ представлять проблемы с безопасностью,но на практике это случается нечасто. News с точки зрения безопасности клиента не сильно отличается от email. Если вы пользуетесь IRC,то,скорее всего,самое главное - это то, что ip-адрес вашего компьютера, версия irc-клиента и зачастую тип и версия операционной системы становятся известными ну действительно всем и каждому - что явно не есть хорошо. Любителей испробовать на вас свои (чаще - не свои ;-) технические достижения может оказаться больше, чем кажется на первый взгляд. Кроме того,скрипт или даже сам irc клиент может содержать закладки с практически любыми возможностями. Больше всего неприятностей может доставить протокол dcc - кстати,его можно просто выключить в большинстве irc-клиентов. Если вы не понимаете в деталях,как работает предлагаемый скрипт - ставить его ни в коем случае нельзя - разве что если знающий человек его проверит. ICQ еще мало изучен в данном вопросе - но известно про него две вещи: a) любой клиент можно "свалить" потоком бессмысленных данных на тот порт,где он отвечает - "И это -только начало" (c) AO MMM b) он не предоставляет никаких дополнительных возможностей по сравнению с irc. Я бы вообще не рекомендовал пользоваться ICQ.

Я использую старый абонентский комплект с MSIE 3.01. Насколько это опасно и что нужно сделать?

Версия 3.01 MS Internet Explorer может принести несколько довольно-таки неприятных проблем с безопасностью: эта версия может запускать программы на том компьютере,где она работает - без согласия пользователя. По этой причине лучше заменить ее на более новую версию 3.02. Версия, которая в настоящий момент входит в абонентский комплект (3.02) тоже не лишена аналогичной ошибки,но во-первых,она относится не к самому MSIE,а к его совместному использованию с РоwеrРоint'ом, а во-вторых _такая_ ошибка (пока) известна только одна. Да и fix к ней имеется. К сожалению,мы пока не имеем возможности заменить версию MSIE внутри абонентского комплекта (программа для его генерации в "лучших" традициях фирмы Microsoft довольно-таки закрытая) на _самый_ последний релиз 3.02 ("Last Updated June 13, 1997"),но эта разница уже не столь принципиальна. Что, вкратце, можно сказать про Netscape Communicator 4.0-release? Эта версия,также как и предыдущие версии Web-brоwsеr'ов фирмы Netscape, имеет баг в реализации javascript, позволяющий (при стандартных установках) читать любой файл с вашего компьютера. Можно либо заменить ее на версию 4.01, либо изменить установки Netscape так, чтобы эта опасность перестала быть актуальной (меню Security Advisor - включить подтверждения на отправку данных по сети).

Почему меры безопасности не принимает провайдер?

Дело в том, что, например, запретив обращения к портам,используемым Windows/Samba для разделения ресурсов, мы лишим наших клиентов возможности использовать это для вполне законных и иногда даже необходимых целей. Поэтому мы обеспечиваем безопасность на своем участке сети - и ваша задача обеспечить ее на своем. Я,конечно, могу помочь это сделать - но не обязан делать это бесплатно ;-). Мне пришло письмо с предупреждением о вирусе, распространяющемся через email.. Подобная бредятина распространяется по сети уже не первый год - это просто очередной вариант "святого письма". Тот факт,что рост Internet'a обеспечивает постоянный приток свежих идиотов,готовых рассылать всем такие вещи - поистине удручает. Самая большая опасность,которой подвергает свои жертвы этот вирус - возможность пополнить вышеупомянутые ряды.

Является ли UUCP подключение полностью безопасным?

Практически - да. Полностью - нет. Практически - потому,что что во-первых, случаи "взлома" uucp почти не известны - для этого нужна прямая связь с атакуемой машиной именно по протоколу uucp. Однако,по крайней мере в некоторых версиях самой популярной реализации uucp для *DOS - uupc/@ by Ache мной был обнаружен баг, позволяющий обойти встроенную защиту обработчика команды uucp и прочитать/записать любой файл в любом директории - всего лишь используя относительный путь вида ~/../../somewhere. А во-вторых, оффлайновый способ взаимодействия машины с сетью делает невозможным получение информации немедленно и облегчает обнаружение попыток взлома. Разумеется,сказанное про email в значительной степени относится в том числе и к uucp подключению.

Набрать телефонный номер.

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

implementation
 {$R *.DFM}
 function tapiRequestMakeCall(DestAddress, AppName,
   CalledParty, Comment: PChar): Longint; stdcall; external 'TAPI32.DLL';
 procedure TForm1.Button1Click(Sender: TObject);
 var
   PhoneNumber, AppName: array[0..255] of Char;
 begin
   PhoneNumber := '1234567890';
   StrPCopy(AppName, Application.Title);
   tapiRequestMakeCall(PhoneNumber, AppName, '', '');
 end;

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

Два мужика разговаривают: - Почему твой сын так быстро растёт, за уши тянешь что-ли? - Не, я просто монитор каждый месяц на 5 см поднимаю!
а ранней стадии создания приложения решите для себя хотите ли вы позволить форме масштабироваться. Преимущество немасштабируемой формы в том, что ничего не меняется во время выполнения. В этом же заключается и недостаток (ваша форма может быть слишком маленькой или слишком большой в некоторых случаях). Если вы Е собираетесь делать форму масштабируемой, установите св-во Scaled=False и дальше не читайте. В противном случае Scaled=True. Установите AutoScroll=False. AutoScroll = True означает не менять размер окна формы при выполнении что не очень хорошо выглядит, когда содержимое формы размер меняет. Установите фонты в форме на TrueType фонты, например Arial. !!!!: Если такого фонта не окажется на пользовательском компьютере, то Windows выберет альтернативный фонт из того же семейства. Этот фонт может не совпадать по размеру, что вызовет проблемы. Установите св-во Position в любое значение, отличное от poDesigned. poDesigned оставляет форму там, где она была во время дизайна, и, например, при разрешении 1280x1024 форма окажется в левом верхнем углу и совершенно за экраном при 640x480. Оставляйте по-крайней мере 4 точки между компонентами, чтобы при смене положения границы на одну позицию компоненты не " наезжали" друг на друга. Для однострочных меток (TLabel) с выравниванием alLeft или alRight установите AutoSize=True. Иначе AutoSize=False. Убедитесь, что достаточно пустого места у TLabel для изменения ширины фонта - 25% пустого места многовато, зато безопасно. При AutoSize=False Убедитесь, что ширина метки правильная, при AutoSize=True убедитесь, что есть ссвободное место для роста метки. Для многострочных меток (word-wrapped labels), оставьте хотя бы одну пустую строку снизу. Будьте осторожны при открытии проекта в среде Delphi при разных разрешениях. Свойство PixelsPerInch меняется при открытии формы. Лучше тестировать приложения при разных разрешениях, запуская готовый скомпилированный проект, а редактировать его при одном разрешении. Иначе это вызовет проблемы с размерами. Не изменяйте свойство PixelsPerInch ! В общем, нет необходимости тестировать приложение для каждого разрешения в отдельности, но стоит проверить его на 640x480 с маленькими и большими фонтами и на более высоком разрешении перед продажей. Уделите пристальное внимание принципиально однострочным компонентам типа TDBLookupCombo. Многострочные компоненты всегда показывают только целые строки, а TEdit покажет урезанную снизу строку. Каждый компонент лучше сделать на несколько точек больше.

Сделать сложный фон окна.

Фон окна может представлять собой повторяющиеся картинки произвольного размера.

procedure TForm1.FormCreate(Sender: TObject);
begin
  bm := TBitMap.Create;
  bm.LoadFromFile('Example.bmp');
end;
procedure TForm1.FormPaint(Sender: TObject);
var
  x, y: integer;
begin
  for x := 0 to Form1.ClientWidth div bm.Width do
    for y := 0 to Form1.ClientHeight div bm.Height do
      Form1.Canvas.Draw(x * bm.Width, y * bm.Height, bm);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  bm.Destroy;
end;

Вы можете использовать для "подложки" формы любой стиль кисти. Таким образом, можно получить окно "в полосочку", "в сеточку" и т.д.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.Brush.Style := bsDiagCross;
end;

Ту же задачу можно решить, используя свойство кисти Bitmap, позволяющее создавать свои стили. Размер картинки при этом всегда равен 8X8.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.Brush.Bitmap := TBitMap.Create;
  Form1.Brush.Bitmap.LoadFromFile('Phone.bmp');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  Form1.Brush.Bitmap.Destroy;
end;

У меня константы могут иметь значение, отличное от заданного.

Автор: Nomadic.

Const из другого unit'а дает неверное значение. Похоже, это действительно bug, причем ОСОБО ОПАСНЫЙ, т.к. может исказить результаты расчетов, не вызвав заметных нарушений работы программы. В общем так. Эксперимент показал, что любая вещественная константа, определенная в интерфейсе модуля, может быть неверно (и не обязательно очень неверно - например, вместо 0.7 может появиться 0.115) прочитана в другом модуле. Баг особенно опасен тем, что он неустойчив и может пропадать и возникать без видимых причин (например, возникнуть, если предыдущая компиляция была неудачной и исчезнуть после использования константы в модуле, где она определена).

Лечится (вроде бы) указанием типа.

const Wko: double = 0.9; 

Правда, теперь это уже не совсем константа...

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

Автор: Nomadic Надо обрабатывать сообщение CM_HITTEST (Это сообщение получают даже потомки от TGraphicsControl, не имеющего своего HWND).

Например, так:

procedure TLine.CMHitTest(var Message: TWMNCHitTest);
begin
  if PointInLineReg(Message.XPos, Message.YPos) then
  begin
    Message.Result := 1;
  end
  else
  begin
    Message.Result := 0;
  end;
end;

Для органов управления Windows, если Вы не используете VCL, требуется обрабатывать сообщение WM_NCHITTEST.

Простой Взлом - AriSoft DirectFTP v1.0.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Page: http://home.coqui.net/alexr/index.htm ========== Вступление: ========== Хороших ФТП клиентов мало, и такого чтобы меня удовлетворил от и до я ещё не видел. Не стала исключением и эта программа. Однако кракать было нечего и я решился. ========== Разведка местности: ========== Я запустил программу и увидел окно с просьбой ввести своё имя и серийный номер (далее - СН). Я ввел имя "vallkor" номер "123321" и нажал "Register" полсе чего выскочил MessageBox с надписью "Invalid serial number". ========== Активные действия: ========== Я загрузил DirectFTP.exe в W32DAsm v 8.93 (со всеми патчами :) и после дизассемблирования я зашёл в String Reference и начал искать ту самую строку "Invalid serial number". И нашел её вот тут:
* Possible StringData Ref from Data Obj ->"Invalid serial number"
                                  |
:0041131F 68FA704B00              push 004B70FA
:00411324 8BC7                    mov eax, edi
:00411326 E8D5540600              call 00476800
:0041132B 50                      push eax
* Reference To: USER32.MessageBoxA, Ord:0000h
В таких ситуациях должно быть ещё одно сообщение типа "Good serial" или "Thank you for 10 dollars, asso", причем в программе располагается все так:
call CheckSerialNumber(char name, char sn)
cmp	cl,0
je	BadMessage
GoodMessage:
	call	MessageBoxA
BadMessage:
	call	MessageBoxA
Это, конечно, очень грубо говоря, но смысл понятен. Т.е. нам надо найти теперь строку хорошего сообщения. Для этого мы опять заходим в String Reference и ищем сообщение об удачной регистрации или ещё чего-нить типа этого и ...... находим строку:
* Possible StringData Ref from Data Obj ->"Thank you for registering "
                                  |
:00410BD7 B843704B00              mov eax, 004B7043
:00410BDC E8EFD50900              call 004AE1D0
Теперь поищем чуть выше условные переходы и найдём вот что:
:00410B61 E88AD20900              call 004ADDF0
:00410B66 59                      pop ecx
:00410B67 84C9                    test cl, cl	<--Если СН введён правильный
:00410B69 0F84F2040000            je 00411061	<--то НЕ прыгаем
:00410B6F 6A40                    push 00000040
Т.е. Заменив je 00411061 на je 00410B6F мы вводя все что угодно будем получать сообщение об удачной регистрации. Но наша цель получить настоящий СН для своего имени. Смотрим чуть выше и видим:
:00410B1F 8D856CFFFFFF            lea eax, dword ptr [ebp+FFFFFF6C]
:00410B25 8B00                    mov eax, dword ptr [eax]
:00410B27 E810960800              call 0049A13C
:00410B2C E887630900              call 004A6EB8
:00410B31 3BD8                    cmp ebx, eax		<--В eax введенный нами СН, а в ebx - ПРАВИЛЬНЫЙ
Теперь достаточно находясь в SoftIce курсором на строке:
:00410B31 3BD8                    cmp ebx, eax
посмотреть значение регистра eax:
:? ebx
0018F33A  0001635130  "у:" 
Полученый десятичный вариант и будет правильным СН: 1635130 Вот, наверное, и все. Рабочий СН получен, а писать КейГен не буду, потому как там основной цикл на 3 экрана, а ещё внутри всякая фигня. Короче не стоит она того. Исследовал и наваял туториал: vallkor //PTDS E-mail: vallkor@chat.ru (vallkor@etel.dn.ua) Page: http://vallkor.chat.ru

Использование PASSTHROUGH для отправки данных прямо на принтер.

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

{ 
  By using the Windows API Escape() function, 
  your application can pass data directly to the printer. 
  If the printer driver supports the PASSTHROUGH printer escape, 
  you can use the Escape() function and the PASSTHROUGH printer escape 
  to send native printer language codes to the printer driver. 
  If the printer driver does not support the PASSTHROUGH printer escape, 
  you must use the DeviceCapabilities() and ExtDevMode() functions instead. 
  Mit der Windows API Funktion Escape() kann man Daten direkt zum Drucker schicken. 
  Wenn der Drucker Treiber dies nicht unterstutzt, mussen die DeviceCapabilities() 
  und ExtDevMode() Funktionen verwendet werden. 
}
 //  DOS like printing using Passthrough command 
// you should use "printer.begindoc" and "printer.enddoc" 
type
   TPrnBuffRec = record
   bufflength: Word;
   Buff_1: array[0..255] of Char;
 end;
 function DirectToPrinter(S: string; NextLine: Boolean): Boolean;
 var
    Buff: TPrnBuffRec;
   TestInt: Integer;
 begin
   TestInt := PassThrough;
   if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TESTINT), @testint, nil) > 0 then
   begin
     if NextLine then  S := S + #13 + #10;
     StrPCopy(Buff.Buff_1, S);
     Buff.bufflength := StrLen(Buff.Buff_1);
     Escape(Printer.Canvas.Handle, Passthrough, 0, @buff, nil);
     Result := True;
   end
   else
     Result := False;
 end;
 // this code works if the printer supports escape commands 
// you can get special esc codes from printer's manual 
//  example: 
printer.BeginDoc;
 try
   directoprinter('This text ');
 finally
   printer.EndDoc;
 end;

DirectX и Delphi.

Автор: Азиз (JINX) Специально для Королевства Delphi Перед тем как приступить я хотел бы сделать пару оговорок. Во-первых для использования DirectX в Delphi необходим файл с заголовками, где объявлены имена функций DirectX API либо какой-нибудь компонент, позволяющий обращаться к интерфейсу DirectX через свои методы. В момент написания сего опуса я использую компонент DelphiX (автор - Hiroyuki Hori), распространяемый бесплатно - http://www.yks.ne.jp/~hori/DelphiX-e.html. (если у вас есть что-нибудь получше и Вы поделитесь со мной - я буду очень признателен.) И еще один адрес, по которому можно скачать компонент DelphiX : http://www.torry.ru/vcl/packs/hhdelphix.zip По возможности я буду писать и названия методов DelphiX и названия соответствующих интерфейсов Directx API – чтоб вам легче было ориентироваться в DirectX SDK. Во-вторых при всем своем гипертрофированном самомнении я не могу назвать себя экспертом в области DirectX – так что не судите чересчур строго. Я надеюсь сие творение хоть как то сможет помочь делающим первые шаги в DirectX – если Вы круче меня – буду признателен за помощь и указание на ошибки (коих увы наверняка сделал немало (честное слово не нарочно :-) ) Оговорка без номера – я пишу эти строки в те времена когда последней версией DirectX является DirectX 6. Ну что ж приступим пожалуй. Как известно DirectX предназначен в основном для программирования игр под Windows 9x. Тем не менее можно придумать еще не мало ему применений (рано или поздно грядет таки эра повсеместного трехмерного пользовательского интерфейса). DirectX состоит из следующих компонентов: DirectDraw® - предназначен для программирования всевозможных анимаций за счет быстрого доступа к изображению на экране и к видеопамяти, а также за счет использования возможностей аппаратуры (видеоадаптера) по манипуляции с буферами. DirectSound® - как видно из названия позволяет выводить звук, используя все что можно выжать из Вашей звуковой карты (ну почти все) DirectMusic™ - музыка. В отличие от DirectSound работает не с оцифрованным звуком (WAV) а с музыкальными командами, посылаемыми звуковой карте. DirectPlay® - упрощает жизнь программиста, решившегося добавить в своей программе возможность совместной работы (игры) по сети и по модему. (это наверняка хорошо знакомо любому геймеру) Direct3D® (мой любимый J) – содержит высокоуровневый интерфейс Retained Mode позволяющий легко выводить 3-хмерные графические обьекты, и низкоуровневый интерфейс Immediate Mode предоставляющий полный конроль над рендерингом. (если кто-нибудь знает как будет рендеринг по-русски – мой адрес в конце статьи) DirectInput® - поддержка устройств ввода. Пока джойстик, мышь, клавиатура и т.д. – впрочем можете быть уверены – если появится еще что – за Microsoft не заржавеет. DirectSetup – предназначен для установки DirectX. AutoPlay – самый обычный AutoPlay – позволяет запускать какую-нибудь программу (инсталяшку или саму игру) при установке CD-диска в дисковод. Вообще-то описание AutoPlay относится к Win 32 SDK и просто повторяется в DirectX SDK (думаю Microsoft включила его в DirectX SDK просто чтоб оно было под рукой у разработчика) Кое что о Direct3DRM®. (Reatined Mode) Система координат В Direct3D она соответствует так называемому правилу "левой руки". Суть правила в том, что если Вы растопырите пальцы левой руки так, что указательный палец будет направлен к экрану, большой к потолку, а средний параллельно столу туда, где обычно лежит мышиный коврик, то большому пальцу будет соответствовать координата Y, среднему – X, указательному Z. Говоря короче координата Z направлена как бы вглубь экрана (я во всяком случае нахожусь по эту его сторону :-)), координата Y – вверх, координата X – вправо (все рисунки из SDK). Возможно Вам это покажется непривычным. А что Вы тогда скажите на это – в DirectX цвета задаются тремя составляющими R,G,B, каждая из которых – число с плавающей точкой в диапазоне [0-1]. Например белый цвет – (1,1,1), серенький (0.5,0.5,0.5), красный (1,0,0) ну и т.д. Статьи по Delphi (005)… Все трехмерные объекты задаются в виде набора (mesh) многоугольников (граней – faces). Каждый многоугольник должен быть выпуклым. Вообще-то лучше всего использовать треугольники – более сложные многоугольники все равно будут разбиты на треугольники (на это уйдет столь драгоценно процессорное время). Грани (faces) состоят из вершин (vertex). Статьи по Delphi (005)…

Грань становится видимой если она повернута так, что образующие ее вершины идут по часовой стрелке с точки зрения наблюдателя. Отсюда вывод – если Ваша грань почему-то не видна – переставьте вершины так, чтоб они были по часовой стрелке. Кроме того имеются другие объекты – источники света (прямой свет - directional light и рассеянный свет – ambient light), т.н. камера, текстуры, которые могут быть "натянуты" на грани и прочая, прочая… Наборы объектов составляют т.н. frames (затрудняюсь дать этому русское название). В Вашей программе всегда будет хоть один главный frame, называемый сцена (scene), не имющий фрейма-родителя, остальные фреймы принадлежат ему или друг другу. Я не буду долго разговаривать о том, как инициализировать все это хозяйство, для Дельфи-программиста достаточно разместить на форме компонент TDXDraw из библиотеки DelphiX.

Перейдем однако к делу. Запустите-ка Дельфи и откройте мою (честно говоря не совсем мою – большую часть кода написал Hiroyuki Hori – однако не будем заострять на этом внимание :-)) учебную программку - Sample3D. Найдите метод
TMainForm.DXDrawInitializeSurface.
Этот метод запускается при инициализации компонента TDXDraw. Обратите внимание, что DXDraw инкапсулирует D3D, D3D2, D3Ddevice, D3DDevice2, D3DRM, D3DRM2, D3DRMDevice, D3DRMDevice2, DDraw – ни что иное как соответствующие интерфейсы DirectX. (только в названиях интерфейсов Microsoft вместо первой буквы D слово IDirect). Инициализация компонента очень подходящее место, чтоб выбрать кое какие режимы (что и делается в программке). Обратите внимание на DXDraw.D3DRMDevice2.SetRenderMode(D3DRMRENDERMODE_BLENDEDTRANSPARENCY or D3DRMRENDERMODE_SORTEDTRANSPARENCY); - Эти два флага установлены вот для чего – если у нас два треугольника находятся один под другим и оба видны (т.е. вершины у них по часовой) нужно их сперва отсортировать по координате Z чтоб понять кто кого загораживает. Включает такую сортировку флаг, названный скромненько эдак, по Microsots-ки: D3DRMRENDERMODE_SORTEDTRANSPARENCY. Однако как говаривал К. Прутков – смотри в корень. Корнем же у нас является метод
TMainForm.DXDrawInitialize(Sender: TObject);
Здесь сначала создаются два фрейма – Mesh и Light, для нашего видимого объектика и для лампочки, его освещающей.
MeshFrame.SetRotation(DXDraw.Scene, 0.0, 10.0, 0.0, 0.05);
(первые три цифры – координаты вектора вращения, последний параметр – угол полворота) . Тонкое (не очень правда :-)) отличие между методами SetRotation и AddRotation в том, что AddRotation поворачивает объект только один раз, а SetRotation – заставляет его поворачиваться на указанный угол при каждом следующей итерации (with every render tick) Потом создается т.н. MeshBuilder – специальный объект, инкапсулирующий методы для добавления к нему граней. Этот обьект может быть загружен из файла (и естественно сохранен в файл). По традиции файлы имеют расширение X. (насколько мне извесно эта традиция возникла еще до появления сериала X-Files :-)) В самом же деле – в конце 20 века задавать координаты каждого треугольника вручную… Можно заставит сделать это кого то еще – а потом просто загрузить готовый файл :-). Ну а если серьезно в DirectX SDK входит специальная утилита - conv3ds. {conv3ds converts 3ds models produced by Autodesk 3D Studio and other modelling packages into X Files. } Однако создадим объект вручную – ну их эти Х-файлы. Наш объект будет состоять из 4-х граней (ни одного трехмерного тела с меньшим количеством граней я не смог придумать). Естественно каждая грань – треугольник, имеющий свой цвет.
MeshBuilder.Scale(3, 3, 3); - Увеличиваем в три раза по всем координатам.
Наконец MeshFrame.AddVisual(MeshBuilder); - наш MeshBuilder готов, присоединяем его как визуальный объект к видимому объекту Mesh.
DXDraw.Scene.SetSceneBackgroundRGB(0,0.7,0.7); -
Как понятно из названия метода цвет фона. (Видите – я не врал RGB-цвет действительно задается числами с плавающей точкой :-)) Интересные дела творятся в методе TMainForm.DXTimerTimer. (небольшая тонкость – это не обычный таймер, а DXTimer из библиотеки DelphiX)
DXDraw.Viewport.ForceUpdate(0, 0, DXDraw.SurfaceWidth, 
  DXDraw.SurfaceHeight); 
указываем область, которую нужно обновить (не мудрствуя лукаво – весь DXDraw.Surface)
DXDraw.Scene.Move(1.0); 
- применяем все трехмерные преобразования, добавленные методами вроде AddRotation и SetRotation к нашей сцене. (вот где собака то порылась… :-) вычисления новых координат точек начнутся не сразу после метода AddRotation а только здесь) DXDraw.Render – Рендерим (ну как же это по русски то? :-)) DXDraw.Flip – выводим результат рендеринга на экран (аминь :-)); (в этом методе помещены также несколько строк, выводящих на экран число кадров в секунду и информацию о поддержке Direct3D аппаратурой или программно – пригодится при отладке) Метод FormKeyDown. Здесь проверяется код нажатой клавиши – если Alt+Enter – переходим из оконного в полноэкранный режим (клево, правда? :-)) и наоборот. Напоследок пара слов о DXDrawClick. Просто выводим FileOpenDialog – Вы можете поэкспериментировать с x-файлами. Пока все. Вот уж не думал, что это будет так трудно. Надеюсь хоть кто-то дочитал до этого места.

Пишите: aziz@telebot.net, error@softhome.net.

DirectX и Delphi - введение.

Автор: Азиз (JINX) Специально для Королевства Delphi Как обычно, начну с оговорок. Первое – для меня большая проблема перевести некоторые термины. Поэтому я думаю может и не стоит их переводить. :-) Вот список терминов, которые вызывают у меня трудности с переводом: blitting - blit сокращение от "bit block transfer" пересылка блоков данных из одной области видеопамяти в другую. flip – переключение между буферами видеопамяти Surface – "поверхность" – область видеопамяти Второе – разговор идет о использовании DirectDraw в Delphi. Для того, чтобы воспользоваться DirectX вообще и DirectDraw в частности, нужно, чтобы во-первых DirectX был установлен на компьютере (скачать его можно у Microsoft например, впрочем я не думаю, что для читателя будет проблемой его найти), во-вторых нужны файлы заголовков DirectX – их существует немало, я по-прежднему считаю компонент DelphiX от Hiroyuki Hori – превосходным , кроме того, существует официально поддерживаемые Воrlаnd'ом заголовки DirectX, составленные в рамках проекта "JEDI" – скачать их можно с (http://www.delphi-jedi.org/DelphiGraphics/). Третье – неплохо если Вы имеете некоторое общее представление о работе видеоадаптера (ну очень общее – тонкости не нужны) и еще более общее о COM-технологии (всего-то нужно знать что такое COM- Interface, впрочем и это не обязательно). DirectDraw – интерфейс DirectX, предназначенный, по существу, для управления видеопамятью.

Прелесть однако заключается в том, что с DirectDraw доступ к видеопамяти становится не зависимым от типа используемой видеоплаты (ну или почти не зависимым). DirectDraw обращается к апаратуре посредством hardware abstraction layer (HAL) – (уровня абстагирования от аппаратуры). Кроме того с помощью hardware emulation layer (HEL) (уровня эмуляции аппаратуры) те возможности, которые не реализованы в данной видеокарте эмулируются программно (к сожалению тут есть пара исключений). Благодаря такому подходу жизнь программиста становится легче и веселее – если, например, видеокарта поддерживает hardware blitting – DirectDraw использует эту возможность через HAL – если нет – эмулирует через HEL (естественно эмуляция всегда медленнее). На рисунке из SDK показаны отношения между DirectDraw, GDI, HAL и HEL.

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

Как видно из рисунка DirectDraw находится вне GUI. DirectDraw может предоставлять области памяти, с которыми он работает в виде контекста устройства (device context, привычный для Windows-программиста), что позволяет использовать функции GDI для работы с ним (например, выводить текст с помощью функции TextOut) DirectDraw можно использовать как при рисовании в окне Windows так и при работе в полноэкранном режиме. Я пока буду говорить только о полноэкранном режиме (с эксклюзивным уровнем кооперации). Видео режимы.Режим определяет размер видимой области экрана в пикселах и число бит, требуемых для представления одного пиксела (“глубина цвета ”) (практически все мониторы поддерживают например режим 640ґ480ґ8). Чем больше ширина и высота экрана в пикселах, и чем больше бит требуется для представления одного пиксела, тем больше видеопамяти требуется для режима. Кроме того видеорежимы бывают палитровыми (palettized) и безпалитровыми (non-palettized). В палитровых режимах “глубина цвета” означает число элементов палитры для данного режима, например 8-битовый палитровый режим означает, что используется палитра, размером 256 элементов. В безпалитровом режиме “глубина цвета” означает число бит для представления цвета (8 бит – 256 цветов, 16 бит – 65535 цветов и т.д.) Чтобы выяснить какие режимы поддерживает ваша видеокарта можно использовать интефейс IDirectDraw4::EnumDisplayModes. Пример:

Выясним все поддерживаемые видеорежимы {используем DirectX headers от JEDI}.

function MyEnumFunction(const lpDDSurfaceDesc: TDDSurfaceDesc; lpContext:
  Pointer): HResult; stdcall
var
  SMode: string;
begin
  SMode := IntToStr(lpDDSurfaceDesc.dwWidth) + ' X ';
  SMode := SMode + IntToStr(lpDDSurfaceDesc.dwHeight) + ' X ';
  SMode := SMode + IntToStr(lpDDSurfaceDesc.ddpfPixelFormat.dwRGBBitCount);
  Form1.ListBox1.Items.Append(SMode);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  DD: IDirectDraw;
  hr: HRESULT;
begin
  hr := DirectDrawCreate(nil, DD, nil);
  if (hr = DD_OK) then
  begin
    ListBox1.Clear;
    DD.EnumDisplayModes(0, nil, nil, MyEnumFunction);
  end;
end;

{то же используя компонент DelphiX}.

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  SMode: string;
begin
  ListBox1.Clear;
  for i := 0 to DXDraw1.Display.Count - 1 do
  begin
    SMode := IntToStr(DXDraw1.Display.Modes[i].Width) + ' X ';
    SMode := SMode + IntToStr(DXDraw1.Display.Modes[i].Height) + ' X ';
    SMode := SMode + IntToStr(DXDraw1.Display.Modes[i].BitCount);
    ListBox1.Items.Append(SMode);
  end;
end;
Чувствуете почему я так люблю Hiroyuki Hori с его компонентом DelphiX? :-) Действительно проще – но, увы, документация у DelphiX очень скудная (и по большей части на японском). Вообще говоря, наверное полезно изучить “классический” способ работы с DirectDraw от JEDI – потом легче пользоваться и DelphiX. Установить видеорежим можно методом IDirectDraw4::SetDisplayMode.

Установим видеорежим 640x480x8 {используем DirectX headers от JEDI}.

procedure TForm1.Button1Click(Sender: TObject);
var
  DD: IDirectDraw;
  DD4: IDirectDraw4;
  hr: HRESULT;
begin
  hr := DirectDrawCreate(nil, DD, nil);
  if (hr = DD_OK) then
  begin
    DD.QueryInterface(IID_IDirectDraw4, DD4);
    DD4.SetCooperativeLevel(Self.Handle, DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN);
    DD4.SetDisplayMode(640, 480, 8, 0, 0);
    //DD4.RestoreDisplayMode;
  end;
end;

{то же используя компонент DelphiX}.

procedure TForm1.Button1Click(Sender: TObject);
begin
  DXDraw1.Display.Width := 640;
  DXDraw1.Display.Height := 480;
  DXDraw1.Display.BitCount := 8;
  DXDraw1.Options := DXDraw1.Options + [doFullScreen];
  DXDraw1.Initialize;
end;
Восстановить тот видеорежим, что был установлен до вызова SetDisplayMode можно функцией IDirectDraw4::RestoreDisplayMode. Впрочем, для программ использующих полноэкранный режим это не так уж важно – прежний режим будет восстановлен автоматически. Кстати пример с JEDI-заголовками хорош тем, что демонстрирует создание объекта IDirectDraw получение ссылки на интерфейс IDirectDraw4 вызовом метода QueryInterface из IDirectDraw (IDirectDraw без номера – базовый (и самый старый) интерфейс DirectDraw; IDirectDraw4 – интерфейс из DirectX 6). Вообще объект IDirectDraw – это самая, что ни на есть, сердцевина DirectDraw – он представляет собой некую абстракцию над видеоадаптером – с помощью его методов создаются все остальные объекты DirectDraw (Surfасе'ы, палитры и т.д.). В принципе можно создавать больше одного объекта IDirectDraw – если у Вас больше одного видеоадаптера и несколько мониторов – в таком случае Вы ровно во столько раз круче меня, на сколько число Ваших видеоадаптеров больше 1-го :-) (для знатоков COM-технологии – для этого при создании объекта DirectDraw нужно передать GUID другого дисплея). Если же монитор у Вас один Вы можете создавать несколько объектов DirectDraw – все они будут управлять одним и тем же видеоадаптером – но мы этот случай рассматривать не будем. В случае же если Вы используете Ноri'вский компонент DelphiX – мучения с инициализацией и деинициализацией сводятся к нулю – достаточно просто разместить на форме компонент DXDraw – он сам позаботится о мелочах жизни, вроде create и release. :-) Итак, переключаться между видеорежимами мы научились. Поговорим теперь о Surfасе'ах. (моя попытка найти хороший русский эквивалент этому слову, не увенчалась успехом). Surface (объект DirectDrawSurface) – в буквальном переводе поверхность, представляет собой линейный участок в видеопамяти. (впрочем можно создавать Surfасе'ы и в системной памяти – но мы на этом не станем задерживаться) По умолчанию Surface создается так, чтобы получить максимальное быстродействие – в видеопамяти, если ее не хватает – в нелокальной видеопамяти (для плат AGP) а если и ее не хватает то в системной памяти (этот случай самый медленный). Объект DirectDrawSurface кроме указателя на область видеопамяти содержит несколько очень полезных методов (и зверски скоростных) для быстрого переноса квадратика видеоизображения из одного участка Surfасе'а в другой (blitting), для быстрой смены одного Surface' а на экране другим – fliping, для работы с палитрами и спрайтами и др. Ну как удалось мне вас заинтересовать? Ну тогда давайте разберемся – как эти самые замечательные Surfасе'ы создавать. Перво-наперво скажем что у каждого Surfасе'а должен быть размер - ширина и высота. Кроме того Surfасе'ы устроены так, что между началом одной строчки и другой расстояние не всегда равное ширине. Скажем мы создали Surface 640X480X8 – казалось бы между первой строчкой и второй ровно 640 байт – ан нет. Может 640 байт а может и больше (это завист от того парня, который писал драйвер Вашего видеоадаптера). Расстояние между строчками в байтах называется Pitch – переводится как шаг. Почему этот самый Pitch не всегда равен ширине видно из рисунка: Статьи по Delphi (005)… Видите – справа от нашего Front-bufera может быть какой-то кэш, если Вы вздумаете писать напрямую в видеопамять – писать туда (в кэш) строго не рекомендуется (за последствия никто не ручается). Кроме того Pitch, в отличие от ширины измеряется в байтах а не в пикселах. Раз уж заговорили, про Front-bufеr'ы – скажем уж и про то, что один Surface, называемый PrimarySurface, является главным - это тот самый Surface, который был виден на экране в момент когда мы начали создавать эти самые Surfасе'ы. Surfасе'ы могут быть обьединены в так называемые flip-цепочки. Когда происходит flip между Surfасе'ами – тот Surface, что сейчас на экране, заменяется следующим в цепочке, на следующем fliр'е – этот – следующим и т.д. – если дошли до последнего в цепочке – то он заменяется на первый. Ну в обычной жизни цепочка может состоять из всего двух Surface' ов – при каждом они просто fliр'е сменяют друг друга. Обратите внимание – при fliр'е смена Surfасе'ов происходит не в результате пересылки всего их содержимого, а просто в результате изменения указателей на области видеопамяти в видеоадаптере – поэтому flip выполняется очень быстро. (Исключение может быть только в случае если Вы создали столько Surfасе'ов, что они не поместились в видеопамяти – тогда за дело возьмется HEL – бедняге придется все это эмулировать и скорость будет – не ахти). C помощью flip можно создавать анимацию, выводим какую-то картинку, затем в BackBuffer'e – чуть-чуть меняем эту картинку, вызываем flip, чуть-чуть меняем картинку в BackBuffer'e, вызываем flip и т.д. в цикле. Вот пример создания Flip-цепочки из двух Surfасе'ов, обьектов IDirectDrawSurface4.

(Ссылки на два созданных Surfасе'а сохраняются в переменных FPrimarySurface и FbackSurface) (этот пример взят из моей демо-программульки, которую Вы может скачать здесь 169K) {используются JEDI – заголовки DirectX}.

uses ... DDraw;
var
  hr: HRESULT;
  SurfaceDesc: TDDSurfaceDesc2;
  DDSCaps: TDDSCAPS2;
  DD: IDirectDraw;
begin
  ///  ...начнем, помолясь
  hr := DirectDrawCreate(nil, DD, nil); ///создали DirectDraw
  if (hr = DD_OK) then
  begin
    // Получим интерфейс IDirectDraw4
    DD.QueryInterface(IID_IDirectDraw4, FDirectDraw);
    // интерфейс DirecDraw1 нам больше не нужен
    DD := nil;
    // Установим эксклюзивный уровень кооперации и полноэкранный режим
    hr := FDirectDraw.SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE or
      DDSCL_FULLSCREEN);
    if (hr = DD_OK) then
    begin
      hr := FDirectDraw.SetDisplayMode(640, 480, 8, 0, 0);
        ///переключаем видеорежим на 640X480X8
      if (hr = DD_OK) then
      begin
        // Создаем главный surface с одним back buffеr'ом
        FillChar(SurfaceDesc, SizeOf(SurfaceDesc), 0);
        SurfaceDesc.dwSize := SizeOf(SurfaceDesc);
        ///говорим что нам нужны back buffеr'ы
        SurfaceDesc.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;
        ////говорим что создаем первый Surface
        SurfaceDesc.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE
          or DDSCAPS_FLIP //// во Flip-цепочке
        or DDSCAPS_COMPLEX; //// а вообще будут и дополнительные Surfасе'ы
        //// число этих самых дополнительных - 1
        SurfaceDesc.dwBackBufferCount := 1;
        ///все готово, создаем Surfасе'ы и запоминаем главный в FPrimarySurface
        hr := FDirectDraw.CreateSurface(SurfaceDesc, FPrimarySurface, nil);
        if (hr = DD_OK) then
        begin
          // А теперь получим указатель на back buffer (создали-то два Surface'a сразу)
          ddscaps.dwCaps := DDSCAPS_BACKBUFFER;
          ///получили и запомнили в FBackSurface
          hr := FPrimarySurface.GetAttachedSurface(ddscaps, FBackSurface);
          if (hr = DD_OK) then
          begin
            {Все нормально - Surfасе'ы созданны - выходим}
            exit;
          end;
        end;
      end;
    end;
  end;
  {где-то была ошибка - сообщаем об этом неприятном факте}
  MessageBox(Self.Handle, PChar('Не удалось инициализировать DirectDraw! ' +
    ErrorString(Hr)), 'ERROR', MB_OK);
  Close();
end;
Создали Surfасе'ы. Теперь было бы интересно что-нибудь на них нарисовать. Интересно также попробовать писать прямо в видеопамять. Получить указатель на область видеопамяти Surfасе'а можно вызвав метод Lock – он вернет указатель в структуре типа TDDSURFACEDESC2, которую получает в качестве параметра.

С фантазией у меня всегда было не очень – поэтому просто заполню всю область Surfасе'ов одним цветом, записав в видеопамять одно и тоже значение.

var
  i, j: integer;
  AdresVideo: PByteArray;
  SurfaceDesc: TDDSURFACEDESC2;
  HR: HResult;
begin
  // Пишем прямо в видеопамять
  FillChar(SurfaceDesc, SizeOf(SurfaceDesc), 0);
  SurfaceDesc.dwSize := SizeOf(SurfaceDesc);
  HR := FPrimarySurface.Lock(nil, SurfaceDesc, {DDLOCK_WAIT or}
    DDLOCK_SURFACEMEMORYPTR, 0);
  if (HR = DD_OK) then
  begin
    AdresVideo := SurfaceDesc.lpSurface;
    for i := 0 to SurfaceDesc.dwHeight - 1 do
    begin
      for j := 0 to SurfaceDesc.dwWidth - 1 do
      begin
        AdresVideo[j + i * SurfaceDesc.lPitch] := $FF;
      end;
    end;
    FPrimarySurface.Unlock(nil);
  end;
end;

Обратите внимание - какой я аккуратный – перехожу между строчками, учитывая Pitch. Да кстати – я просто демонстрирую как обратится к каждому байту видеопамяти Surface'a на самом деле если нужно закрасить весь Surface одним цветом то заносить значения в каждый байт слишком медленно – для этого можно воспользоваться методом IDirectDrawSurface4.Blt, передав ему флаг DDBLT_COLORFILL. Кроме того можно выводить на Surface и привычными функциями GDI – ТехtОut'ом например:

var
  DC: HDC;
begin
  if (FPrimarySurface.GetDC(DC) = DD_OK) then
  begin
    {Выводим текст на 1-й surface, используя GDI-фуекцию TextOut}
    SetBkColor(DC, RGB(255, 255, 255));
    SetTextColor(DC, RGB(255, 0, 0));
    TextOut(DC, 10, 20, ' Проба пера', Length(' Проба пера'));
    FPrimarySurface.ReleaseDC(DC);
  end;
end;
Небольшое лирическое отступление – между вызовами LOCK и UNLOCK, а также между GetDC и ReleaseDC выполнение всех других программ останавливается (в том числе и отладчика). Отсюда выводы – первое – не стоит делать что-то слишком долго между этими вызовами, второе, отладить программу пошагово между этими вызовами – невозможно (если только Вы не вооружились Kernel-dеbugеr'ом).

Теперь попробуем fliр'ануть наши Surfасе'ы. Переключимся на другой Surface.

hr := FPrimarySurface.Flip(nil, 0);
DDERR_NOEXCLUSIVEMODE – этот код возврата означает, что наша программа потеряла эксклюзивный режим. Произойти такое может, если мы fliр'уем в цикле по таймеру, а пользователь зачем-то ушел из нашей программы, свернув ее или нажав Alt-TAB. В таком случае, чтобы зря не тратить процессорные циклы, лучше подождать его возвращения, вызывая функцию Sleep(0) или WaitMessage. DDERR_SURFACELOST – потеря Surfасе'ов пользователь уходил, но вернулся, Surfасе'ы нужно забрать назад, вызвав IDirectDraw4.RestoreAllSurfaces, содержимое их придется восстановить. Все вышесказанное касается классического стиля использования DirectDraw в стиле С от JEDI. Поклонники же Ноri'вского набора DelphiX могут поэкспериментировать c Surfасе'ами используя TDXDraw.DDraw.SurfaceCount, TDXDraw.DDraw.Surfaces, TDXDraw.Flip – вместе с набором компонент распространяются отличные примеры. Я очень рад, что Вы дочитали до этого места (если Вы просто пролистали в конец, не читая, сделайте вид, что это не так, порадуйте меня, старика) :-). На этом пока все. Если Вы заинтересовались – скачайте демо-программку и поэкспериментируйте.

Пишите мне – aziz@telebot.com или error@softhome.net – с удовольствием приму Ваши пожелания и предложения (особенно если предложите какую-нибудь работу) :-).

Содержание.